Compare commits
4 Commits
mononaut/f
...
mononaut/t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
420cac42aa | ||
|
|
1b68f32adc | ||
|
|
f2f6e3769a | ||
|
|
f9f8bd25f8 |
10
.github/workflows/on-tag.yml
vendored
10
.github/workflows/on-tag.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
run: |
|
||||
sudo swapoff /mnt/swapfile
|
||||
sudo rm -v /mnt/swapfile
|
||||
sudo fallocate -l 13G /mnt/swapfile
|
||||
sudo fallocate -l 10G /mnt/swapfile
|
||||
sudo chmod 600 /mnt/swapfile
|
||||
sudo mkswap /mnt/swapfile
|
||||
sudo swapon /mnt/swapfile
|
||||
@@ -68,24 +68,24 @@ jobs:
|
||||
run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
|
||||
|
||||
- name: Checkout project
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@e2f20e631ae6d7dd3b768f56a5d2af784dd54791 # v2.5.0
|
||||
|
||||
- name: Init repo for Dockerization
|
||||
run: docker/init.sh "$TAG"
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # v2.1.0
|
||||
id: qemu
|
||||
|
||||
- name: Setup Docker buildx action
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@8c0edbc76e98fa90f69d9a2c020dcb50019dc325 # v2.2.1
|
||||
id: buildx
|
||||
|
||||
- name: Available platforms
|
||||
run: echo ${{ steps.buildx.outputs.platforms }}
|
||||
|
||||
- name: Cache Docker layers
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11
|
||||
id: cache
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
|
||||
@@ -27,8 +27,7 @@
|
||||
"POOLS_JSON_TREE_URL": "https://api.github.com/repos/mempool/mining-pools/git/trees/master",
|
||||
"ADVANCED_GBT_AUDIT": false,
|
||||
"ADVANCED_GBT_MEMPOOL": false,
|
||||
"TRANSACTION_INDEXING": false,
|
||||
"FIRST_SEEN_INDEXING_DAYS": 0
|
||||
"TRANSACTION_INDEXING": false
|
||||
},
|
||||
"CORE_RPC": {
|
||||
"HOST": "127.0.0.1",
|
||||
|
||||
@@ -28,8 +28,7 @@
|
||||
"POOLS_JSON_URL": "__POOLS_JSON_URL__",
|
||||
"ADVANCED_GBT_AUDIT": "__ADVANCED_GBT_AUDIT__",
|
||||
"ADVANCED_GBT_MEMPOOL": "__ADVANCED_GBT_MEMPOOL__",
|
||||
"TRANSACTION_INDEXING": "__TRANSACTION_INDEXING__",
|
||||
"FIRST_SEEN_INDEXING_DAYS": "__FIRST_SEEN_INDEXING_DAYS__"
|
||||
"TRANSACTION_INDEXING": "__TRANSACTION_INDEXING__"
|
||||
},
|
||||
"CORE_RPC": {
|
||||
"HOST": "__CORE_RPC_HOST__",
|
||||
|
||||
@@ -41,7 +41,6 @@ describe('Mempool Backend Config', () => {
|
||||
ADVANCED_GBT_AUDIT: false,
|
||||
ADVANCED_GBT_MEMPOOL: false,
|
||||
TRANSACTION_INDEXING: false,
|
||||
FIRST_SEEN_INDEXING_DAYS: 0,
|
||||
});
|
||||
|
||||
expect(config.ELECTRUM).toStrictEqual({ HOST: '127.0.0.1', PORT: 3306, TLS_ENABLED: true });
|
||||
|
||||
@@ -25,7 +25,6 @@ class BitcoinRoutes {
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'transaction-times', this.getTransactionTimes)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'outspends', this.$getBatchedOutspends)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'cpfp/:txId', this.$getCpfpInfo)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'extras/:txId', this.$getTransactionExtras)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'difficulty-adjustment', this.getDifficultyChange)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'fees/recommended', this.getRecommendedFees)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'fees/mempool-blocks', this.getMempoolBlocks)
|
||||
@@ -222,42 +221,6 @@ class BitcoinRoutes {
|
||||
res.status(404).send(`Transaction has no CPFP info available.`);
|
||||
}
|
||||
|
||||
private async $getTransactionExtras(req: Request, res: Response): Promise<void> {
|
||||
if (!/^[a-fA-F0-9]{64}$/.test(req.params.txId)) {
|
||||
res.status(501).send(`Invalid transaction ID.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const tx = mempool.getMempool()[req.params.txId];
|
||||
if (tx) {
|
||||
if (tx?.cpfpChecked) {
|
||||
res.json({
|
||||
ancestors: tx.ancestors,
|
||||
bestDescendant: tx.bestDescendant || null,
|
||||
descendants: tx.descendants || null,
|
||||
effectiveFeePerVsize: tx.effectiveFeePerVsize || null,
|
||||
firstSeen: tx.firstSeen,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const cpfpInfo = Common.setRelativesAndGetCpfpInfo(tx, mempool.getMempool());
|
||||
|
||||
res.json({
|
||||
...cpfpInfo,
|
||||
firstSeen: tx.firstSeen,
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
const extras = await transactionRepository.$getTransactionExtras(req.params.txId);
|
||||
if (extras) {
|
||||
res.json(extras);
|
||||
return;
|
||||
}
|
||||
}
|
||||
res.status(404).send(`Transaction has no extra info available.`);
|
||||
}
|
||||
|
||||
private getBackendInfo(req: Request, res: Response) {
|
||||
res.json(backendInfo.getBackendInfo());
|
||||
}
|
||||
|
||||
@@ -296,7 +296,7 @@ class Blocks {
|
||||
const runningFor = Math.max(1, Math.round((new Date().getTime() / 1000) - startedAt));
|
||||
const blockPerSeconds = Math.max(1, indexedThisRun / elapsedSeconds);
|
||||
const progress = Math.round(totalIndexed / indexedBlocks.length * 10000) / 100;
|
||||
logger.debug(`Indexing block summary for #${block.height} | ~${blockPerSeconds.toFixed(2)} blocks/sec | total: ${totalIndexed}/${indexedBlocks.length} (${progress}%) | elapsed: ${runningFor} seconds`, logger.tags.mining);
|
||||
logger.debug(`Indexing block summary for #${block.height} | ~${blockPerSeconds.toFixed(2)} blocks/sec | total: ${totalIndexed}/${indexedBlocks.length} (${progress}%) | elapsed: ${runningFor} seconds`);
|
||||
timer = new Date().getTime() / 1000;
|
||||
indexedThisRun = 0;
|
||||
}
|
||||
@@ -309,12 +309,12 @@ class Blocks {
|
||||
newlyIndexed++;
|
||||
}
|
||||
if (newlyIndexed > 0) {
|
||||
logger.notice(`Blocks summaries indexing completed: indexed ${newlyIndexed} blocks`, logger.tags.mining);
|
||||
logger.notice(`Blocks summaries indexing completed: indexed ${newlyIndexed} blocks`);
|
||||
} else {
|
||||
logger.debug(`Blocks summaries indexing completed: indexed ${newlyIndexed} blocks`, logger.tags.mining);
|
||||
logger.debug(`Blocks summaries indexing completed: indexed ${newlyIndexed} blocks`);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.err(`Blocks summaries indexing failed. Trying again in 10 seconds. Reason: ${(e instanceof Error ? e.message : e)}`, logger.tags.mining);
|
||||
logger.err(`Blocks summaries indexing failed. Trying again in 10 seconds. Reason: ${(e instanceof Error ? e.message : e)}`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@@ -385,7 +385,7 @@ class Blocks {
|
||||
|
||||
const lastBlockToIndex = Math.max(0, currentBlockHeight - indexingBlockAmount + 1);
|
||||
|
||||
logger.debug(`Indexing blocks from #${currentBlockHeight} to #${lastBlockToIndex}`, logger.tags.mining);
|
||||
logger.debug(`Indexing blocks from #${currentBlockHeight} to #${lastBlockToIndex}`);
|
||||
loadingIndicators.setProgress('block-indexing', 0);
|
||||
|
||||
const chunkSize = 10000;
|
||||
@@ -405,7 +405,7 @@ class Blocks {
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.info(`Indexing ${missingBlockHeights.length} blocks from #${currentBlockHeight} to #${endBlock}`, logger.tags.mining);
|
||||
logger.info(`Indexing ${missingBlockHeights.length} blocks from #${currentBlockHeight} to #${endBlock}`);
|
||||
|
||||
for (const blockHeight of missingBlockHeights) {
|
||||
if (blockHeight < lastBlockToIndex) {
|
||||
@@ -418,7 +418,7 @@ class Blocks {
|
||||
const runningFor = Math.max(1, Math.round((new Date().getTime() / 1000) - startedAt));
|
||||
const blockPerSeconds = Math.max(1, indexedThisRun / elapsedSeconds);
|
||||
const progress = Math.round(totalIndexed / indexingBlockAmount * 10000) / 100;
|
||||
logger.debug(`Indexing block #${blockHeight} | ~${blockPerSeconds.toFixed(2)} blocks/sec | total: ${totalIndexed}/${indexingBlockAmount} (${progress}%) | elapsed: ${runningFor} seconds`, logger.tags.mining);
|
||||
logger.debug(`Indexing block #${blockHeight} | ~${blockPerSeconds.toFixed(2)} blocks/sec | total: ${totalIndexed}/${indexingBlockAmount} (${progress}%) | elapsed: ${runningFor} seconds`);
|
||||
timer = new Date().getTime() / 1000;
|
||||
indexedThisRun = 0;
|
||||
loadingIndicators.setProgress('block-indexing', progress, false);
|
||||
@@ -435,13 +435,13 @@ class Blocks {
|
||||
currentBlockHeight -= chunkSize;
|
||||
}
|
||||
if (newlyIndexed > 0) {
|
||||
logger.notice(`Block indexing completed: indexed ${newlyIndexed} blocks`, logger.tags.mining);
|
||||
logger.notice(`Block indexing completed: indexed ${newlyIndexed} blocks`);
|
||||
} else {
|
||||
logger.debug(`Block indexing completed: indexed ${newlyIndexed} blocks`, logger.tags.mining);
|
||||
logger.debug(`Block indexing completed: indexed ${newlyIndexed} blocks`);
|
||||
}
|
||||
loadingIndicators.setProgress('block-indexing', 100);
|
||||
} catch (e) {
|
||||
logger.err('Block indexing failed. Trying again in 10 seconds. Reason: ' + (e instanceof Error ? e.message : e), logger.tags.mining);
|
||||
logger.err('Block indexing failed. Trying again in 10 seconds. Reason: ' + (e instanceof Error ? e.message : e));
|
||||
loadingIndicators.setProgress('block-indexing', 100);
|
||||
throw e;
|
||||
}
|
||||
@@ -537,7 +537,7 @@ class Blocks {
|
||||
priceId: lastestPriceId,
|
||||
}]);
|
||||
} else {
|
||||
logger.info(`Cannot save block price for ${blockExtended.height} because the price updater hasnt completed yet. Trying again in 10 seconds.`, logger.tags.mining);
|
||||
logger.info(`Cannot save block price for ${blockExtended.height} because the price updater hasnt completed yet. Trying again in 10 seconds.`)
|
||||
setTimeout(() => {
|
||||
indexer.runSingleTask('blocksPrices');
|
||||
}, 10000);
|
||||
@@ -677,7 +677,7 @@ class Blocks {
|
||||
}
|
||||
|
||||
public async $getBlocks(fromHeight?: number, limit: number = 15): Promise<BlockExtended[]> {
|
||||
let currentHeight = fromHeight !== undefined ? fromHeight : this.currentBlockHeight;
|
||||
let currentHeight = fromHeight !== undefined ? fromHeight : await blocksRepository.$mostRecentBlockHeight();
|
||||
const returnBlocks: BlockExtended[] = [];
|
||||
|
||||
if (currentHeight < 0) {
|
||||
|
||||
@@ -194,13 +194,6 @@ export class Common {
|
||||
);
|
||||
}
|
||||
|
||||
static firstSeenIndexingEnabled(): boolean {
|
||||
return (
|
||||
Common.indexingEnabled() &&
|
||||
config.MEMPOOL.FIRST_SEEN_INDEXING_DAYS !== 0
|
||||
);
|
||||
}
|
||||
|
||||
static setDateMidnight(date: Date): void {
|
||||
date.setUTCHours(0);
|
||||
date.setUTCMinutes(0);
|
||||
|
||||
@@ -4,7 +4,7 @@ import logger from '../logger';
|
||||
import { Common } from './common';
|
||||
|
||||
class DatabaseMigration {
|
||||
private static currentVersion = 50;
|
||||
private static currentVersion = 49;
|
||||
private queryTimeout = 3600_000;
|
||||
private statisticsAddedIndexed = false;
|
||||
private uniqueLogs: string[] = [];
|
||||
@@ -442,11 +442,6 @@ class DatabaseMigration {
|
||||
await this.$executeQuery('TRUNCATE TABLE `blocks_audits`');
|
||||
await this.updateToSchemaVersion(49);
|
||||
}
|
||||
|
||||
if (databaseSchemaVersion < 50 && isBitcoin === true) {
|
||||
await this.$executeQuery('ALTER TABLE `transactions` ADD first_seen datetime DEFAULT NULL, ADD INDEX (first_seen)');
|
||||
await this.updateToSchemaVersion(50);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -670,7 +670,9 @@ class ChannelsApi {
|
||||
AND status != 2
|
||||
`);
|
||||
if (result[0].changedRows ?? 0 > 0) {
|
||||
logger.debug(`Marked ${result[0].changedRows} channels as inactive because they are not in the graph`, logger.tags.ln);
|
||||
logger.info(`Marked ${result[0].changedRows} channels as inactive because they are not in the graph`);
|
||||
} else {
|
||||
logger.debug(`Marked ${result[0].changedRows} channels as inactive because they are not in the graph`);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.err('$setChannelsInactive() error: ' + (e instanceof Error ? e.message : e));
|
||||
|
||||
@@ -685,7 +685,9 @@ class NodesApi {
|
||||
)
|
||||
`);
|
||||
if (result[0].changedRows ?? 0 > 0) {
|
||||
logger.debug(`Marked ${result[0].changedRows} nodes as inactive because they are not in the graph`, logger.tags.ln);
|
||||
logger.info(`Marked ${result[0].changedRows} nodes as inactive because they are not in the graph`);
|
||||
} else {
|
||||
logger.debug(`Marked ${result[0].changedRows} nodes as inactive because they are not in the graph`);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.err('$setNodesInactive() error: ' + (e instanceof Error ? e.message : e));
|
||||
|
||||
@@ -141,13 +141,13 @@ export default class CLightningClient extends EventEmitter implements AbstractLi
|
||||
// main data directory provided, default to using the bitcoin mainnet subdirectory
|
||||
// to be removed in v0.2.0
|
||||
else if (fExists(rpcPath, 'bitcoin', 'lightning-rpc')) {
|
||||
logger.warn(`${rpcPath}/lightning-rpc is missing, using the bitcoin mainnet subdirectory at ${rpcPath}/bitcoin instead.`, logger.tags.ln)
|
||||
logger.warn(`specifying the main lightning data directory is deprecated, please specify the network directory explicitly.\n`, logger.tags.ln)
|
||||
logger.warn(`[CLightningClient] ${rpcPath}/lightning-rpc is missing, using the bitcoin mainnet subdirectory at ${rpcPath}/bitcoin instead.`)
|
||||
logger.warn(`[CLightningClient] specifying the main lightning data directory is deprecated, please specify the network directory explicitly.\n`)
|
||||
rpcPath = path.join(rpcPath, 'bitcoin', 'lightning-rpc')
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(`Connecting to ${rpcPath}`, logger.tags.ln);
|
||||
logger.debug(`[CLightningClient] Connecting to ${rpcPath}`);
|
||||
|
||||
super();
|
||||
this.rpcPath = rpcPath;
|
||||
@@ -172,19 +172,19 @@ export default class CLightningClient extends EventEmitter implements AbstractLi
|
||||
|
||||
this.clientConnectionPromise = new Promise<void>(resolve => {
|
||||
_self.client.on('connect', () => {
|
||||
logger.info(`CLightning client connected`, logger.tags.ln);
|
||||
logger.info(`[CLightningClient] Lightning client connected`);
|
||||
_self.reconnectWait = 1;
|
||||
resolve();
|
||||
});
|
||||
|
||||
_self.client.on('end', () => {
|
||||
logger.err(`CLightning client connection closed, reconnecting`, logger.tags.ln);
|
||||
logger.err('[CLightningClient] Lightning client connection closed, reconnecting');
|
||||
_self.increaseWaitTime();
|
||||
_self.reconnect();
|
||||
});
|
||||
|
||||
_self.client.on('error', error => {
|
||||
logger.err(`CLightning client connection error: ${error}`, logger.tags.ln);
|
||||
logger.err(`[CLightningClient] Lightning client connection error: ${error}`);
|
||||
_self.increaseWaitTime();
|
||||
_self.reconnect();
|
||||
});
|
||||
@@ -196,6 +196,7 @@ export default class CLightningClient extends EventEmitter implements AbstractLi
|
||||
return;
|
||||
}
|
||||
const data = JSON.parse(line);
|
||||
// logger.debug(`[CLightningClient] #${data.id} <-- ${JSON.stringify(data.error || data.result)}`);
|
||||
_self.emit('res:' + data.id, data);
|
||||
});
|
||||
}
|
||||
@@ -216,7 +217,7 @@ export default class CLightningClient extends EventEmitter implements AbstractLi
|
||||
}
|
||||
|
||||
this.reconnectTimeout = setTimeout(() => {
|
||||
logger.debug(`Trying to reconnect...`, logger.tags.ln);
|
||||
logger.debug('[CLightningClient] Trying to reconnect...');
|
||||
|
||||
_self.client.connect(_self.rpcPath);
|
||||
_self.reconnectTimeout = null;
|
||||
@@ -234,6 +235,7 @@ export default class CLightningClient extends EventEmitter implements AbstractLi
|
||||
id: '' + callInt
|
||||
};
|
||||
|
||||
// logger.debug(`[CLightningClient] #${callInt} --> ${method} ${args}`);
|
||||
|
||||
// Wait for the client to connect
|
||||
return this.clientConnectionPromise
|
||||
|
||||
@@ -2,7 +2,6 @@ import { ILightningApi } from '../lightning-api.interface';
|
||||
import FundingTxFetcher from '../../../tasks/lightning/sync-tasks/funding-tx-fetcher';
|
||||
import logger from '../../../logger';
|
||||
import { Common } from '../../common';
|
||||
import config from '../../../config';
|
||||
|
||||
/**
|
||||
* Convert a clightning "listnode" entry to a lnd node entry
|
||||
@@ -41,7 +40,7 @@ export function convertNode(clNode: any): ILightningApi.Node {
|
||||
* Convert clightning "listchannels" response to lnd "describegraph.edges" format
|
||||
*/
|
||||
export async function convertAndmergeBidirectionalChannels(clChannels: any[]): Promise<ILightningApi.Channel[]> {
|
||||
logger.debug(`Converting clightning nodes and channels to lnd graph format`, logger.tags.ln);
|
||||
logger.info('Converting clightning nodes and channels to lnd graph format');
|
||||
|
||||
let loggerTimer = new Date().getTime() / 1000;
|
||||
let channelProcessed = 0;
|
||||
@@ -63,8 +62,8 @@ export async function convertAndmergeBidirectionalChannels(clChannels: any[]): P
|
||||
}
|
||||
|
||||
const elapsedSeconds = Math.round((new Date().getTime() / 1000) - loggerTimer);
|
||||
if (elapsedSeconds > config.LIGHTNING.LOGGER_UPDATE_INTERVAL) {
|
||||
logger.info(`Building complete channels from clightning output. Channels processed: ${channelProcessed + 1} of ${clChannels.length}`, logger.tags.ln);
|
||||
if (elapsedSeconds > 10) {
|
||||
logger.info(`Building complete channels from clightning output. Channels processed: ${channelProcessed + 1} of ${clChannels.length}`);
|
||||
loggerTimer = new Date().getTime() / 1000;
|
||||
}
|
||||
|
||||
@@ -77,7 +76,7 @@ export async function convertAndmergeBidirectionalChannels(clChannels: any[]): P
|
||||
consolidatedChannelList.push(await buildIncompleteChannel(clChannelsDict[short_channel_id]));
|
||||
|
||||
const elapsedSeconds = Math.round((new Date().getTime() / 1000) - loggerTimer);
|
||||
if (elapsedSeconds > config.LIGHTNING.LOGGER_UPDATE_INTERVAL) {
|
||||
if (elapsedSeconds > 10) {
|
||||
logger.info(`Building partial channels from clightning output. Channels processed: ${channelProcessed + 1} of ${keys.length}`);
|
||||
loggerTimer = new Date().getTime() / 1000;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import loadingIndicators from './loading-indicators';
|
||||
import bitcoinClient from './bitcoin/bitcoin-client';
|
||||
import bitcoinSecondClient from './bitcoin/bitcoin-second-client';
|
||||
import rbfCache from './rbf-cache';
|
||||
import transactionRepository from '../repositories/TransactionRepository';
|
||||
|
||||
class Mempool {
|
||||
private static WEBSOCKET_REFRESH_RATE_MS = 10000;
|
||||
@@ -218,14 +217,6 @@ class Mempool {
|
||||
}
|
||||
}
|
||||
|
||||
public async $saveTxFirstSeenTimes(transactions: TransactionExtended[], mempool: { [txid: string]: TransactionExtended }) {
|
||||
for (const tx of transactions) {
|
||||
if (mempool[tx.txid]) {
|
||||
await transactionRepository.$saveTxFirstSeen(tx.txid, tx.firstSeen || Date.now());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateTxPerSecond() {
|
||||
const nowMinusTimeSpan = new Date().getTime() - (1000 * config.STATISTICS.TX_PER_SECOND_SAMPLE_PERIOD);
|
||||
this.txPerSecondArray = this.txPerSecondArray.filter((unixTime) => unixTime > nowMinusTimeSpan);
|
||||
|
||||
@@ -265,9 +265,9 @@ class Mining {
|
||||
}
|
||||
await HashratesRepository.$setLatestRun('last_weekly_hashrates_indexing', new Date().getUTCDate());
|
||||
if (newlyIndexed > 0) {
|
||||
logger.notice(`Weekly mining pools hashrates indexing completed: indexed ${newlyIndexed}`, logger.tags.mining);
|
||||
logger.notice(`Weekly mining pools hashrates indexing completed: indexed ${newlyIndexed}`);
|
||||
} else {
|
||||
logger.debug(`Weekly mining pools hashrates indexing completed: indexed ${newlyIndexed}`, logger.tags.mining);
|
||||
logger.debug(`Weekly mining pools hashrates indexing completed: indexed ${newlyIndexed}`);
|
||||
}
|
||||
loadingIndicators.setProgress('weekly-hashrate-indexing', 100);
|
||||
} catch (e) {
|
||||
@@ -370,14 +370,14 @@ class Mining {
|
||||
|
||||
await HashratesRepository.$setLatestRun('last_hashrates_indexing', new Date().getUTCDate());
|
||||
if (newlyIndexed > 0) {
|
||||
logger.notice(`Daily network hashrate indexing completed: indexed ${newlyIndexed} days`, logger.tags.mining);
|
||||
logger.notice(`Daily network hashrate indexing completed: indexed ${newlyIndexed} days`);
|
||||
} else {
|
||||
logger.debug(`Daily network hashrate indexing completed: indexed ${newlyIndexed} days`, logger.tags.mining);
|
||||
logger.debug(`Daily network hashrate indexing completed: indexed ${newlyIndexed} days`);
|
||||
}
|
||||
loadingIndicators.setProgress('daily-hashrate-indexing', 100);
|
||||
} catch (e) {
|
||||
loadingIndicators.setProgress('daily-hashrate-indexing', 100);
|
||||
logger.err(`Daily network hashrate indexing failed. Trying again in 10 seconds. Reason: ${(e instanceof Error ? e.message : e)}`, logger.tags.mining);
|
||||
logger.err(`Daily network hashrate indexing failed. Trying again in 10 seconds. Reason: ${(e instanceof Error ? e.message : e)}`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@@ -449,9 +449,9 @@ class Mining {
|
||||
}
|
||||
|
||||
if (totalIndexed > 0) {
|
||||
logger.notice(`Indexed ${totalIndexed} difficulty adjustments`, logger.tags.mining);
|
||||
logger.notice(`Indexed ${totalIndexed} difficulty adjustments`);
|
||||
} else {
|
||||
logger.debug(`Indexed ${totalIndexed} difficulty adjustments`, logger.tags.mining);
|
||||
logger.debug(`Indexed ${totalIndexed} difficulty adjustments`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ class PoolsParser {
|
||||
poolNames.push(poolsDuplicated[i].name);
|
||||
}
|
||||
}
|
||||
logger.debug(`Found ${poolNames.length} unique mining pools`, logger.tags.mining);
|
||||
logger.debug(`Found ${poolNames.length} unique mining pools`);
|
||||
|
||||
// Get existing pools from the db
|
||||
let existingPools;
|
||||
@@ -72,7 +72,7 @@ class PoolsParser {
|
||||
existingPools = [];
|
||||
}
|
||||
} catch (e) {
|
||||
logger.err('Cannot get existing pools from the database, skipping pools.json import', logger.tags.mining);
|
||||
logger.err('Cannot get existing pools from the database, skipping pools.json import');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ class PoolsParser {
|
||||
slug = poolsJson['slugs'][poolNames[i]];
|
||||
} catch (e) {
|
||||
if (this.slugWarnFlag === false) {
|
||||
logger.warn(`pools.json does not seem to contain the 'slugs' object`, logger.tags.mining);
|
||||
logger.warn(`pools.json does not seem to contain the 'slugs' object`);
|
||||
this.slugWarnFlag = true;
|
||||
}
|
||||
}
|
||||
@@ -107,7 +107,7 @@ class PoolsParser {
|
||||
if (slug === undefined) {
|
||||
// Only keep alphanumerical
|
||||
slug = poolNames[i].replace(/[^a-z0-9]/gi, '').toLowerCase();
|
||||
logger.warn(`No slug found for '${poolNames[i]}', generating it => '${slug}'`, logger.tags.mining);
|
||||
logger.warn(`No slug found for '${poolNames[i]}', generating it => '${slug}'`);
|
||||
}
|
||||
|
||||
const poolObj = {
|
||||
@@ -143,9 +143,9 @@ class PoolsParser {
|
||||
'addresses': allAddresses,
|
||||
'slug': slug
|
||||
});
|
||||
logger.debug(`Rename '${poolToRename[0].name}' mining pool to ${poolObj.name}`, logger.tags.mining);
|
||||
logger.debug(`Rename '${poolToRename[0].name}' mining pool to ${poolObj.name}`);
|
||||
} else {
|
||||
logger.debug(`Add '${finalPoolName}' mining pool`, logger.tags.mining);
|
||||
logger.debug(`Add '${finalPoolName}' mining pool`);
|
||||
finalPoolDataAdd.push(poolObj);
|
||||
}
|
||||
}
|
||||
@@ -160,14 +160,14 @@ class PoolsParser {
|
||||
}
|
||||
|
||||
if (config.DATABASE.ENABLED === false) { // Don't run db operations
|
||||
logger.info('Mining pools.json import completed (no database)', logger.tags.mining);
|
||||
logger.info('Mining pools.json import completed (no database)');
|
||||
return;
|
||||
}
|
||||
|
||||
if (finalPoolDataAdd.length > 0 || finalPoolDataUpdate.length > 0 ||
|
||||
finalPoolDataRename.length > 0
|
||||
) {
|
||||
logger.debug(`Update pools table now`, logger.tags.mining);
|
||||
logger.debug(`Update pools table now`);
|
||||
|
||||
// Add new mining pools into the database
|
||||
let queryAdd: string = 'INSERT INTO pools(name, link, regexes, addresses, slug) VALUES ';
|
||||
@@ -217,9 +217,9 @@ class PoolsParser {
|
||||
await DB.query({ sql: query, timeout: 120000 });
|
||||
}
|
||||
await this.insertUnknownPool();
|
||||
logger.info('Mining pools.json import completed', logger.tags.mining);
|
||||
logger.info('Mining pools.json import completed');
|
||||
} catch (e) {
|
||||
logger.err(`Cannot import pools in the database`, logger.tags.mining);
|
||||
logger.err(`Cannot import pools in the database`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@@ -227,7 +227,7 @@ class PoolsParser {
|
||||
try {
|
||||
await this.insertUnknownPool();
|
||||
} catch (e) {
|
||||
logger.err(`Cannot insert unknown pool in the database`, logger.tags.mining);
|
||||
logger.err(`Cannot insert unknown pool in the database`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@@ -252,7 +252,7 @@ class PoolsParser {
|
||||
`);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.err('Unable to insert "Unknown" mining pool', logger.tags.mining);
|
||||
logger.err('Unable to insert "Unknown" mining pool');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,17 +272,17 @@ class PoolsParser {
|
||||
for (const updatedPool of finalPoolDataUpdate) {
|
||||
const [pool]: any[] = await DB.query(`SELECT id, name from pools where slug = "${updatedPool.slug}"`);
|
||||
if (pool.length > 0) {
|
||||
logger.notice(`Deleting blocks from ${pool[0].name} mining pool for future re-indexing`, logger.tags.mining);
|
||||
logger.notice(`Deleting blocks from ${pool[0].name} mining pool for future re-indexing`);
|
||||
await DB.query(`DELETE FROM blocks WHERE pool_id = ${pool[0].id}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore early days of Bitcoin as there were not mining pool yet
|
||||
logger.notice(`Deleting blocks with unknown mining pool from height 130635 for future re-indexing`, logger.tags.mining);
|
||||
logger.notice('Deleting blocks with unknown mining pool from height 130635 for future re-indexing');
|
||||
const [unknownPool] = await DB.query(`SELECT id from pools where slug = "unknown"`);
|
||||
await DB.query(`DELETE FROM blocks WHERE pool_id = ${unknownPool[0].id} AND height > 130635`);
|
||||
|
||||
logger.notice(`Truncating hashrates for future re-indexing`, logger.tags.mining);
|
||||
logger.notice('Truncating hashrates for future re-indexing');
|
||||
await DB.query(`DELETE FROM hashrates`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import feeApi from './fee-api';
|
||||
import BlocksAuditsRepository from '../repositories/BlocksAuditsRepository';
|
||||
import BlocksSummariesRepository from '../repositories/BlocksSummariesRepository';
|
||||
import Audit from './audit';
|
||||
import mempool from './mempool';
|
||||
|
||||
class WebsocketHandler {
|
||||
private wss: WebSocket.Server | undefined;
|
||||
@@ -463,10 +462,6 @@ class WebsocketHandler {
|
||||
}
|
||||
}
|
||||
|
||||
if (Common.firstSeenIndexingEnabled()) {
|
||||
await mempool.$saveTxFirstSeenTimes(transactions, _memPool);
|
||||
}
|
||||
|
||||
const removed: string[] = [];
|
||||
// Update mempool to remove transactions included in the new block
|
||||
for (const txId of txIds) {
|
||||
|
||||
@@ -32,7 +32,6 @@ interface IConfig {
|
||||
ADVANCED_GBT_AUDIT: boolean;
|
||||
ADVANCED_GBT_MEMPOOL: boolean;
|
||||
TRANSACTION_INDEXING: boolean;
|
||||
FIRST_SEEN_INDEXING_DAYS: number;
|
||||
};
|
||||
ESPLORA: {
|
||||
REST_API_URL: string;
|
||||
@@ -154,7 +153,6 @@ const defaults: IConfig = {
|
||||
'ADVANCED_GBT_AUDIT': false,
|
||||
'ADVANCED_GBT_MEMPOOL': false,
|
||||
'TRANSACTION_INDEXING': false,
|
||||
'FIRST_SEEN_INDEXING_DAYS': 0,
|
||||
},
|
||||
'ESPLORA': {
|
||||
'REST_API_URL': 'http://127.0.0.1:3000',
|
||||
|
||||
@@ -7,7 +7,6 @@ import HashratesRepository from './repositories/HashratesRepository';
|
||||
import bitcoinClient from './api/bitcoin/bitcoin-client';
|
||||
import priceUpdater from './tasks/price-updater';
|
||||
import PricesRepository from './repositories/PricesRepository';
|
||||
import TransactionRepository from './repositories/TransactionRepository';
|
||||
|
||||
class Indexer {
|
||||
runIndexer = true;
|
||||
@@ -79,7 +78,6 @@ class Indexer {
|
||||
await mining.$generatePoolHashrateHistory();
|
||||
await blocks.$generateBlocksSummariesDatabase();
|
||||
await blocks.$generateCPFPDatabase();
|
||||
await TransactionRepository.$clearOldFirstSeen();
|
||||
} catch (e) {
|
||||
this.indexerRunning = false;
|
||||
logger.err(`Indexer failed, trying again in 10 seconds. Reason: ` + (e instanceof Error ? e.message : e));
|
||||
|
||||
@@ -32,27 +32,22 @@ class Logger {
|
||||
local7: 23
|
||||
};
|
||||
|
||||
public tags = {
|
||||
mining: 'Mining',
|
||||
ln: 'Lightning',
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
public emerg: ((msg: string, tag?: string) => void);
|
||||
public emerg: ((msg: string) => void);
|
||||
// @ts-ignore
|
||||
public alert: ((msg: string, tag?: string) => void);
|
||||
public alert: ((msg: string) => void);
|
||||
// @ts-ignore
|
||||
public crit: ((msg: string, tag?: string) => void);
|
||||
public crit: ((msg: string) => void);
|
||||
// @ts-ignore
|
||||
public err: ((msg: string, tag?: string) => void);
|
||||
public err: ((msg: string) => void);
|
||||
// @ts-ignore
|
||||
public warn: ((msg: string, tag?: string) => void);
|
||||
public warn: ((msg: string) => void);
|
||||
// @ts-ignore
|
||||
public notice: ((msg: string, tag?: string) => void);
|
||||
public notice: ((msg: string) => void);
|
||||
// @ts-ignore
|
||||
public info: ((msg: string, tag?: string) => void);
|
||||
public info: ((msg: string) => void);
|
||||
// @ts-ignore
|
||||
public debug: ((msg: string, tag?: string) => void);
|
||||
public debug: ((msg: string) => void);
|
||||
|
||||
private name = 'mempool';
|
||||
private client: dgram.Socket;
|
||||
@@ -71,8 +66,8 @@ class Logger {
|
||||
|
||||
private addprio(prio): void {
|
||||
this[prio] = (function(_this) {
|
||||
return function(msg, tag?: string) {
|
||||
return _this.msg(prio, msg, tag);
|
||||
return function(msg) {
|
||||
return _this.msg(prio, msg);
|
||||
};
|
||||
})(this);
|
||||
}
|
||||
@@ -90,7 +85,7 @@ class Logger {
|
||||
return '';
|
||||
}
|
||||
|
||||
private msg(priority, msg, tag?: string) {
|
||||
private msg(priority, msg) {
|
||||
let consolemsg, prionum, syslogmsg;
|
||||
if (typeof msg === 'string' && msg.length > 0) {
|
||||
while (msg[msg.length - 1].charCodeAt(0) === 10) {
|
||||
@@ -99,10 +94,10 @@ class Logger {
|
||||
}
|
||||
const network = this.network ? ' <' + this.network + '>' : '';
|
||||
prionum = Logger.priorities[priority] || Logger.priorities.info;
|
||||
consolemsg = `${this.ts()} [${process.pid}] ${priority.toUpperCase()}:${network} ${tag ? '[' + tag + '] ' : ''}${msg}`;
|
||||
consolemsg = `${this.ts()} [${process.pid}] ${priority.toUpperCase()}:${network} ${msg}`;
|
||||
|
||||
if (config.SYSLOG.ENABLED && Logger.priorities[priority] <= Logger.priorities[config.SYSLOG.MIN_PRIORITY]) {
|
||||
syslogmsg = `<${(Logger.facilities[config.SYSLOG.FACILITY] * 8 + prionum)}> ${this.name}[${process.pid}]: ${priority.toUpperCase()}${network} ${tag ? '[' + tag + '] ' : ''}${msg}`;
|
||||
syslogmsg = `<${(Logger.facilities[config.SYSLOG.FACILITY] * 8 + prionum)}> ${this.name}[${process.pid}]: ${priority.toUpperCase()}${network} ${msg}`;
|
||||
this.syslog(syslogmsg);
|
||||
}
|
||||
if (Logger.priorities[priority] > Logger.priorities[config.MEMPOOL.STDOUT_LOG_MIN_PRIORITY]) {
|
||||
|
||||
@@ -136,10 +136,6 @@ export interface CpfpInfo {
|
||||
effectiveFeePerVsize?: number;
|
||||
}
|
||||
|
||||
export interface TransactionExtras extends CpfpInfo {
|
||||
firstSeen?: number;
|
||||
}
|
||||
|
||||
export interface TransactionStripped {
|
||||
txid: string;
|
||||
fee: number;
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
import config from '../config';
|
||||
import DB from '../database';
|
||||
import logger from '../logger';
|
||||
import { Ancestor, CpfpInfo, TransactionExtras } from '../mempool.interfaces';
|
||||
import { Ancestor, CpfpInfo } from '../mempool.interfaces';
|
||||
|
||||
interface TxInfo {
|
||||
interface CpfpSummary {
|
||||
txid: string;
|
||||
cluster: string;
|
||||
root: string;
|
||||
txs: Ancestor[];
|
||||
height: number;
|
||||
fee_rate: number;
|
||||
firstSeen: number;
|
||||
}
|
||||
|
||||
class TransactionRepository {
|
||||
@@ -35,46 +33,6 @@ class TransactionRepository {
|
||||
}
|
||||
}
|
||||
|
||||
public async $saveTxFirstSeen(txid: string, seenAt: number) {
|
||||
try {
|
||||
await DB.query(
|
||||
`
|
||||
INSERT INTO transactions
|
||||
(
|
||||
txid,
|
||||
first_seen
|
||||
)
|
||||
VALUE (?, FROM_UNIXTIME(?))
|
||||
ON DUPLICATE KEY UPDATE
|
||||
first_seen = FROM_UNIXTIME(?)
|
||||
;`,
|
||||
[txid, seenAt, seenAt]
|
||||
);
|
||||
} catch (e: any) {
|
||||
logger.err(`Cannot save transaction first seen time into db. Reason: ` + (e instanceof Error ? e.message : e));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public async $getTransactionExtras(txid: string): Promise<TransactionExtras | void> {
|
||||
try {
|
||||
let query = `
|
||||
SELECT *, UNIX_TIMESTAMP(first_seen) as firstSeen
|
||||
FROM transactions
|
||||
LEFT JOIN cpfp_clusters AS cluster ON cluster.root = transactions.cluster
|
||||
WHERE transactions.txid = ?
|
||||
`;
|
||||
const [rows]: any = await DB.query(query, [txid]);
|
||||
if (rows.length) {
|
||||
rows[0].txs = JSON.parse(rows[0].txs) as Ancestor[];
|
||||
return this.convertCpfp(rows[0]);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.err('Cannot get transaction cpfp info from db. Reason: ' + (e instanceof Error ? e.message : e));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public async $getCpfpInfo(txid: string): Promise<CpfpInfo | void> {
|
||||
try {
|
||||
let query = `
|
||||
@@ -96,34 +54,12 @@ class TransactionRepository {
|
||||
}
|
||||
}
|
||||
|
||||
public async $clearOldFirstSeen() {
|
||||
if (config.MEMPOOL.FIRST_SEEN_INDEXING_DAYS > 0) {
|
||||
const cutoff = Math.floor(Date.now() / 1000) - (config.MEMPOOL.FIRST_SEEN_INDEXING_DAYS * 86400);
|
||||
await this.$clearFirstSeenBefore(cutoff);
|
||||
}
|
||||
}
|
||||
|
||||
private async $clearFirstSeenBefore(cutoff: number) {
|
||||
try {
|
||||
const result = await DB.query(
|
||||
`
|
||||
DELETE FROM transactions
|
||||
WHERE cluster is null AND first_seen < FROM_UNIXTIME(?)
|
||||
;`,
|
||||
[cutoff]
|
||||
);
|
||||
} catch (e: any) {
|
||||
logger.err(`Cannot clear old tx first seen times from db. Reason: ` + (e instanceof Error ? e.message : e));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private convertCpfp(info: TxInfo): TransactionExtras {
|
||||
private convertCpfp(cpfp: CpfpSummary): CpfpInfo {
|
||||
const descendants: Ancestor[] = [];
|
||||
const ancestors: Ancestor[] = [];
|
||||
let matched = false;
|
||||
for (const tx of (info.txs || [])) {
|
||||
if (tx.txid === info.txid) {
|
||||
for (const tx of cpfp.txs) {
|
||||
if (tx.txid === cpfp.txid) {
|
||||
matched = true;
|
||||
} else if (!matched) {
|
||||
descendants.push(tx);
|
||||
@@ -132,10 +68,9 @@ class TransactionRepository {
|
||||
}
|
||||
}
|
||||
return {
|
||||
descendants: descendants?.length ? descendants : undefined,
|
||||
ancestors: ancestors,
|
||||
effectiveFeePerVsize: info.fee_rate,
|
||||
firstSeen: info.firstSeen || undefined,
|
||||
descendants,
|
||||
ancestors,
|
||||
effectiveFeePerVsize: cpfp.fee_rate
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class NetworkSyncService {
|
||||
constructor() {}
|
||||
|
||||
public async $startService(): Promise<void> {
|
||||
logger.info(`Starting lightning network sync service`, logger.tags.ln);
|
||||
logger.info('Starting lightning network sync service');
|
||||
|
||||
this.loggerTimer = new Date().getTime() / 1000;
|
||||
|
||||
@@ -33,11 +33,11 @@ class NetworkSyncService {
|
||||
private async $runTasks(): Promise<void> {
|
||||
const taskStartTime = Date.now();
|
||||
try {
|
||||
logger.debug(`Updating nodes and channels`, logger.tags.ln);
|
||||
logger.info(`Updating nodes and channels`);
|
||||
|
||||
const networkGraph = await lightningApi.$getNetworkGraph();
|
||||
if (networkGraph.nodes.length === 0 || networkGraph.edges.length === 0) {
|
||||
logger.info(`LN Network graph is empty, retrying in 10 seconds`, logger.tags.ln);
|
||||
logger.info(`LN Network graph is empty, retrying in 10 seconds`);
|
||||
setTimeout(() => { this.$runTasks(); }, 10000);
|
||||
return;
|
||||
}
|
||||
@@ -55,7 +55,7 @@ class NetworkSyncService {
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
logger.err(`$runTasks() error: ${e instanceof Error ? e.message : e}`, logger.tags.ln);
|
||||
logger.err('$runTasks() error: ' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
|
||||
setTimeout(() => { this.$runTasks(); }, Math.max(1, (1000 * config.LIGHTNING.GRAPH_REFRESH_INTERVAL) - (Date.now() - taskStartTime)));
|
||||
@@ -79,8 +79,8 @@ class NetworkSyncService {
|
||||
++progress;
|
||||
|
||||
const elapsedSeconds = Math.round((new Date().getTime() / 1000) - this.loggerTimer);
|
||||
if (elapsedSeconds > config.LIGHTNING.LOGGER_UPDATE_INTERVAL) {
|
||||
logger.debug(`Updating node ${progress}/${nodes.length}`, logger.tags.ln);
|
||||
if (elapsedSeconds > 10) {
|
||||
logger.info(`Updating node ${progress}/${nodes.length}`);
|
||||
this.loggerTimer = new Date().getTime() / 1000;
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ class NetworkSyncService {
|
||||
deletedRecords += await NodeRecordsRepository.$deleteUnusedRecords(node.pub_key, customRecordTypes);
|
||||
}
|
||||
}
|
||||
logger.debug(`${progress} nodes updated. ${deletedSockets} sockets deleted. ${deletedRecords} custom records deleted.`);
|
||||
logger.info(`${progress} nodes updated. ${deletedSockets} sockets deleted. ${deletedRecords} custom records deleted.`);
|
||||
|
||||
// If a channel if not present in the graph, mark it as inactive
|
||||
await nodesApi.$setNodesInactive(graphNodesPubkeys);
|
||||
@@ -138,18 +138,18 @@ class NetworkSyncService {
|
||||
++progress;
|
||||
|
||||
const elapsedSeconds = Math.round((new Date().getTime() / 1000) - this.loggerTimer);
|
||||
if (elapsedSeconds > config.LIGHTNING.LOGGER_UPDATE_INTERVAL) {
|
||||
logger.debug(`Updating channel ${progress}/${channels.length}`, logger.tags.ln);
|
||||
if (elapsedSeconds > 10) {
|
||||
logger.info(`Updating channel ${progress}/${channels.length}`);
|
||||
this.loggerTimer = new Date().getTime() / 1000;
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(`${progress} channels updated`, logger.tags.ln);
|
||||
logger.info(`${progress} channels updated`);
|
||||
|
||||
// If a channel if not present in the graph, mark it as inactive
|
||||
await channelsApi.$setChannelsInactive(graphChannelsIds);
|
||||
} catch (e) {
|
||||
logger.err(` Cannot update channel list. Reason: ${(e instanceof Error ? e.message : e)}`, logger.tags.ln);
|
||||
logger.err(`Cannot update channel list. Reason: ${(e instanceof Error ? e.message : e)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,28 +184,26 @@ class NetworkSyncService {
|
||||
if (lowest < node.first_seen) {
|
||||
const query = `UPDATE nodes SET first_seen = FROM_UNIXTIME(?) WHERE public_key = ?`;
|
||||
const params = [lowest, node.public_key];
|
||||
++updated;
|
||||
await DB.query(query, params);
|
||||
}
|
||||
++progress;
|
||||
const elapsedSeconds = Math.round((new Date().getTime() / 1000) - this.loggerTimer);
|
||||
if (elapsedSeconds > config.LIGHTNING.LOGGER_UPDATE_INTERVAL) {
|
||||
logger.debug(`Updating node first seen date ${progress}/${nodes.length}`, logger.tags.ln);
|
||||
if (elapsedSeconds > 10) {
|
||||
logger.info(`Updating node first seen date ${progress}/${nodes.length}`);
|
||||
this.loggerTimer = new Date().getTime() / 1000;
|
||||
++updated;
|
||||
}
|
||||
}
|
||||
if (updated > 0) {
|
||||
logger.debug(`Updated ${updated} node first seen dates`, logger.tags.ln);
|
||||
}
|
||||
logger.info(`Updated ${updated} node first seen dates`);
|
||||
} catch (e) {
|
||||
logger.err(`$updateNodeFirstSeen() error: ${e instanceof Error ? e.message : e}`, logger.tags.ln);
|
||||
logger.err('$updateNodeFirstSeen() error: ' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
}
|
||||
|
||||
private async $lookUpCreationDateFromChain(): Promise<void> {
|
||||
let progress = 0;
|
||||
|
||||
logger.debug(`Running channel creation date lookup`, logger.tags.ln);
|
||||
logger.info(`Running channel creation date lookup`);
|
||||
try {
|
||||
const channels = await channelsApi.$getChannelsWithoutCreatedDate();
|
||||
for (const channel of channels) {
|
||||
@@ -216,17 +214,14 @@ class NetworkSyncService {
|
||||
);
|
||||
++progress;
|
||||
const elapsedSeconds = Math.round((new Date().getTime() / 1000) - this.loggerTimer);
|
||||
if (elapsedSeconds > config.LIGHTNING.LOGGER_UPDATE_INTERVAL) {
|
||||
logger.debug(`Updating channel creation date ${progress}/${channels.length}`, logger.tags.ln);
|
||||
if (elapsedSeconds > 10) {
|
||||
logger.info(`Updating channel creation date ${progress}/${channels.length}`);
|
||||
this.loggerTimer = new Date().getTime() / 1000;
|
||||
}
|
||||
}
|
||||
|
||||
if (channels.length > 0) {
|
||||
logger.debug(`Updated ${channels.length} channels' creation date`, logger.tags.ln);
|
||||
}
|
||||
logger.info(`Updated ${channels.length} channels' creation date`);
|
||||
} catch (e) {
|
||||
logger.err(`$lookUpCreationDateFromChain() error: ${e instanceof Error ? e.message : e}`, logger.tags.ln);
|
||||
logger.err('$lookUpCreationDateFromChain() error: ' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,7 +230,7 @@ class NetworkSyncService {
|
||||
* mark that channel as inactive
|
||||
*/
|
||||
private async $deactivateChannelsWithoutActiveNodes(): Promise<void> {
|
||||
logger.debug(`Find channels which nodes are offline`, logger.tags.ln);
|
||||
logger.info(`Find channels which nodes are offline`);
|
||||
|
||||
try {
|
||||
const result = await DB.query<ResultSetHeader>(`
|
||||
@@ -258,10 +253,12 @@ class NetworkSyncService {
|
||||
`);
|
||||
|
||||
if (result[0].changedRows ?? 0 > 0) {
|
||||
logger.debug(`Marked ${result[0].changedRows} channels as inactive because they are not linked to any active node`, logger.tags.ln);
|
||||
logger.info(`Marked ${result[0].changedRows} channels as inactive because they are not linked to any active node`);
|
||||
} else {
|
||||
logger.debug(`Marked ${result[0].changedRows} channels as inactive because they are not linked to any active node`);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.err(`$deactivateChannelsWithoutActiveNodes() error: ${e instanceof Error ? e.message : e}`, logger.tags.ln);
|
||||
logger.err('$deactivateChannelsWithoutActiveNodes() error: ' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,13 +277,13 @@ class NetworkSyncService {
|
||||
} else {
|
||||
log += ` for the first time`;
|
||||
}
|
||||
logger.info(`${log}`, logger.tags.ln);
|
||||
logger.info(log);
|
||||
|
||||
const channels = await channelsApi.$getChannelsByStatus([0, 1]);
|
||||
for (const channel of channels) {
|
||||
const spendingTx = await bitcoinApi.$getOutspend(channel.transaction_id, channel.transaction_vout);
|
||||
if (spendingTx.spent === true && spendingTx.status?.confirmed === true) {
|
||||
logger.debug(`Marking channel: ${channel.id} as closed.`, logger.tags.ln);
|
||||
logger.debug('Marking channel: ' + channel.id + ' as closed.');
|
||||
await DB.query(`UPDATE channels SET status = 2, closing_date = FROM_UNIXTIME(?) WHERE id = ?`,
|
||||
[spendingTx.status.block_time, channel.id]);
|
||||
if (spendingTx.txid && !channel.closing_transaction_id) {
|
||||
@@ -296,16 +293,16 @@ class NetworkSyncService {
|
||||
|
||||
++progress;
|
||||
const elapsedSeconds = Math.round((new Date().getTime() / 1000) - this.loggerTimer);
|
||||
if (elapsedSeconds > config.LIGHTNING.LOGGER_UPDATE_INTERVAL) {
|
||||
logger.info(`Checking if channel has been closed ${progress}/${channels.length}`, logger.tags.ln);
|
||||
if (elapsedSeconds > 10) {
|
||||
logger.info(`Checking if channel has been closed ${progress}/${channels.length}`);
|
||||
this.loggerTimer = new Date().getTime() / 1000;
|
||||
}
|
||||
}
|
||||
|
||||
this.closedChannelsScanBlock = blocks.getCurrentBlockHeight();
|
||||
logger.debug(`Closed channels scan completed at block ${this.closedChannelsScanBlock}`, logger.tags.ln);
|
||||
logger.info(`Closed channels scan completed at block ${this.closedChannelsScanBlock}`);
|
||||
} catch (e) {
|
||||
logger.err(`$scanForClosedChannels() error: ${e instanceof Error ? e.message : e}`, logger.tags.ln);
|
||||
logger.err('$scanForClosedChannels() error: ' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Common } from '../../api/common';
|
||||
|
||||
class LightningStatsUpdater {
|
||||
public async $startService(): Promise<void> {
|
||||
logger.info(`Starting Lightning Stats service`, logger.tags.ln);
|
||||
logger.info('Starting Lightning Stats service');
|
||||
|
||||
await this.$runTasks();
|
||||
LightningStatsImporter.$run();
|
||||
@@ -27,7 +27,7 @@ class LightningStatsUpdater {
|
||||
const networkGraph = await lightningApi.$getNetworkGraph();
|
||||
await LightningStatsImporter.computeNetworkStats(date.getTime() / 1000, networkGraph);
|
||||
|
||||
logger.debug(`Updated latest network stats`, logger.tags.ln);
|
||||
logger.info(`Updated latest network stats`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,10 +21,10 @@ class FundingTxFetcher {
|
||||
try {
|
||||
this.fundingTxCache = JSON.parse(await fsPromises.readFile(CACHE_FILE_NAME, 'utf-8'));
|
||||
} catch (e) {
|
||||
logger.err(`Unable to parse channels funding txs disk cache. Starting from scratch`, logger.tags.ln);
|
||||
logger.err(`Unable to parse channels funding txs disk cache. Starting from scratch`);
|
||||
this.fundingTxCache = {};
|
||||
}
|
||||
logger.debug(`Imported ${Object.keys(this.fundingTxCache).length} funding tx amount from the disk cache`, logger.tags.ln);
|
||||
logger.debug(`Imported ${Object.keys(this.fundingTxCache).length} funding tx amount from the disk cache`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,27 +44,26 @@ class FundingTxFetcher {
|
||||
++channelProcessed;
|
||||
|
||||
let elapsedSeconds = Math.round((new Date().getTime() / 1000) - loggerTimer);
|
||||
if (elapsedSeconds > config.LIGHTNING.LOGGER_UPDATE_INTERVAL) {
|
||||
if (elapsedSeconds > 10) {
|
||||
elapsedSeconds = Math.round((new Date().getTime() / 1000) - globalTimer);
|
||||
logger.info(`Indexing channels funding tx ${channelProcessed + 1} of ${channelIds.length} ` +
|
||||
`(${Math.floor(channelProcessed / channelIds.length * 10000) / 100}%) | ` +
|
||||
`elapsed: ${elapsedSeconds} seconds`,
|
||||
logger.tags.ln
|
||||
`elapsed: ${elapsedSeconds} seconds`
|
||||
);
|
||||
loggerTimer = new Date().getTime() / 1000;
|
||||
}
|
||||
|
||||
elapsedSeconds = Math.round((new Date().getTime() / 1000) - cacheTimer);
|
||||
if (elapsedSeconds > 60) {
|
||||
logger.debug(`Saving ${Object.keys(this.fundingTxCache).length} funding txs cache into disk`, logger.tags.ln);
|
||||
logger.debug(`Saving ${Object.keys(this.fundingTxCache).length} funding txs cache into disk`);
|
||||
fsPromises.writeFile(CACHE_FILE_NAME, JSON.stringify(this.fundingTxCache));
|
||||
cacheTimer = new Date().getTime() / 1000;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.channelNewlyProcessed > 0) {
|
||||
logger.info(`Indexed ${this.channelNewlyProcessed} additional channels funding tx`, logger.tags.ln);
|
||||
logger.debug(`Saving ${Object.keys(this.fundingTxCache).length} funding txs cache into disk`, logger.tags.ln);
|
||||
logger.info(`Indexed ${this.channelNewlyProcessed} additional channels funding tx`);
|
||||
logger.debug(`Saving ${Object.keys(this.fundingTxCache).length} funding txs cache into disk`);
|
||||
fsPromises.writeFile(CACHE_FILE_NAME, JSON.stringify(this.fundingTxCache));
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ export async function $lookupNodeLocation(): Promise<void> {
|
||||
let nodesUpdated = 0;
|
||||
let geoNamesInserted = 0;
|
||||
|
||||
logger.debug(`Running node location updater using Maxmind`, logger.tags.ln);
|
||||
logger.info(`Running node location updater using Maxmind`);
|
||||
try {
|
||||
const nodes = await nodesApi.$getAllNodes();
|
||||
const lookupCity = await maxmind.open<CityResponse>(config.MAXMIND.GEOLITE2_CITY);
|
||||
@@ -152,8 +152,8 @@ export async function $lookupNodeLocation(): Promise<void> {
|
||||
|
||||
++progress;
|
||||
const elapsedSeconds = Math.round((new Date().getTime() / 1000) - loggerTimer);
|
||||
if (elapsedSeconds > config.LIGHTNING.LOGGER_UPDATE_INTERVAL) {
|
||||
logger.debug(`Updating node location data ${progress}/${nodes.length}`);
|
||||
if (elapsedSeconds > 10) {
|
||||
logger.info(`Updating node location data ${progress}/${nodes.length}`);
|
||||
loggerTimer = new Date().getTime() / 1000;
|
||||
}
|
||||
}
|
||||
@@ -161,7 +161,9 @@ export async function $lookupNodeLocation(): Promise<void> {
|
||||
}
|
||||
|
||||
if (nodesUpdated > 0) {
|
||||
logger.debug(`${nodesUpdated} nodes maxmind data updated, ${geoNamesInserted} geo names inserted`, logger.tags.ln);
|
||||
logger.info(`${nodesUpdated} nodes maxmind data updated, ${geoNamesInserted} geo names inserted`);
|
||||
} else {
|
||||
logger.debug(`${nodesUpdated} nodes maxmind data updated, ${geoNamesInserted} geo names inserted`);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.err('$lookupNodeLocation() error: ' + (e instanceof Error ? e.message : e));
|
||||
|
||||
@@ -8,6 +8,7 @@ import { isIP } from 'net';
|
||||
import { Common } from '../../../api/common';
|
||||
import channelsApi from '../../../api/explorer/channels.api';
|
||||
import nodesApi from '../../../api/explorer/nodes.api';
|
||||
import { ResultSetHeader } from 'mysql2';
|
||||
|
||||
const fsPromises = promises;
|
||||
|
||||
@@ -16,7 +17,7 @@ class LightningStatsImporter {
|
||||
|
||||
async $run(): Promise<void> {
|
||||
const [channels]: any[] = await DB.query('SELECT short_id from channels;');
|
||||
logger.info(`Caching funding txs for currently existing channels`, logger.tags.ln);
|
||||
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) {
|
||||
@@ -107,7 +108,7 @@ class LightningStatsImporter {
|
||||
|
||||
const tx = await fundingTxFetcher.$fetchChannelOpenTx(short_id);
|
||||
if (!tx) {
|
||||
logger.err(`Unable to fetch funding tx for channel ${short_id}. Capacity and creation date is unknown. Skipping channel.`, logger.tags.ln);
|
||||
logger.err(`Unable to fetch funding tx for channel ${short_id}. Capacity and creation date is unknown. Skipping channel.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -320,7 +321,7 @@ class LightningStatsImporter {
|
||||
try {
|
||||
fileList = await fsPromises.readdir(this.topologiesFolder);
|
||||
} catch (e) {
|
||||
logger.err(`Unable to open topology folder at ${this.topologiesFolder}`, logger.tags.ln);
|
||||
logger.err(`Unable to open topology folder at ${this.topologiesFolder}`);
|
||||
throw e;
|
||||
}
|
||||
// Insert history from the most recent to the oldest
|
||||
@@ -358,7 +359,7 @@ class LightningStatsImporter {
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.debug(`Reading ${this.topologiesFolder}/${filename}`, logger.tags.ln);
|
||||
logger.debug(`Reading ${this.topologiesFolder}/${filename}`);
|
||||
let fileContent = '';
|
||||
try {
|
||||
fileContent = await fsPromises.readFile(`${this.topologiesFolder}/${filename}`, 'utf8');
|
||||
@@ -367,7 +368,7 @@ class LightningStatsImporter {
|
||||
totalProcessed++;
|
||||
continue;
|
||||
}
|
||||
logger.err(`Unable to open ${this.topologiesFolder}/${filename}`, logger.tags.ln);
|
||||
logger.err(`Unable to open ${this.topologiesFolder}/${filename}`);
|
||||
totalProcessed++;
|
||||
continue;
|
||||
}
|
||||
@@ -377,7 +378,7 @@ class LightningStatsImporter {
|
||||
graph = JSON.parse(fileContent);
|
||||
graph = await this.cleanupTopology(graph);
|
||||
} catch (e) {
|
||||
logger.debug(`Invalid topology file ${this.topologiesFolder}/${filename}, cannot parse the content. Reason: ${e instanceof Error ? e.message : e}`, logger.tags.ln);
|
||||
logger.debug(`Invalid topology file ${this.topologiesFolder}/${filename}, cannot parse the content. Reason: ${e instanceof Error ? e.message : e}`);
|
||||
totalProcessed++;
|
||||
continue;
|
||||
}
|
||||
@@ -389,20 +390,20 @@ class LightningStatsImporter {
|
||||
}
|
||||
|
||||
if (!logStarted) {
|
||||
logger.info(`Founds a topology file that we did not import. Importing historical lightning stats now.`, logger.tags.ln);
|
||||
logger.info(`Founds a topology file that we did not import. Importing historical lightning stats now.`);
|
||||
logStarted = true;
|
||||
}
|
||||
|
||||
const datestr = `${new Date(timestamp * 1000).toUTCString()} (${timestamp})`;
|
||||
logger.debug(`${datestr}: Found ${graph.nodes.length} nodes and ${graph.edges.length} channels`, logger.tags.ln);
|
||||
logger.debug(`${datestr}: Found ${graph.nodes.length} nodes and ${graph.edges.length} channels`);
|
||||
|
||||
totalProcessed++;
|
||||
|
||||
if (processed > 10) {
|
||||
logger.info(`Generating LN network stats for ${datestr}. Processed ${totalProcessed}/${fileList.length} files`, logger.tags.ln);
|
||||
logger.info(`Generating LN network stats for ${datestr}. Processed ${totalProcessed}/${fileList.length} files`);
|
||||
processed = 0;
|
||||
} else {
|
||||
logger.debug(`Generating LN network stats for ${datestr}. Processed ${totalProcessed}/${fileList.length} files`, logger.tags.ln);
|
||||
logger.debug(`Generating LN network stats for ${datestr}. Processed ${totalProcessed}/${fileList.length} files`);
|
||||
}
|
||||
await fundingTxFetcher.$fetchChannelsFundingTxs(graph.edges.map(channel => channel.channel_id.slice(0, -2)));
|
||||
const stat = await this.computeNetworkStats(timestamp, graph, true);
|
||||
@@ -411,10 +412,10 @@ class LightningStatsImporter {
|
||||
}
|
||||
|
||||
if (totalProcessed > 0) {
|
||||
logger.notice(`Lightning network stats historical import completed`, logger.tags.ln);
|
||||
logger.info(`Lightning network stats historical import completed`);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.err(`Lightning network stats historical failed. Reason: ${e instanceof Error ? e.message : e}`, logger.tags.ln);
|
||||
logger.err(`Lightning network stats historical failed. Reason: ${e instanceof Error ? e.message : e}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,9 +32,9 @@ class PoolsUpdater {
|
||||
this.lastRun = now;
|
||||
|
||||
if (config.SOCKS5PROXY.ENABLED) {
|
||||
logger.info(`Updating latest mining pools from ${this.poolsUrl} over the Tor network`, logger.tags.mining);
|
||||
logger.info(`Updating latest mining pools from ${this.poolsUrl} over the Tor network`);
|
||||
} else {
|
||||
logger.info(`Updating latest mining pools from ${this.poolsUrl} over clearnet`, logger.tags.mining);
|
||||
logger.info(`Updating latest mining pools from ${this.poolsUrl} over clearnet`);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -53,9 +53,9 @@ class PoolsUpdater {
|
||||
}
|
||||
|
||||
if (this.currentSha === undefined) {
|
||||
logger.info(`Downloading pools.json for the first time from ${this.poolsUrl}`, logger.tags.mining);
|
||||
logger.info(`Downloading pools.json for the first time from ${this.poolsUrl}`);
|
||||
} else {
|
||||
logger.warn(`Pools.json is outdated, fetch latest from ${this.poolsUrl}`, logger.tags.mining);
|
||||
logger.warn(`Pools.json is outdated, fetch latest from ${this.poolsUrl}`);
|
||||
}
|
||||
const poolsJson = await this.query(this.poolsUrl);
|
||||
if (poolsJson === undefined) {
|
||||
@@ -63,11 +63,11 @@ class PoolsUpdater {
|
||||
}
|
||||
await poolsParser.migratePoolsJson(poolsJson);
|
||||
await this.updateDBSha(githubSha);
|
||||
logger.notice(`PoolsUpdater completed`, logger.tags.mining);
|
||||
logger.notice('PoolsUpdater completed');
|
||||
|
||||
} catch (e) {
|
||||
this.lastRun = now - (oneWeek - oneDay); // Try again in 24h instead of waiting next week
|
||||
logger.err(`PoolsUpdater failed. Will try again in 24h. Reason: ${e instanceof Error ? e.message : e}`, logger.tags.mining);
|
||||
logger.err('PoolsUpdater failed. Will try again in 24h. Reason: ' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ class PoolsUpdater {
|
||||
await DB.query('DELETE FROM state where name="pools_json_sha"');
|
||||
await DB.query(`INSERT INTO state VALUES('pools_json_sha', NULL, '${githubSha}')`);
|
||||
} catch (e) {
|
||||
logger.err('Cannot save github pools.json sha into the db. Reason: ' + (e instanceof Error ? e.message : e), logger.tags.mining);
|
||||
logger.err('Cannot save github pools.json sha into the db. Reason: ' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,7 +94,7 @@ class PoolsUpdater {
|
||||
const [rows]: any[] = await DB.query('SELECT string FROM state WHERE name="pools_json_sha"');
|
||||
return (rows.length > 0 ? rows[0].string : undefined);
|
||||
} catch (e) {
|
||||
logger.err('Cannot fetch pools.json sha from db. Reason: ' + (e instanceof Error ? e.message : e), logger.tags.mining);
|
||||
logger.err('Cannot fetch pools.json sha from db. Reason: ' + (e instanceof Error ? e.message : e));
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -113,7 +113,7 @@ class PoolsUpdater {
|
||||
}
|
||||
}
|
||||
|
||||
logger.err(`Cannot find "pools.json" in git tree (${this.treeUrl})`, logger.tags.mining);
|
||||
logger.err(`Cannot find "pools.json" in git tree (${this.treeUrl})`);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ class KrakenApi implements PriceFeed {
|
||||
}
|
||||
|
||||
if (Object.keys(priceHistory).length > 0) {
|
||||
logger.notice(`Inserted ${Object.keys(priceHistory).length} Kraken EUR, USD, GBP, JPY, CAD, CHF and AUD weekly price history into db`, logger.tags.mining);
|
||||
logger.notice(`Inserted ${Object.keys(priceHistory).length} Kraken EUR, USD, GBP, JPY, CAD, CHF and AUD weekly price history into db`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ class PriceUpdater {
|
||||
await this.$updatePrice();
|
||||
}
|
||||
} catch (e) {
|
||||
logger.err(`Cannot save BTC prices in db. Reason: ${e instanceof Error ? e.message : e}`, logger.tags.mining);
|
||||
logger.err(`Cannot save BTC prices in db. Reason: ${e instanceof Error ? e.message : e}`);
|
||||
}
|
||||
|
||||
this.running = false;
|
||||
@@ -115,14 +115,14 @@ class PriceUpdater {
|
||||
if (price > 0) {
|
||||
prices.push(price);
|
||||
}
|
||||
logger.debug(`${feed.name} BTC/${currency} price: ${price}`, logger.tags.mining);
|
||||
logger.debug(`${feed.name} BTC/${currency} price: ${price}`);
|
||||
} catch (e) {
|
||||
logger.debug(`Could not fetch BTC/${currency} price at ${feed.name}. Reason: ${(e instanceof Error ? e.message : e)}`, logger.tags.mining);
|
||||
logger.debug(`Could not fetch BTC/${currency} price at ${feed.name}. Reason: ${(e instanceof Error ? e.message : e)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (prices.length === 1) {
|
||||
logger.debug(`Only ${prices.length} feed available for BTC/${currency} price`, logger.tags.mining);
|
||||
logger.debug(`Only ${prices.length} feed available for BTC/${currency} price`);
|
||||
}
|
||||
|
||||
// Compute average price, non weighted
|
||||
@@ -175,9 +175,9 @@ class PriceUpdater {
|
||||
++insertedCount;
|
||||
}
|
||||
if (insertedCount > 0) {
|
||||
logger.notice(`Inserted ${insertedCount} MtGox USD weekly price history into db`, logger.tags.mining);
|
||||
logger.notice(`Inserted ${insertedCount} MtGox USD weekly price history into db`);
|
||||
} else {
|
||||
logger.debug(`Inserted ${insertedCount} MtGox USD weekly price history into db`, logger.tags.mining);
|
||||
logger.debug(`Inserted ${insertedCount} MtGox USD weekly price history into db`);
|
||||
}
|
||||
|
||||
// Insert Kraken weekly prices
|
||||
@@ -198,7 +198,7 @@ class PriceUpdater {
|
||||
private async $insertMissingRecentPrices(type: 'hour' | 'day'): Promise<void> {
|
||||
const existingPriceTimes = await PricesRepository.$getPricesTimes();
|
||||
|
||||
logger.info(`Fetching ${type === 'day' ? 'dai' : 'hour'}ly price history from exchanges and saving missing ones into the database`, logger.tags.mining);
|
||||
logger.info(`Fetching ${type === 'day' ? 'dai' : 'hour'}ly price history from exchanges and saving missing ones into the database, this may take a while`);
|
||||
|
||||
const historicalPrices: PriceHistory[] = [];
|
||||
|
||||
@@ -207,7 +207,7 @@ class PriceUpdater {
|
||||
try {
|
||||
historicalPrices.push(await feed.$fetchRecentPrice(this.currencies, type));
|
||||
} catch (e) {
|
||||
logger.err(`Cannot fetch hourly historical price from ${feed.name}. Ignoring this feed. Reason: ${e instanceof Error ? e.message : e}`, logger.tags.mining);
|
||||
logger.err(`Cannot fetch hourly historical price from ${feed.name}. Ignoring this feed. Reason: ${e instanceof Error ? e.message : e}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,9 +252,9 @@ class PriceUpdater {
|
||||
}
|
||||
|
||||
if (totalInserted > 0) {
|
||||
logger.notice(`Inserted ${totalInserted} ${type === 'day' ? 'dai' : 'hour'}ly historical prices into the db`, logger.tags.mining);
|
||||
logger.notice(`Inserted ${totalInserted} ${type === 'day' ? 'dai' : 'hour'}ly historical prices into the db`);
|
||||
} else {
|
||||
logger.debug(`Inserted ${totalInserted} ${type === 'day' ? 'dai' : 'hour'}ly historical prices into the db`, logger.tags.mining);
|
||||
logger.debug(`Inserted ${totalInserted} ${type === 'day' ? 'dai' : 'hour'}ly historical prices into the db`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,9 +31,6 @@ __LIQUID_WEBSITE_URL__=${LIQUID_WEBSITE_URL:=https://liquid.network}
|
||||
__BISQ_WEBSITE_URL__=${BISQ_WEBSITE_URL:=https://bisq.markets}
|
||||
__MINING_DASHBOARD__=${MINING_DASHBOARD:=true}
|
||||
__LIGHTNING__=${LIGHTNING:=false}
|
||||
__MAINNET_BLOCK_AUDIT_START_HEIGHT__=${MAINNET_BLOCK_AUDIT_START_HEIGHT:=0}
|
||||
__TESTNET_BLOCK_AUDIT_START_HEIGHT__=${TESTNET_BLOCK_AUDIT_START_HEIGHT:=0}
|
||||
__SIGNET_BLOCK_AUDIT_START_HEIGHT__=${SIGNET_BLOCK_AUDIT_START_HEIGHT:=0}
|
||||
|
||||
# Export as environment variables to be used by envsubst
|
||||
export __TESTNET_ENABLED__
|
||||
@@ -55,9 +52,6 @@ export __LIQUID_WEBSITE_URL__
|
||||
export __BISQ_WEBSITE_URL__
|
||||
export __MINING_DASHBOARD__
|
||||
export __LIGHTNING__
|
||||
export __MAINNET_BLOCK_AUDIT_START_HEIGHT__
|
||||
export __TESTNET_BLOCK_AUDIT_START_HEIGHT__
|
||||
export __SIGNET_BLOCK_AUDIT_START_HEIGHT__
|
||||
|
||||
folder=$(find /var/www/mempool -name "config.js" | xargs dirname)
|
||||
echo ${folder}
|
||||
|
||||
@@ -162,6 +162,11 @@
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.scss",
|
||||
{
|
||||
"input": "src/theme-contrast.scss",
|
||||
"bundleName": "contrast",
|
||||
"inject": false
|
||||
},
|
||||
"node_modules/@fortawesome/fontawesome-svg-core/styles.css"
|
||||
],
|
||||
"vendorChunk": true,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const mempoolFeeColors = [
|
||||
export const defaultMempoolFeeColors = [
|
||||
'557d00',
|
||||
'5d7d01',
|
||||
'637d02',
|
||||
@@ -31,6 +31,39 @@ export const mempoolFeeColors = [
|
||||
'b9254b',
|
||||
];
|
||||
|
||||
export const contrastMempoolFeeColors = [
|
||||
'0082e6',
|
||||
'0984df',
|
||||
'1285d9',
|
||||
'1a87d2',
|
||||
'2388cb',
|
||||
'2c8ac5',
|
||||
'358bbe',
|
||||
'3e8db7',
|
||||
'468eb0',
|
||||
'4f90aa',
|
||||
'5892a3',
|
||||
'61939c',
|
||||
'6a9596',
|
||||
'72968f',
|
||||
'7b9888',
|
||||
'849982',
|
||||
'8d9b7b',
|
||||
'959c74',
|
||||
'9e9e6e',
|
||||
'a79f67',
|
||||
'b0a160',
|
||||
'b9a35a',
|
||||
'c1a453',
|
||||
'caa64c',
|
||||
'd3a745',
|
||||
'dca93f',
|
||||
'e5aa38',
|
||||
'edac31',
|
||||
'f6ad2b',
|
||||
'ffaf24',
|
||||
];
|
||||
|
||||
export const chartColors = [
|
||||
"#D81B60",
|
||||
"#8E24AA",
|
||||
|
||||
@@ -15,6 +15,7 @@ import { SharedModule } from './shared/shared.module';
|
||||
import { StorageService } from './services/storage.service';
|
||||
import { HttpCacheInterceptor } from './services/http-cache.interceptor';
|
||||
import { LanguageService } from './services/language.service';
|
||||
import { ThemeService } from './services/theme.service';
|
||||
import { FiatShortenerPipe } from './shared/pipes/fiat-shortener.pipe';
|
||||
import { ShortenStringPipe } from './shared/pipes/shorten-string-pipe/shorten-string.pipe';
|
||||
import { CapAddressPipe } from './shared/pipes/cap-address-pipe/cap-address-pipe';
|
||||
@@ -30,6 +31,7 @@ const providers = [
|
||||
StorageService,
|
||||
EnterpriseService,
|
||||
LanguageService,
|
||||
ThemeService,
|
||||
ShortenStringPipe,
|
||||
FiatShortenerPipe,
|
||||
CapAddressPipe,
|
||||
|
||||
@@ -61,18 +61,18 @@
|
||||
}
|
||||
|
||||
.big-fiat {
|
||||
color: #3bcc49;
|
||||
color: var(--green);
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
|
||||
.card {
|
||||
background-color: #1d1f31;
|
||||
background-color: var(--bg);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
color: #4a68b9;
|
||||
color: var(--title-fg);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
.progress {
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
background-color: #2d3348;
|
||||
background-color: var(--secondary);
|
||||
height: 1.1rem;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
width: 150px;
|
||||
}
|
||||
.mobile-even tr:nth-of-type(even) {
|
||||
background-color: #181b2d;
|
||||
background-color: var(--stat-box-bg);
|
||||
}
|
||||
.mobile-even tr:nth-of-type(odd) {
|
||||
background-color: inherit;
|
||||
|
||||
@@ -12,15 +12,15 @@
|
||||
}
|
||||
|
||||
.green {
|
||||
color:#28a745;
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
.red {
|
||||
color:#dc3545;
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
.grey {
|
||||
color:#6c757d;
|
||||
color: var(--grey);
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
.green-color {
|
||||
color: #3bcc49;
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
.green-color {
|
||||
color: #3bcc49;
|
||||
color: var(--green);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { Router, NavigationEnd } from '@angular/router';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { OpenGraphService } from '../../services/opengraph.service';
|
||||
import { NgbTooltipConfig } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { ThemeService } from 'src/app/services/theme.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@@ -19,6 +20,7 @@ export class AppComponent implements OnInit {
|
||||
private stateService: StateService,
|
||||
private openGraphService: OpenGraphService,
|
||||
private location: Location,
|
||||
private theme: ThemeService,
|
||||
tooltipConfig: NgbTooltipConfig,
|
||||
@Inject(LOCALE_ID) private locale: string,
|
||||
) {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: #1d1f31;
|
||||
background-color: var(--bg);
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
align-items: center;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: #1d1f31;
|
||||
background-color: var(--bg);
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
li.nav-item.active {
|
||||
background-color: #653b9c;
|
||||
background-color: var(--tertiary);
|
||||
}
|
||||
|
||||
fa-icon {
|
||||
@@ -95,23 +95,23 @@ nav {
|
||||
}
|
||||
|
||||
.mainnet.active {
|
||||
background-color: #653b9c;
|
||||
background-color: var(--tertiary);
|
||||
}
|
||||
|
||||
.liquid.active {
|
||||
background-color: #116761;
|
||||
background-color: var(--liquid);
|
||||
}
|
||||
|
||||
.liquidtestnet.active {
|
||||
background-color: #494a4a;
|
||||
background-color: var(--liquidtestnet);
|
||||
}
|
||||
|
||||
.testnet.active {
|
||||
background-color: #1d486f;
|
||||
background-color: var(--testnet);
|
||||
}
|
||||
|
||||
.signet.active {
|
||||
background-color: #6f1d5d;
|
||||
background-color: var(--signet);
|
||||
}
|
||||
|
||||
.dropdown-divider {
|
||||
|
||||
@@ -170,7 +170,7 @@ export class BlockFeeRatesGraphComponent implements OnInit {
|
||||
borderRadius: 4,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
textStyle: {
|
||||
color: '#b1b1b1',
|
||||
color: 'var(--tooltip-grey)',
|
||||
align: 'left',
|
||||
},
|
||||
borderColor: '#000',
|
||||
@@ -238,7 +238,7 @@ export class BlockFeeRatesGraphComponent implements OnInit {
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dotted',
|
||||
color: '#ffffff66',
|
||||
color: 'var(--transparent-fg)',
|
||||
opacity: 0.25,
|
||||
}
|
||||
},
|
||||
@@ -305,7 +305,7 @@ export class BlockFeeRatesGraphComponent implements OnInit {
|
||||
const now = new Date();
|
||||
// @ts-ignore
|
||||
this.chartOptions.grid.bottom = 40;
|
||||
this.chartOptions.backgroundColor = '#11131f';
|
||||
this.chartOptions.backgroundColor = 'var(--active-bg)';
|
||||
this.chartInstance.setOption(this.chartOptions);
|
||||
download(this.chartInstance.getDataURL({
|
||||
pixelRatio: 2,
|
||||
|
||||
@@ -142,7 +142,7 @@ export class BlockFeesGraphComponent implements OnInit {
|
||||
borderRadius: 4,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
textStyle: {
|
||||
color: '#b1b1b1',
|
||||
color: 'var(--tooltip-grey)',
|
||||
align: 'left',
|
||||
},
|
||||
borderColor: '#000',
|
||||
@@ -205,7 +205,7 @@ export class BlockFeesGraphComponent implements OnInit {
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dotted',
|
||||
color: '#ffffff66',
|
||||
color: 'var(--transparent-fg)',
|
||||
opacity: 0.25,
|
||||
}
|
||||
},
|
||||
@@ -296,7 +296,7 @@ export class BlockFeesGraphComponent implements OnInit {
|
||||
const now = new Date();
|
||||
// @ts-ignore
|
||||
this.chartOptions.grid.bottom = 40;
|
||||
this.chartOptions.backgroundColor = '#11131f';
|
||||
this.chartOptions.backgroundColor = 'var(--active-bg)';
|
||||
this.chartInstance.setOption(this.chartOptions);
|
||||
download(this.chartInstance.getDataURL({
|
||||
pixelRatio: 2,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding-bottom: 100%;
|
||||
background: #181b2d;
|
||||
background: var(--stat-box-bg);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
@@ -5,6 +5,8 @@ import BlockScene from './block-scene';
|
||||
import TxSprite from './tx-sprite';
|
||||
import TxView from './tx-view';
|
||||
import { Position } from './sprite-types';
|
||||
import { ThemeService } from 'src/app/services/theme.service';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'app-block-overview-graph',
|
||||
@@ -26,6 +28,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
||||
|
||||
@ViewChild('blockCanvas')
|
||||
canvas: ElementRef<HTMLCanvasElement>;
|
||||
themeChangedSubscription: Subscription;
|
||||
|
||||
gl: WebGLRenderingContext;
|
||||
animationFrameRequest: number;
|
||||
@@ -48,6 +51,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
||||
constructor(
|
||||
readonly ngZone: NgZone,
|
||||
readonly elRef: ElementRef,
|
||||
private themeService: ThemeService,
|
||||
) {
|
||||
this.vertexArray = new FastVertexArray(512, TxSprite.dataSize);
|
||||
}
|
||||
@@ -59,6 +63,11 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
||||
this.initCanvas();
|
||||
|
||||
this.resizeCanvas();
|
||||
|
||||
this.themeChangedSubscription = this.themeService.themeChanged$.subscribe(() => {
|
||||
// force full re-render
|
||||
this.resizeCanvas();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnChanges(changes): void {
|
||||
@@ -79,6 +88,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
||||
}
|
||||
this.canvas.nativeElement.removeEventListener('webglcontextlost', this.handleContextLost);
|
||||
this.canvas.nativeElement.removeEventListener('webglcontextrestored', this.handleContextRestored);
|
||||
this.themeChangedSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
clear(direction): void {
|
||||
@@ -195,7 +205,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
||||
this.start();
|
||||
} else {
|
||||
this.scene = new BlockScene({ width: this.displayWidth, height: this.displayHeight, resolution: this.resolution,
|
||||
blockLimit: this.blockLimit, orientation: this.orientation, flip: this.flip, vertexArray: this.vertexArray });
|
||||
blockLimit: this.blockLimit, orientation: this.orientation, flip: this.flip, vertexArray: this.vertexArray, theme: this.themeService });
|
||||
this.start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,13 @@ import { FastVertexArray } from './fast-vertex-array';
|
||||
import TxView from './tx-view';
|
||||
import { TransactionStripped } from '../../interfaces/websocket.interface';
|
||||
import { Position, Square, ViewUpdateParams } from './sprite-types';
|
||||
import { ThemeService } from 'src/app/services/theme.service';
|
||||
|
||||
export default class BlockScene {
|
||||
scene: { count: number, offset: { x: number, y: number}};
|
||||
vertexArray: FastVertexArray;
|
||||
txs: { [key: string]: TxView };
|
||||
theme: ThemeService;
|
||||
orientation: string;
|
||||
flip: boolean;
|
||||
width: number;
|
||||
@@ -22,11 +24,11 @@ export default class BlockScene {
|
||||
animateUntil = 0;
|
||||
dirty: boolean;
|
||||
|
||||
constructor({ width, height, resolution, blockLimit, orientation, flip, vertexArray }:
|
||||
constructor({ width, height, resolution, blockLimit, orientation, flip, vertexArray, theme }:
|
||||
{ width: number, height: number, resolution: number, blockLimit: number,
|
||||
orientation: string, flip: boolean, vertexArray: FastVertexArray }
|
||||
orientation: string, flip: boolean, vertexArray: FastVertexArray, theme: ThemeService }
|
||||
) {
|
||||
this.init({ width, height, resolution, blockLimit, orientation, flip, vertexArray });
|
||||
this.init({ width, height, resolution, blockLimit, orientation, flip, vertexArray, theme });
|
||||
}
|
||||
|
||||
resize({ width = this.width, height = this.height, animate = true }: { width?: number, height?: number, animate: boolean }): void {
|
||||
@@ -67,7 +69,7 @@ export default class BlockScene {
|
||||
});
|
||||
this.layout = new BlockLayout({ width: this.gridWidth, height: this.gridHeight });
|
||||
txs.forEach(tx => {
|
||||
const txView = new TxView(tx, this.vertexArray);
|
||||
const txView = new TxView(tx, this.vertexArray, this.theme);
|
||||
this.txs[tx.txid] = txView;
|
||||
this.place(txView);
|
||||
this.saveGridToScreenPosition(txView);
|
||||
@@ -114,7 +116,7 @@ export default class BlockScene {
|
||||
});
|
||||
txs.forEach(tx => {
|
||||
if (!this.txs[tx.txid]) {
|
||||
this.txs[tx.txid] = new TxView(tx, this.vertexArray);
|
||||
this.txs[tx.txid] = new TxView(tx, this.vertexArray, this.theme);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -156,7 +158,7 @@ export default class BlockScene {
|
||||
if (resetLayout) {
|
||||
add.forEach(tx => {
|
||||
if (!this.txs[tx.txid]) {
|
||||
this.txs[tx.txid] = new TxView(tx, this.vertexArray);
|
||||
this.txs[tx.txid] = new TxView(tx, this.vertexArray, this.theme);
|
||||
}
|
||||
});
|
||||
this.layout = new BlockLayout({ width: this.gridWidth, height: this.gridHeight });
|
||||
@@ -166,7 +168,7 @@ export default class BlockScene {
|
||||
} else {
|
||||
// try to insert new txs directly
|
||||
const remaining = [];
|
||||
add.map(tx => new TxView(tx, this.vertexArray)).sort(feeRateDescending).forEach(tx => {
|
||||
add.map(tx => new TxView(tx, this.vertexArray, this.theme)).sort(feeRateDescending).forEach(tx => {
|
||||
if (!this.tryInsertByFee(tx)) {
|
||||
remaining.push(tx);
|
||||
}
|
||||
@@ -192,13 +194,14 @@ export default class BlockScene {
|
||||
this.animateUntil = Math.max(this.animateUntil, tx.setHover(value));
|
||||
}
|
||||
|
||||
private init({ width, height, resolution, blockLimit, orientation, flip, vertexArray }:
|
||||
private init({ width, height, resolution, blockLimit, orientation, flip, vertexArray, theme }:
|
||||
{ width: number, height: number, resolution: number, blockLimit: number,
|
||||
orientation: string, flip: boolean, vertexArray: FastVertexArray }
|
||||
orientation: string, flip: boolean, vertexArray: FastVertexArray, theme: ThemeService }
|
||||
): void {
|
||||
this.orientation = orientation;
|
||||
this.flip = flip;
|
||||
this.vertexArray = vertexArray;
|
||||
this.theme = theme;
|
||||
|
||||
this.scene = {
|
||||
count: 0,
|
||||
|
||||
@@ -2,20 +2,10 @@ import TxSprite from './tx-sprite';
|
||||
import { FastVertexArray } from './fast-vertex-array';
|
||||
import { TransactionStripped } from '../../interfaces/websocket.interface';
|
||||
import { SpriteUpdateParams, Square, Color, ViewUpdateParams } from './sprite-types';
|
||||
import { feeLevels, mempoolFeeColors } from '../../app.constants';
|
||||
import BlockScene from './block-scene';
|
||||
import { feeLevels } from '../../app.constants';
|
||||
import { ThemeService } from 'src/app/services/theme.service';
|
||||
|
||||
const hoverTransitionTime = 300;
|
||||
const defaultHoverColor = hexToColor('1bd8f4');
|
||||
|
||||
const feeColors = mempoolFeeColors.map(hexToColor);
|
||||
const auditFeeColors = feeColors.map((color) => darken(desaturate(color, 0.3), 0.9));
|
||||
const auditColors = {
|
||||
censored: hexToColor('f344df'),
|
||||
missing: darken(desaturate(hexToColor('f344df'), 0.3), 0.7),
|
||||
added: hexToColor('0099ff'),
|
||||
selected: darken(desaturate(hexToColor('0099ff'), 0.3), 0.7),
|
||||
}
|
||||
|
||||
// convert from this class's update format to TxSprite's update format
|
||||
function toSpriteUpdate(params: ViewUpdateParams): SpriteUpdateParams {
|
||||
@@ -37,6 +27,7 @@ export default class TxView implements TransactionStripped {
|
||||
feerate: number;
|
||||
status?: 'found' | 'missing' | 'fresh' | 'added' | 'censored' | 'selected';
|
||||
context?: 'projected' | 'actual';
|
||||
theme: ThemeService;
|
||||
|
||||
initialised: boolean;
|
||||
vertexArray: FastVertexArray;
|
||||
@@ -49,7 +40,7 @@ export default class TxView implements TransactionStripped {
|
||||
|
||||
dirty: boolean;
|
||||
|
||||
constructor(tx: TransactionStripped, vertexArray: FastVertexArray) {
|
||||
constructor(tx: TransactionStripped, vertexArray: FastVertexArray, theme: ThemeService) {
|
||||
this.context = tx.context;
|
||||
this.txid = tx.txid;
|
||||
this.fee = tx.fee;
|
||||
@@ -59,6 +50,7 @@ export default class TxView implements TransactionStripped {
|
||||
this.status = tx.status;
|
||||
this.initialised = false;
|
||||
this.vertexArray = vertexArray;
|
||||
this.theme = theme;
|
||||
|
||||
this.hover = false;
|
||||
|
||||
@@ -131,10 +123,10 @@ export default class TxView implements TransactionStripped {
|
||||
|
||||
// Temporarily override the tx color
|
||||
// returns minimum transition end time
|
||||
setHover(hoverOn: boolean, color: Color | void = defaultHoverColor): number {
|
||||
setHover(hoverOn: boolean, color: Color | void): number {
|
||||
if (hoverOn) {
|
||||
this.hover = true;
|
||||
this.hoverColor = color;
|
||||
this.hoverColor = color || this.theme.defaultHoverColor;
|
||||
|
||||
this.sprite.update({
|
||||
...this.hoverColor,
|
||||
@@ -155,22 +147,22 @@ export default class TxView implements TransactionStripped {
|
||||
|
||||
getColor(): Color {
|
||||
const feeLevelIndex = feeLevels.findIndex((feeLvl) => Math.max(1, this.feerate) < feeLvl) - 1;
|
||||
const feeLevelColor = feeColors[feeLevelIndex] || feeColors[mempoolFeeColors.length - 1];
|
||||
const feeLevelColor = this.theme.feeColors[feeLevelIndex] || this.theme.feeColors[this.theme.mempoolFeeColors.length - 1];
|
||||
// Block audit
|
||||
switch(this.status) {
|
||||
case 'censored':
|
||||
return auditColors.censored;
|
||||
return this.theme.auditColors.censored;
|
||||
case 'missing':
|
||||
return auditColors.missing;
|
||||
return this.theme.auditColors.missing;
|
||||
case 'fresh':
|
||||
return auditColors.missing;
|
||||
return this.theme.auditColors.missing;
|
||||
case 'added':
|
||||
return auditColors.added;
|
||||
return this.theme.auditColors.added;
|
||||
case 'selected':
|
||||
return auditColors.selected;
|
||||
return this.theme.auditColors.selected;
|
||||
case 'found':
|
||||
if (this.context === 'projected') {
|
||||
return auditFeeColors[feeLevelIndex] || auditFeeColors[mempoolFeeColors.length - 1];
|
||||
return this.theme.auditFeeColors[feeLevelIndex] || this.theme.auditFeeColors[this.theme.mempoolFeeColors.length - 1];
|
||||
} else {
|
||||
return feeLevelColor;
|
||||
}
|
||||
@@ -179,31 +171,3 @@ export default class TxView implements TransactionStripped {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function hexToColor(hex: string): Color {
|
||||
return {
|
||||
r: parseInt(hex.slice(0, 2), 16) / 255,
|
||||
g: parseInt(hex.slice(2, 4), 16) / 255,
|
||||
b: parseInt(hex.slice(4, 6), 16) / 255,
|
||||
a: 1
|
||||
};
|
||||
}
|
||||
|
||||
function desaturate(color: Color, amount: number): Color {
|
||||
const gray = (color.r + color.g + color.b) / 6;
|
||||
return {
|
||||
r: color.r + ((gray - color.r) * amount),
|
||||
g: color.g + ((gray - color.g) * amount),
|
||||
b: color.b + ((gray - color.b) * amount),
|
||||
a: color.a,
|
||||
};
|
||||
}
|
||||
|
||||
function darken(color: Color, amount: number): Color {
|
||||
return {
|
||||
r: color.r * amount,
|
||||
g: color.g * amount,
|
||||
b: color.b * amount,
|
||||
a: color.a,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
background: rgba(#11131f, 0.95);
|
||||
border-radius: 4px;
|
||||
box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
|
||||
color: #b1b1b1;
|
||||
color: var(--tooltip-grey);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
@@ -130,7 +130,7 @@ export class BlockPredictionGraphComponent implements OnInit {
|
||||
borderRadius: 4,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
textStyle: {
|
||||
color: '#b1b1b1',
|
||||
color: 'var(--tooltip-grey)',
|
||||
align: 'left',
|
||||
},
|
||||
borderColor: '#000',
|
||||
@@ -177,7 +177,7 @@ export class BlockPredictionGraphComponent implements OnInit {
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dotted',
|
||||
color: '#ffffff66',
|
||||
color: 'var(--transparent-fg)',
|
||||
opacity: 0.25,
|
||||
}
|
||||
},
|
||||
@@ -289,7 +289,7 @@ export class BlockPredictionGraphComponent implements OnInit {
|
||||
const now = new Date();
|
||||
// @ts-ignore
|
||||
this.chartOptions.grid.bottom = 40;
|
||||
this.chartOptions.backgroundColor = '#11131f';
|
||||
this.chartOptions.backgroundColor = 'var(--active-bg)';
|
||||
this.chartInstance.setOption(this.chartOptions);
|
||||
download(this.chartInstance.getDataURL({
|
||||
pixelRatio: 2,
|
||||
|
||||
@@ -142,7 +142,7 @@ export class BlockRewardsGraphComponent implements OnInit {
|
||||
borderRadius: 4,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
textStyle: {
|
||||
color: '#b1b1b1',
|
||||
color: 'var(--tooltip-grey)',
|
||||
align: 'left',
|
||||
},
|
||||
borderColor: '#000',
|
||||
@@ -211,7 +211,7 @@ export class BlockRewardsGraphComponent implements OnInit {
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dotted',
|
||||
color: '#ffffff66',
|
||||
color: 'var(--transparent-fg)',
|
||||
opacity: 0.25,
|
||||
}
|
||||
},
|
||||
@@ -307,7 +307,7 @@ export class BlockRewardsGraphComponent implements OnInit {
|
||||
const now = new Date();
|
||||
// @ts-ignore
|
||||
this.chartOptions.grid.bottom = 40;
|
||||
this.chartOptions.backgroundColor = '#11131f';
|
||||
this.chartOptions.backgroundColor = 'var(--active-bg)';
|
||||
this.chartInstance.setOption(this.chartOptions);
|
||||
download(this.chartInstance.getDataURL({
|
||||
pixelRatio: 2,
|
||||
|
||||
@@ -141,7 +141,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
|
||||
borderRadius: 4,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
textStyle: {
|
||||
color: '#b1b1b1',
|
||||
color: 'var(--tooltip-grey)',
|
||||
align: 'left',
|
||||
},
|
||||
borderColor: '#000',
|
||||
@@ -214,7 +214,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dotted',
|
||||
color: '#ffffff66',
|
||||
color: 'var(--transparent-fg)',
|
||||
opacity: 0.25,
|
||||
}
|
||||
},
|
||||
@@ -236,7 +236,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
|
||||
symbol: 'none',
|
||||
lineStyle: {
|
||||
type: 'solid',
|
||||
color: '#ffffff66',
|
||||
color: 'var(--transparent-fg)',
|
||||
opacity: 1,
|
||||
width: 1,
|
||||
},
|
||||
@@ -314,7 +314,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
|
||||
const now = new Date();
|
||||
// @ts-ignore
|
||||
this.chartOptions.grid.bottom = 40;
|
||||
this.chartOptions.backgroundColor = '#11131f';
|
||||
this.chartOptions.backgroundColor = 'var(--active-bg)';
|
||||
this.chartInstance.setOption(this.chartOptions);
|
||||
download(this.chartInstance.getDataURL({
|
||||
pixelRatio: 2,
|
||||
|
||||
@@ -118,9 +118,7 @@ h1 {
|
||||
}
|
||||
|
||||
a {
|
||||
color: #1ad8f4;
|
||||
&:hover, &:focus {
|
||||
color: #09a3ba;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
@@ -196,7 +194,7 @@ h1 {
|
||||
cursor: pointer;
|
||||
|
||||
&.active {
|
||||
background: #24273e;
|
||||
background: var(--box-bg);
|
||||
}
|
||||
|
||||
&.active, &:hover {
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
}
|
||||
|
||||
.black-background {
|
||||
background-color: #11131f;
|
||||
background-color: var(--active-bg);
|
||||
z-index: 100;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -37,12 +37,12 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
||||
timeLtr: boolean;
|
||||
|
||||
gradientColors = {
|
||||
'': ['#9339f4', '#105fb0'],
|
||||
bisq: ['#9339f4', '#105fb0'],
|
||||
liquid: ['#116761', '#183550'],
|
||||
'liquidtestnet': ['#494a4a', '#272e46'],
|
||||
testnet: ['#1d486f', '#183550'],
|
||||
signet: ['#6f1d5d', '#471850'],
|
||||
'': ['var(--mainnet-alt)', 'var(--primary)'],
|
||||
bisq: ['var(--mainnet-alt)', 'var(--primary)'],
|
||||
liquid: ['var(--liquid)', 'var(--testnet-alt)'],
|
||||
'liquidtestnet': ['var(--liquidtestnet)', 'var(--liquidtestnet-alt)'],
|
||||
testnet: ['var(--testnet)', 'var(--testnet-alt)'],
|
||||
signet: ['var(--signet)', 'var(--signet-alt)'],
|
||||
};
|
||||
|
||||
constructor(
|
||||
@@ -181,7 +181,7 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
||||
left: addLeft + 155 * this.blocks.indexOf(block) + 'px',
|
||||
background: `repeating-linear-gradient(
|
||||
#2d3348,
|
||||
#2d3348 ${greenBackgroundHeight}%,
|
||||
var(--secondary) ${greenBackgroundHeight}%,
|
||||
${this.gradientColors[this.network][0]} ${Math.max(greenBackgroundHeight, 0)}%,
|
||||
${this.gradientColors[this.network][1]} 100%
|
||||
)`,
|
||||
@@ -198,7 +198,7 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
||||
|
||||
return {
|
||||
left: addLeft + 155 * this.emptyBlocks.indexOf(block) + 'px',
|
||||
background: "#2d3348",
|
||||
background: "var(--secondary)",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
}
|
||||
|
||||
.black-background {
|
||||
background-color: #11131f;
|
||||
background-color: var(--active-bg);
|
||||
z-index: 100;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ tr, td, th {
|
||||
}
|
||||
|
||||
.progress {
|
||||
background-color: #2d3348;
|
||||
background-color: var(--secondary);
|
||||
}
|
||||
|
||||
.pool {
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<h5 class="card-title" i18n="difficulty-box.current-period">Current Period</h5>
|
||||
<div class="card-text">{{ epochData.progress | number: '1.2-2' }} <span class="symbol">%</span></div>
|
||||
<div class="progress small-bar">
|
||||
<div class="progress-bar" role="progressbar" style="width: 15%; background-color: #105fb0" [ngStyle]="{'width': epochData.base}"> </div>
|
||||
<div class="progress-bar" role="progressbar" style="width: 15%; background-color: var(--primary)" [ngStyle]="{'width': epochData.base}"> </div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item" *ngIf="showHalving">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
justify-content: space-around;
|
||||
height: 76px;
|
||||
.shared-block {
|
||||
color: #ffffff66;
|
||||
color: var(--transparent-fg);
|
||||
font-size: 12px;
|
||||
}
|
||||
.item {
|
||||
@@ -77,19 +77,19 @@
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: #1d1f31;
|
||||
background-color: var(--bg);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
color: #4a68b9;
|
||||
color: var(--title-fg);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.progress {
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
background-color: #2d3348;
|
||||
background-color: var(--secondary);
|
||||
height: 1.1rem;
|
||||
max-width: 180px;
|
||||
}
|
||||
|
||||
@@ -43,24 +43,24 @@ export class DifficultyComponent implements OnInit {
|
||||
])
|
||||
.pipe(
|
||||
map(([block, da]) => {
|
||||
let colorAdjustments = '#ffffff66';
|
||||
let colorAdjustments = 'var(--transparent-fg)';
|
||||
if (da.difficultyChange > 0) {
|
||||
colorAdjustments = '#3bcc49';
|
||||
colorAdjustments = 'var(--green)';
|
||||
}
|
||||
if (da.difficultyChange < 0) {
|
||||
colorAdjustments = '#dc3545';
|
||||
colorAdjustments = 'var(--red)';
|
||||
}
|
||||
|
||||
let colorPreviousAdjustments = '#dc3545';
|
||||
let colorPreviousAdjustments = 'var(--red)';
|
||||
if (da.previousRetarget) {
|
||||
if (da.previousRetarget >= 0) {
|
||||
colorPreviousAdjustments = '#3bcc49';
|
||||
colorPreviousAdjustments = 'var(--green)';
|
||||
}
|
||||
if (da.previousRetarget === 0) {
|
||||
colorPreviousAdjustments = '#ffffff66';
|
||||
colorPreviousAdjustments = 'var(--transparent-fg)';
|
||||
}
|
||||
} else {
|
||||
colorPreviousAdjustments = '#ffffff66';
|
||||
colorPreviousAdjustments = 'var(--transparent-fg)';
|
||||
}
|
||||
|
||||
const blocksUntilHalving = 210000 - (block.height % 210000);
|
||||
|
||||
@@ -45,7 +45,7 @@ export class FeeDistributionGraphComponent implements OnInit, OnChanges {
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dotted',
|
||||
color: '#ffffff66',
|
||||
color: 'var(--transparent-fg)',
|
||||
opacity: 0.25,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.card-title {
|
||||
color: #4a68b9;
|
||||
color: var(--title-fg);
|
||||
font-size: 10px;
|
||||
margin-bottom: 4px;
|
||||
font-size: 1rem;
|
||||
@@ -36,7 +36,7 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.card-text span {
|
||||
color: #ffffff66;
|
||||
color: var(--transparent-fg);
|
||||
font-size: 12px;
|
||||
top: 0px;
|
||||
}
|
||||
@@ -79,6 +79,7 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
transition: background-color 1s;
|
||||
color: var(--color-fg);
|
||||
&.priority {
|
||||
@media (767px < width < 992px), (width < 576px) {
|
||||
width: 100%;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { Component, OnInit, ChangeDetectionStrategy, OnDestroy, ChangeDetectorRef } from '@angular/core';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { Recommendedfees } from '../../interfaces/websocket.interface';
|
||||
import { feeLevels, mempoolFeeColors } from '../../app.constants';
|
||||
import { feeLevels } from '../../app.constants';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { ThemeService } from 'src/app/services/theme.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-fees-box',
|
||||
@@ -11,14 +12,18 @@ import { tap } from 'rxjs/operators';
|
||||
styleUrls: ['./fees-box.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class FeesBoxComponent implements OnInit {
|
||||
export class FeesBoxComponent implements OnInit, OnDestroy {
|
||||
isLoadingWebSocket$: Observable<boolean>;
|
||||
recommendedFees$: Observable<Recommendedfees>;
|
||||
themeSubscription: Subscription;
|
||||
gradient = 'linear-gradient(to right, #2e324e, #2e324e)';
|
||||
noPriority = '#2e324e';
|
||||
fees: Recommendedfees;
|
||||
|
||||
constructor(
|
||||
private stateService: StateService
|
||||
private stateService: StateService,
|
||||
private themeService: ThemeService,
|
||||
private cd: ChangeDetectorRef,
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
@@ -26,18 +31,32 @@ export class FeesBoxComponent implements OnInit {
|
||||
this.recommendedFees$ = this.stateService.recommendedFees$
|
||||
.pipe(
|
||||
tap((fees) => {
|
||||
let feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => fees.minimumFee >= feeLvl);
|
||||
feeLevelIndex = feeLevelIndex >= 0 ? feeLevels.length - feeLevelIndex : feeLevelIndex;
|
||||
const startColor = '#' + (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]);
|
||||
|
||||
this.gradient = `linear-gradient(to right, ${startColor}, ${endColor})`;
|
||||
this.noPriority = startColor;
|
||||
this.fees = fees;
|
||||
this.setFeeGradient();
|
||||
}
|
||||
)
|
||||
);
|
||||
this.themeSubscription = this.themeService.themeChanged$.subscribe(() => {
|
||||
this.setFeeGradient();
|
||||
})
|
||||
}
|
||||
|
||||
setFeeGradient() {
|
||||
let feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => this.fees.minimumFee >= feeLvl);
|
||||
feeLevelIndex = feeLevelIndex >= 0 ? feeLevels.length - feeLevelIndex : feeLevelIndex;
|
||||
const startColor = '#' + (this.themeService.mempoolFeeColors[feeLevelIndex - 1] || this.themeService.mempoolFeeColors[this.themeService.mempoolFeeColors.length - 1]);
|
||||
|
||||
feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => this.fees.fastestFee >= feeLvl);
|
||||
feeLevelIndex = feeLevelIndex >= 0 ? feeLevels.length - feeLevelIndex : feeLevelIndex;
|
||||
const endColor = '#' + (this.themeService.mempoolFeeColors[feeLevelIndex - 1] || this.themeService.mempoolFeeColors[this.themeService.mempoolFeeColors.length - 1]);
|
||||
|
||||
this.gradient = `linear-gradient(to right, ${startColor}, ${endColor})`;
|
||||
this.noPriority = startColor;
|
||||
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.themeSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
background-color: #1d1f31;
|
||||
background-color: var(--bg);
|
||||
box-shadow: 15px 15px 15px 15px #000;
|
||||
z-index: 10;
|
||||
}
|
||||
@@ -34,16 +34,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
.txPerSecond {
|
||||
color: #4a9ff4;
|
||||
}
|
||||
|
||||
.mempoolSize {
|
||||
color: #4a68b9;
|
||||
}
|
||||
|
||||
.unconfirmedTx {
|
||||
color: #f14d80;
|
||||
color: var(--title-fg);
|
||||
}
|
||||
|
||||
.info-block {
|
||||
@@ -55,7 +47,7 @@
|
||||
.progress {
|
||||
display: inline-flex;
|
||||
width: 160px;
|
||||
background-color: #2d3348;
|
||||
background-color: var(--secondary);
|
||||
height: 1.1rem;
|
||||
}
|
||||
|
||||
|
||||
@@ -115,12 +115,12 @@
|
||||
}
|
||||
.card-title {
|
||||
font-size: 1rem;
|
||||
color: #4a68b9;
|
||||
color: var(--title-fg);
|
||||
}
|
||||
.card-text {
|
||||
font-size: 18px;
|
||||
span {
|
||||
color: #ffffff66;
|
||||
color: var(--transparent-fg);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +210,7 @@ export class HashrateChartComponent implements OnInit {
|
||||
borderRadius: 4,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
textStyle: {
|
||||
color: '#b1b1b1',
|
||||
color: 'var(--tooltip-grey)',
|
||||
align: 'left',
|
||||
},
|
||||
borderColor: '#000',
|
||||
@@ -325,7 +325,7 @@ export class HashrateChartComponent implements OnInit {
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dotted',
|
||||
color: '#ffffff66',
|
||||
color: 'var(--transparent-fg)',
|
||||
opacity: 0.25,
|
||||
}
|
||||
},
|
||||
@@ -436,7 +436,7 @@ export class HashrateChartComponent implements OnInit {
|
||||
const now = new Date();
|
||||
// @ts-ignore
|
||||
this.chartOptions.grid.bottom = 30;
|
||||
this.chartOptions.backgroundColor = '#11131f';
|
||||
this.chartOptions.backgroundColor = 'var(--active-bg)';
|
||||
this.chartInstance.setOption(this.chartOptions);
|
||||
download(this.chartInstance.getDataURL({
|
||||
pixelRatio: 2,
|
||||
|
||||
@@ -189,7 +189,7 @@ export class HashrateChartPoolsComponent implements OnInit {
|
||||
borderRadius: 4,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
textStyle: {
|
||||
color: '#b1b1b1',
|
||||
color: 'var(--tooltip-grey)',
|
||||
align: 'left',
|
||||
},
|
||||
borderColor: '#000',
|
||||
@@ -271,7 +271,7 @@ export class HashrateChartPoolsComponent implements OnInit {
|
||||
const now = new Date();
|
||||
// @ts-ignore
|
||||
this.chartOptions.grid.bottom = 30;
|
||||
this.chartOptions.backgroundColor = '#11131f';
|
||||
this.chartOptions.backgroundColor = 'var(--active-bg)';
|
||||
this.chartInstance.setOption(this.chartOptions);
|
||||
download(this.chartInstance.getDataURL({
|
||||
pixelRatio: 2,
|
||||
|
||||
@@ -151,7 +151,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dotted',
|
||||
color: '#ffffff66',
|
||||
color: 'var(--transparent-fg)',
|
||||
opacity: 0.25,
|
||||
}
|
||||
}
|
||||
@@ -239,7 +239,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
|
||||
const now = new Date();
|
||||
// @ts-ignore
|
||||
this.mempoolStatsChartOption.grid.height = prevHeight + 20;
|
||||
this.mempoolStatsChartOption.backgroundColor = '#11131f';
|
||||
this.mempoolStatsChartOption.backgroundColor = 'var(--active-bg)';
|
||||
this.chartInstance.setOption(this.mempoolStatsChartOption);
|
||||
download(this.chartInstance.getDataURL({
|
||||
pixelRatio: 2,
|
||||
|
||||
@@ -99,7 +99,7 @@ export class LbtcPegsGraphComponent implements OnInit, OnChanges {
|
||||
type: 'line',
|
||||
},
|
||||
formatter: (params: any) => {
|
||||
const colorSpan = (color: string) => `<span class="indicator" style="background-color: #116761;"></span>`;
|
||||
const colorSpan = (color: string) => `<span class="indicator" style="background-color: var(--liquid);"></span>`;
|
||||
let itemFormatted = '<div class="title">' + params[0].axisValue + '</div>';
|
||||
params.map((item: any, index: number) => {
|
||||
if (index < 26) {
|
||||
@@ -131,7 +131,7 @@ export class LbtcPegsGraphComponent implements OnInit, OnChanges {
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dotted',
|
||||
color: '#ffffff66',
|
||||
color: 'var(--transparent-fg)',
|
||||
opacity: 0.25,
|
||||
}
|
||||
}
|
||||
@@ -145,11 +145,11 @@ export class LbtcPegsGraphComponent implements OnInit, OnChanges {
|
||||
showSymbol: false,
|
||||
areaStyle: {
|
||||
opacity: 0.2,
|
||||
color: '#116761',
|
||||
color: 'var(--liquid)',
|
||||
},
|
||||
lineStyle: {
|
||||
width: 3,
|
||||
color: '#116761',
|
||||
color: 'var(--liquid)',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
li.nav-item.active {
|
||||
background-color: #653b9c;
|
||||
background-color: var(--tertiary);
|
||||
}
|
||||
|
||||
fa-icon {
|
||||
@@ -34,7 +34,7 @@ li.nav-item {
|
||||
}
|
||||
|
||||
.navbar-nav {
|
||||
background: #212121;
|
||||
background: var(--navbar-bg);
|
||||
bottom: 0;
|
||||
box-shadow: 0px 0px 15px 0px #000;
|
||||
flex-direction: row;
|
||||
@@ -95,23 +95,23 @@ nav {
|
||||
}
|
||||
|
||||
.mainnet.active {
|
||||
background-color: #653b9c;
|
||||
background-color: var(--tertiary);
|
||||
}
|
||||
|
||||
.liquid.active {
|
||||
background-color: #116761;
|
||||
background-color: var(--liquid);
|
||||
}
|
||||
|
||||
.liquidtestnet.active {
|
||||
background-color: #494a4a;
|
||||
background-color: var(--liquidtestnet);
|
||||
}
|
||||
|
||||
.testnet.active {
|
||||
background-color: #1d486f;
|
||||
background-color: var(--testnet);
|
||||
}
|
||||
|
||||
.signet.active {
|
||||
background-color: #6f1d5d;
|
||||
background-color: var(--signet);
|
||||
}
|
||||
|
||||
.dropdown-divider {
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: #11131f;
|
||||
background: var(--active-bg);
|
||||
text-align: start;
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
li.nav-item.active {
|
||||
background-color: #653b9c;
|
||||
background-color: var(--tertiary);
|
||||
}
|
||||
|
||||
fa-icon {
|
||||
@@ -40,7 +40,7 @@ li.nav-item {
|
||||
}
|
||||
|
||||
.navbar-nav {
|
||||
background: #212121;
|
||||
background: var(--navbar-bg);
|
||||
bottom: 0;
|
||||
box-shadow: 0px 0px 15px 0px #000;
|
||||
flex-direction: row;
|
||||
@@ -110,23 +110,23 @@ nav {
|
||||
}
|
||||
|
||||
.mainnet.active {
|
||||
background-color: #653b9c;
|
||||
background-color: var(--tertiary);
|
||||
}
|
||||
|
||||
.liquid.active {
|
||||
background-color: #116761;
|
||||
background-color: var(--liquid);
|
||||
}
|
||||
|
||||
.liquidtestnet.active {
|
||||
background-color: #494a4a;
|
||||
background-color: var(--liquidtestnet);
|
||||
}
|
||||
|
||||
.testnet.active {
|
||||
background-color: #1d486f;
|
||||
background-color: var(--testnet);
|
||||
}
|
||||
|
||||
.signet.active {
|
||||
background-color: #6f1d5d;
|
||||
background-color: var(--signet);
|
||||
}
|
||||
|
||||
.dropdown-divider {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.progress {
|
||||
background-color: #2d3348;
|
||||
background-color: var(--secondary);
|
||||
position: relative;
|
||||
top: 5px;
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
}
|
||||
|
||||
.black-background {
|
||||
background-color: #11131f;
|
||||
background-color: var(--active-bg);
|
||||
z-index: 100;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -4,11 +4,12 @@ import { MempoolBlock } from '../../interfaces/websocket.interface';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { take, map, switchMap } from 'rxjs/operators';
|
||||
import { feeLevels, mempoolFeeColors } from '../../app.constants';
|
||||
import { feeLevels } from '../../app.constants';
|
||||
import { specialBlocks } from '../../app.constants';
|
||||
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
||||
import { Location } from '@angular/common';
|
||||
import { DifficultyAdjustment } from '../../interfaces/node-api.interface';
|
||||
import { ThemeService } from 'src/app/services/theme.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-mempool-blocks',
|
||||
@@ -58,6 +59,7 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
|
||||
constructor(
|
||||
private router: Router,
|
||||
public stateService: StateService,
|
||||
private themeService: ThemeService,
|
||||
private cd: ChangeDetectorRef,
|
||||
private relativeUrlPipe: RelativeUrlPipe,
|
||||
private location: Location
|
||||
@@ -245,7 +247,7 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
|
||||
trimmedFeeRange.forEach((fee: number) => {
|
||||
let feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => fee >= feeLvl);
|
||||
feeLevelIndex = feeLevelIndex >= 0 ? feeLevels.length - feeLevelIndex : feeLevelIndex;
|
||||
gradientColors.push(mempoolFeeColors[feeLevelIndex - 1] || mempoolFeeColors[mempoolFeeColors.length - 1]);
|
||||
gradientColors.push(this.themeService.mempoolFeeColors[feeLevelIndex - 1] || this.themeService.mempoolFeeColors[this.themeService.mempoolFeeColors.length - 1]);
|
||||
});
|
||||
|
||||
gradientColors.forEach((color, i, gc) => {
|
||||
|
||||
@@ -347,7 +347,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dotted',
|
||||
color: '#ffffff66',
|
||||
color: 'var(--transparent-fg)',
|
||||
opacity: 0.25,
|
||||
}
|
||||
}
|
||||
@@ -396,7 +396,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||
const now = new Date();
|
||||
// @ts-ignore
|
||||
this.mempoolVsizeFeesOptions.grid.height = prevHeight + 20;
|
||||
this.mempoolVsizeFeesOptions.backgroundColor = '#11131f';
|
||||
this.mempoolVsizeFeesOptions.backgroundColor = 'var(--active-bg)';
|
||||
this.chartInstance.setOption(this.mempoolVsizeFeesOptions);
|
||||
download(this.chartInstance.getDataURL({
|
||||
pixelRatio: 2,
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
<a class="title-link" href="" [routerLink]="['/blocks' | relativeUrl]">
|
||||
<h5 class="card-title d-inline" i18n="dashboard.latest-blocks">Latest blocks</h5>
|
||||
<span> </span>
|
||||
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: 'text-top'; font-size: 13px; color: '#4a68b9'"></fa-icon>
|
||||
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: 'text-top'; font-size: 13px; color: 'var(--title-fg)'"></fa-icon>
|
||||
</a>
|
||||
<app-blocks-list [attr.data-cy]="'latest-blocks'" [widget]=true></app-blocks-list>
|
||||
</div>
|
||||
@@ -66,7 +66,7 @@
|
||||
<a class="title-link" href="" [routerLink]="['/graphs/mining/hashrate-difficulty' | relativeUrl]">
|
||||
<h5 class="card-title d-inline" i18n="dashboard.adjustments">Adjustments</h5>
|
||||
<span> </span>
|
||||
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: 'text-top'; font-size: 13px; color: '#4a68b9'"></fa-icon>
|
||||
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: 'text-top'; font-size: 13px; color: 'var(--title-fg)'"></fa-icon>
|
||||
</a>
|
||||
<app-difficulty-adjustments-table [attr.data-cy]="'difficulty-adjustments-table'"></app-difficulty-adjustments-table>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: #1d1f31;
|
||||
background-color: var(--bg);
|
||||
}
|
||||
|
||||
.graph-card {
|
||||
@@ -23,10 +23,10 @@
|
||||
|
||||
.card-title {
|
||||
font-size: 1rem;
|
||||
color: #4a68b9;
|
||||
color: var(--title-fg);
|
||||
}
|
||||
.card-title > a {
|
||||
color: #4a68b9;
|
||||
color: var(--title-fg);
|
||||
}
|
||||
|
||||
.card-body.pool-ranking {
|
||||
|
||||
@@ -118,7 +118,7 @@
|
||||
}
|
||||
.card-title {
|
||||
font-size: 1rem;
|
||||
color: #4a68b9;
|
||||
color: var(--title-fg);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@@ -126,7 +126,7 @@
|
||||
.card-text {
|
||||
font-size: 18px;
|
||||
span {
|
||||
color: #ffffff66;
|
||||
color: var(--transparent-fg);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ export class PoolRankingComponent implements OnInit {
|
||||
name: pool.name + ((isMobile() || this.widget) ? `` : ` (${pool.share}%)`),
|
||||
label: {
|
||||
overflow: 'none',
|
||||
color: '#b1b1b1',
|
||||
color: 'var(--tooltip-grey)',
|
||||
alignTo: 'edge',
|
||||
edgeDistance: edgeDistance,
|
||||
},
|
||||
@@ -154,7 +154,7 @@ export class PoolRankingComponent implements OnInit {
|
||||
borderRadius: 4,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
textStyle: {
|
||||
color: '#b1b1b1',
|
||||
color: 'var(--tooltip-grey)',
|
||||
},
|
||||
borderColor: '#000',
|
||||
formatter: () => {
|
||||
@@ -182,7 +182,7 @@ export class PoolRankingComponent implements OnInit {
|
||||
name: 'Other' + (isMobile() ? `` : ` (${totalShareOther.toFixed(2)}%)`),
|
||||
label: {
|
||||
overflow: 'none',
|
||||
color: '#b1b1b1',
|
||||
color: 'var(--tooltip-grey)',
|
||||
alignTo: 'edge',
|
||||
edgeDistance: edgeDistance
|
||||
},
|
||||
@@ -191,7 +191,7 @@ export class PoolRankingComponent implements OnInit {
|
||||
borderRadius: 4,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
textStyle: {
|
||||
color: '#b1b1b1',
|
||||
color: 'var(--tooltip-grey)',
|
||||
},
|
||||
borderColor: '#000',
|
||||
formatter: () => {
|
||||
@@ -301,7 +301,7 @@ export class PoolRankingComponent implements OnInit {
|
||||
|
||||
onSaveChart() {
|
||||
const now = new Date();
|
||||
this.chartOptions.backgroundColor = '#11131f';
|
||||
this.chartOptions.backgroundColor = 'var(--active-bg)';
|
||||
this.chartInstance.setOption(this.chartOptions);
|
||||
download(this.chartInstance.getDataURL({
|
||||
pixelRatio: 2,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
margin-left: 15px;
|
||||
background: #181b2d;
|
||||
background: var(--stat-box-bg);
|
||||
padding: 0.75rem;
|
||||
width: 0;
|
||||
flex-grow: 1;
|
||||
@@ -43,7 +43,7 @@
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 315px;
|
||||
background: #181b2d;
|
||||
background: var(--stat-box-bg);
|
||||
}
|
||||
|
||||
.row {
|
||||
@@ -65,7 +65,7 @@
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
background: #24273e;
|
||||
background: var(--box-bg);
|
||||
|
||||
&.noimg {
|
||||
opacity: 0;
|
||||
|
||||
@@ -82,7 +82,7 @@ div.scrollable {
|
||||
}
|
||||
|
||||
.progress {
|
||||
background-color: #2d3348;
|
||||
background-color: var(--secondary);
|
||||
}
|
||||
|
||||
.coinbase {
|
||||
@@ -184,7 +184,7 @@ div.scrollable {
|
||||
}
|
||||
|
||||
.block-count-title {
|
||||
color: #4a68b9;
|
||||
color: var(--title-fg);
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
@media (max-width: 767.98px) {
|
||||
|
||||
@@ -145,7 +145,7 @@ export class PoolComponent implements OnInit {
|
||||
borderRadius: 4,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
textStyle: {
|
||||
color: '#b1b1b1',
|
||||
color: 'var(--tooltip-grey)',
|
||||
align: 'left',
|
||||
},
|
||||
borderColor: '#000',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.card-title {
|
||||
color: #4a68b9;
|
||||
color: var(--title-fg);
|
||||
font-size: 10px;
|
||||
margin-bottom: 4px;
|
||||
font-size: 1rem;
|
||||
@@ -50,7 +50,7 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.card-text span {
|
||||
color: #ffffff66;
|
||||
color: var(--transparent-fg);
|
||||
font-size: 12px;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.card-title {
|
||||
color: #4a68b9;
|
||||
color: var(--title-fg);
|
||||
font-size: 10px;
|
||||
margin-bottom: 4px;
|
||||
font-size: 1rem;
|
||||
|
||||
@@ -117,7 +117,7 @@
|
||||
}
|
||||
.inactive {
|
||||
.square {
|
||||
background-color: #ffffff66 !important;
|
||||
background-color: var(--transparent-fg) !important;
|
||||
}
|
||||
.fee-text {
|
||||
text-decoration: line-through;
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<div [formGroup]="themeForm" class="text-small text-center ml-2">
|
||||
<select formControlName="theme" class="custom-select custom-select-sm form-control-secondary form-control mx-auto" style="width: 160px;" (change)="changeTheme()">
|
||||
<option value="default" i18n="theme.mempool-theme">Mempool Theme</option>
|
||||
<option value="contrast" i18n="theme.high-contrast">High Contrast</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -0,0 +1,31 @@
|
||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
|
||||
import { ThemeService } from '../../services/theme.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-theme-selector',
|
||||
templateUrl: './theme-selector.component.html',
|
||||
styleUrls: ['./theme-selector.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ThemeSelectorComponent implements OnInit {
|
||||
themeForm: UntypedFormGroup;
|
||||
themes = ['default', 'contrast'];
|
||||
|
||||
constructor(
|
||||
private formBuilder: UntypedFormBuilder,
|
||||
private themeService: ThemeService,
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.themeForm = this.formBuilder.group({
|
||||
theme: ['default']
|
||||
});
|
||||
this.themeForm.get('theme')?.setValue(this.themeService.theme);
|
||||
}
|
||||
|
||||
changeTheme() {
|
||||
const newTheme = this.themeForm.get('theme')?.value;
|
||||
this.themeService.apply(newTheme);
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,9 @@ export class TimeSpanComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
calculate() {
|
||||
const seconds = Math.floor(this.time);
|
||||
if (seconds < 60) {
|
||||
return $localize`:@@date-base.just-now:Just now`;
|
||||
}
|
||||
let counter: number;
|
||||
for (const i in this.intervals) {
|
||||
if (this.intervals.hasOwnProperty(i)) {
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #ffffff66;
|
||||
color: var(--transparent-fg);
|
||||
}
|
||||
|
||||
&.pair > *:first-child {
|
||||
@@ -79,7 +79,7 @@
|
||||
|
||||
.graph-wrapper {
|
||||
position: relative;
|
||||
background: #181b2d;
|
||||
background: var(--stat-box-bg);
|
||||
padding: 10px 0;
|
||||
padding-bottom: 0;
|
||||
|
||||
|
||||
@@ -54,18 +54,6 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<ng-template [ngIf]="transactionTime !== 0">
|
||||
<tr *ngIf="transactionTime === -1; else firstSeenTmpl">
|
||||
<td><span class="skeleton-loader"></span></td>
|
||||
<td><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<ng-template #firstSeenTmpl>
|
||||
<tr>
|
||||
<td i18n="transaction.first-seen|Transaction first seen">First seen</td>
|
||||
<td><i><app-time-since [time]="transactionTime" [fastRender]="true"></app-time-since></i></td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
<tr *ngIf="latestBlock && tx.status.block_height <= latestBlock.height - 8">
|
||||
<td class="td-width" i18n="transaction.included-in-block|Transaction included in block">Included in block</td>
|
||||
<td>
|
||||
@@ -75,10 +63,10 @@
|
||||
<ng-template [ngIf]="transactionTime > 0">
|
||||
<tr>
|
||||
<td i18n="transaction.confirmed|Transaction Confirmed state">Confirmed</td>
|
||||
<td><app-time-span [time]="tx.status.block_time - transactionTime"></app-time-span></td>
|
||||
<td><app-time-span [time]="tx.status.block_time - transactionTime" [fastRender]="true"></app-time-span></td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet' && (cpfpInfo && (cpfpInfo?.bestDescendant || cpfpInfo?.descendants?.length || cpfpInfo?.ancestors?.length) || !(transactionTime > 0))">
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td class="td-width" i18n="transaction.features|Transaction features">Features</td>
|
||||
<td>
|
||||
<app-tx-features [tx]="tx"></app-tx-features>
|
||||
@@ -509,12 +497,6 @@
|
||||
<button type="button" class="btn btn-outline-info btn-sm btn-small-height float-right" (click)="showCpfpDetails = !showCpfpDetails">CPFP <fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon></button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr *ngIf="tx?.status?.confirmed && (!cpfpInfo || (!cpfpInfo?.bestDescendant && !cpfpInfo?.descendants?.length && !cpfpInfo?.ancestors?.length)) && transactionTime > 0 && network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td class="td-width" i18n="transaction.features|Transaction Features">Features</td>
|
||||
<td>
|
||||
<app-tx-features [tx]="tx"></app-tx-features>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-template>
|
||||
|
||||
@@ -55,11 +55,11 @@
|
||||
}
|
||||
|
||||
.arrow-green {
|
||||
color: #1a9436;
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.arrow-red {
|
||||
color: #dc3545;
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
.container-xl {
|
||||
@@ -85,7 +85,7 @@
|
||||
.graph-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
background: #181b2d;
|
||||
background: var(--stat-box-bg);
|
||||
padding: 10px 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
.pipe(
|
||||
switchMap((txId) =>
|
||||
this.apiService
|
||||
.getTransactionExtras$(txId)
|
||||
.getCpfpinfo$(txId)
|
||||
.pipe(retryWhen((errors) => errors.pipe(
|
||||
mergeMap((error) => {
|
||||
if (!this.tx?.status || this.tx.status.confirmed) {
|
||||
@@ -156,9 +156,6 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
txFeePerVSize: this.tx.effectiveFeePerVsize,
|
||||
});
|
||||
}
|
||||
if (cpfpInfo.firstSeen) {
|
||||
this.transactionTime = cpfpInfo.firstSeen;
|
||||
}
|
||||
this.cpfpInfo = cpfpInfo;
|
||||
});
|
||||
|
||||
|
||||
@@ -13,15 +13,15 @@
|
||||
}
|
||||
}
|
||||
.green {
|
||||
color:#28a745;
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
.red {
|
||||
color:#dc3545;
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
.grey {
|
||||
color:#6c757d;
|
||||
color: var(--grey);
|
||||
}
|
||||
|
||||
.mobile-bottomcol {
|
||||
@@ -56,7 +56,7 @@
|
||||
text-align: right;
|
||||
}
|
||||
.sats {
|
||||
color: #ffffff66;
|
||||
color: var(--transparent-fg);
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,7 @@
|
||||
padding: 0.75rem;
|
||||
font-size: 12px;
|
||||
&:first-child {
|
||||
color: #ffffff66;
|
||||
color: var(--transparent-fg);
|
||||
white-space: pre-wrap;
|
||||
@media (min-width: 476px) {
|
||||
white-space: nowrap;
|
||||
@@ -123,7 +123,7 @@ h2 {
|
||||
}
|
||||
|
||||
.highlight {
|
||||
background-color: #181b2d;
|
||||
background-color: var(--stat-box-bg);
|
||||
}
|
||||
|
||||
.summary {
|
||||
@@ -142,7 +142,7 @@ h2 {
|
||||
}
|
||||
|
||||
.grey-info-text {
|
||||
color:#6c757d;
|
||||
color: var(--grey);
|
||||
font-style: italic;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
background: rgba(#11131f, 0.95);
|
||||
border-radius: 4px;
|
||||
box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
|
||||
color: #b1b1b1;
|
||||
color: var(--tooltip-grey);
|
||||
padding: 10px 15px;
|
||||
text-align: left;
|
||||
pointer-events: none;
|
||||
|
||||
@@ -69,12 +69,12 @@
|
||||
<linearGradient id="input-highlight-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" [attr.stop-color]="gradient[0]" />
|
||||
<stop offset="2%" [attr.stop-color]="gradient[0]" />
|
||||
<stop offset="30%" stop-color="#1bd8f4" />
|
||||
<stop offset="30%" stop-color="var(--info)" />
|
||||
<stop offset="100%" [attr.stop-color]="gradient[1]" />
|
||||
</linearGradient>
|
||||
<linearGradient id="output-highlight-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" [attr.stop-color]="gradient[1]" />
|
||||
<stop offset="70%" stop-color="#1bd8f4" />
|
||||
<stop offset="70%" stop-color="var(--info)" />
|
||||
<stop offset="98%" [attr.stop-color]="gradient[0]" />
|
||||
<stop offset="100%" [attr.stop-color]="gradient[0]" />
|
||||
</linearGradient>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
stroke: url(#output-highlight-gradient);
|
||||
}
|
||||
&.zerovalue {
|
||||
stroke: #1bd8f4;
|
||||
stroke: var(--info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
<a class="title-link" href="" [routerLink]="['/blocks' | relativeUrl]">
|
||||
<h5 class="card-title d-inline" i18n="dashboard.latest-blocks">Latest blocks</h5>
|
||||
<span> </span>
|
||||
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: 'text-top'; font-size: 13px; color: '#4a68b9'"></fa-icon>
|
||||
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: 'text-top'; font-size: 13px; color: 'var(--title-fg)'"></fa-icon>
|
||||
</a>
|
||||
<table class="table lastest-blocks-table">
|
||||
<thead>
|
||||
@@ -140,7 +140,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<app-language-selector></app-language-selector>
|
||||
<div class="pref-selectors">
|
||||
<app-language-selector></app-language-selector>
|
||||
<app-theme-selector></app-theme-selector>
|
||||
</div>
|
||||
|
||||
<div class="terms-of-service">
|
||||
<a [routerLink]="['/terms-of-service']" i18n="shared.terms-of-service|Terms of Service">Terms of Service</a>
|
||||
|
||||
@@ -11,12 +11,12 @@
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: #1d1f31;
|
||||
background-color: var(--bg);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
color: #4a68b9;
|
||||
color: var(--title-fg);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
.progress {
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
background-color: #2d3348;
|
||||
background-color: var(--secondary);
|
||||
height: 1.1rem;
|
||||
max-width: 180px;
|
||||
}
|
||||
@@ -98,7 +98,7 @@
|
||||
.card-text {
|
||||
font-size: 18px;
|
||||
span {
|
||||
color: #ffffff66;
|
||||
color: var(--transparent-fg);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
@@ -323,4 +323,11 @@
|
||||
margin-bottom: 10px;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.pref-selectors {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user