Compare commits
12 Commits
mononaut/f
...
mononaut/d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0f4f8f920 | ||
|
|
49529627f8 | ||
|
|
17dd02ed4e | ||
|
|
ffd7831efc | ||
|
|
f8636d20c2 | ||
|
|
3b4dd7e633 | ||
|
|
ea101e65bb | ||
|
|
8a713c3880 | ||
|
|
42d5650bc0 | ||
|
|
5257716e1a | ||
|
|
47b95af8ae | ||
|
|
822934828a |
@@ -130,8 +130,9 @@ class BitcoinRoutes {
|
|||||||
|
|
||||||
private getInitData(req: Request, res: Response) {
|
private getInitData(req: Request, res: Response) {
|
||||||
try {
|
try {
|
||||||
const result = websocketHandler.getInitData();
|
const result = websocketHandler.getSerializedInitData();
|
||||||
res.json(result);
|
res.set('Content-Type', 'application/json');
|
||||||
|
res.send(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
res.status(500).send(e instanceof Error ? e.message : e);
|
res.status(500).send(e instanceof Error ? e.message : e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,11 +34,12 @@ export function calcDifficultyAdjustment(
|
|||||||
const remainingBlocks = EPOCH_BLOCK_LENGTH - blocksInEpoch;
|
const remainingBlocks = EPOCH_BLOCK_LENGTH - blocksInEpoch;
|
||||||
const nextRetargetHeight = (blockHeight >= 0) ? blockHeight + remainingBlocks : 0;
|
const nextRetargetHeight = (blockHeight >= 0) ? blockHeight + remainingBlocks : 0;
|
||||||
const expectedBlocks = diffSeconds / BLOCK_SECONDS_TARGET;
|
const expectedBlocks = diffSeconds / BLOCK_SECONDS_TARGET;
|
||||||
|
const actualTimespan = (blocksInEpoch === 2015 ? latestBlockTimestamp : nowSeconds) - DATime;
|
||||||
|
|
||||||
let difficultyChange = 0;
|
let difficultyChange = 0;
|
||||||
let timeAvgSecs = blocksInEpoch ? diffSeconds / blocksInEpoch : BLOCK_SECONDS_TARGET;
|
let timeAvgSecs = blocksInEpoch ? diffSeconds / blocksInEpoch : BLOCK_SECONDS_TARGET;
|
||||||
|
|
||||||
difficultyChange = (BLOCK_SECONDS_TARGET / timeAvgSecs - 1) * 100;
|
difficultyChange = (BLOCK_SECONDS_TARGET / (actualTimespan / (blocksInEpoch + 1)) - 1) * 100;
|
||||||
// Max increase is x4 (+300%)
|
// Max increase is x4 (+300%)
|
||||||
if (difficultyChange > 300) {
|
if (difficultyChange > 300) {
|
||||||
difficultyChange = 300;
|
difficultyChange = 300;
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ class WebsocketHandler {
|
|||||||
private numConnected = 0;
|
private numConnected = 0;
|
||||||
private numDisconnected = 0;
|
private numDisconnected = 0;
|
||||||
|
|
||||||
|
private initData: { [key: string]: string } = {};
|
||||||
|
private serializedInitData: string = '{}';
|
||||||
|
|
||||||
constructor() { }
|
constructor() { }
|
||||||
|
|
||||||
setWebsocketServer(wss: WebSocket.Server) {
|
setWebsocketServer(wss: WebSocket.Server) {
|
||||||
@@ -38,6 +41,41 @@ class WebsocketHandler {
|
|||||||
|
|
||||||
setExtraInitProperties(property: string, value: any) {
|
setExtraInitProperties(property: string, value: any) {
|
||||||
this.extraInitProperties[property] = value;
|
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() {
|
setupConnectionHandling() {
|
||||||
@@ -157,11 +195,13 @@ class WebsocketHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (parsedMessage.action === 'init') {
|
if (parsedMessage.action === 'init') {
|
||||||
const _blocks = blocks.getBlocks().slice(-config.MEMPOOL.INITIAL_BLOCKS_AMOUNT);
|
if (!this.initData['blocks']?.length || !this.initData['da']) {
|
||||||
if (!_blocks) {
|
this.updateInitData();
|
||||||
|
}
|
||||||
|
if (!this.initData['blocks']?.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
client.send(JSON.stringify(this.getInitData(_blocks)));
|
client.send(this.serializedInitData);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsedMessage.action === 'ping') {
|
if (parsedMessage.action === 'ping') {
|
||||||
@@ -210,11 +250,14 @@ class WebsocketHandler {
|
|||||||
throw new Error('WebSocket.Server is not set');
|
throw new Error('WebSocket.Server is not set');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.setInitDataFields({ 'loadingIndicators': indicators });
|
||||||
|
|
||||||
|
const response = JSON.stringify({ loadingIndicators: indicators });
|
||||||
this.wss.clients.forEach((client) => {
|
this.wss.clients.forEach((client) => {
|
||||||
if (client.readyState !== WebSocket.OPEN) {
|
if (client.readyState !== WebSocket.OPEN) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
client.send(JSON.stringify({ loadingIndicators: indicators }));
|
client.send(response);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,34 +266,17 @@ class WebsocketHandler {
|
|||||||
throw new Error('WebSocket.Server is not set');
|
throw new Error('WebSocket.Server is not set');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.setInitDataFields({ 'conversions': conversionRates });
|
||||||
|
|
||||||
|
const response = JSON.stringify({ conversions: conversionRates });
|
||||||
this.wss.clients.forEach((client) => {
|
this.wss.clients.forEach((client) => {
|
||||||
if (client.readyState !== WebSocket.OPEN) {
|
if (client.readyState !== WebSocket.OPEN) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
client.send(JSON.stringify({ conversions: conversionRates }));
|
client.send(response);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
handleNewStatistic(stats: OptimizedStatistic) {
|
||||||
if (!this.wss) {
|
if (!this.wss) {
|
||||||
throw new Error('WebSocket.Server is not set');
|
throw new Error('WebSocket.Server is not set');
|
||||||
@@ -258,6 +284,10 @@ class WebsocketHandler {
|
|||||||
|
|
||||||
this.printLogs();
|
this.printLogs();
|
||||||
|
|
||||||
|
const response = JSON.stringify({
|
||||||
|
'live-2h-chart': stats
|
||||||
|
});
|
||||||
|
|
||||||
this.wss.clients.forEach((client) => {
|
this.wss.clients.forEach((client) => {
|
||||||
if (client.readyState !== WebSocket.OPEN) {
|
if (client.readyState !== WebSocket.OPEN) {
|
||||||
return;
|
return;
|
||||||
@@ -267,9 +297,7 @@ class WebsocketHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
client.send(JSON.stringify({
|
client.send(response);
|
||||||
'live-2h-chart': stats
|
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,6 +334,43 @@ class WebsocketHandler {
|
|||||||
}
|
}
|
||||||
const recommendedFees = feeApi.getRecommendedFee();
|
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) => {
|
this.wss.clients.forEach(async (client) => {
|
||||||
if (client.readyState !== WebSocket.OPEN) {
|
if (client.readyState !== WebSocket.OPEN) {
|
||||||
return;
|
return;
|
||||||
@@ -314,17 +379,17 @@ class WebsocketHandler {
|
|||||||
const response = {};
|
const response = {};
|
||||||
|
|
||||||
if (client['want-stats']) {
|
if (client['want-stats']) {
|
||||||
response['mempoolInfo'] = mempoolInfo;
|
response['mempoolInfo'] = getCachedResponse('mempoolInfo', mempoolInfo);
|
||||||
response['vBytesPerSecond'] = vBytesPerSecond;
|
response['vBytesPerSecond'] = getCachedResponse('vBytesPerSecond', vBytesPerSecond);
|
||||||
response['transactions'] = newTransactions.slice(0, 6).map((tx) => Common.stripTransaction(tx));
|
response['transactions'] = getCachedResponse('transactions', latestTransactions);
|
||||||
if (da?.previousTime) {
|
if (da?.previousTime) {
|
||||||
response['da'] = da;
|
response['da'] = getCachedResponse('da', da);
|
||||||
}
|
}
|
||||||
response['fees'] = recommendedFees;
|
response['fees'] = getCachedResponse('fees', recommendedFees);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client['want-mempool-blocks']) {
|
if (client['want-mempool-blocks']) {
|
||||||
response['mempool-blocks'] = mBlocks;
|
response['mempool-blocks'] = getCachedResponse('mempool-blocks', mBlocks);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client['track-mempool-tx']) {
|
if (client['track-mempool-tx']) {
|
||||||
@@ -333,12 +398,12 @@ class WebsocketHandler {
|
|||||||
if (config.MEMPOOL.BACKEND !== 'esplora') {
|
if (config.MEMPOOL.BACKEND !== 'esplora') {
|
||||||
try {
|
try {
|
||||||
const fullTx = await transactionUtils.$getTransactionExtended(tx.txid, true);
|
const fullTx = await transactionUtils.$getTransactionExtended(tx.txid, true);
|
||||||
response['tx'] = fullTx;
|
response['tx'] = JSON.stringify(fullTx);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.debug('Error finding transaction in mempool: ' + (e instanceof Error ? e.message : e));
|
logger.debug('Error finding transaction in mempool: ' + (e instanceof Error ? e.message : e));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
response['tx'] = tx;
|
response['tx'] = JSON.stringify(tx);
|
||||||
}
|
}
|
||||||
client['track-mempool-tx'] = null;
|
client['track-mempool-tx'] = null;
|
||||||
}
|
}
|
||||||
@@ -378,7 +443,7 @@ class WebsocketHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (foundTransactions.length) {
|
if (foundTransactions.length) {
|
||||||
response['address-transactions'] = foundTransactions;
|
response['address-transactions'] = JSON.stringify(foundTransactions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,65 +472,60 @@ class WebsocketHandler {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (foundTransactions.length) {
|
if (foundTransactions.length) {
|
||||||
response['address-transactions'] = foundTransactions;
|
response['address-transactions'] = JSON.stringify(foundTransactions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client['track-tx']) {
|
if (client['track-tx']) {
|
||||||
const trackTxid = client['track-tx'];
|
const trackTxid = client['track-tx'];
|
||||||
const outspends: object = {};
|
const outspends = outspendCache[trackTxid];
|
||||||
newTransactions.forEach((tx) => tx.vin.forEach((vin, i) => {
|
|
||||||
if (vin.txid === trackTxid) {
|
|
||||||
outspends[vin.vout] = {
|
|
||||||
vin: i,
|
|
||||||
txid: tx.txid,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (Object.keys(outspends).length) {
|
if (outspends && Object.keys(outspends).length) {
|
||||||
response['utxoSpent'] = outspends;
|
response['utxoSpent'] = JSON.stringify(outspends);
|
||||||
}
|
}
|
||||||
|
|
||||||
const rbfReplacedBy = rbfCache.getReplacedBy(client['track-tx']);
|
const rbfReplacedBy = rbfCache.getReplacedBy(client['track-tx']);
|
||||||
if (rbfReplacedBy) {
|
if (rbfReplacedBy) {
|
||||||
response['rbfTransaction'] = {
|
response['rbfTransaction'] = JSON.stringify({
|
||||||
txid: rbfReplacedBy,
|
txid: rbfReplacedBy,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const rbfChange = rbfChanges.map[client['track-tx']];
|
const rbfChange = rbfChanges.map[client['track-tx']];
|
||||||
if (rbfChange) {
|
if (rbfChange) {
|
||||||
response['rbfInfo'] = rbfChanges.trees[rbfChange];
|
response['rbfInfo'] = JSON.stringify(rbfChanges.trees[rbfChange]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const mempoolTx = newMempool[trackTxid];
|
const mempoolTx = newMempool[trackTxid];
|
||||||
if (mempoolTx && mempoolTx.position) {
|
if (mempoolTx && mempoolTx.position) {
|
||||||
response['txPosition'] = {
|
response['txPosition'] = JSON.stringify({
|
||||||
txid: trackTxid,
|
txid: trackTxid,
|
||||||
position: mempoolTx.position,
|
position: mempoolTx.position,
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client['track-mempool-block'] >= 0) {
|
if (client['track-mempool-block'] >= 0) {
|
||||||
const index = client['track-mempool-block'];
|
const index = client['track-mempool-block'];
|
||||||
if (mBlockDeltas[index]) {
|
if (mBlockDeltas[index]) {
|
||||||
response['projected-block-transactions'] = {
|
response['projected-block-transactions'] = getCachedResponse(`projected-block-transactions-${index}`, {
|
||||||
index: index,
|
index: index,
|
||||||
delta: mBlockDeltas[index],
|
delta: mBlockDeltas[index],
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client['track-rbf'] === 'all' && rbfReplacements) {
|
if (client['track-rbf'] === 'all' && rbfReplacements) {
|
||||||
response['rbfLatest'] = rbfReplacements;
|
response['rbfLatest'] = getCachedResponse('rbfLatest', rbfReplacements);
|
||||||
} else if (client['track-rbf'] === 'fullRbf' && fullRbfReplacements) {
|
} else if (client['track-rbf'] === 'fullRbf' && fullRbfReplacements) {
|
||||||
response['rbfLatest'] = fullRbfReplacements;
|
response['rbfLatest'] = getCachedResponse('fullrbfLatest', fullRbfReplacements);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Object.keys(response).length) {
|
if (Object.keys(response).length) {
|
||||||
client.send(JSON.stringify(response));
|
const serializedResponse = '{'
|
||||||
|
+ Object.keys(response).map(key => `"${key}": ${response[key]}`).join(', ')
|
||||||
|
+ '}';
|
||||||
|
client.send(serializedResponse);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -556,6 +616,19 @@ class WebsocketHandler {
|
|||||||
const da = difficultyAdjustment.getDifficultyAdjustment();
|
const da = difficultyAdjustment.getDifficultyAdjustment();
|
||||||
const fees = feeApi.getRecommendedFee();
|
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) => {
|
this.wss.clients.forEach((client) => {
|
||||||
if (client.readyState !== WebSocket.OPEN) {
|
if (client.readyState !== WebSocket.OPEN) {
|
||||||
return;
|
return;
|
||||||
@@ -565,28 +638,27 @@ class WebsocketHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = {
|
const response = {};
|
||||||
'block': block,
|
response['block'] = getCachedResponse('block', block);
|
||||||
'mempoolInfo': memPool.getMempoolInfo(),
|
response['mempoolInfo'] = getCachedResponse('mempoolInfo', mempoolInfo);
|
||||||
'da': da?.previousTime ? da : undefined,
|
response['da'] = getCachedResponse('da', da?.previousTime ? da : undefined);
|
||||||
'fees': fees,
|
response['fees'] = getCachedResponse('fees', fees);
|
||||||
};
|
|
||||||
|
|
||||||
if (mBlocks && client['want-mempool-blocks']) {
|
if (mBlocks && client['want-mempool-blocks']) {
|
||||||
response['mempool-blocks'] = mBlocks;
|
response['mempool-blocks'] = getCachedResponse('mempool-blocks', mBlocks);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client['track-tx']) {
|
if (client['track-tx']) {
|
||||||
const trackTxid = client['track-tx'];
|
const trackTxid = client['track-tx'];
|
||||||
if (txIds.indexOf(trackTxid) > -1) {
|
if (txIds.indexOf(trackTxid) > -1) {
|
||||||
response['txConfirmed'] = true;
|
response['txConfirmed'] = 'true';
|
||||||
} else {
|
} else {
|
||||||
const mempoolTx = _memPool[trackTxid];
|
const mempoolTx = _memPool[trackTxid];
|
||||||
if (mempoolTx && mempoolTx.position) {
|
if (mempoolTx && mempoolTx.position) {
|
||||||
response['txPosition'] = {
|
response['txPosition'] = JSON.stringify({
|
||||||
txid: trackTxid,
|
txid: trackTxid,
|
||||||
position: mempoolTx.position,
|
position: mempoolTx.position,
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -614,7 +686,7 @@ class WebsocketHandler {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
response['block-transactions'] = foundTransactions;
|
response['block-transactions'] = JSON.stringify(foundTransactions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -651,21 +723,24 @@ class WebsocketHandler {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
response['block-transactions'] = foundTransactions;
|
response['block-transactions'] = JSON.stringify(foundTransactions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client['track-mempool-block'] >= 0) {
|
if (client['track-mempool-block'] >= 0) {
|
||||||
const index = client['track-mempool-block'];
|
const index = client['track-mempool-block'];
|
||||||
if (mBlockDeltas && mBlockDeltas[index]) {
|
if (mBlockDeltas && mBlockDeltas[index]) {
|
||||||
response['projected-block-transactions'] = {
|
response['projected-block-transactions'] = getCachedResponse(`projected-block-transactions-${index}`, {
|
||||||
index: index,
|
index: index,
|
||||||
delta: mBlockDeltas[index],
|
delta: mBlockDeltas[index],
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
client.send(JSON.stringify(response));
|
const serializedResponse = '{'
|
||||||
|
+ Object.keys(response).map(key => `"${key}": ${response[key]}`).join(', ')
|
||||||
|
+ '}';
|
||||||
|
client.send(serializedResponse);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,14 @@ export const mempoolFeeColors = [
|
|||||||
'ba3243',
|
'ba3243',
|
||||||
'b92b48',
|
'b92b48',
|
||||||
'b9254b',
|
'b9254b',
|
||||||
|
'b8214d',
|
||||||
|
'b71d4f',
|
||||||
|
'b61951',
|
||||||
|
'b41453',
|
||||||
|
'b30e55',
|
||||||
|
'b10857',
|
||||||
|
'b00259',
|
||||||
|
'ae005b',
|
||||||
];
|
];
|
||||||
|
|
||||||
export const chartColors = [
|
export const chartColors = [
|
||||||
@@ -69,6 +77,7 @@ export const chartColors = [
|
|||||||
"#3E2723",
|
"#3E2723",
|
||||||
"#212121",
|
"#212121",
|
||||||
"#263238",
|
"#263238",
|
||||||
|
"#801313",
|
||||||
];
|
];
|
||||||
|
|
||||||
export const poolsColor = {
|
export const poolsColor = {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
repeatCount="indefinite"/>
|
repeatCount="indefinite"/>
|
||||||
</rect>
|
</rect>
|
||||||
</svg>
|
</svg>
|
||||||
|
<span *ngIf="lockedIn" class="lock-in-msg" i18n="difficulty-box.adjustment-locked-in">Difficulty adjustment locked in</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="difficulty-stats">
|
<div class="difficulty-stats">
|
||||||
<div class="item">
|
<div class="item">
|
||||||
|
|||||||
@@ -172,6 +172,18 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 22px;
|
height: 22px;
|
||||||
margin-bottom: 12px;
|
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 {
|
.epoch-blocks {
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ export class DifficultyComponent implements OnInit {
|
|||||||
expectedHeight: number;
|
expectedHeight: number;
|
||||||
expectedIndex: number;
|
expectedIndex: number;
|
||||||
difference: number;
|
difference: number;
|
||||||
|
lockedIn: boolean = false;
|
||||||
shapes: DiffShape[];
|
shapes: DiffShape[];
|
||||||
|
|
||||||
tooltipPosition = { x: 0, y: 0 };
|
tooltipPosition = { x: 0, y: 0 };
|
||||||
@@ -104,6 +105,7 @@ export class DifficultyComponent implements OnInit {
|
|||||||
this.currentIndex = this.currentHeight - this.epochStart;
|
this.currentIndex = this.currentHeight - this.epochStart;
|
||||||
this.expectedIndex = Math.min(this.expectedHeight - this.epochStart, 2016) - 1;
|
this.expectedIndex = Math.min(this.expectedHeight - this.epochStart, 2016) - 1;
|
||||||
this.difference = this.currentIndex - this.expectedIndex;
|
this.difference = this.currentIndex - this.expectedIndex;
|
||||||
|
this.lockedIn = this.currentIndex === 2015;
|
||||||
|
|
||||||
this.shapes = [];
|
this.shapes = [];
|
||||||
this.shapes = this.shapes.concat(this.blocksToShapes(
|
this.shapes = this.shapes.concat(this.blocksToShapes(
|
||||||
|
|||||||
@@ -23,8 +23,7 @@ import { download, formatterXAxis, formatterXAxisLabel } from '../../shared/grap
|
|||||||
})
|
})
|
||||||
export class MempoolGraphComponent implements OnInit, OnChanges {
|
export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||||
@Input() data: any[];
|
@Input() data: any[];
|
||||||
@Input() limitFee = 350;
|
@Input() filterSize = 100000;
|
||||||
@Input() limitFilterFee = 1;
|
|
||||||
@Input() height: number | string = 200;
|
@Input() height: number | string = 200;
|
||||||
@Input() top: number | string = 20;
|
@Input() top: number | string = 20;
|
||||||
@Input() right: number | string = 10;
|
@Input() right: number | string = 10;
|
||||||
@@ -99,16 +98,20 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
generateArray(mempoolStats: OptimizedMempoolStats[]) {
|
generateArray(mempoolStats: OptimizedMempoolStats[]) {
|
||||||
const finalArray: number[][][] = [];
|
let finalArray: number[][][] = [];
|
||||||
let feesArray: number[][] = [];
|
let feesArray: number[][] = [];
|
||||||
const limitFeesTemplate = this.template === 'advanced' ? 26 : 20;
|
let maxTier = 0;
|
||||||
for (let index = limitFeesTemplate; index > -1; index--) {
|
for (let index = 37; index > -1; index--) {
|
||||||
feesArray = [];
|
feesArray = [];
|
||||||
mempoolStats.forEach((stats) => {
|
mempoolStats.forEach((stats) => {
|
||||||
|
if (stats.vsizes[index] >= this.filterSize) {
|
||||||
|
maxTier = Math.max(maxTier, index);
|
||||||
|
}
|
||||||
feesArray.push([stats.added * 1000, stats.vsizes[index] ? stats.vsizes[index] : 0]);
|
feesArray.push([stats.added * 1000, stats.vsizes[index] ? stats.vsizes[index] : 0]);
|
||||||
});
|
});
|
||||||
finalArray.push(feesArray);
|
finalArray.push(feesArray);
|
||||||
}
|
}
|
||||||
|
this.feeLimitIndex = maxTier;
|
||||||
finalArray.reverse();
|
finalArray.reverse();
|
||||||
return finalArray;
|
return finalArray;
|
||||||
}
|
}
|
||||||
@@ -121,7 +124,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
|||||||
const newColors = [];
|
const newColors = [];
|
||||||
for (let index = 0; index < series.length; index++) {
|
for (let index = 0; index < series.length; index++) {
|
||||||
const value = series[index];
|
const value = series[index];
|
||||||
if (index >= this.feeLimitIndex) {
|
if (index < this.feeLimitIndex) {
|
||||||
newColors.push(this.chartColorsOrdered[index]);
|
newColors.push(this.chartColorsOrdered[index]);
|
||||||
seriesGraph.push({
|
seriesGraph.push({
|
||||||
zlevel: 0,
|
zlevel: 0,
|
||||||
@@ -371,17 +374,21 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
|||||||
|
|
||||||
orderLevels() {
|
orderLevels() {
|
||||||
this.feeLevelsOrdered = [];
|
this.feeLevelsOrdered = [];
|
||||||
for (let i = 0; i < feeLevels.length; i++) {
|
let maxIndex = Math.min(feeLevels.length, this.feeLimitIndex);
|
||||||
if (feeLevels[i] === this.limitFilterFee) {
|
for (let i = 0; i < maxIndex; i++) {
|
||||||
this.feeLimitIndex = i;
|
|
||||||
}
|
|
||||||
if (feeLevels[i] <= this.limitFee) {
|
|
||||||
if (this.stateService.network === 'liquid' || this.stateService.network === 'liquidtestnet') {
|
if (this.stateService.network === 'liquid' || this.stateService.network === 'liquidtestnet') {
|
||||||
this.feeLevelsOrdered.push(`${(feeLevels[i] / 10).toFixed(1)} - ${(feeLevels[i + 1] / 10).toFixed(1)}`);
|
if (i === maxIndex - 1) {
|
||||||
|
this.feeLevelsOrdered.push(`${(feeLevels[i] / 10).toFixed(1)}+`);
|
||||||
|
} else {
|
||||||
|
this.feeLevelsOrdered.push(`${(feeLevels[i] / 10).toFixed(1)} - ${(feeLevels[i + 1] / 10).toFixed(1)}`);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.feeLevelsOrdered.push(`${feeLevels[i]} - ${feeLevels[i + 1]}`);
|
if (i === maxIndex - 1) {
|
||||||
|
this.feeLevelsOrdered.push(`${feeLevels[i]}+`);
|
||||||
|
} else {
|
||||||
|
this.feeLevelsOrdered.push(`${feeLevels[i]} - ${feeLevels[i + 1]}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
this.chartColorsOrdered = chartColors.slice(0, this.feeLevelsOrdered.length);
|
this.chartColorsOrdered = chartColors.slice(0, this.feeLevelsOrdered.length);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,8 +84,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="incoming-transactions-graph">
|
<div class="incoming-transactions-graph">
|
||||||
<app-mempool-graph #mempoolgraph dir="ltr" [template]="'advanced'" [limitFee]="500"
|
<app-mempool-graph #mempoolgraph dir="ltr" [template]="'advanced'" [height]="500" [left]="65" [right]="10"
|
||||||
[limitFilterFee]="filterFeeIndex" [height]="500" [left]="65" [right]="10"
|
|
||||||
[data]="mempoolStats && mempoolStats.length ? mempoolStats : null"></app-mempool-graph>
|
[data]="mempoolStats && mempoolStats.length ? mempoolStats : null"></app-mempool-graph>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
<div class="chart-holder">
|
<div class="chart-holder">
|
||||||
<app-mempool-graph
|
<app-mempool-graph
|
||||||
[template]="'advanced'"
|
[template]="'advanced'"
|
||||||
[limitFee]="500"
|
|
||||||
[height]="600"
|
[height]="600"
|
||||||
[left]="60"
|
[left]="60"
|
||||||
[right]="10"
|
[right]="10"
|
||||||
|
|||||||
@@ -26,8 +26,7 @@
|
|||||||
<div class="mempool-graph">
|
<div class="mempool-graph">
|
||||||
<app-mempool-graph
|
<app-mempool-graph
|
||||||
[template]="'widget'"
|
[template]="'widget'"
|
||||||
[limitFee]="150"
|
[filterSize]="1000000"
|
||||||
[limitFilterFee]="1"
|
|
||||||
[data]="mempoolStats.value?.mempool"
|
[data]="mempoolStats.value?.mempool"
|
||||||
[windowPreferenceOverride]="'2h'"
|
[windowPreferenceOverride]="'2h'"
|
||||||
></app-mempool-graph>
|
></app-mempool-graph>
|
||||||
|
|||||||
@@ -1,22 +1,76 @@
|
|||||||
<footer *ngIf="networkPaths$ | async as networkPaths">
|
<footer *ngIf="networkPaths$ | async as networkPaths">
|
||||||
<div class="pref-selectors">
|
<div class="container-fluid">
|
||||||
<div class="selector">
|
<div class="row main">
|
||||||
<app-language-selector></app-language-selector>
|
<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>
|
</div>
|
||||||
<div class="selector">
|
<div class="row version">
|
||||||
<app-fiat-selector></app-fiat-selector>
|
<div class="col-sm-12">
|
||||||
</div>
|
<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>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="terms-of-service">
|
<p *ngIf="!officialMempoolSpace">v{{ packetJsonVersion }} [<a target="_blank" href="https://github.com/mempool/mempool/commit/{{ frontendGitCommitHash }}">{{ frontendGitCommitHash }}</a>]</p>
|
||||||
<a [routerLink]="['/terms-of-service']" i18n="shared.terms-of-service|Terms of Service">Terms of Service</a>
|
|
||||||
|
|
</div>
|
||||||
<a [routerLink]="['/privacy-policy']" i18n="shared.privacy-policy|Privacy Policy">Privacy Policy</a>
|
</div>
|
||||||
|
|
|
||||||
<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>
|
</div>
|
||||||
<br>
|
|
||||||
</footer>
|
</footer>
|
||||||
@@ -1,22 +1,70 @@
|
|||||||
footer {
|
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;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.terms-of-service {
|
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) {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pref-selectors {
|
footer .selector {
|
||||||
display: flex;
|
margin: 20px 0;
|
||||||
flex-direction: row;
|
}
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
footer .row.version .col-sm-12 {
|
||||||
|
padding: 20px !important;
|
||||||
.selector {
|
background-color: #11131f;
|
||||||
margin-left: .5em;
|
}
|
||||||
margin-bottom: .5em;
|
|
||||||
&:first {
|
footer .row.version .col-sm-12 p {
|
||||||
margin-left: 0;
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
|||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { NavigationService } from '../../../services/navigation.service';
|
import { NavigationService } from '../../../services/navigation.service';
|
||||||
import { Env, StateService } from '../../../services/state.service';
|
import { Env, StateService } from '../../../services/state.service';
|
||||||
|
import { IBackendInfo } from '../../../interfaces/websocket.interface';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-global-footer',
|
selector: 'app-global-footer',
|
||||||
@@ -14,6 +15,9 @@ export class GlobalFooterComponent implements OnInit {
|
|||||||
networkPaths: { [network: string]: string };
|
networkPaths: { [network: string]: string };
|
||||||
officialMempoolSpace = this.stateService.env.OFFICIAL_MEMPOOL_SPACE;
|
officialMempoolSpace = this.stateService.env.OFFICIAL_MEMPOOL_SPACE;
|
||||||
networkPaths$: Observable<Record<string, string>>;
|
networkPaths$: Observable<Record<string, string>>;
|
||||||
|
backendInfo$: Observable<IBackendInfo>;
|
||||||
|
frontendGitCommitHash = this.stateService.env.GIT_COMMIT_HASH;
|
||||||
|
packetJsonVersion = this.stateService.env.PACKAGE_JSON_VERSION;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public stateService: StateService,
|
public stateService: StateService,
|
||||||
@@ -23,6 +27,7 @@ export class GlobalFooterComponent implements OnInit {
|
|||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.env = this.stateService.env;
|
this.env = this.stateService.env;
|
||||||
this.networkPaths$ = this.navigationService.subnetPaths;
|
this.networkPaths$ = this.navigationService.subnetPaths;
|
||||||
|
this.backendInfo$ = this.stateService.backendInfo$;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user