Compare commits
61 Commits
v2.5.0-dev
...
mononaut/t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
420cac42aa | ||
|
|
1b68f32adc | ||
|
|
f2f6e3769a | ||
|
|
f9f8bd25f8 | ||
|
|
26a92cda45 | ||
|
|
a51b4e88d8 | ||
|
|
a975936d3c | ||
|
|
fbbd86d8e0 | ||
|
|
8ccfa5b038 | ||
|
|
04006b8c98 | ||
|
|
3317f5e6db | ||
|
|
79e1beb2ca | ||
|
|
811d0c824f | ||
|
|
f3adab7d26 | ||
|
|
d730366ea7 | ||
|
|
7e60526887 | ||
|
|
ab69c03d8d | ||
|
|
efa352821a | ||
|
|
1dcf6ab599 | ||
|
|
869d7df844 | ||
|
|
597536efaf | ||
|
|
1f5754943a | ||
|
|
e98d03431c | ||
|
|
39d92f57fa | ||
|
|
313df79e33 | ||
|
|
13ceb368e2 | ||
|
|
72e6e36cbb | ||
|
|
729f2aff3e | ||
|
|
1348e2318d | ||
|
|
731443f670 | ||
|
|
fc2edbf1d1 | ||
|
|
8ac514733a | ||
|
|
e986aaf1d9 | ||
|
|
606e6df834 | ||
|
|
743f2a1cd4 | ||
|
|
5e633344c5 | ||
|
|
c4d5ea971e | ||
|
|
04fec6c894 | ||
|
|
343a48818b | ||
|
|
028a26f574 | ||
|
|
dcd0a53fba | ||
|
|
ea5ec7bc32 | ||
|
|
1513c61cd5 | ||
|
|
f18ea6a7a3 | ||
|
|
35ee58befb | ||
|
|
79f79b0e3b | ||
|
|
250df0d56c | ||
|
|
fb137e6247 | ||
|
|
1a4f699c95 | ||
|
|
56b6f79f97 | ||
|
|
4d0637768d | ||
|
|
28c21b3770 | ||
|
|
5658e053d0 | ||
|
|
d17ccbc5ae | ||
|
|
ade3c09b2a | ||
|
|
3572ba837d | ||
|
|
33775f32e2 | ||
|
|
5d95eb475e | ||
|
|
c57542c8ae | ||
|
|
dd5a1847d0 | ||
|
|
a28544d046 |
12
.github/dependabot.yml
vendored
12
.github/dependabot.yml
vendored
@@ -6,23 +6,27 @@ updates:
|
|||||||
interval: daily
|
interval: daily
|
||||||
open-pull-requests-limit: 10
|
open-pull-requests-limit: 10
|
||||||
ignore:
|
ignore:
|
||||||
- update-types: ["version-update:semver-major"]
|
- dependency-name: "*"
|
||||||
|
update-types: ["version-update:semver-major"]
|
||||||
- package-ecosystem: npm
|
- package-ecosystem: npm
|
||||||
directory: "/frontend"
|
directory: "/frontend"
|
||||||
schedule:
|
schedule:
|
||||||
interval: daily
|
interval: daily
|
||||||
open-pull-requests-limit: 10
|
open-pull-requests-limit: 10
|
||||||
ignore:
|
ignore:
|
||||||
- update-types: ["version-update:semver-major"]
|
- dependency-name: "*"
|
||||||
|
update-types: ["version-update:semver-major"]
|
||||||
- package-ecosystem: docker
|
- package-ecosystem: docker
|
||||||
directory: "/docker/backend"
|
directory: "/docker/backend"
|
||||||
schedule:
|
schedule:
|
||||||
interval: daily
|
interval: daily
|
||||||
ignore:
|
ignore:
|
||||||
- update-types: ["version-update:semver-major"]
|
- dependency-name: "*"
|
||||||
|
update-types: ["version-update:semver-major"]
|
||||||
- package-ecosystem: "github-actions"
|
- package-ecosystem: "github-actions"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: daily
|
interval: daily
|
||||||
ignore:
|
ignore:
|
||||||
- update-types: ["version-update:semver-major"]
|
- dependency-name: "*"
|
||||||
|
update-types: ["version-update:semver-major"]
|
||||||
|
|||||||
18
.github/workflows/cypress.yml
vendored
18
.github/workflows/cypress.yml
vendored
@@ -2,7 +2,7 @@ name: Cypress Tests
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [ opened, review_requested, synchronize ]
|
types: [opened, review_requested, synchronize]
|
||||||
jobs:
|
jobs:
|
||||||
cypress:
|
cypress:
|
||||||
if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')"
|
if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')"
|
||||||
@@ -24,36 +24,36 @@ jobs:
|
|||||||
- module: "bisq"
|
- module: "bisq"
|
||||||
spec: |
|
spec: |
|
||||||
cypress/e2e/bisq/bisq.spec.ts
|
cypress/e2e/bisq/bisq.spec.ts
|
||||||
|
|
||||||
name: E2E tests for ${{ matrix.module }}
|
name: E2E tests for ${{ matrix.module }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
path: ${{ matrix.module }}
|
path: ${{ matrix.module }}
|
||||||
|
|
||||||
- name: Setup node
|
- name: Setup node
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 16.15.0
|
node-version: 16.15.0
|
||||||
cache: 'npm'
|
cache: "npm"
|
||||||
cache-dependency-path: ${{ matrix.module }}/frontend/package-lock.json
|
cache-dependency-path: ${{ matrix.module }}/frontend/package-lock.json
|
||||||
|
|
||||||
- name: Chrome browser tests (${{ matrix.module }})
|
- name: Chrome browser tests (${{ matrix.module }})
|
||||||
uses: cypress-io/github-action@v4
|
uses: cypress-io/github-action@v5
|
||||||
with:
|
with:
|
||||||
tag: ${{ github.event_name }}
|
tag: ${{ github.event_name }}
|
||||||
working-directory: ${{ matrix.module }}/frontend
|
working-directory: ${{ matrix.module }}/frontend
|
||||||
build: npm run config:defaults:${{ matrix.module }}
|
build: npm run config:defaults:${{ matrix.module }}
|
||||||
start: npm run start:local-staging
|
start: npm run start:local-staging
|
||||||
wait-on: 'http://localhost:4200'
|
wait-on: "http://localhost:4200"
|
||||||
wait-on-timeout: 120
|
wait-on-timeout: 120
|
||||||
record: true
|
record: true
|
||||||
parallel: true
|
parallel: true
|
||||||
spec: ${{ matrix.spec }}
|
spec: ${{ matrix.spec }}
|
||||||
group: Tests on Chrome (${{ matrix.module }})
|
group: Tests on Chrome (${{ matrix.module }})
|
||||||
browser: "chrome"
|
browser: "chrome"
|
||||||
ci-build-id: '${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}'
|
ci-build-id: "${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}"
|
||||||
env:
|
env:
|
||||||
COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }}
|
COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }}
|
||||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||||
|
|||||||
@@ -742,7 +742,7 @@ class Blocks {
|
|||||||
|
|
||||||
public async $indexCPFP(hash: string, height: number): Promise<void> {
|
public async $indexCPFP(hash: string, height: number): Promise<void> {
|
||||||
let transactions;
|
let transactions;
|
||||||
if (false/*Common.blocksSummariesIndexingEnabled()*/) {
|
if (Common.blocksSummariesIndexingEnabled()) {
|
||||||
transactions = await this.$getStrippedBlockTransactions(hash);
|
transactions = await this.$getStrippedBlockTransactions(hash);
|
||||||
const rawBlock = await bitcoinApi.$getRawBlock(hash);
|
const rawBlock = await bitcoinApi.$getRawBlock(hash);
|
||||||
const block = Block.fromBuffer(rawBlock);
|
const block = Block.fromBuffer(rawBlock);
|
||||||
@@ -751,10 +751,11 @@ class Blocks {
|
|||||||
txMap[tx.getId()] = tx;
|
txMap[tx.getId()] = tx;
|
||||||
}
|
}
|
||||||
for (const tx of transactions) {
|
for (const tx of transactions) {
|
||||||
|
// convert from bitcoinjs to esplora vin format
|
||||||
if (txMap[tx.txid]?.ins) {
|
if (txMap[tx.txid]?.ins) {
|
||||||
tx.vin = txMap[tx.txid].ins.map(vin => {
|
tx.vin = txMap[tx.txid].ins.map(vin => {
|
||||||
return {
|
return {
|
||||||
txid: vin.hash
|
txid: vin.hash.slice().reverse().toString('hex')
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -763,6 +764,7 @@ class Blocks {
|
|||||||
const block = await bitcoinClient.getBlock(hash, 2);
|
const block = await bitcoinClient.getBlock(hash, 2);
|
||||||
transactions = block.tx.map(tx => {
|
transactions = block.tx.map(tx => {
|
||||||
tx.vsize = tx.weight / 4;
|
tx.vsize = tx.weight / 4;
|
||||||
|
tx.fee *= 100_000_000;
|
||||||
return tx;
|
return tx;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -778,9 +780,9 @@ class Blocks {
|
|||||||
totalFee += tx?.fee || 0;
|
totalFee += tx?.fee || 0;
|
||||||
totalVSize += tx.vsize;
|
totalVSize += tx.vsize;
|
||||||
});
|
});
|
||||||
const effectiveFeePerVsize = (totalFee * 100_000_000) / totalVSize;
|
const effectiveFeePerVsize = totalFee / totalVSize;
|
||||||
if (cluster.length > 1) {
|
if (cluster.length > 1) {
|
||||||
await cpfpRepository.$saveCluster(height, cluster.map(tx => { return { txid: tx.txid, weight: tx.vsize * 4, fee: (tx.fee || 0) * 100_000_000 }; }), effectiveFeePerVsize);
|
await cpfpRepository.$saveCluster(height, cluster.map(tx => { return { txid: tx.txid, weight: tx.vsize * 4, fee: tx.fee || 0 }; }), effectiveFeePerVsize);
|
||||||
for (const tx of cluster) {
|
for (const tx of cluster) {
|
||||||
await transactionRepository.$setCluster(tx.txid, cluster[0].txid);
|
await transactionRepository.$setCluster(tx.txid, cluster[0].txid);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
import { MempoolBlock, TransactionExtended, TransactionStripped, MempoolBlockWithTransactions, MempoolBlockDelta } from '../mempool.interfaces';
|
import { MempoolBlock, TransactionExtended, ThreadTransaction, TransactionStripped, MempoolBlockWithTransactions, MempoolBlockDelta, Ancestor } from '../mempool.interfaces';
|
||||||
import { Common } from './common';
|
import { Common } from './common';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import { StaticPool } from 'node-worker-threads-pool';
|
import { Worker } from 'worker_threads';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
class MempoolBlocks {
|
class MempoolBlocks {
|
||||||
private mempoolBlocks: MempoolBlockWithTransactions[] = [];
|
private mempoolBlocks: MempoolBlockWithTransactions[] = [];
|
||||||
private mempoolBlockDeltas: MempoolBlockDelta[] = [];
|
private mempoolBlockDeltas: MempoolBlockDelta[] = [];
|
||||||
private makeTemplatesPool = new StaticPool({
|
private txSelectionWorker: Worker | null = null;
|
||||||
size: 1,
|
|
||||||
task: path.resolve(__dirname, './tx-selection-worker.js'),
|
|
||||||
});
|
|
||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
@@ -146,27 +143,159 @@ class MempoolBlocks {
|
|||||||
return mempoolBlockDeltas;
|
return mempoolBlockDeltas;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async makeBlockTemplates(newMempool: { [txid: string]: TransactionExtended }, blockLimit: number, weightLimit: number | null = null, condenseRest = false): Promise<void> {
|
public async makeBlockTemplates(newMempool: { [txid: string]: TransactionExtended }): Promise<void> {
|
||||||
const { mempool, blocks } = await this.makeTemplatesPool.exec({ mempool: newMempool, blockLimit, weightLimit, condenseRest });
|
// prepare a stripped down version of the mempool with only the minimum necessary data
|
||||||
const deltas = this.calculateMempoolDeltas(this.mempoolBlocks, blocks);
|
// to reduce the overhead of passing this data to the worker thread
|
||||||
|
const strippedMempool: { [txid: string]: ThreadTransaction } = {};
|
||||||
// copy CPFP info across to main thread's mempool
|
Object.values(newMempool).forEach(entry => {
|
||||||
Object.keys(newMempool).forEach((txid) => {
|
strippedMempool[entry.txid] = {
|
||||||
if (newMempool[txid] && mempool[txid]) {
|
txid: entry.txid,
|
||||||
newMempool[txid].effectiveFeePerVsize = mempool[txid].effectiveFeePerVsize;
|
fee: entry.fee,
|
||||||
newMempool[txid].ancestors = mempool[txid].ancestors;
|
weight: entry.weight,
|
||||||
newMempool[txid].descendants = mempool[txid].descendants;
|
feePerVsize: entry.fee / (entry.weight / 4),
|
||||||
newMempool[txid].bestDescendant = mempool[txid].bestDescendant;
|
effectiveFeePerVsize: entry.fee / (entry.weight / 4),
|
||||||
newMempool[txid].cpfpChecked = mempool[txid].cpfpChecked;
|
vin: entry.vin.map(v => v.txid),
|
||||||
}
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
this.mempoolBlocks = blocks;
|
// (re)initialize tx selection worker thread
|
||||||
|
if (!this.txSelectionWorker) {
|
||||||
|
this.txSelectionWorker = new Worker(path.resolve(__dirname, './tx-selection-worker.js'));
|
||||||
|
// if the thread throws an unexpected error, or exits for any other reason,
|
||||||
|
// reset worker state so that it will be re-initialized on the next run
|
||||||
|
this.txSelectionWorker.once('error', () => {
|
||||||
|
this.txSelectionWorker = null;
|
||||||
|
});
|
||||||
|
this.txSelectionWorker.once('exit', () => {
|
||||||
|
this.txSelectionWorker = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// run the block construction algorithm in a separate thread, and wait for a result
|
||||||
|
let threadErrorListener;
|
||||||
|
try {
|
||||||
|
const workerResultPromise = new Promise<{ blocks: ThreadTransaction[][], clusters: { [root: string]: string[] } }>((resolve, reject) => {
|
||||||
|
threadErrorListener = reject;
|
||||||
|
this.txSelectionWorker?.once('message', (result): void => {
|
||||||
|
resolve(result);
|
||||||
|
});
|
||||||
|
this.txSelectionWorker?.once('error', reject);
|
||||||
|
});
|
||||||
|
this.txSelectionWorker.postMessage({ type: 'set', mempool: strippedMempool });
|
||||||
|
const { blocks, clusters } = await workerResultPromise;
|
||||||
|
|
||||||
|
this.processBlockTemplates(newMempool, blocks, clusters);
|
||||||
|
|
||||||
|
// clean up thread error listener
|
||||||
|
this.txSelectionWorker?.removeListener('error', threadErrorListener);
|
||||||
|
} catch (e) {
|
||||||
|
logger.err('makeBlockTemplates failed. ' + (e instanceof Error ? e.message : e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateBlockTemplates(newMempool: { [txid: string]: TransactionExtended }, added: TransactionExtended[], removed: string[]): Promise<void> {
|
||||||
|
if (!this.txSelectionWorker) {
|
||||||
|
// need to reset the worker
|
||||||
|
return this.makeBlockTemplates(newMempool);
|
||||||
|
}
|
||||||
|
// prepare a stripped down version of the mempool with only the minimum necessary data
|
||||||
|
// to reduce the overhead of passing this data to the worker thread
|
||||||
|
const addedStripped: ThreadTransaction[] = added.map(entry => {
|
||||||
|
return {
|
||||||
|
txid: entry.txid,
|
||||||
|
fee: entry.fee,
|
||||||
|
weight: entry.weight,
|
||||||
|
feePerVsize: entry.fee / (entry.weight / 4),
|
||||||
|
effectiveFeePerVsize: entry.fee / (entry.weight / 4),
|
||||||
|
vin: entry.vin.map(v => v.txid),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// run the block construction algorithm in a separate thread, and wait for a result
|
||||||
|
let threadErrorListener;
|
||||||
|
try {
|
||||||
|
const workerResultPromise = new Promise<{ blocks: ThreadTransaction[][], clusters: { [root: string]: string[] } }>((resolve, reject) => {
|
||||||
|
threadErrorListener = reject;
|
||||||
|
this.txSelectionWorker?.once('message', (result): void => {
|
||||||
|
resolve(result);
|
||||||
|
});
|
||||||
|
this.txSelectionWorker?.once('error', reject);
|
||||||
|
});
|
||||||
|
this.txSelectionWorker.postMessage({ type: 'update', added: addedStripped, removed });
|
||||||
|
const { blocks, clusters } = await workerResultPromise;
|
||||||
|
|
||||||
|
this.processBlockTemplates(newMempool, blocks, clusters);
|
||||||
|
|
||||||
|
// clean up thread error listener
|
||||||
|
this.txSelectionWorker?.removeListener('error', threadErrorListener);
|
||||||
|
} catch (e) {
|
||||||
|
logger.err('updateBlockTemplates failed. ' + (e instanceof Error ? e.message : e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private processBlockTemplates(mempool, blocks, clusters): void {
|
||||||
|
// update this thread's mempool with the results
|
||||||
|
blocks.forEach(block => {
|
||||||
|
block.forEach(tx => {
|
||||||
|
if (tx.txid in mempool) {
|
||||||
|
if (tx.effectiveFeePerVsize != null) {
|
||||||
|
mempool[tx.txid].effectiveFeePerVsize = tx.effectiveFeePerVsize;
|
||||||
|
}
|
||||||
|
if (tx.cpfpRoot && tx.cpfpRoot in clusters) {
|
||||||
|
const ancestors: Ancestor[] = [];
|
||||||
|
const descendants: Ancestor[] = [];
|
||||||
|
const cluster = clusters[tx.cpfpRoot];
|
||||||
|
let matched = false;
|
||||||
|
cluster.forEach(txid => {
|
||||||
|
if (txid === tx.txid) {
|
||||||
|
matched = true;
|
||||||
|
} else {
|
||||||
|
const relative = {
|
||||||
|
txid: txid,
|
||||||
|
fee: mempool[txid].fee,
|
||||||
|
weight: mempool[txid].weight,
|
||||||
|
};
|
||||||
|
if (matched) {
|
||||||
|
descendants.push(relative);
|
||||||
|
} else {
|
||||||
|
ancestors.push(relative);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mempool[tx.txid].ancestors = ancestors;
|
||||||
|
mempool[tx.txid].descendants = descendants;
|
||||||
|
mempool[tx.txid].bestDescendant = null;
|
||||||
|
}
|
||||||
|
mempool[tx.txid].cpfpChecked = tx.cpfpChecked;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// unpack the condensed blocks into proper mempool blocks
|
||||||
|
const mempoolBlocks = blocks.map((transactions, blockIndex) => {
|
||||||
|
return this.dataToMempoolBlocks(transactions.map(tx => {
|
||||||
|
return mempool[tx.txid] || null;
|
||||||
|
}).filter(tx => !!tx), undefined, undefined, blockIndex);
|
||||||
|
});
|
||||||
|
|
||||||
|
const deltas = this.calculateMempoolDeltas(this.mempoolBlocks, mempoolBlocks);
|
||||||
|
|
||||||
|
this.mempoolBlocks = mempoolBlocks;
|
||||||
this.mempoolBlockDeltas = deltas;
|
this.mempoolBlockDeltas = deltas;
|
||||||
}
|
}
|
||||||
|
|
||||||
private dataToMempoolBlocks(transactions: TransactionExtended[],
|
private dataToMempoolBlocks(transactions: TransactionExtended[],
|
||||||
blockSize: number, blockWeight: number, blocksIndex: number): MempoolBlockWithTransactions {
|
blockSize: number | undefined, blockWeight: number | undefined, blocksIndex: number): MempoolBlockWithTransactions {
|
||||||
|
let totalSize = blockSize || 0;
|
||||||
|
let totalWeight = blockWeight || 0;
|
||||||
|
if (blockSize === undefined && blockWeight === undefined) {
|
||||||
|
totalSize = 0;
|
||||||
|
totalWeight = 0;
|
||||||
|
transactions.forEach(tx => {
|
||||||
|
totalSize += tx.size;
|
||||||
|
totalWeight += tx.weight;
|
||||||
|
});
|
||||||
|
}
|
||||||
let rangeLength = 4;
|
let rangeLength = 4;
|
||||||
if (blocksIndex === 0) {
|
if (blocksIndex === 0) {
|
||||||
rangeLength = 8;
|
rangeLength = 8;
|
||||||
@@ -177,8 +306,8 @@ class MempoolBlocks {
|
|||||||
rangeLength = 8;
|
rangeLength = 8;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
blockSize: blockSize,
|
blockSize: totalSize,
|
||||||
blockVSize: blockWeight / 4,
|
blockVSize: totalWeight / 4,
|
||||||
nTx: transactions.length,
|
nTx: transactions.length,
|
||||||
totalFees: transactions.reduce((acc, cur) => acc + cur.fee, 0),
|
totalFees: transactions.reduce((acc, cur) => acc + cur.fee, 0),
|
||||||
medianFee: Common.percentile(transactions.map((tx) => tx.effectiveFeePerVsize), config.MEMPOOL.RECOMMENDED_FEE_PERCENTILE),
|
medianFee: Common.percentile(transactions.map((tx) => tx.effectiveFeePerVsize), config.MEMPOOL.RECOMMENDED_FEE_PERCENTILE),
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class Mempool {
|
|||||||
private mempoolChangedCallback: ((newMempool: {[txId: string]: TransactionExtended; }, newTransactions: TransactionExtended[],
|
private mempoolChangedCallback: ((newMempool: {[txId: string]: TransactionExtended; }, newTransactions: TransactionExtended[],
|
||||||
deletedTransactions: TransactionExtended[]) => void) | undefined;
|
deletedTransactions: TransactionExtended[]) => void) | undefined;
|
||||||
private asyncMempoolChangedCallback: ((newMempool: {[txId: string]: TransactionExtended; }, newTransactions: TransactionExtended[],
|
private asyncMempoolChangedCallback: ((newMempool: {[txId: string]: TransactionExtended; }, newTransactions: TransactionExtended[],
|
||||||
deletedTransactions: TransactionExtended[]) => void) | undefined;
|
deletedTransactions: TransactionExtended[]) => Promise<void>) | undefined;
|
||||||
|
|
||||||
private txPerSecondArray: number[] = [];
|
private txPerSecondArray: number[] = [];
|
||||||
private txPerSecond: number = 0;
|
private txPerSecond: number = 0;
|
||||||
|
|||||||
@@ -1,17 +1,30 @@
|
|||||||
import config from '../config';
|
import config from '../config';
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
import { TransactionExtended, MempoolBlockWithTransactions, AuditTransaction } from '../mempool.interfaces';
|
import { ThreadTransaction, MempoolBlockWithTransactions, AuditTransaction } from '../mempool.interfaces';
|
||||||
import { PairingHeap } from '../utils/pairing-heap';
|
import { PairingHeap } from '../utils/pairing-heap';
|
||||||
import { Common } from './common';
|
import { Common } from './common';
|
||||||
import { parentPort } from 'worker_threads';
|
import { parentPort } from 'worker_threads';
|
||||||
|
|
||||||
|
let mempool: { [txid: string]: ThreadTransaction } = {};
|
||||||
|
|
||||||
if (parentPort) {
|
if (parentPort) {
|
||||||
parentPort.on('message', (params: { mempool: { [txid: string]: TransactionExtended }, blockLimit: number, weightLimit: number | null, condenseRest: boolean}) => {
|
parentPort.on('message', (params) => {
|
||||||
const { mempool, blocks } = makeBlockTemplates(params);
|
if (params.type === 'set') {
|
||||||
|
mempool = params.mempool;
|
||||||
|
} else if (params.type === 'update') {
|
||||||
|
params.added.forEach(tx => {
|
||||||
|
mempool[tx.txid] = tx;
|
||||||
|
});
|
||||||
|
params.removed.forEach(txid => {
|
||||||
|
delete mempool[txid];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { blocks, clusters } = makeBlockTemplates(mempool);
|
||||||
|
|
||||||
// return the result to main thread.
|
// return the result to main thread.
|
||||||
if (parentPort) {
|
if (parentPort) {
|
||||||
parentPort.postMessage({ mempool, blocks });
|
parentPort.postMessage({ blocks, clusters });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -19,35 +32,24 @@ if (parentPort) {
|
|||||||
/*
|
/*
|
||||||
* Build projected mempool blocks using an approximation of the transaction selection algorithm from Bitcoin Core
|
* Build projected mempool blocks using an approximation of the transaction selection algorithm from Bitcoin Core
|
||||||
* (see BlockAssembler in https://github.com/bitcoin/bitcoin/blob/master/src/node/miner.cpp)
|
* (see BlockAssembler in https://github.com/bitcoin/bitcoin/blob/master/src/node/miner.cpp)
|
||||||
*
|
|
||||||
* blockLimit: number of blocks to build in total.
|
|
||||||
* weightLimit: maximum weight of transactions to consider using the selection algorithm.
|
|
||||||
* if weightLimit is significantly lower than the mempool size, results may start to diverge from getBlockTemplate
|
|
||||||
* condenseRest: whether to ignore excess transactions or append them to the final block.
|
|
||||||
*/
|
*/
|
||||||
function makeBlockTemplates({ mempool, blockLimit, weightLimit, condenseRest }: { mempool: { [txid: string]: TransactionExtended }, blockLimit: number, weightLimit?: number | null, condenseRest?: boolean | null })
|
function makeBlockTemplates(mempool: { [txid: string]: ThreadTransaction })
|
||||||
: { mempool: { [txid: string]: TransactionExtended }, blocks: MempoolBlockWithTransactions[] } {
|
: { blocks: ThreadTransaction[][], clusters: { [root: string]: string[] } } {
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
const auditPool: { [txid: string]: AuditTransaction } = {};
|
const auditPool: { [txid: string]: AuditTransaction } = {};
|
||||||
const mempoolArray: AuditTransaction[] = [];
|
const mempoolArray: AuditTransaction[] = [];
|
||||||
const restOfArray: TransactionExtended[] = [];
|
const restOfArray: ThreadTransaction[] = [];
|
||||||
|
const cpfpClusters: { [root: string]: string[] } = {};
|
||||||
|
|
||||||
let weight = 0;
|
|
||||||
const maxWeight = weightLimit ? Math.max(4_000_000 * blockLimit, weightLimit) : Infinity;
|
|
||||||
// grab the top feerate txs up to maxWeight
|
// grab the top feerate txs up to maxWeight
|
||||||
Object.values(mempool).sort((a, b) => b.feePerVsize - a.feePerVsize).forEach(tx => {
|
Object.values(mempool).sort((a, b) => b.feePerVsize - a.feePerVsize).forEach(tx => {
|
||||||
weight += tx.weight;
|
|
||||||
if (weight >= maxWeight) {
|
|
||||||
restOfArray.push(tx);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// initializing everything up front helps V8 optimize property access later
|
// initializing everything up front helps V8 optimize property access later
|
||||||
auditPool[tx.txid] = {
|
auditPool[tx.txid] = {
|
||||||
txid: tx.txid,
|
txid: tx.txid,
|
||||||
fee: tx.fee,
|
fee: tx.fee,
|
||||||
size: tx.size,
|
|
||||||
weight: tx.weight,
|
weight: tx.weight,
|
||||||
feePerVsize: tx.feePerVsize,
|
feePerVsize: tx.feePerVsize,
|
||||||
|
effectiveFeePerVsize: tx.feePerVsize,
|
||||||
vin: tx.vin,
|
vin: tx.vin,
|
||||||
relativesSet: false,
|
relativesSet: false,
|
||||||
ancestorMap: new Map<string, AuditTransaction>(),
|
ancestorMap: new Map<string, AuditTransaction>(),
|
||||||
@@ -74,7 +76,7 @@ function makeBlockTemplates({ mempool, blockLimit, weightLimit, condenseRest }:
|
|||||||
|
|
||||||
// Build blocks by greedily choosing the highest feerate package
|
// Build blocks by greedily choosing the highest feerate package
|
||||||
// (i.e. the package rooted in the transaction with the best ancestor score)
|
// (i.e. the package rooted in the transaction with the best ancestor score)
|
||||||
const blocks: MempoolBlockWithTransactions[] = [];
|
const blocks: ThreadTransaction[][] = [];
|
||||||
let blockWeight = 4000;
|
let blockWeight = 4000;
|
||||||
let blockSize = 0;
|
let blockSize = 0;
|
||||||
let transactions: AuditTransaction[] = [];
|
let transactions: AuditTransaction[] = [];
|
||||||
@@ -82,7 +84,7 @@ function makeBlockTemplates({ mempool, blockLimit, weightLimit, condenseRest }:
|
|||||||
let overflow: AuditTransaction[] = [];
|
let overflow: AuditTransaction[] = [];
|
||||||
let failures = 0;
|
let failures = 0;
|
||||||
let top = 0;
|
let top = 0;
|
||||||
while ((top < mempoolArray.length || !modified.isEmpty()) && (condenseRest || blocks.length < blockLimit)) {
|
while ((top < mempoolArray.length || !modified.isEmpty())) {
|
||||||
// skip invalid transactions
|
// skip invalid transactions
|
||||||
while (top < mempoolArray.length && (mempoolArray[top].used || mempoolArray[top].modified)) {
|
while (top < mempoolArray.length && (mempoolArray[top].used || mempoolArray[top].modified)) {
|
||||||
top++;
|
top++;
|
||||||
@@ -107,9 +109,13 @@ function makeBlockTemplates({ mempool, blockLimit, weightLimit, condenseRest }:
|
|||||||
// Check if the package fits into this block
|
// Check if the package fits into this block
|
||||||
if (blockWeight + nextTx.ancestorWeight < config.MEMPOOL.BLOCK_WEIGHT_UNITS) {
|
if (blockWeight + nextTx.ancestorWeight < config.MEMPOOL.BLOCK_WEIGHT_UNITS) {
|
||||||
const ancestors: AuditTransaction[] = Array.from(nextTx.ancestorMap.values());
|
const ancestors: AuditTransaction[] = Array.from(nextTx.ancestorMap.values());
|
||||||
const descendants: AuditTransaction[] = [];
|
|
||||||
// sort ancestors by dependency graph (equivalent to sorting by ascending ancestor count)
|
// sort ancestors by dependency graph (equivalent to sorting by ascending ancestor count)
|
||||||
const sortedTxSet = [...ancestors.sort((a, b) => { return (a.ancestorMap.size || 0) - (b.ancestorMap.size || 0); }), nextTx];
|
const sortedTxSet = [...ancestors.sort((a, b) => { return (a.ancestorMap.size || 0) - (b.ancestorMap.size || 0); }), nextTx];
|
||||||
|
let isCluster = false;
|
||||||
|
if (sortedTxSet.length > 1) {
|
||||||
|
cpfpClusters[nextTx.txid] = sortedTxSet.map(tx => tx.txid);
|
||||||
|
isCluster = true;
|
||||||
|
}
|
||||||
const effectiveFeeRate = nextTx.ancestorFee / (nextTx.ancestorWeight / 4);
|
const effectiveFeeRate = nextTx.ancestorFee / (nextTx.ancestorWeight / 4);
|
||||||
const used: AuditTransaction[] = [];
|
const used: AuditTransaction[] = [];
|
||||||
while (sortedTxSet.length) {
|
while (sortedTxSet.length) {
|
||||||
@@ -119,21 +125,9 @@ function makeBlockTemplates({ mempool, blockLimit, weightLimit, condenseRest }:
|
|||||||
ancestor.usedBy = nextTx.txid;
|
ancestor.usedBy = nextTx.txid;
|
||||||
// update original copy of this tx with effective fee rate & relatives data
|
// update original copy of this tx with effective fee rate & relatives data
|
||||||
mempoolTx.effectiveFeePerVsize = effectiveFeeRate;
|
mempoolTx.effectiveFeePerVsize = effectiveFeeRate;
|
||||||
mempoolTx.ancestors = sortedTxSet.map((a) => {
|
if (isCluster) {
|
||||||
return {
|
mempoolTx.cpfpRoot = nextTx.txid;
|
||||||
txid: a.txid,
|
}
|
||||||
fee: a.fee,
|
|
||||||
weight: a.weight,
|
|
||||||
};
|
|
||||||
}).reverse();
|
|
||||||
mempoolTx.descendants = descendants.map((a) => {
|
|
||||||
return {
|
|
||||||
txid: a.txid,
|
|
||||||
fee: a.fee,
|
|
||||||
weight: a.weight,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
descendants.push(ancestor);
|
|
||||||
mempoolTx.cpfpChecked = true;
|
mempoolTx.cpfpChecked = true;
|
||||||
transactions.push(ancestor);
|
transactions.push(ancestor);
|
||||||
blockSize += ancestor.size;
|
blockSize += ancestor.size;
|
||||||
@@ -159,10 +153,10 @@ function makeBlockTemplates({ mempool, blockLimit, weightLimit, condenseRest }:
|
|||||||
// this block is full
|
// this block is full
|
||||||
const exceededPackageTries = failures > 1000 && blockWeight > (config.MEMPOOL.BLOCK_WEIGHT_UNITS - 4000);
|
const exceededPackageTries = failures > 1000 && blockWeight > (config.MEMPOOL.BLOCK_WEIGHT_UNITS - 4000);
|
||||||
const queueEmpty = top >= mempoolArray.length && modified.isEmpty();
|
const queueEmpty = top >= mempoolArray.length && modified.isEmpty();
|
||||||
if ((exceededPackageTries || queueEmpty) && (!condenseRest || blocks.length < blockLimit - 1)) {
|
if ((exceededPackageTries || queueEmpty) && blocks.length < 7) {
|
||||||
// construct this block
|
// construct this block
|
||||||
if (transactions.length) {
|
if (transactions.length) {
|
||||||
blocks.push(dataToMempoolBlocks(transactions.map(t => mempool[t.txid]), blockSize, blockWeight, blocks.length));
|
blocks.push(transactions.map(t => mempool[t.txid]));
|
||||||
}
|
}
|
||||||
// reset for the next block
|
// reset for the next block
|
||||||
transactions = [];
|
transactions = [];
|
||||||
@@ -181,55 +175,40 @@ function makeBlockTemplates({ mempool, blockLimit, weightLimit, condenseRest }:
|
|||||||
overflow = [];
|
overflow = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (condenseRest) {
|
// pack any leftover transactions into the last block
|
||||||
// pack any leftover transactions into the last block
|
for (const tx of overflow) {
|
||||||
for (const tx of overflow) {
|
if (!tx || tx?.used) {
|
||||||
if (!tx || tx?.used) {
|
continue;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
blockWeight += tx.weight;
|
|
||||||
blockSize += tx.size;
|
|
||||||
const mempoolTx = mempool[tx.txid];
|
|
||||||
// update original copy of this tx with effective fee rate & relatives data
|
|
||||||
mempoolTx.effectiveFeePerVsize = tx.score;
|
|
||||||
mempoolTx.ancestors = (Array.from(tx.ancestorMap?.values()) as AuditTransaction[]).map((a) => {
|
|
||||||
return {
|
|
||||||
txid: a.txid,
|
|
||||||
fee: a.fee,
|
|
||||||
weight: a.weight,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
mempoolTx.bestDescendant = null;
|
|
||||||
mempoolTx.cpfpChecked = true;
|
|
||||||
transactions.push(tx);
|
|
||||||
tx.used = true;
|
|
||||||
}
|
}
|
||||||
const blockTransactions = transactions.map(t => mempool[t.txid]);
|
blockWeight += tx.weight;
|
||||||
restOfArray.forEach(tx => {
|
const mempoolTx = mempool[tx.txid];
|
||||||
blockWeight += tx.weight;
|
// update original copy of this tx with effective fee rate & relatives data
|
||||||
blockSize += tx.size;
|
mempoolTx.effectiveFeePerVsize = tx.score;
|
||||||
tx.effectiveFeePerVsize = tx.feePerVsize;
|
if (tx.ancestorMap.size > 0) {
|
||||||
tx.cpfpChecked = false;
|
cpfpClusters[tx.txid] = Array.from(tx.ancestorMap?.values()).map(a => a.txid);
|
||||||
tx.ancestors = [];
|
mempoolTx.cpfpRoot = tx.txid;
|
||||||
tx.bestDescendant = null;
|
|
||||||
blockTransactions.push(tx);
|
|
||||||
});
|
|
||||||
if (blockTransactions.length) {
|
|
||||||
blocks.push(dataToMempoolBlocks(blockTransactions, blockSize, blockWeight, blocks.length));
|
|
||||||
}
|
}
|
||||||
transactions = [];
|
mempoolTx.cpfpChecked = true;
|
||||||
} else if (transactions.length) {
|
transactions.push(tx);
|
||||||
blocks.push(dataToMempoolBlocks(transactions.map(t => mempool[t.txid]), blockSize, blockWeight, blocks.length));
|
tx.used = true;
|
||||||
}
|
}
|
||||||
|
const blockTransactions = transactions.map(t => mempool[t.txid]);
|
||||||
|
restOfArray.forEach(tx => {
|
||||||
|
blockWeight += tx.weight;
|
||||||
|
tx.effectiveFeePerVsize = tx.feePerVsize;
|
||||||
|
tx.cpfpChecked = false;
|
||||||
|
blockTransactions.push(tx);
|
||||||
|
});
|
||||||
|
if (blockTransactions.length) {
|
||||||
|
blocks.push(blockTransactions);
|
||||||
|
}
|
||||||
|
transactions = [];
|
||||||
|
|
||||||
const end = Date.now();
|
const end = Date.now();
|
||||||
const time = end - start;
|
const time = end - start;
|
||||||
logger.debug('Mempool templates calculated in ' + time / 1000 + ' seconds');
|
logger.debug('Mempool templates calculated in ' + time / 1000 + ' seconds');
|
||||||
|
|
||||||
return {
|
return { blocks, clusters: cpfpClusters };
|
||||||
mempool,
|
|
||||||
blocks
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// traverse in-mempool ancestors
|
// traverse in-mempool ancestors
|
||||||
@@ -239,9 +218,9 @@ function setRelatives(
|
|||||||
mempool: { [txid: string]: AuditTransaction },
|
mempool: { [txid: string]: AuditTransaction },
|
||||||
): void {
|
): void {
|
||||||
for (const parent of tx.vin) {
|
for (const parent of tx.vin) {
|
||||||
const parentTx = mempool[parent.txid];
|
const parentTx = mempool[parent];
|
||||||
if (parentTx && !tx.ancestorMap?.has(parent.txid)) {
|
if (parentTx && !tx.ancestorMap?.has(parent)) {
|
||||||
tx.ancestorMap.set(parent.txid, parentTx);
|
tx.ancestorMap.set(parent, parentTx);
|
||||||
parentTx.children.add(tx);
|
parentTx.children.add(tx);
|
||||||
// visit each node only once
|
// visit each node only once
|
||||||
if (!parentTx.relativesSet) {
|
if (!parentTx.relativesSet) {
|
||||||
@@ -312,27 +291,4 @@ function updateDescendants(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function dataToMempoolBlocks(transactions: TransactionExtended[],
|
|
||||||
blockSize: number, blockWeight: number, blocksIndex: number): MempoolBlockWithTransactions {
|
|
||||||
let rangeLength = 4;
|
|
||||||
if (blocksIndex === 0) {
|
|
||||||
rangeLength = 8;
|
|
||||||
}
|
|
||||||
if (transactions.length > 4000) {
|
|
||||||
rangeLength = 6;
|
|
||||||
} else if (transactions.length > 10000) {
|
|
||||||
rangeLength = 8;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
blockSize: blockSize,
|
|
||||||
blockVSize: blockWeight / 4,
|
|
||||||
nTx: transactions.length,
|
|
||||||
totalFees: transactions.reduce((acc, cur) => acc + cur.fee, 0),
|
|
||||||
medianFee: Common.percentile(transactions.map((tx) => tx.effectiveFeePerVsize), config.MEMPOOL.RECOMMENDED_FEE_PERCENTILE),
|
|
||||||
feeRange: Common.getFeesInRange(transactions, rangeLength),
|
|
||||||
transactionIds: transactions.map((tx) => tx.txid),
|
|
||||||
transactions: transactions.map((tx) => Common.stripTransaction(tx)),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
@@ -251,7 +251,7 @@ class WebsocketHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {
|
if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {
|
||||||
await mempoolBlocks.makeBlockTemplates(newMempool, 8, null, true);
|
await mempoolBlocks.updateBlockTemplates(newMempool, newTransactions, deletedTransactions.map(tx => tx.txid));
|
||||||
} else {
|
} else {
|
||||||
mempoolBlocks.updateMempoolBlocks(newMempool);
|
mempoolBlocks.updateMempoolBlocks(newMempool);
|
||||||
}
|
}
|
||||||
@@ -419,7 +419,7 @@ class WebsocketHandler {
|
|||||||
const _memPool = memPool.getMempool();
|
const _memPool = memPool.getMempool();
|
||||||
|
|
||||||
if (config.MEMPOOL.ADVANCED_GBT_AUDIT) {
|
if (config.MEMPOOL.ADVANCED_GBT_AUDIT) {
|
||||||
await mempoolBlocks.makeBlockTemplates(_memPool, 2);
|
await mempoolBlocks.makeBlockTemplates(_memPool);
|
||||||
} else {
|
} else {
|
||||||
mempoolBlocks.updateMempoolBlocks(_memPool);
|
mempoolBlocks.updateMempoolBlocks(_memPool);
|
||||||
}
|
}
|
||||||
@@ -462,13 +462,15 @@ class WebsocketHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const removed: string[] = [];
|
||||||
// Update mempool to remove transactions included in the new block
|
// Update mempool to remove transactions included in the new block
|
||||||
for (const txId of txIds) {
|
for (const txId of txIds) {
|
||||||
delete _memPool[txId];
|
delete _memPool[txId];
|
||||||
|
removed.push(txId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {
|
if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {
|
||||||
await mempoolBlocks.makeBlockTemplates(_memPool, 8, null, true);
|
await mempoolBlocks.updateBlockTemplates(_memPool, [], removed);
|
||||||
} else {
|
} else {
|
||||||
mempoolBlocks.updateMempoolBlocks(_memPool);
|
mempoolBlocks.updateMempoolBlocks(_memPool);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,10 +81,10 @@ export interface TransactionExtended extends IEsploraApi.Transaction {
|
|||||||
export interface AuditTransaction {
|
export interface AuditTransaction {
|
||||||
txid: string;
|
txid: string;
|
||||||
fee: number;
|
fee: number;
|
||||||
size: number;
|
|
||||||
weight: number;
|
weight: number;
|
||||||
feePerVsize: number;
|
feePerVsize: number;
|
||||||
vin: IEsploraApi.Vin[];
|
effectiveFeePerVsize: number;
|
||||||
|
vin: string[];
|
||||||
relativesSet: boolean;
|
relativesSet: boolean;
|
||||||
ancestorMap: Map<string, AuditTransaction>;
|
ancestorMap: Map<string, AuditTransaction>;
|
||||||
children: Set<AuditTransaction>;
|
children: Set<AuditTransaction>;
|
||||||
@@ -96,6 +96,17 @@ export interface AuditTransaction {
|
|||||||
modifiedNode: HeapNode<AuditTransaction>;
|
modifiedNode: HeapNode<AuditTransaction>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ThreadTransaction {
|
||||||
|
txid: string;
|
||||||
|
fee: number;
|
||||||
|
weight: number;
|
||||||
|
feePerVsize: number;
|
||||||
|
effectiveFeePerVsize?: number;
|
||||||
|
vin: string[];
|
||||||
|
cpfpRoot?: string;
|
||||||
|
cpfpChecked?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Ancestor {
|
export interface Ancestor {
|
||||||
txid: string;
|
txid: string;
|
||||||
weight: number;
|
weight: number;
|
||||||
|
|||||||
@@ -44,7 +44,9 @@ class TransactionRepository {
|
|||||||
const [rows]: any = await DB.query(query, [txid]);
|
const [rows]: any = await DB.query(query, [txid]);
|
||||||
if (rows.length) {
|
if (rows.length) {
|
||||||
rows[0].txs = JSON.parse(rows[0].txs) as Ancestor[];
|
rows[0].txs = JSON.parse(rows[0].txs) as Ancestor[];
|
||||||
return this.convertCpfp(rows[0]);
|
if (rows[0]?.txs?.length) {
|
||||||
|
return this.convertCpfp(rows[0]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.err('Cannot get transaction cpfp info from db. Reason: ' + (e instanceof Error ? e.message : e));
|
logger.err('Cannot get transaction cpfp info from db. Reason: ' + (e instanceof Error ? e.message : e));
|
||||||
|
|||||||
@@ -310,6 +310,11 @@ class LightningStatsImporter {
|
|||||||
* Import topology files LN historical data into the database
|
* Import topology files LN historical data into the database
|
||||||
*/
|
*/
|
||||||
async $importHistoricalLightningStats(): Promise<void> {
|
async $importHistoricalLightningStats(): Promise<void> {
|
||||||
|
if (!config.LIGHTNING.TOPOLOGY_FOLDER) {
|
||||||
|
logger.info(`Lightning topology folder is not set. Not importing historical LN stats`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
logger.debug('Run the historical importer');
|
logger.debug('Run the historical importer');
|
||||||
try {
|
try {
|
||||||
let fileList: string[] = [];
|
let fileList: string[] = [];
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ https://www.transifex.com/mempool/mempool/dashboard/
|
|||||||
* Thai @Gusb3ll
|
* Thai @Gusb3ll
|
||||||
* Turkish @stackmore
|
* Turkish @stackmore
|
||||||
* Ukrainian @volbil
|
* Ukrainian @volbil
|
||||||
* Vietnamese @bitcoin_vietnam
|
* Vietnamese @BitcoinvnNews
|
||||||
* Chinese @wdljt
|
* Chinese @wdljt
|
||||||
* Russian @TonyCrusoe @Bitconan
|
* Russian @TonyCrusoe @Bitconan
|
||||||
* Romanian @mirceavesa
|
* Romanian @mirceavesa
|
||||||
|
|||||||
@@ -137,6 +137,10 @@
|
|||||||
"hi": {
|
"hi": {
|
||||||
"translation": "src/locale/messages.hi.xlf",
|
"translation": "src/locale/messages.hi.xlf",
|
||||||
"baseHref": "/hi/"
|
"baseHref": "/hi/"
|
||||||
|
},
|
||||||
|
"lt": {
|
||||||
|
"translation": "src/locale/messages.lt.xlf",
|
||||||
|
"baseHref": "/lt/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -158,6 +162,11 @@
|
|||||||
],
|
],
|
||||||
"styles": [
|
"styles": [
|
||||||
"src/styles.scss",
|
"src/styles.scss",
|
||||||
|
{
|
||||||
|
"input": "src/theme-contrast.scss",
|
||||||
|
"bundleName": "contrast",
|
||||||
|
"inject": false
|
||||||
|
},
|
||||||
"node_modules/@fortawesome/fontawesome-svg-core/styles.css"
|
"node_modules/@fortawesome/fontawesome-svg-core/styles.css"
|
||||||
],
|
],
|
||||||
"vendorChunk": true,
|
"vendorChunk": true,
|
||||||
|
|||||||
31
frontend/package-lock.json
generated
31
frontend/package-lock.json
generated
@@ -31,6 +31,7 @@
|
|||||||
"bootstrap": "~4.6.1",
|
"bootstrap": "~4.6.1",
|
||||||
"browserify": "^17.0.0",
|
"browserify": "^17.0.0",
|
||||||
"clipboard": "^2.0.11",
|
"clipboard": "^2.0.11",
|
||||||
|
"cypress": "^12.1.0",
|
||||||
"domino": "^2.1.6",
|
"domino": "^2.1.6",
|
||||||
"echarts": "~5.4.0",
|
"echarts": "~5.4.0",
|
||||||
"echarts-gl": "^2.0.9",
|
"echarts-gl": "^2.0.9",
|
||||||
@@ -57,8 +58,8 @@
|
|||||||
"typescript": "~4.6.4"
|
"typescript": "~4.6.4"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@cypress/schematic": "~2.3.0",
|
"@cypress/schematic": "^2.4.0",
|
||||||
"cypress": "^11.2.0",
|
"cypress": "^12.1.0",
|
||||||
"cypress-fail-on-console-error": "~4.0.2",
|
"cypress-fail-on-console-error": "~4.0.2",
|
||||||
"cypress-wait-until": "^1.7.2",
|
"cypress-wait-until": "^1.7.2",
|
||||||
"mock-socket": "~9.1.5",
|
"mock-socket": "~9.1.5",
|
||||||
@@ -3225,9 +3226,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@cypress/schematic": {
|
"node_modules/@cypress/schematic": {
|
||||||
"version": "2.3.0",
|
"version": "2.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@cypress/schematic/-/schematic-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@cypress/schematic/-/schematic-2.4.0.tgz",
|
||||||
"integrity": "sha512-LBKX20MUUYF2Xu+1+KpVbLCoMvt2Osa80yQfonduVsLJ/p8JxtLHqufuf/ryJp9Gm9R5sDfk/YhHL+rB7a+gsg==",
|
"integrity": "sha512-aor8hQ+gMXqx/ASdo7CUGo/sMEWwwfSRsLr99rM2GjvW+pZnCKKTnRG4UPf8Ro9SevLJj7KRZAZWxa5MAkJzZA==",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular-devkit/architect": "^0.1402.1",
|
"@angular-devkit/architect": "^0.1402.1",
|
||||||
@@ -7019,9 +7020,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/cypress": {
|
"node_modules/cypress": {
|
||||||
"version": "11.2.0",
|
"version": "12.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-11.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/cypress/-/cypress-12.1.0.tgz",
|
||||||
"integrity": "sha512-u61UGwtu7lpsNWLUma/FKNOsrjcI6wleNmda/TyKHe0dOBcVjbCPlp1N6uwFZ0doXev7f/91YDpU9bqDCFeBLA==",
|
"integrity": "sha512-7fz8N84uhN1+ePNDsfQvoWEl4P3/VGKKmAg+bJQFY4onhA37Ys+6oBkGbNdwGeC7n2QqibNVPhk8x3YuQLwzfw==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -7072,7 +7073,7 @@
|
|||||||
"cypress": "bin/cypress"
|
"cypress": "bin/cypress"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.0.0"
|
"node": "^14.0.0 || ^16.0.0 || >=18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/cypress-fail-on-console-error": {
|
"node_modules/cypress-fail-on-console-error": {
|
||||||
@@ -19345,9 +19346,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@cypress/schematic": {
|
"@cypress/schematic": {
|
||||||
"version": "2.3.0",
|
"version": "2.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@cypress/schematic/-/schematic-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@cypress/schematic/-/schematic-2.4.0.tgz",
|
||||||
"integrity": "sha512-LBKX20MUUYF2Xu+1+KpVbLCoMvt2Osa80yQfonduVsLJ/p8JxtLHqufuf/ryJp9Gm9R5sDfk/YhHL+rB7a+gsg==",
|
"integrity": "sha512-aor8hQ+gMXqx/ASdo7CUGo/sMEWwwfSRsLr99rM2GjvW+pZnCKKTnRG4UPf8Ro9SevLJj7KRZAZWxa5MAkJzZA==",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@angular-devkit/architect": "^0.1402.1",
|
"@angular-devkit/architect": "^0.1402.1",
|
||||||
@@ -22282,9 +22283,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"cypress": {
|
"cypress": {
|
||||||
"version": "11.2.0",
|
"version": "12.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-11.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/cypress/-/cypress-12.1.0.tgz",
|
||||||
"integrity": "sha512-u61UGwtu7lpsNWLUma/FKNOsrjcI6wleNmda/TyKHe0dOBcVjbCPlp1N6uwFZ0doXev7f/91YDpU9bqDCFeBLA==",
|
"integrity": "sha512-7fz8N84uhN1+ePNDsfQvoWEl4P3/VGKKmAg+bJQFY4onhA37Ys+6oBkGbNdwGeC7n2QqibNVPhk8x3YuQLwzfw==",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@cypress/request": "^2.88.10",
|
"@cypress/request": "^2.88.10",
|
||||||
|
|||||||
@@ -109,8 +109,8 @@
|
|||||||
"typescript": "~4.6.4"
|
"typescript": "~4.6.4"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@cypress/schematic": "~2.3.0",
|
"@cypress/schematic": "^2.4.0",
|
||||||
"cypress": "^11.2.0",
|
"cypress": "^12.1.0",
|
||||||
"cypress-fail-on-console-error": "~4.0.2",
|
"cypress-fail-on-console-error": "~4.0.2",
|
||||||
"cypress-wait-until": "^1.7.2",
|
"cypress-wait-until": "^1.7.2",
|
||||||
"mock-socket": "~9.1.5",
|
"mock-socket": "~9.1.5",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export const mempoolFeeColors = [
|
export const defaultMempoolFeeColors = [
|
||||||
'557d00',
|
'557d00',
|
||||||
'5d7d01',
|
'5d7d01',
|
||||||
'637d02',
|
'637d02',
|
||||||
@@ -31,6 +31,39 @@ export const mempoolFeeColors = [
|
|||||||
'b9254b',
|
'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 = [
|
export const chartColors = [
|
||||||
"#D81B60",
|
"#D81B60",
|
||||||
"#8E24AA",
|
"#8E24AA",
|
||||||
@@ -120,7 +153,7 @@ export const languages: Language[] = [
|
|||||||
{ code: 'he', name: 'עברית' }, // Hebrew
|
{ code: 'he', name: 'עברית' }, // Hebrew
|
||||||
{ code: 'ka', name: 'ქართული' }, // Georgian
|
{ code: 'ka', name: 'ქართული' }, // Georgian
|
||||||
// { code: 'lv', name: 'Latviešu' }, // Latvian
|
// { code: 'lv', name: 'Latviešu' }, // Latvian
|
||||||
// { code: 'lt', name: 'Lietuvių' }, // Lithuanian
|
{ code: 'lt', name: 'Lietuvių' }, // Lithuanian
|
||||||
{ code: 'hu', name: 'Magyar' }, // Hungarian
|
{ code: 'hu', name: 'Magyar' }, // Hungarian
|
||||||
{ code: 'mk', name: 'Македонски' }, // Macedonian
|
{ code: 'mk', name: 'Македонски' }, // Macedonian
|
||||||
// { code: 'ms', name: 'Bahasa Melayu' }, // Malay
|
// { code: 'ms', name: 'Bahasa Melayu' }, // Malay
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { SharedModule } from './shared/shared.module';
|
|||||||
import { StorageService } from './services/storage.service';
|
import { StorageService } from './services/storage.service';
|
||||||
import { HttpCacheInterceptor } from './services/http-cache.interceptor';
|
import { HttpCacheInterceptor } from './services/http-cache.interceptor';
|
||||||
import { LanguageService } from './services/language.service';
|
import { LanguageService } from './services/language.service';
|
||||||
|
import { ThemeService } from './services/theme.service';
|
||||||
import { FiatShortenerPipe } from './shared/pipes/fiat-shortener.pipe';
|
import { FiatShortenerPipe } from './shared/pipes/fiat-shortener.pipe';
|
||||||
import { ShortenStringPipe } from './shared/pipes/shorten-string-pipe/shorten-string.pipe';
|
import { ShortenStringPipe } from './shared/pipes/shorten-string-pipe/shorten-string.pipe';
|
||||||
import { CapAddressPipe } from './shared/pipes/cap-address-pipe/cap-address-pipe';
|
import { CapAddressPipe } from './shared/pipes/cap-address-pipe/cap-address-pipe';
|
||||||
@@ -30,6 +31,7 @@ const providers = [
|
|||||||
StorageService,
|
StorageService,
|
||||||
EnterpriseService,
|
EnterpriseService,
|
||||||
LanguageService,
|
LanguageService,
|
||||||
|
ThemeService,
|
||||||
ShortenStringPipe,
|
ShortenStringPipe,
|
||||||
FiatShortenerPipe,
|
FiatShortenerPipe,
|
||||||
CapAddressPipe,
|
CapAddressPipe,
|
||||||
|
|||||||
@@ -61,18 +61,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.big-fiat {
|
.big-fiat {
|
||||||
color: #3bcc49;
|
color: var(--green);
|
||||||
font-size: 26px;
|
font-size: 26px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
background-color: #1d1f31;
|
background-color: var(--bg);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-title {
|
.card-title {
|
||||||
color: #4a68b9;
|
color: var(--title-fg);
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@
|
|||||||
.progress {
|
.progress {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: #2d3348;
|
background-color: var(--secondary);
|
||||||
height: 1.1rem;
|
height: 1.1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
width: 150px;
|
width: 150px;
|
||||||
}
|
}
|
||||||
.mobile-even tr:nth-of-type(even) {
|
.mobile-even tr:nth-of-type(even) {
|
||||||
background-color: #181b2d;
|
background-color: var(--stat-box-bg);
|
||||||
}
|
}
|
||||||
.mobile-even tr:nth-of-type(odd) {
|
.mobile-even tr:nth-of-type(odd) {
|
||||||
background-color: inherit;
|
background-color: inherit;
|
||||||
|
|||||||
@@ -12,15 +12,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.green {
|
.green {
|
||||||
color:#28a745;
|
color: var(--green);
|
||||||
}
|
}
|
||||||
|
|
||||||
.red {
|
.red {
|
||||||
color:#dc3545;
|
color: var(--red);
|
||||||
}
|
}
|
||||||
|
|
||||||
.grey {
|
.grey {
|
||||||
color:#6c757d;
|
color: var(--grey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 767.98px) {
|
@media (max-width: 767.98px) {
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
.green-color {
|
.green-color {
|
||||||
color: #3bcc49;
|
color: var(--green);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
.green-color {
|
.green-color {
|
||||||
color: #3bcc49;
|
color: var(--green);
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ import { Router, NavigationEnd } from '@angular/router';
|
|||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
import { OpenGraphService } from '../../services/opengraph.service';
|
import { OpenGraphService } from '../../services/opengraph.service';
|
||||||
import { NgbTooltipConfig } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbTooltipConfig } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { ThemeService } from 'src/app/services/theme.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
@@ -19,6 +20,7 @@ export class AppComponent implements OnInit {
|
|||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
private openGraphService: OpenGraphService,
|
private openGraphService: OpenGraphService,
|
||||||
private location: Location,
|
private location: Location,
|
||||||
|
private theme: ThemeService,
|
||||||
tooltipConfig: NgbTooltipConfig,
|
tooltipConfig: NgbTooltipConfig,
|
||||||
@Inject(LOCALE_ID) private locale: string,
|
@Inject(LOCALE_ID) private locale: string,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
background-color: #1d1f31;
|
background-color: var(--bg);
|
||||||
width: 200px;
|
width: 200px;
|
||||||
height: 200px;
|
height: 200px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
background-color: #1d1f31;
|
background-color: var(--bg);
|
||||||
width: 200px;
|
width: 200px;
|
||||||
height: 200px;
|
height: 200px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
li.nav-item.active {
|
li.nav-item.active {
|
||||||
background-color: #653b9c;
|
background-color: var(--tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
fa-icon {
|
fa-icon {
|
||||||
@@ -95,23 +95,23 @@ nav {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mainnet.active {
|
.mainnet.active {
|
||||||
background-color: #653b9c;
|
background-color: var(--tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.liquid.active {
|
.liquid.active {
|
||||||
background-color: #116761;
|
background-color: var(--liquid);
|
||||||
}
|
}
|
||||||
|
|
||||||
.liquidtestnet.active {
|
.liquidtestnet.active {
|
||||||
background-color: #494a4a;
|
background-color: var(--liquidtestnet);
|
||||||
}
|
}
|
||||||
|
|
||||||
.testnet.active {
|
.testnet.active {
|
||||||
background-color: #1d486f;
|
background-color: var(--testnet);
|
||||||
}
|
}
|
||||||
|
|
||||||
.signet.active {
|
.signet.active {
|
||||||
background-color: #6f1d5d;
|
background-color: var(--signet);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-divider {
|
.dropdown-divider {
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ export class BlockFeeRatesGraphComponent implements OnInit {
|
|||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: '#b1b1b1',
|
color: 'var(--tooltip-grey)',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
},
|
},
|
||||||
borderColor: '#000',
|
borderColor: '#000',
|
||||||
@@ -238,7 +238,7 @@ export class BlockFeeRatesGraphComponent implements OnInit {
|
|||||||
splitLine: {
|
splitLine: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
type: 'dotted',
|
type: 'dotted',
|
||||||
color: '#ffffff66',
|
color: 'var(--transparent-fg)',
|
||||||
opacity: 0.25,
|
opacity: 0.25,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -305,7 +305,7 @@ export class BlockFeeRatesGraphComponent implements OnInit {
|
|||||||
const now = new Date();
|
const now = new Date();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.chartOptions.grid.bottom = 40;
|
this.chartOptions.grid.bottom = 40;
|
||||||
this.chartOptions.backgroundColor = '#11131f';
|
this.chartOptions.backgroundColor = 'var(--active-bg)';
|
||||||
this.chartInstance.setOption(this.chartOptions);
|
this.chartInstance.setOption(this.chartOptions);
|
||||||
download(this.chartInstance.getDataURL({
|
download(this.chartInstance.getDataURL({
|
||||||
pixelRatio: 2,
|
pixelRatio: 2,
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ export class BlockFeesGraphComponent implements OnInit {
|
|||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: '#b1b1b1',
|
color: 'var(--tooltip-grey)',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
},
|
},
|
||||||
borderColor: '#000',
|
borderColor: '#000',
|
||||||
@@ -205,7 +205,7 @@ export class BlockFeesGraphComponent implements OnInit {
|
|||||||
splitLine: {
|
splitLine: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
type: 'dotted',
|
type: 'dotted',
|
||||||
color: '#ffffff66',
|
color: 'var(--transparent-fg)',
|
||||||
opacity: 0.25,
|
opacity: 0.25,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -296,7 +296,7 @@ export class BlockFeesGraphComponent implements OnInit {
|
|||||||
const now = new Date();
|
const now = new Date();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.chartOptions.grid.bottom = 40;
|
this.chartOptions.grid.bottom = 40;
|
||||||
this.chartOptions.backgroundColor = '#11131f';
|
this.chartOptions.backgroundColor = 'var(--active-bg)';
|
||||||
this.chartInstance.setOption(this.chartOptions);
|
this.chartInstance.setOption(this.chartOptions);
|
||||||
download(this.chartInstance.getDataURL({
|
download(this.chartInstance.getDataURL({
|
||||||
pixelRatio: 2,
|
pixelRatio: 2,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-bottom: 100%;
|
padding-bottom: 100%;
|
||||||
background: #181b2d;
|
background: var(--stat-box-bg);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import BlockScene from './block-scene';
|
|||||||
import TxSprite from './tx-sprite';
|
import TxSprite from './tx-sprite';
|
||||||
import TxView from './tx-view';
|
import TxView from './tx-view';
|
||||||
import { Position } from './sprite-types';
|
import { Position } from './sprite-types';
|
||||||
|
import { ThemeService } from 'src/app/services/theme.service';
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-block-overview-graph',
|
selector: 'app-block-overview-graph',
|
||||||
@@ -26,6 +28,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
|
|
||||||
@ViewChild('blockCanvas')
|
@ViewChild('blockCanvas')
|
||||||
canvas: ElementRef<HTMLCanvasElement>;
|
canvas: ElementRef<HTMLCanvasElement>;
|
||||||
|
themeChangedSubscription: Subscription;
|
||||||
|
|
||||||
gl: WebGLRenderingContext;
|
gl: WebGLRenderingContext;
|
||||||
animationFrameRequest: number;
|
animationFrameRequest: number;
|
||||||
@@ -48,6 +51,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
constructor(
|
constructor(
|
||||||
readonly ngZone: NgZone,
|
readonly ngZone: NgZone,
|
||||||
readonly elRef: ElementRef,
|
readonly elRef: ElementRef,
|
||||||
|
private themeService: ThemeService,
|
||||||
) {
|
) {
|
||||||
this.vertexArray = new FastVertexArray(512, TxSprite.dataSize);
|
this.vertexArray = new FastVertexArray(512, TxSprite.dataSize);
|
||||||
}
|
}
|
||||||
@@ -59,6 +63,11 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
this.initCanvas();
|
this.initCanvas();
|
||||||
|
|
||||||
this.resizeCanvas();
|
this.resizeCanvas();
|
||||||
|
|
||||||
|
this.themeChangedSubscription = this.themeService.themeChanged$.subscribe(() => {
|
||||||
|
// force full re-render
|
||||||
|
this.resizeCanvas();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(changes): void {
|
ngOnChanges(changes): void {
|
||||||
@@ -77,6 +86,9 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
cancelAnimationFrame(this.animationFrameRequest);
|
cancelAnimationFrame(this.animationFrameRequest);
|
||||||
clearTimeout(this.animationHeartBeat);
|
clearTimeout(this.animationHeartBeat);
|
||||||
}
|
}
|
||||||
|
this.canvas.nativeElement.removeEventListener('webglcontextlost', this.handleContextLost);
|
||||||
|
this.canvas.nativeElement.removeEventListener('webglcontextrestored', this.handleContextRestored);
|
||||||
|
this.themeChangedSubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
clear(direction): void {
|
clear(direction): void {
|
||||||
@@ -193,7 +205,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
this.start();
|
this.start();
|
||||||
} else {
|
} else {
|
||||||
this.scene = new BlockScene({ width: this.displayWidth, height: this.displayHeight, resolution: this.resolution,
|
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();
|
this.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ import { FastVertexArray } from './fast-vertex-array';
|
|||||||
import TxView from './tx-view';
|
import TxView from './tx-view';
|
||||||
import { TransactionStripped } from '../../interfaces/websocket.interface';
|
import { TransactionStripped } from '../../interfaces/websocket.interface';
|
||||||
import { Position, Square, ViewUpdateParams } from './sprite-types';
|
import { Position, Square, ViewUpdateParams } from './sprite-types';
|
||||||
|
import { ThemeService } from 'src/app/services/theme.service';
|
||||||
|
|
||||||
export default class BlockScene {
|
export default class BlockScene {
|
||||||
scene: { count: number, offset: { x: number, y: number}};
|
scene: { count: number, offset: { x: number, y: number}};
|
||||||
vertexArray: FastVertexArray;
|
vertexArray: FastVertexArray;
|
||||||
txs: { [key: string]: TxView };
|
txs: { [key: string]: TxView };
|
||||||
|
theme: ThemeService;
|
||||||
orientation: string;
|
orientation: string;
|
||||||
flip: boolean;
|
flip: boolean;
|
||||||
width: number;
|
width: number;
|
||||||
@@ -22,11 +24,11 @@ export default class BlockScene {
|
|||||||
animateUntil = 0;
|
animateUntil = 0;
|
||||||
dirty: boolean;
|
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,
|
{ 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 {
|
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 });
|
this.layout = new BlockLayout({ width: this.gridWidth, height: this.gridHeight });
|
||||||
txs.forEach(tx => {
|
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.txs[tx.txid] = txView;
|
||||||
this.place(txView);
|
this.place(txView);
|
||||||
this.saveGridToScreenPosition(txView);
|
this.saveGridToScreenPosition(txView);
|
||||||
@@ -114,7 +116,7 @@ export default class BlockScene {
|
|||||||
});
|
});
|
||||||
txs.forEach(tx => {
|
txs.forEach(tx => {
|
||||||
if (!this.txs[tx.txid]) {
|
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) {
|
if (resetLayout) {
|
||||||
add.forEach(tx => {
|
add.forEach(tx => {
|
||||||
if (!this.txs[tx.txid]) {
|
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 });
|
this.layout = new BlockLayout({ width: this.gridWidth, height: this.gridHeight });
|
||||||
@@ -166,7 +168,7 @@ export default class BlockScene {
|
|||||||
} else {
|
} else {
|
||||||
// try to insert new txs directly
|
// try to insert new txs directly
|
||||||
const remaining = [];
|
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)) {
|
if (!this.tryInsertByFee(tx)) {
|
||||||
remaining.push(tx);
|
remaining.push(tx);
|
||||||
}
|
}
|
||||||
@@ -192,13 +194,14 @@ export default class BlockScene {
|
|||||||
this.animateUntil = Math.max(this.animateUntil, tx.setHover(value));
|
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,
|
{ width: number, height: number, resolution: number, blockLimit: number,
|
||||||
orientation: string, flip: boolean, vertexArray: FastVertexArray }
|
orientation: string, flip: boolean, vertexArray: FastVertexArray, theme: ThemeService }
|
||||||
): void {
|
): void {
|
||||||
this.orientation = orientation;
|
this.orientation = orientation;
|
||||||
this.flip = flip;
|
this.flip = flip;
|
||||||
this.vertexArray = vertexArray;
|
this.vertexArray = vertexArray;
|
||||||
|
this.theme = theme;
|
||||||
|
|
||||||
this.scene = {
|
this.scene = {
|
||||||
count: 0,
|
count: 0,
|
||||||
|
|||||||
@@ -2,20 +2,10 @@ import TxSprite from './tx-sprite';
|
|||||||
import { FastVertexArray } from './fast-vertex-array';
|
import { FastVertexArray } from './fast-vertex-array';
|
||||||
import { TransactionStripped } from '../../interfaces/websocket.interface';
|
import { TransactionStripped } from '../../interfaces/websocket.interface';
|
||||||
import { SpriteUpdateParams, Square, Color, ViewUpdateParams } from './sprite-types';
|
import { SpriteUpdateParams, Square, Color, ViewUpdateParams } from './sprite-types';
|
||||||
import { feeLevels, mempoolFeeColors } from '../../app.constants';
|
import { feeLevels } from '../../app.constants';
|
||||||
import BlockScene from './block-scene';
|
import { ThemeService } from 'src/app/services/theme.service';
|
||||||
|
|
||||||
const hoverTransitionTime = 300;
|
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
|
// convert from this class's update format to TxSprite's update format
|
||||||
function toSpriteUpdate(params: ViewUpdateParams): SpriteUpdateParams {
|
function toSpriteUpdate(params: ViewUpdateParams): SpriteUpdateParams {
|
||||||
@@ -37,6 +27,7 @@ export default class TxView implements TransactionStripped {
|
|||||||
feerate: number;
|
feerate: number;
|
||||||
status?: 'found' | 'missing' | 'fresh' | 'added' | 'censored' | 'selected';
|
status?: 'found' | 'missing' | 'fresh' | 'added' | 'censored' | 'selected';
|
||||||
context?: 'projected' | 'actual';
|
context?: 'projected' | 'actual';
|
||||||
|
theme: ThemeService;
|
||||||
|
|
||||||
initialised: boolean;
|
initialised: boolean;
|
||||||
vertexArray: FastVertexArray;
|
vertexArray: FastVertexArray;
|
||||||
@@ -49,7 +40,7 @@ export default class TxView implements TransactionStripped {
|
|||||||
|
|
||||||
dirty: boolean;
|
dirty: boolean;
|
||||||
|
|
||||||
constructor(tx: TransactionStripped, vertexArray: FastVertexArray) {
|
constructor(tx: TransactionStripped, vertexArray: FastVertexArray, theme: ThemeService) {
|
||||||
this.context = tx.context;
|
this.context = tx.context;
|
||||||
this.txid = tx.txid;
|
this.txid = tx.txid;
|
||||||
this.fee = tx.fee;
|
this.fee = tx.fee;
|
||||||
@@ -59,6 +50,7 @@ export default class TxView implements TransactionStripped {
|
|||||||
this.status = tx.status;
|
this.status = tx.status;
|
||||||
this.initialised = false;
|
this.initialised = false;
|
||||||
this.vertexArray = vertexArray;
|
this.vertexArray = vertexArray;
|
||||||
|
this.theme = theme;
|
||||||
|
|
||||||
this.hover = false;
|
this.hover = false;
|
||||||
|
|
||||||
@@ -131,10 +123,10 @@ export default class TxView implements TransactionStripped {
|
|||||||
|
|
||||||
// Temporarily override the tx color
|
// Temporarily override the tx color
|
||||||
// returns minimum transition end time
|
// returns minimum transition end time
|
||||||
setHover(hoverOn: boolean, color: Color | void = defaultHoverColor): number {
|
setHover(hoverOn: boolean, color: Color | void): number {
|
||||||
if (hoverOn) {
|
if (hoverOn) {
|
||||||
this.hover = true;
|
this.hover = true;
|
||||||
this.hoverColor = color;
|
this.hoverColor = color || this.theme.defaultHoverColor;
|
||||||
|
|
||||||
this.sprite.update({
|
this.sprite.update({
|
||||||
...this.hoverColor,
|
...this.hoverColor,
|
||||||
@@ -155,22 +147,22 @@ export default class TxView implements TransactionStripped {
|
|||||||
|
|
||||||
getColor(): Color {
|
getColor(): Color {
|
||||||
const feeLevelIndex = feeLevels.findIndex((feeLvl) => Math.max(1, this.feerate) < feeLvl) - 1;
|
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
|
// Block audit
|
||||||
switch(this.status) {
|
switch(this.status) {
|
||||||
case 'censored':
|
case 'censored':
|
||||||
return auditColors.censored;
|
return this.theme.auditColors.censored;
|
||||||
case 'missing':
|
case 'missing':
|
||||||
return auditColors.missing;
|
return this.theme.auditColors.missing;
|
||||||
case 'fresh':
|
case 'fresh':
|
||||||
return auditColors.missing;
|
return this.theme.auditColors.missing;
|
||||||
case 'added':
|
case 'added':
|
||||||
return auditColors.added;
|
return this.theme.auditColors.added;
|
||||||
case 'selected':
|
case 'selected':
|
||||||
return auditColors.selected;
|
return this.theme.auditColors.selected;
|
||||||
case 'found':
|
case 'found':
|
||||||
if (this.context === 'projected') {
|
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 {
|
} else {
|
||||||
return feeLevelColor;
|
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);
|
background: rgba(#11131f, 0.95);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
|
box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
|
||||||
color: #b1b1b1;
|
color: var(--tooltip-grey);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ export class BlockPredictionGraphComponent implements OnInit {
|
|||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: '#b1b1b1',
|
color: 'var(--tooltip-grey)',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
},
|
},
|
||||||
borderColor: '#000',
|
borderColor: '#000',
|
||||||
@@ -177,7 +177,7 @@ export class BlockPredictionGraphComponent implements OnInit {
|
|||||||
splitLine: {
|
splitLine: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
type: 'dotted',
|
type: 'dotted',
|
||||||
color: '#ffffff66',
|
color: 'var(--transparent-fg)',
|
||||||
opacity: 0.25,
|
opacity: 0.25,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -289,7 +289,7 @@ export class BlockPredictionGraphComponent implements OnInit {
|
|||||||
const now = new Date();
|
const now = new Date();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.chartOptions.grid.bottom = 40;
|
this.chartOptions.grid.bottom = 40;
|
||||||
this.chartOptions.backgroundColor = '#11131f';
|
this.chartOptions.backgroundColor = 'var(--active-bg)';
|
||||||
this.chartInstance.setOption(this.chartOptions);
|
this.chartInstance.setOption(this.chartOptions);
|
||||||
download(this.chartInstance.getDataURL({
|
download(this.chartInstance.getDataURL({
|
||||||
pixelRatio: 2,
|
pixelRatio: 2,
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ export class BlockRewardsGraphComponent implements OnInit {
|
|||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: '#b1b1b1',
|
color: 'var(--tooltip-grey)',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
},
|
},
|
||||||
borderColor: '#000',
|
borderColor: '#000',
|
||||||
@@ -211,7 +211,7 @@ export class BlockRewardsGraphComponent implements OnInit {
|
|||||||
splitLine: {
|
splitLine: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
type: 'dotted',
|
type: 'dotted',
|
||||||
color: '#ffffff66',
|
color: 'var(--transparent-fg)',
|
||||||
opacity: 0.25,
|
opacity: 0.25,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -307,7 +307,7 @@ export class BlockRewardsGraphComponent implements OnInit {
|
|||||||
const now = new Date();
|
const now = new Date();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.chartOptions.grid.bottom = 40;
|
this.chartOptions.grid.bottom = 40;
|
||||||
this.chartOptions.backgroundColor = '#11131f';
|
this.chartOptions.backgroundColor = 'var(--active-bg)';
|
||||||
this.chartInstance.setOption(this.chartOptions);
|
this.chartInstance.setOption(this.chartOptions);
|
||||||
download(this.chartInstance.getDataURL({
|
download(this.chartInstance.getDataURL({
|
||||||
pixelRatio: 2,
|
pixelRatio: 2,
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
|
|||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: '#b1b1b1',
|
color: 'var(--tooltip-grey)',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
},
|
},
|
||||||
borderColor: '#000',
|
borderColor: '#000',
|
||||||
@@ -214,7 +214,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
|
|||||||
splitLine: {
|
splitLine: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
type: 'dotted',
|
type: 'dotted',
|
||||||
color: '#ffffff66',
|
color: 'var(--transparent-fg)',
|
||||||
opacity: 0.25,
|
opacity: 0.25,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -236,7 +236,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
|
|||||||
symbol: 'none',
|
symbol: 'none',
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
type: 'solid',
|
type: 'solid',
|
||||||
color: '#ffffff66',
|
color: 'var(--transparent-fg)',
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
width: 1,
|
width: 1,
|
||||||
},
|
},
|
||||||
@@ -314,7 +314,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
|
|||||||
const now = new Date();
|
const now = new Date();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.chartOptions.grid.bottom = 40;
|
this.chartOptions.grid.bottom = 40;
|
||||||
this.chartOptions.backgroundColor = '#11131f';
|
this.chartOptions.backgroundColor = 'var(--active-bg)';
|
||||||
this.chartInstance.setOption(this.chartOptions);
|
this.chartInstance.setOption(this.chartOptions);
|
||||||
download(this.chartInstance.getDataURL({
|
download(this.chartInstance.getDataURL({
|
||||||
pixelRatio: 2,
|
pixelRatio: 2,
|
||||||
|
|||||||
@@ -53,13 +53,13 @@
|
|||||||
<td i18n="block.miner">Miner</td>
|
<td i18n="block.miner">Miner</td>
|
||||||
<td *ngIf="stateService.env.MINING_DASHBOARD">
|
<td *ngIf="stateService.env.MINING_DASHBOARD">
|
||||||
<a [attr.data-cy]="'block-details-miner-badge'" placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block?.extras.pool.slug]" class="badge"
|
<a [attr.data-cy]="'block-details-miner-badge'" placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block?.extras.pool.slug]" class="badge"
|
||||||
[class]="block?.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
[class]="!block?.extras.pool.name || block?.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||||
{{ block?.extras.pool.name }}
|
{{ block?.extras.pool.name }}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td *ngIf="!stateService.env.MINING_DASHBOARD && stateService.env.BASE_MODULE === 'mempool'">
|
<td *ngIf="!stateService.env.MINING_DASHBOARD && stateService.env.BASE_MODULE === 'mempool'">
|
||||||
<span [attr.data-cy]="'block-details-miner-badge'" placement="bottom" class="badge"
|
<span [attr.data-cy]="'block-details-miner-badge'" placement="bottom" class="badge"
|
||||||
[class]="block?.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
[class]="!block?.extras.pool.name || block?.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||||
{{ block?.extras.pool.name }}
|
{{ block?.extras.pool.name }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -56,3 +56,7 @@
|
|||||||
::ng-deep .symbol {
|
::ng-deep .symbol {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,10 +32,10 @@
|
|||||||
|
|
||||||
<div class="box" *ngIf="!error">
|
<div class="box" *ngIf="!error">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<ng-template [ngIf]="!isLoadingBlock">
|
<div class="col-sm">
|
||||||
<div class="col-sm">
|
<table class="table table-borderless table-striped">
|
||||||
<table class="table table-borderless table-striped">
|
<tbody>
|
||||||
<tbody>
|
<ng-container *ngIf="!isLoadingBlock; else skeletonRows">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="td-width" i18n="block.hash">Hash</td>
|
<td class="td-width" i18n="block.hash">Hash</td>
|
||||||
<td>‎<a [routerLink]="['/block/' | relativeUrl, block.id]" title="{{ block.id }}">{{ block.id | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="block.id"></app-clipboard></td>
|
<td>‎<a [routerLink]="['/block/' | relativeUrl, block.id]" title="{{ block.id }}">{{ block.id | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="block.id"></app-clipboard></td>
|
||||||
@@ -54,83 +54,28 @@
|
|||||||
<td i18n="block.weight">Weight</td>
|
<td i18n="block.weight">Weight</td>
|
||||||
<td [innerHTML]="'‎' + (block.weight | wuBytes: 2)"></td>
|
<td [innerHTML]="'‎' + (block.weight | wuBytes: 2)"></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr *ngIf="auditEnabled">
|
<tr *ngIf="!auditDataMissing && indexingAvailable">
|
||||||
<td i18n="block.health">Block health</td>
|
<td i18n="block.health">Block health</td>
|
||||||
<td>
|
<td>
|
||||||
<span *ngIf="blockAudit?.matchRate != null">{{ blockAudit.matchRate }}%</span>
|
<span
|
||||||
<span *ngIf="blockAudit?.matchRate === null" i18n="unknown">Unknown</span>
|
class="health-badge badge"
|
||||||
|
[class.badge-success]="blockAudit?.matchRate >= 99"
|
||||||
|
[class.badge-warning]="blockAudit?.matchRate >= 75 && blockAudit?.matchRate < 99"
|
||||||
|
[class.badge-danger]="blockAudit?.matchRate < 75"
|
||||||
|
*ngIf="blockAudit?.matchRate != null; else nullHealth"
|
||||||
|
>{{ blockAudit?.matchRate }}%</span>
|
||||||
|
<ng-template #nullHealth>
|
||||||
|
<ng-container *ngIf="!isLoadingAudit; else loadingHealth">
|
||||||
|
<span class="health-badge badge badge-secondary" i18n="unknown">Unknown</span>
|
||||||
|
</ng-container>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #loadingHealth>
|
||||||
|
<span class="skeleton-loader" style="max-width: 60px"></span>
|
||||||
|
</ng-template>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<ng-container *ngIf="webGlEnabled && (auditDataMissing || !indexingAvailable)">
|
</ng-container>
|
||||||
<tr *ngIf="isMobile && auditEnabled"></tr>
|
<ng-template #skeletonRows>
|
||||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
|
||||||
<td i18n="mempool-block.fee-span">Fee span</td>
|
|
||||||
<td><span>{{ block.extras.feeRange[0] | number:'1.0-0' }} - {{ block.extras.feeRange[block.extras.feeRange.length - 1] | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></span></td>
|
|
||||||
</tr>
|
|
||||||
<tr *ngIf="block?.extras?.medianFee != undefined">
|
|
||||||
<td class="td-width" i18n="block.median-fee">Median fee</td>
|
|
||||||
<td>~{{ block?.extras?.medianFee | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> <span class="fiat"><app-fiat [value]="block?.extras?.medianFee * 140" digitsInfo="1.2-2" i18n-ngbTooltip="Transaction fee tooltip" ngbTooltip="Based on average native segwit transaction of 140 vBytes" placement="bottom"></app-fiat></span></td>
|
|
||||||
</tr>
|
|
||||||
<ng-template [ngIf]="fees !== undefined" [ngIfElse]="loadingFees">
|
|
||||||
<tr>
|
|
||||||
<td i18n="block.total-fees|Total fees in a block">Total fees</td>
|
|
||||||
<td *ngIf="network !== 'liquid' && network !== 'liquidtestnet'; else liquidTotalFees">
|
|
||||||
<app-amount [satoshis]="block.extras.totalFees" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
|
|
||||||
<span class="fiat">
|
|
||||||
<app-fiat [value]="block.extras.totalFees" digitsInfo="1.0-0"></app-fiat>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<ng-template #liquidTotalFees>
|
|
||||||
<td>
|
|
||||||
<app-amount [satoshis]="fees * 100000000" digitsInfo="1.2-2" [noFiat]="true"></app-amount> <app-fiat
|
|
||||||
[value]="fees * 100000000" digitsInfo="1.2-2"></app-fiat>
|
|
||||||
</td>
|
|
||||||
</ng-template>
|
|
||||||
</tr>
|
|
||||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
|
||||||
<td i18n="block.subsidy-and-fees|Total subsidy and fees in a block">Subsidy + fees:</td>
|
|
||||||
<td>
|
|
||||||
<app-amount [satoshis]="block.extras.reward" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
|
|
||||||
<span class="fiat">
|
|
||||||
<app-fiat [value]="(blockSubsidy + fees) * 100000000" digitsInfo="1.0-0"></app-fiat>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template #loadingFees>
|
|
||||||
<tr>
|
|
||||||
<td i18n="block.total-fees|Total fees in a block">Total fees</td>
|
|
||||||
<td style="width: 75%;"><span class="skeleton-loader"></span></td>
|
|
||||||
</tr>
|
|
||||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
|
||||||
<td i18n="block.subsidy-and-fees|Total subsidy and fees in a block">Subsidy + fees:</td>
|
|
||||||
<td><span class="skeleton-loader"></span></td>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
|
||||||
<td i18n="block.miner">Miner</td>
|
|
||||||
<td *ngIf="stateService.env.MINING_DASHBOARD">
|
|
||||||
<a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block.extras.pool.slug]" class="badge"
|
|
||||||
[class]="block.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
|
||||||
{{ block.extras.pool.name }}
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td *ngIf="!stateService.env.MINING_DASHBOARD && stateService.env.BASE_MODULE === 'mempool'">
|
|
||||||
<span placement="bottom" class="badge"
|
|
||||||
[class]="block.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
|
||||||
{{ block.extras.pool.name }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</ng-container>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template [ngIf]="isLoadingBlock">
|
|
||||||
<div class="col-sm">
|
|
||||||
<table class="table table-borderless table-striped">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td class="td-width" colspan="2"><span class="skeleton-loader"></span></td>
|
<td class="td-width" colspan="2"><span class="skeleton-loader"></span></td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -143,114 +88,18 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
<tr *ngIf="!auditDataMissing && indexingAvailable">
|
||||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||||
</tr>
|
</tr>
|
||||||
<ng-container *ngIf="webGlEnabled && (!indexingAvailable || auditDataMissing)">
|
|
||||||
<tr *ngIf="isMobile && !auditEnabled"></tr>
|
|
||||||
<tr>
|
|
||||||
<td class="td-width" colspan="2"><span class="skeleton-loader"></span></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
|
||||||
</tr>
|
|
||||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
|
||||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
|
||||||
</tr>
|
|
||||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
|
||||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
|
||||||
</tr>
|
|
||||||
</ng-container>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
<div class="col-sm">
|
|
||||||
<table class="table table-borderless table-striped" *ngIf="!isLoadingBlock && (!auditDataMissing || indexingAvailable && !webGlEnabled)">
|
|
||||||
<tbody>
|
|
||||||
<tr *ngIf="isMobile && auditEnabled"></tr>
|
|
||||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
|
||||||
<td i18n="mempool-block.fee-span">Fee span</td>
|
|
||||||
<td><span>{{ block.extras.feeRange[0] | number:'1.0-0' }} - {{ block.extras.feeRange[block.extras.feeRange.length - 1] | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></span></td>
|
|
||||||
</tr>
|
|
||||||
<tr *ngIf="block?.extras?.medianFee != undefined">
|
|
||||||
<td class="td-width" i18n="block.median-fee">Median fee</td>
|
|
||||||
<td>~{{ block?.extras?.medianFee | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> <span class="fiat"><app-fiat [value]="block?.extras?.medianFee * 140" digitsInfo="1.2-2" i18n-ngbTooltip="Transaction fee tooltip" ngbTooltip="Based on average native segwit transaction of 140 vBytes" placement="bottom"></app-fiat></span></td>
|
|
||||||
</tr>
|
|
||||||
<ng-template [ngIf]="fees !== undefined" [ngIfElse]="loadingFees">
|
|
||||||
<tr>
|
|
||||||
<td i18n="block.total-fees|Total fees in a block">Total fees</td>
|
|
||||||
<td *ngIf="network !== 'liquid' && network !== 'liquidtestnet'; else liquidTotalFees">
|
|
||||||
<app-amount [satoshis]="block.extras.totalFees" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
|
|
||||||
<span class="fiat">
|
|
||||||
<app-fiat [value]="block.extras.totalFees" digitsInfo="1.0-0"></app-fiat>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<ng-template #liquidTotalFees>
|
|
||||||
<td>
|
|
||||||
<app-amount [satoshis]="fees * 100000000" digitsInfo="1.2-2" [noFiat]="true"></app-amount> <app-fiat
|
|
||||||
[value]="fees * 100000000" digitsInfo="1.2-2"></app-fiat>
|
|
||||||
</td>
|
|
||||||
</ng-template>
|
|
||||||
</tr>
|
|
||||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
|
||||||
<td i18n="block.subsidy-and-fees|Total subsidy and fees in a block">Subsidy + fees:</td>
|
|
||||||
<td>
|
|
||||||
<app-amount [satoshis]="block.extras.reward" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
|
|
||||||
<span class="fiat">
|
|
||||||
<app-fiat [value]="(blockSubsidy + fees) * 100000000" digitsInfo="1.0-0"></app-fiat>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template #loadingFees>
|
<ng-container *ngIf="isMobile || (webGlEnabled && (auditDataMissing || !indexingAvailable)); then restOfTable;"></ng-container>
|
||||||
<tr>
|
|
||||||
<td i18n="block.total-fees|Total fees in a block">Total fees</td>
|
|
||||||
<td style="width: 75%;"><span class="skeleton-loader"></span></td>
|
|
||||||
</tr>
|
|
||||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
|
||||||
<td i18n="block.subsidy-and-fees|Total subsidy and fees in a block">Subsidy + fees:</td>
|
|
||||||
<td><span class="skeleton-loader"></span></td>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
|
||||||
<td i18n="block.miner">Miner</td>
|
|
||||||
<td *ngIf="stateService.env.MINING_DASHBOARD">
|
|
||||||
<a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block.extras.pool.slug]" class="badge"
|
|
||||||
[class]="block.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
|
||||||
{{ block.extras.pool.name }}
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td *ngIf="!stateService.env.MINING_DASHBOARD && stateService.env.BASE_MODULE === 'mempool'">
|
|
||||||
<span placement="bottom" class="badge"
|
|
||||||
[class]="block.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
|
||||||
{{ block.extras.pool.name }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<table class="table table-borderless table-striped" *ngIf="isLoadingBlock && !auditDataMissing && (indexingAvailable || !webGlEnabled)">
|
</div>
|
||||||
|
<div class="col-sm">
|
||||||
|
<table class="table table-borderless table-striped" *ngIf="!isMobile && !(webGlEnabled && (auditDataMissing || !indexingAvailable))">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngIf="isMobile && !auditEnabled"></tr>
|
<ng-container *ngTemplateOutlet="restOfTable"></ng-container>
|
||||||
<tr>
|
|
||||||
<td class="td-width" colspan="2"><span class="skeleton-loader"></span></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
|
||||||
</tr>
|
|
||||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
|
||||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div class="col-sm chart-container" *ngIf="webGlEnabled && (!indexingAvailable || auditDataMissing)">
|
<div class="col-sm chart-container" *ngIf="webGlEnabled && (!indexingAvailable || auditDataMissing)">
|
||||||
@@ -268,6 +117,87 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ng-template #restOfTable>
|
||||||
|
<ng-container *ngIf="!isLoadingBlock; else loadingRest">
|
||||||
|
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||||
|
<td i18n="mempool-block.fee-span">Fee span</td>
|
||||||
|
<td><span>{{ block.extras.feeRange[0] | number:'1.0-0' }} - {{ block.extras.feeRange[block.extras.feeRange.length - 1] | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></span></td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngIf="block?.extras?.medianFee != undefined">
|
||||||
|
<td class="td-width" i18n="block.median-fee">Median fee</td>
|
||||||
|
<td>~{{ block?.extras?.medianFee | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> <span class="fiat"><app-fiat [value]="block?.extras?.medianFee * 140" digitsInfo="1.2-2" i18n-ngbTooltip="Transaction fee tooltip" ngbTooltip="Based on average native segwit transaction of 140 vBytes" placement="bottom"></app-fiat></span></td>
|
||||||
|
</tr>
|
||||||
|
<ng-template [ngIf]="fees !== undefined" [ngIfElse]="loadingFees">
|
||||||
|
<tr>
|
||||||
|
<td i18n="block.total-fees|Total fees in a block">Total fees</td>
|
||||||
|
<td *ngIf="network !== 'liquid' && network !== 'liquidtestnet'; else liquidTotalFees">
|
||||||
|
<app-amount [satoshis]="block.extras.totalFees" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
|
||||||
|
<span class="fiat">
|
||||||
|
<app-fiat [value]="block.extras.totalFees" digitsInfo="1.0-0"></app-fiat>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<ng-template #liquidTotalFees>
|
||||||
|
<td>
|
||||||
|
<app-amount [satoshis]="fees * 100000000" digitsInfo="1.2-2" [noFiat]="true"></app-amount> <app-fiat
|
||||||
|
[value]="fees * 100000000" digitsInfo="1.2-2"></app-fiat>
|
||||||
|
</td>
|
||||||
|
</ng-template>
|
||||||
|
</tr>
|
||||||
|
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||||
|
<td i18n="block.subsidy-and-fees|Total subsidy and fees in a block">Subsidy + fees:</td>
|
||||||
|
<td>
|
||||||
|
<app-amount [satoshis]="block.extras.reward" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
|
||||||
|
<span class="fiat">
|
||||||
|
<app-fiat [value]="(blockSubsidy + fees) * 100000000" digitsInfo="1.0-0"></app-fiat>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #loadingFees>
|
||||||
|
<tr>
|
||||||
|
<td i18n="block.total-fees|Total fees in a block">Total fees</td>
|
||||||
|
<td style="width: 75%;"><span class="skeleton-loader"></span></td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||||
|
<td i18n="block.subsidy-and-fees|Total subsidy and fees in a block">Subsidy + fees:</td>
|
||||||
|
<td><span class="skeleton-loader"></span></td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||||
|
<td i18n="block.miner">Miner</td>
|
||||||
|
<td *ngIf="stateService.env.MINING_DASHBOARD">
|
||||||
|
<a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block.extras.pool.slug]" class="badge"
|
||||||
|
[class]="block.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||||
|
{{ block.extras.pool.name }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td *ngIf="!stateService.env.MINING_DASHBOARD && stateService.env.BASE_MODULE === 'mempool'">
|
||||||
|
<span placement="bottom" class="badge"
|
||||||
|
[class]="block.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||||
|
{{ block.extras.pool.name }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #loadingRest>
|
||||||
|
<tr>
|
||||||
|
<td class="td-width" colspan="2"><span class="skeleton-loader"></span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||||
|
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
<span id="overview"></span>
|
<span id="overview"></span>
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|||||||
@@ -118,9 +118,7 @@ h1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: #1ad8f4;
|
|
||||||
&:hover, &:focus {
|
&:hover, &:focus {
|
||||||
color: #09a3ba;
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,7 +194,7 @@ h1 {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
background: #24273e;
|
background: var(--box-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active, &:hover {
|
&.active, &:hover {
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
strippedTransactions: TransactionStripped[];
|
strippedTransactions: TransactionStripped[];
|
||||||
overviewTransitionDirection: string;
|
overviewTransitionDirection: string;
|
||||||
isLoadingOverview = true;
|
isLoadingOverview = true;
|
||||||
|
isLoadingAudit = true;
|
||||||
error: any;
|
error: any;
|
||||||
blockSubsidy: number;
|
blockSubsidy: number;
|
||||||
fees: number;
|
fees: number;
|
||||||
@@ -297,13 +298,18 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
this.auditSubscription = block$.pipe(
|
this.auditSubscription = block$.pipe(
|
||||||
startWith(null),
|
startWith(null),
|
||||||
pairwise(),
|
pairwise(),
|
||||||
switchMap(([prevBlock, block]) => this.apiService.getBlockAudit$(block.id)
|
switchMap(([prevBlock, block]) => {
|
||||||
.pipe(
|
this.isLoadingAudit = true;
|
||||||
catchError((err) => {
|
this.blockAudit = null;
|
||||||
this.overviewError = err;
|
return this.apiService.getBlockAudit$(block.id)
|
||||||
return of([]);
|
.pipe(
|
||||||
})
|
catchError((err) => {
|
||||||
)
|
this.overviewError = err;
|
||||||
|
this.isLoadingAudit = false;
|
||||||
|
return of([]);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
),
|
),
|
||||||
filter((response) => response != null),
|
filter((response) => response != null),
|
||||||
map((response) => {
|
map((response) => {
|
||||||
@@ -375,12 +381,14 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
console.log(err);
|
console.log(err);
|
||||||
this.error = err;
|
this.error = err;
|
||||||
this.isLoadingOverview = false;
|
this.isLoadingOverview = false;
|
||||||
|
this.isLoadingAudit = false;
|
||||||
return of(null);
|
return of(null);
|
||||||
}),
|
}),
|
||||||
).subscribe((blockAudit) => {
|
).subscribe((blockAudit) => {
|
||||||
this.blockAudit = blockAudit;
|
this.blockAudit = blockAudit;
|
||||||
this.setupBlockGraphs();
|
this.setupBlockGraphs();
|
||||||
this.isLoadingOverview = false;
|
this.isLoadingOverview = false;
|
||||||
|
this.isLoadingAudit = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -97,7 +97,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.black-background {
|
.black-background {
|
||||||
background-color: #11131f;
|
background-color: var(--active-bg);
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,12 +37,12 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
|||||||
timeLtr: boolean;
|
timeLtr: boolean;
|
||||||
|
|
||||||
gradientColors = {
|
gradientColors = {
|
||||||
'': ['#9339f4', '#105fb0'],
|
'': ['var(--mainnet-alt)', 'var(--primary)'],
|
||||||
bisq: ['#9339f4', '#105fb0'],
|
bisq: ['var(--mainnet-alt)', 'var(--primary)'],
|
||||||
liquid: ['#116761', '#183550'],
|
liquid: ['var(--liquid)', 'var(--testnet-alt)'],
|
||||||
'liquidtestnet': ['#494a4a', '#272e46'],
|
'liquidtestnet': ['var(--liquidtestnet)', 'var(--liquidtestnet-alt)'],
|
||||||
testnet: ['#1d486f', '#183550'],
|
testnet: ['var(--testnet)', 'var(--testnet-alt)'],
|
||||||
signet: ['#6f1d5d', '#471850'],
|
signet: ['var(--signet)', 'var(--signet-alt)'],
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -181,7 +181,7 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
|||||||
left: addLeft + 155 * this.blocks.indexOf(block) + 'px',
|
left: addLeft + 155 * this.blocks.indexOf(block) + 'px',
|
||||||
background: `repeating-linear-gradient(
|
background: `repeating-linear-gradient(
|
||||||
#2d3348,
|
#2d3348,
|
||||||
#2d3348 ${greenBackgroundHeight}%,
|
var(--secondary) ${greenBackgroundHeight}%,
|
||||||
${this.gradientColors[this.network][0]} ${Math.max(greenBackgroundHeight, 0)}%,
|
${this.gradientColors[this.network][0]} ${Math.max(greenBackgroundHeight, 0)}%,
|
||||||
${this.gradientColors[this.network][1]} 100%
|
${this.gradientColors[this.network][1]} 100%
|
||||||
)`,
|
)`,
|
||||||
@@ -198,7 +198,7 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
left: addLeft + 155 * this.emptyBlocks.indexOf(block) + 'px',
|
left: addLeft + 155 * this.emptyBlocks.indexOf(block) + 'px',
|
||||||
background: "#2d3348",
|
background: "var(--secondary)",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.black-background {
|
.black-background {
|
||||||
background-color: #11131f;
|
background-color: var(--active-bg);
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
i18n-ngbTooltip="mining.pool-name" ngbTooltip="Pool" placement="bottom" #miningpool [disableTooltip]="!isEllipsisActive(miningpool)">Pool</th>
|
i18n-ngbTooltip="mining.pool-name" ngbTooltip="Pool" placement="bottom" #miningpool [disableTooltip]="!isEllipsisActive(miningpool)">Pool</th>
|
||||||
<th class="timestamp" i18n="latest-blocks.timestamp" *ngIf="!widget" [class]="indexingAvailable ? '' : 'legacy'">Timestamp</th>
|
<th class="timestamp" i18n="latest-blocks.timestamp" *ngIf="!widget" [class]="indexingAvailable ? '' : 'legacy'">Timestamp</th>
|
||||||
<th class="mined" i18n="latest-blocks.mined" *ngIf="!widget" [class]="indexingAvailable ? '' : 'legacy'">Mined</th>
|
<th class="mined" i18n="latest-blocks.mined" *ngIf="!widget" [class]="indexingAvailable ? '' : 'legacy'">Mined</th>
|
||||||
<th *ngIf="indexingAvailable" class="health text-left" i18n="latest-blocks.health" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}"
|
<th *ngIf="indexingAvailable" class="health text-right" i18n="latest-blocks.health" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}"
|
||||||
i18n-ngbTooltip="latest-blocks.health" ngbTooltip="Health" placement="bottom" #health [disableTooltip]="!isEllipsisActive(health)">Health</th>
|
i18n-ngbTooltip="latest-blocks.health" ngbTooltip="Health" placement="bottom" #health [disableTooltip]="!isEllipsisActive(health)">Health</th>
|
||||||
<th *ngIf="indexingAvailable" class="reward text-right" i18n="latest-blocks.reward" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}"
|
<th *ngIf="indexingAvailable" class="reward text-right" i18n="latest-blocks.reward" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}"
|
||||||
i18n-ngbTooltip="latest-blocks.reward" ngbTooltip="Reward" placement="bottom" #reward [disableTooltip]="!isEllipsisActive(reward)">Reward</th>
|
i18n-ngbTooltip="latest-blocks.reward" ngbTooltip="Reward" placement="bottom" #reward [disableTooltip]="!isEllipsisActive(reward)">Reward</th>
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
<tbody *ngIf="blocks$ | async as blocks; else skeleton" [style]="isLoading ? 'opacity: 0.75' : ''">
|
<tbody *ngIf="blocks$ | async as blocks; else skeleton" [style]="isLoading ? 'opacity: 0.75' : ''">
|
||||||
<tr *ngFor="let block of blocks; let i= index; trackBy: trackByBlock">
|
<tr *ngFor="let block of blocks; let i= index; trackBy: trackByBlock">
|
||||||
<td class="text-left" [class]="widget ? 'widget' : ''">
|
<td class="text-left" [class]="widget ? 'widget' : ''">
|
||||||
<a [routerLink]="['/block' | relativeUrl, block.id]">{{ block.height }}</a>
|
<a [routerLink]="['/block' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a>
|
||||||
</td>
|
</td>
|
||||||
<td *ngIf="indexingAvailable" class="pool text-left" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
|
<td *ngIf="indexingAvailable" class="pool text-left" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
|
||||||
<div class="tooltip-custom">
|
<div class="tooltip-custom">
|
||||||
@@ -46,16 +46,23 @@
|
|||||||
<app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since>
|
<app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since>
|
||||||
</td>
|
</td>
|
||||||
<td *ngIf="indexingAvailable" class="health text-right" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
|
<td *ngIf="indexingAvailable" class="health text-right" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
|
||||||
<a class="clear-link" [routerLink]="auditScores[block.id] != null ? ['/block/' | relativeUrl, block.id] : null">
|
<a
|
||||||
<div class="progress progress-health">
|
class="health-badge badge"
|
||||||
<div class="progress-bar progress-bar-health" role="progressbar"
|
[class.badge-success]="auditScores[block.id] >= 99"
|
||||||
[ngStyle]="{'width': (100 - (auditScores[block.id] || 0)) + '%' }"></div>
|
[class.badge-warning]="auditScores[block.id] >= 75 && auditScores[block.id] < 99"
|
||||||
<div class="progress-text">
|
[class.badge-danger]="auditScores[block.id] < 75"
|
||||||
<span *ngIf="auditScores[block.id] != null;">{{ auditScores[block.id] }}%</span>
|
[routerLink]="auditScores[block.id] != null ? ['/block/' | relativeUrl, block.id] : null"
|
||||||
<span *ngIf="auditScores[block.id] == null">~</span>
|
[state]="{ data: { block: block } }"
|
||||||
</div>
|
*ngIf="auditScores[block.id] != null; else nullHealth"
|
||||||
</div>
|
>{{ auditScores[block.id] }}%</a>
|
||||||
</a>
|
<ng-template #nullHealth>
|
||||||
|
<ng-container *ngIf="!loadingScores; else loadingHealth">
|
||||||
|
<span class="health-badge badge badge-secondary" i18n="unknown">Unknown</span>
|
||||||
|
</ng-container>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #loadingHealth>
|
||||||
|
<span class="skeleton-loader" style="max-width: 60px"></span>
|
||||||
|
</ng-template>
|
||||||
</td>
|
</td>
|
||||||
<td *ngIf="indexingAvailable" class="reward text-right" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
|
<td *ngIf="indexingAvailable" class="reward text-right" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
|
||||||
<app-amount [satoshis]="block.extras.reward" [noFiat]="true" digitsInfo="1.2-2"></app-amount>
|
<app-amount [satoshis]="block.extras.reward" [noFiat]="true" digitsInfo="1.2-2"></app-amount>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ tr, td, th {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.progress {
|
.progress {
|
||||||
background-color: #2d3348;
|
background-color: var(--secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.pool {
|
.pool {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export class BlocksList implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
indexingAvailable = false;
|
indexingAvailable = false;
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
|
loadingScores = true;
|
||||||
fromBlockHeight = undefined;
|
fromBlockHeight = undefined;
|
||||||
paginationMaxSize: number;
|
paginationMaxSize: number;
|
||||||
page = 1;
|
page = 1;
|
||||||
@@ -113,6 +114,7 @@ export class BlocksList implements OnInit, OnDestroy {
|
|||||||
if (this.indexingAvailable) {
|
if (this.indexingAvailable) {
|
||||||
this.auditScoreSubscription = this.fromHeightSubject.pipe(
|
this.auditScoreSubscription = this.fromHeightSubject.pipe(
|
||||||
switchMap((fromBlockHeight) => {
|
switchMap((fromBlockHeight) => {
|
||||||
|
this.loadingScores = true;
|
||||||
return this.apiService.getBlockAuditScores$(this.page === 1 ? undefined : fromBlockHeight)
|
return this.apiService.getBlockAuditScores$(this.page === 1 ? undefined : fromBlockHeight)
|
||||||
.pipe(
|
.pipe(
|
||||||
catchError(() => {
|
catchError(() => {
|
||||||
@@ -124,6 +126,7 @@ export class BlocksList implements OnInit, OnDestroy {
|
|||||||
Object.values(scores).forEach(score => {
|
Object.values(scores).forEach(score => {
|
||||||
this.auditScores[score.hash] = score?.matchRate != null ? score.matchRate : null;
|
this.auditScores[score.hash] = score?.matchRate != null ? score.matchRate : null;
|
||||||
});
|
});
|
||||||
|
this.loadingScores = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.latestScoreSubscription = this.stateService.blocks$.pipe(
|
this.latestScoreSubscription = this.stateService.blocks$.pipe(
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
<h5 class="card-title" i18n="difficulty-box.current-period">Current Period</h5>
|
<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="card-text">{{ epochData.progress | number: '1.2-2' }} <span class="symbol">%</span></div>
|
||||||
<div class="progress small-bar">
|
<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>
|
</div>
|
||||||
<div class="item" *ngIf="showHalving">
|
<div class="item" *ngIf="showHalving">
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
height: 76px;
|
height: 76px;
|
||||||
.shared-block {
|
.shared-block {
|
||||||
color: #ffffff66;
|
color: var(--transparent-fg);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
.item {
|
.item {
|
||||||
@@ -77,19 +77,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
background-color: #1d1f31;
|
background-color: var(--bg);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-title {
|
.card-title {
|
||||||
color: #4a68b9;
|
color: var(--title-fg);
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress {
|
.progress {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: #2d3348;
|
background-color: var(--secondary);
|
||||||
height: 1.1rem;
|
height: 1.1rem;
|
||||||
max-width: 180px;
|
max-width: 180px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,24 +43,24 @@ export class DifficultyComponent implements OnInit {
|
|||||||
])
|
])
|
||||||
.pipe(
|
.pipe(
|
||||||
map(([block, da]) => {
|
map(([block, da]) => {
|
||||||
let colorAdjustments = '#ffffff66';
|
let colorAdjustments = 'var(--transparent-fg)';
|
||||||
if (da.difficultyChange > 0) {
|
if (da.difficultyChange > 0) {
|
||||||
colorAdjustments = '#3bcc49';
|
colorAdjustments = 'var(--green)';
|
||||||
}
|
}
|
||||||
if (da.difficultyChange < 0) {
|
if (da.difficultyChange < 0) {
|
||||||
colorAdjustments = '#dc3545';
|
colorAdjustments = 'var(--red)';
|
||||||
}
|
}
|
||||||
|
|
||||||
let colorPreviousAdjustments = '#dc3545';
|
let colorPreviousAdjustments = 'var(--red)';
|
||||||
if (da.previousRetarget) {
|
if (da.previousRetarget) {
|
||||||
if (da.previousRetarget >= 0) {
|
if (da.previousRetarget >= 0) {
|
||||||
colorPreviousAdjustments = '#3bcc49';
|
colorPreviousAdjustments = 'var(--green)';
|
||||||
}
|
}
|
||||||
if (da.previousRetarget === 0) {
|
if (da.previousRetarget === 0) {
|
||||||
colorPreviousAdjustments = '#ffffff66';
|
colorPreviousAdjustments = 'var(--transparent-fg)';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
colorPreviousAdjustments = '#ffffff66';
|
colorPreviousAdjustments = 'var(--transparent-fg)';
|
||||||
}
|
}
|
||||||
|
|
||||||
const blocksUntilHalving = 210000 - (block.height % 210000);
|
const blocksUntilHalving = 210000 - (block.height % 210000);
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export class FeeDistributionGraphComponent implements OnInit, OnChanges {
|
|||||||
splitLine: {
|
splitLine: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
type: 'dotted',
|
type: 'dotted',
|
||||||
color: '#ffffff66',
|
color: 'var(--transparent-fg)',
|
||||||
opacity: 0.25,
|
opacity: 0.25,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
.card-title {
|
.card-title {
|
||||||
color: #4a68b9;
|
color: var(--title-fg);
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
.card-text span {
|
.card-text span {
|
||||||
color: #ffffff66;
|
color: var(--transparent-fg);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
}
|
}
|
||||||
@@ -79,6 +79,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
transition: background-color 1s;
|
transition: background-color 1s;
|
||||||
|
color: var(--color-fg);
|
||||||
&.priority {
|
&.priority {
|
||||||
@media (767px < width < 992px), (width < 576px) {
|
@media (767px < width < 992px), (width < 576px) {
|
||||||
width: 100%;
|
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 { StateService } from '../../services/state.service';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable, Subscription } from 'rxjs';
|
||||||
import { Recommendedfees } from '../../interfaces/websocket.interface';
|
import { Recommendedfees } from '../../interfaces/websocket.interface';
|
||||||
import { feeLevels, mempoolFeeColors } from '../../app.constants';
|
import { feeLevels } from '../../app.constants';
|
||||||
import { tap } from 'rxjs/operators';
|
import { tap } from 'rxjs/operators';
|
||||||
|
import { ThemeService } from 'src/app/services/theme.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-fees-box',
|
selector: 'app-fees-box',
|
||||||
@@ -11,14 +12,18 @@ import { tap } from 'rxjs/operators';
|
|||||||
styleUrls: ['./fees-box.component.scss'],
|
styleUrls: ['./fees-box.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class FeesBoxComponent implements OnInit {
|
export class FeesBoxComponent implements OnInit, OnDestroy {
|
||||||
isLoadingWebSocket$: Observable<boolean>;
|
isLoadingWebSocket$: Observable<boolean>;
|
||||||
recommendedFees$: Observable<Recommendedfees>;
|
recommendedFees$: Observable<Recommendedfees>;
|
||||||
|
themeSubscription: Subscription;
|
||||||
gradient = 'linear-gradient(to right, #2e324e, #2e324e)';
|
gradient = 'linear-gradient(to right, #2e324e, #2e324e)';
|
||||||
noPriority = '#2e324e';
|
noPriority = '#2e324e';
|
||||||
|
fees: Recommendedfees;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private stateService: StateService
|
private stateService: StateService,
|
||||||
|
private themeService: ThemeService,
|
||||||
|
private cd: ChangeDetectorRef,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@@ -26,18 +31,32 @@ export class FeesBoxComponent implements OnInit {
|
|||||||
this.recommendedFees$ = this.stateService.recommendedFees$
|
this.recommendedFees$ = this.stateService.recommendedFees$
|
||||||
.pipe(
|
.pipe(
|
||||||
tap((fees) => {
|
tap((fees) => {
|
||||||
let feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => fees.minimumFee >= feeLvl);
|
this.fees = fees;
|
||||||
feeLevelIndex = feeLevelIndex >= 0 ? feeLevels.length - feeLevelIndex : feeLevelIndex;
|
this.setFeeGradient();
|
||||||
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.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;
|
bottom: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
background-color: #1d1f31;
|
background-color: var(--bg);
|
||||||
box-shadow: 15px 15px 15px 15px #000;
|
box-shadow: 15px 15px 15px 15px #000;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
@@ -34,16 +34,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.txPerSecond {
|
|
||||||
color: #4a9ff4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mempoolSize {
|
.mempoolSize {
|
||||||
color: #4a68b9;
|
color: var(--title-fg);
|
||||||
}
|
|
||||||
|
|
||||||
.unconfirmedTx {
|
|
||||||
color: #f14d80;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-block {
|
.info-block {
|
||||||
@@ -55,7 +47,7 @@
|
|||||||
.progress {
|
.progress {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
width: 160px;
|
width: 160px;
|
||||||
background-color: #2d3348;
|
background-color: var(--secondary);
|
||||||
height: 1.1rem;
|
height: 1.1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -115,12 +115,12 @@
|
|||||||
}
|
}
|
||||||
.card-title {
|
.card-title {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: #4a68b9;
|
color: var(--title-fg);
|
||||||
}
|
}
|
||||||
.card-text {
|
.card-text {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
span {
|
span {
|
||||||
color: #ffffff66;
|
color: var(--transparent-fg);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ export class HashrateChartComponent implements OnInit {
|
|||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: '#b1b1b1',
|
color: 'var(--tooltip-grey)',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
},
|
},
|
||||||
borderColor: '#000',
|
borderColor: '#000',
|
||||||
@@ -325,7 +325,7 @@ export class HashrateChartComponent implements OnInit {
|
|||||||
splitLine: {
|
splitLine: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
type: 'dotted',
|
type: 'dotted',
|
||||||
color: '#ffffff66',
|
color: 'var(--transparent-fg)',
|
||||||
opacity: 0.25,
|
opacity: 0.25,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -436,7 +436,7 @@ export class HashrateChartComponent implements OnInit {
|
|||||||
const now = new Date();
|
const now = new Date();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.chartOptions.grid.bottom = 30;
|
this.chartOptions.grid.bottom = 30;
|
||||||
this.chartOptions.backgroundColor = '#11131f';
|
this.chartOptions.backgroundColor = 'var(--active-bg)';
|
||||||
this.chartInstance.setOption(this.chartOptions);
|
this.chartInstance.setOption(this.chartOptions);
|
||||||
download(this.chartInstance.getDataURL({
|
download(this.chartInstance.getDataURL({
|
||||||
pixelRatio: 2,
|
pixelRatio: 2,
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ export class HashrateChartPoolsComponent implements OnInit {
|
|||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: '#b1b1b1',
|
color: 'var(--tooltip-grey)',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
},
|
},
|
||||||
borderColor: '#000',
|
borderColor: '#000',
|
||||||
@@ -271,7 +271,7 @@ export class HashrateChartPoolsComponent implements OnInit {
|
|||||||
const now = new Date();
|
const now = new Date();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.chartOptions.grid.bottom = 30;
|
this.chartOptions.grid.bottom = 30;
|
||||||
this.chartOptions.backgroundColor = '#11131f';
|
this.chartOptions.backgroundColor = 'var(--active-bg)';
|
||||||
this.chartInstance.setOption(this.chartOptions);
|
this.chartInstance.setOption(this.chartOptions);
|
||||||
download(this.chartInstance.getDataURL({
|
download(this.chartInstance.getDataURL({
|
||||||
pixelRatio: 2,
|
pixelRatio: 2,
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
|
|||||||
splitLine: {
|
splitLine: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
type: 'dotted',
|
type: 'dotted',
|
||||||
color: '#ffffff66',
|
color: 'var(--transparent-fg)',
|
||||||
opacity: 0.25,
|
opacity: 0.25,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -239,7 +239,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
|
|||||||
const now = new Date();
|
const now = new Date();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.mempoolStatsChartOption.grid.height = prevHeight + 20;
|
this.mempoolStatsChartOption.grid.height = prevHeight + 20;
|
||||||
this.mempoolStatsChartOption.backgroundColor = '#11131f';
|
this.mempoolStatsChartOption.backgroundColor = 'var(--active-bg)';
|
||||||
this.chartInstance.setOption(this.mempoolStatsChartOption);
|
this.chartInstance.setOption(this.mempoolStatsChartOption);
|
||||||
download(this.chartInstance.getDataURL({
|
download(this.chartInstance.getDataURL({
|
||||||
pixelRatio: 2,
|
pixelRatio: 2,
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ export class LbtcPegsGraphComponent implements OnInit, OnChanges {
|
|||||||
type: 'line',
|
type: 'line',
|
||||||
},
|
},
|
||||||
formatter: (params: any) => {
|
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>';
|
let itemFormatted = '<div class="title">' + params[0].axisValue + '</div>';
|
||||||
params.map((item: any, index: number) => {
|
params.map((item: any, index: number) => {
|
||||||
if (index < 26) {
|
if (index < 26) {
|
||||||
@@ -131,7 +131,7 @@ export class LbtcPegsGraphComponent implements OnInit, OnChanges {
|
|||||||
splitLine: {
|
splitLine: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
type: 'dotted',
|
type: 'dotted',
|
||||||
color: '#ffffff66',
|
color: 'var(--transparent-fg)',
|
||||||
opacity: 0.25,
|
opacity: 0.25,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,11 +145,11 @@ export class LbtcPegsGraphComponent implements OnInit, OnChanges {
|
|||||||
showSymbol: false,
|
showSymbol: false,
|
||||||
areaStyle: {
|
areaStyle: {
|
||||||
opacity: 0.2,
|
opacity: 0.2,
|
||||||
color: '#116761',
|
color: 'var(--liquid)',
|
||||||
},
|
},
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
width: 3,
|
width: 3,
|
||||||
color: '#116761',
|
color: 'var(--liquid)',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
li.nav-item.active {
|
li.nav-item.active {
|
||||||
background-color: #653b9c;
|
background-color: var(--tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
fa-icon {
|
fa-icon {
|
||||||
@@ -34,7 +34,7 @@ li.nav-item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.navbar-nav {
|
.navbar-nav {
|
||||||
background: #212121;
|
background: var(--navbar-bg);
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
box-shadow: 0px 0px 15px 0px #000;
|
box-shadow: 0px 0px 15px 0px #000;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@@ -95,23 +95,23 @@ nav {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mainnet.active {
|
.mainnet.active {
|
||||||
background-color: #653b9c;
|
background-color: var(--tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.liquid.active {
|
.liquid.active {
|
||||||
background-color: #116761;
|
background-color: var(--liquid);
|
||||||
}
|
}
|
||||||
|
|
||||||
.liquidtestnet.active {
|
.liquidtestnet.active {
|
||||||
background-color: #494a4a;
|
background-color: var(--liquidtestnet);
|
||||||
}
|
}
|
||||||
|
|
||||||
.testnet.active {
|
.testnet.active {
|
||||||
background-color: #1d486f;
|
background-color: var(--testnet);
|
||||||
}
|
}
|
||||||
|
|
||||||
.signet.active {
|
.signet.active {
|
||||||
background-color: #6f1d5d;
|
background-color: var(--signet);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-divider {
|
.dropdown-divider {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: #11131f;
|
background: var(--active-bg);
|
||||||
text-align: start;
|
text-align: start;
|
||||||
font-size: 1.8em;
|
font-size: 1.8em;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
li.nav-item.active {
|
li.nav-item.active {
|
||||||
background-color: #653b9c;
|
background-color: var(--tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
fa-icon {
|
fa-icon {
|
||||||
@@ -40,7 +40,7 @@ li.nav-item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.navbar-nav {
|
.navbar-nav {
|
||||||
background: #212121;
|
background: var(--navbar-bg);
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
box-shadow: 0px 0px 15px 0px #000;
|
box-shadow: 0px 0px 15px 0px #000;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@@ -110,23 +110,23 @@ nav {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mainnet.active {
|
.mainnet.active {
|
||||||
background-color: #653b9c;
|
background-color: var(--tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.liquid.active {
|
.liquid.active {
|
||||||
background-color: #116761;
|
background-color: var(--liquid);
|
||||||
}
|
}
|
||||||
|
|
||||||
.liquidtestnet.active {
|
.liquidtestnet.active {
|
||||||
background-color: #494a4a;
|
background-color: var(--liquidtestnet);
|
||||||
}
|
}
|
||||||
|
|
||||||
.testnet.active {
|
.testnet.active {
|
||||||
background-color: #1d486f;
|
background-color: var(--testnet);
|
||||||
}
|
}
|
||||||
|
|
||||||
.signet.active {
|
.signet.active {
|
||||||
background-color: #6f1d5d;
|
background-color: var(--signet);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-divider {
|
.dropdown-divider {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
.progress {
|
.progress {
|
||||||
background-color: #2d3348;
|
background-color: var(--secondary);
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 5px;
|
top: 5px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,7 +101,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.black-background {
|
.black-background {
|
||||||
background-color: #11131f;
|
background-color: var(--active-bg);
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ import { MempoolBlock } from '../../interfaces/websocket.interface';
|
|||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { take, map, switchMap } from 'rxjs/operators';
|
import { take, map, switchMap } from 'rxjs/operators';
|
||||||
import { feeLevels, mempoolFeeColors } from '../../app.constants';
|
import { feeLevels } from '../../app.constants';
|
||||||
import { specialBlocks } from '../../app.constants';
|
import { specialBlocks } from '../../app.constants';
|
||||||
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
||||||
import { Location } from '@angular/common';
|
import { Location } from '@angular/common';
|
||||||
import { DifficultyAdjustment } from '../../interfaces/node-api.interface';
|
import { DifficultyAdjustment } from '../../interfaces/node-api.interface';
|
||||||
|
import { ThemeService } from 'src/app/services/theme.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-mempool-blocks',
|
selector: 'app-mempool-blocks',
|
||||||
@@ -58,6 +59,7 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
|
|||||||
constructor(
|
constructor(
|
||||||
private router: Router,
|
private router: Router,
|
||||||
public stateService: StateService,
|
public stateService: StateService,
|
||||||
|
private themeService: ThemeService,
|
||||||
private cd: ChangeDetectorRef,
|
private cd: ChangeDetectorRef,
|
||||||
private relativeUrlPipe: RelativeUrlPipe,
|
private relativeUrlPipe: RelativeUrlPipe,
|
||||||
private location: Location
|
private location: Location
|
||||||
@@ -245,7 +247,7 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
|
|||||||
trimmedFeeRange.forEach((fee: number) => {
|
trimmedFeeRange.forEach((fee: number) => {
|
||||||
let feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => fee >= feeLvl);
|
let feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => fee >= feeLvl);
|
||||||
feeLevelIndex = feeLevelIndex >= 0 ? feeLevels.length - feeLevelIndex : feeLevelIndex;
|
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) => {
|
gradientColors.forEach((color, i, gc) => {
|
||||||
|
|||||||
@@ -347,7 +347,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
|||||||
splitLine: {
|
splitLine: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
type: 'dotted',
|
type: 'dotted',
|
||||||
color: '#ffffff66',
|
color: 'var(--transparent-fg)',
|
||||||
opacity: 0.25,
|
opacity: 0.25,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -396,7 +396,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
|||||||
const now = new Date();
|
const now = new Date();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.mempoolVsizeFeesOptions.grid.height = prevHeight + 20;
|
this.mempoolVsizeFeesOptions.grid.height = prevHeight + 20;
|
||||||
this.mempoolVsizeFeesOptions.backgroundColor = '#11131f';
|
this.mempoolVsizeFeesOptions.backgroundColor = 'var(--active-bg)';
|
||||||
this.chartInstance.setOption(this.mempoolVsizeFeesOptions);
|
this.chartInstance.setOption(this.mempoolVsizeFeesOptions);
|
||||||
download(this.chartInstance.getDataURL({
|
download(this.chartInstance.getDataURL({
|
||||||
pixelRatio: 2,
|
pixelRatio: 2,
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
<a class="title-link" href="" [routerLink]="['/blocks' | relativeUrl]">
|
<a class="title-link" href="" [routerLink]="['/blocks' | relativeUrl]">
|
||||||
<h5 class="card-title d-inline" i18n="dashboard.latest-blocks">Latest blocks</h5>
|
<h5 class="card-title d-inline" i18n="dashboard.latest-blocks">Latest blocks</h5>
|
||||||
<span> </span>
|
<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>
|
</a>
|
||||||
<app-blocks-list [attr.data-cy]="'latest-blocks'" [widget]=true></app-blocks-list>
|
<app-blocks-list [attr.data-cy]="'latest-blocks'" [widget]=true></app-blocks-list>
|
||||||
</div>
|
</div>
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
<a class="title-link" href="" [routerLink]="['/graphs/mining/hashrate-difficulty' | relativeUrl]">
|
<a class="title-link" href="" [routerLink]="['/graphs/mining/hashrate-difficulty' | relativeUrl]">
|
||||||
<h5 class="card-title d-inline" i18n="dashboard.adjustments">Adjustments</h5>
|
<h5 class="card-title d-inline" i18n="dashboard.adjustments">Adjustments</h5>
|
||||||
<span> </span>
|
<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>
|
</a>
|
||||||
<app-difficulty-adjustments-table [attr.data-cy]="'difficulty-adjustments-table'"></app-difficulty-adjustments-table>
|
<app-difficulty-adjustments-table [attr.data-cy]="'difficulty-adjustments-table'"></app-difficulty-adjustments-table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
background-color: #1d1f31;
|
background-color: var(--bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.graph-card {
|
.graph-card {
|
||||||
@@ -23,10 +23,10 @@
|
|||||||
|
|
||||||
.card-title {
|
.card-title {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: #4a68b9;
|
color: var(--title-fg);
|
||||||
}
|
}
|
||||||
.card-title > a {
|
.card-title > a {
|
||||||
color: #4a68b9;
|
color: var(--title-fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-body.pool-ranking {
|
.card-body.pool-ranking {
|
||||||
|
|||||||
@@ -118,7 +118,7 @@
|
|||||||
}
|
}
|
||||||
.card-title {
|
.card-title {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: #4a68b9;
|
color: var(--title-fg);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@@ -126,7 +126,7 @@
|
|||||||
.card-text {
|
.card-text {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
span {
|
span {
|
||||||
color: #ffffff66;
|
color: var(--transparent-fg);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ export class PoolRankingComponent implements OnInit {
|
|||||||
name: pool.name + ((isMobile() || this.widget) ? `` : ` (${pool.share}%)`),
|
name: pool.name + ((isMobile() || this.widget) ? `` : ` (${pool.share}%)`),
|
||||||
label: {
|
label: {
|
||||||
overflow: 'none',
|
overflow: 'none',
|
||||||
color: '#b1b1b1',
|
color: 'var(--tooltip-grey)',
|
||||||
alignTo: 'edge',
|
alignTo: 'edge',
|
||||||
edgeDistance: edgeDistance,
|
edgeDistance: edgeDistance,
|
||||||
},
|
},
|
||||||
@@ -154,7 +154,7 @@ export class PoolRankingComponent implements OnInit {
|
|||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: '#b1b1b1',
|
color: 'var(--tooltip-grey)',
|
||||||
},
|
},
|
||||||
borderColor: '#000',
|
borderColor: '#000',
|
||||||
formatter: () => {
|
formatter: () => {
|
||||||
@@ -182,7 +182,7 @@ export class PoolRankingComponent implements OnInit {
|
|||||||
name: 'Other' + (isMobile() ? `` : ` (${totalShareOther.toFixed(2)}%)`),
|
name: 'Other' + (isMobile() ? `` : ` (${totalShareOther.toFixed(2)}%)`),
|
||||||
label: {
|
label: {
|
||||||
overflow: 'none',
|
overflow: 'none',
|
||||||
color: '#b1b1b1',
|
color: 'var(--tooltip-grey)',
|
||||||
alignTo: 'edge',
|
alignTo: 'edge',
|
||||||
edgeDistance: edgeDistance
|
edgeDistance: edgeDistance
|
||||||
},
|
},
|
||||||
@@ -191,7 +191,7 @@ export class PoolRankingComponent implements OnInit {
|
|||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: '#b1b1b1',
|
color: 'var(--tooltip-grey)',
|
||||||
},
|
},
|
||||||
borderColor: '#000',
|
borderColor: '#000',
|
||||||
formatter: () => {
|
formatter: () => {
|
||||||
@@ -301,7 +301,7 @@ export class PoolRankingComponent implements OnInit {
|
|||||||
|
|
||||||
onSaveChart() {
|
onSaveChart() {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
this.chartOptions.backgroundColor = '#11131f';
|
this.chartOptions.backgroundColor = 'var(--active-bg)';
|
||||||
this.chartInstance.setOption(this.chartOptions);
|
this.chartInstance.setOption(this.chartOptions);
|
||||||
download(this.chartInstance.getDataURL({
|
download(this.chartInstance.getDataURL({
|
||||||
pixelRatio: 2,
|
pixelRatio: 2,
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-left: 15px;
|
margin-left: 15px;
|
||||||
background: #181b2d;
|
background: var(--stat-box-bg);
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
width: 0;
|
width: 0;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
.chart {
|
.chart {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 315px;
|
height: 315px;
|
||||||
background: #181b2d;
|
background: var(--stat-box-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
background: #24273e;
|
background: var(--box-bg);
|
||||||
|
|
||||||
&.noimg {
|
&.noimg {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ div.scrollable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.progress {
|
.progress {
|
||||||
background-color: #2d3348;
|
background-color: var(--secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.coinbase {
|
.coinbase {
|
||||||
@@ -184,7 +184,7 @@ div.scrollable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.block-count-title {
|
.block-count-title {
|
||||||
color: #4a68b9;
|
color: var(--title-fg);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
@media (max-width: 767.98px) {
|
@media (max-width: 767.98px) {
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ export class PoolComponent implements OnInit {
|
|||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: '#b1b1b1',
|
color: 'var(--tooltip-grey)',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
},
|
},
|
||||||
borderColor: '#000',
|
borderColor: '#000',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
.card-title {
|
.card-title {
|
||||||
color: #4a68b9;
|
color: var(--title-fg);
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
.card-text span {
|
.card-text span {
|
||||||
color: #ffffff66;
|
color: var(--transparent-fg);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
.card-title {
|
.card-title {
|
||||||
color: #4a68b9;
|
color: var(--title-fg);
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
|
|||||||
@@ -117,7 +117,7 @@
|
|||||||
}
|
}
|
||||||
.inactive {
|
.inactive {
|
||||||
.square {
|
.square {
|
||||||
background-color: #ffffff66 !important;
|
background-color: var(--transparent-fg) !important;
|
||||||
}
|
}
|
||||||
.fee-text {
|
.fee-text {
|
||||||
text-decoration: line-through;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
color: #ffffff66;
|
color: var(--transparent-fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.pair > *:first-child {
|
&.pair > *:first-child {
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
|
|
||||||
.graph-wrapper {
|
.graph-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
background: #181b2d;
|
background: var(--stat-box-bg);
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
|
|
||||||
|
|||||||
@@ -55,11 +55,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.arrow-green {
|
.arrow-green {
|
||||||
color: #1a9436;
|
color: var(--success);
|
||||||
}
|
}
|
||||||
|
|
||||||
.arrow-red {
|
.arrow-red {
|
||||||
color: #dc3545;
|
color: var(--red);
|
||||||
}
|
}
|
||||||
|
|
||||||
.container-xl {
|
.container-xl {
|
||||||
@@ -85,7 +85,7 @@
|
|||||||
.graph-container {
|
.graph-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: #181b2d;
|
background: var(--stat-box-bg);
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,15 +13,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.green {
|
.green {
|
||||||
color:#28a745;
|
color: var(--green);
|
||||||
}
|
}
|
||||||
|
|
||||||
.red {
|
.red {
|
||||||
color:#dc3545;
|
color: var(--red);
|
||||||
}
|
}
|
||||||
|
|
||||||
.grey {
|
.grey {
|
||||||
color:#6c757d;
|
color: var(--grey);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mobile-bottomcol {
|
.mobile-bottomcol {
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
.sats {
|
.sats {
|
||||||
color: #ffffff66;
|
color: var(--transparent-fg);
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -95,7 +95,7 @@
|
|||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
&:first-child {
|
&:first-child {
|
||||||
color: #ffffff66;
|
color: var(--transparent-fg);
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
@media (min-width: 476px) {
|
@media (min-width: 476px) {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@@ -123,7 +123,7 @@ h2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.highlight {
|
.highlight {
|
||||||
background-color: #181b2d;
|
background-color: var(--stat-box-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.summary {
|
.summary {
|
||||||
@@ -142,7 +142,7 @@ h2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.grey-info-text {
|
.grey-info-text {
|
||||||
color:#6c757d;
|
color: var(--grey);
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,9 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
|||||||
filter(() => this.stateService.env.LIGHTNING),
|
filter(() => this.stateService.env.LIGHTNING),
|
||||||
switchMap((txIds) => this.apiService.getChannelByTxIds$(txIds)),
|
switchMap((txIds) => this.apiService.getChannelByTxIds$(txIds)),
|
||||||
tap((channels) => {
|
tap((channels) => {
|
||||||
|
if (!this.transactions) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const transactions = this.transactions.filter((tx) => !tx._channels);
|
const transactions = this.transactions.filter((tx) => !tx._channels);
|
||||||
channels.forEach((channel, i) => {
|
channels.forEach((channel, i) => {
|
||||||
transactions[i]._channels = channel;
|
transactions[i]._channels = channel;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
background: rgba(#11131f, 0.95);
|
background: rgba(#11131f, 0.95);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
|
box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
|
||||||
color: #b1b1b1;
|
color: var(--tooltip-grey);
|
||||||
padding: 10px 15px;
|
padding: 10px 15px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|||||||
@@ -69,12 +69,12 @@
|
|||||||
<linearGradient id="input-highlight-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
<linearGradient id="input-highlight-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
<stop offset="0%" [attr.stop-color]="gradient[0]" />
|
<stop offset="0%" [attr.stop-color]="gradient[0]" />
|
||||||
<stop offset="2%" [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]" />
|
<stop offset="100%" [attr.stop-color]="gradient[1]" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<linearGradient id="output-highlight-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
<linearGradient id="output-highlight-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
<stop offset="0%" [attr.stop-color]="gradient[1]" />
|
<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="98%" [attr.stop-color]="gradient[0]" />
|
||||||
<stop offset="100%" [attr.stop-color]="gradient[0]" />
|
<stop offset="100%" [attr.stop-color]="gradient[0]" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
@@ -88,7 +88,7 @@
|
|||||||
<stop offset="100%" stop-color="transparent" />
|
<stop offset="100%" stop-color="transparent" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
<path [attr.d]="middle.path" class="line middle" [style]="middle.style"/>
|
<path *ngIf="hasLine" [attr.d]="middle.path" class="line middle" [style]="middle.style"/>
|
||||||
<ng-container *ngFor="let input of inputs; let i = index">
|
<ng-container *ngFor="let input of inputs; let i = index">
|
||||||
<path *ngIf="connectors && !inputData[i].coinbase && !inputData[i].pegin"
|
<path *ngIf="connectors && !inputData[i].coinbase && !inputData[i].pegin"
|
||||||
[attr.d]="input.connectorPath"
|
[attr.d]="input.connectorPath"
|
||||||
@@ -106,7 +106,7 @@
|
|||||||
(pointerout)="onBlur($event, 'input', i);"
|
(pointerout)="onBlur($event, 'input', i);"
|
||||||
(click)="onClick($event, 'input', inputData[i].index);"
|
(click)="onClick($event, 'input', inputData[i].index);"
|
||||||
/>
|
/>
|
||||||
<path
|
<path *ngIf="!input.zeroValue"
|
||||||
[attr.d]="input.path"
|
[attr.d]="input.path"
|
||||||
class="line {{input.class}}"
|
class="line {{input.class}}"
|
||||||
[class.highlight]="inputIndex != null && inputData[i].index === inputIndex"
|
[class.highlight]="inputIndex != null && inputData[i].index === inputIndex"
|
||||||
@@ -116,6 +116,16 @@
|
|||||||
(pointerout)="onBlur($event, 'input', i);"
|
(pointerout)="onBlur($event, 'input', i);"
|
||||||
(click)="onClick($event, 'input', inputData[i].index);"
|
(click)="onClick($event, 'input', inputData[i].index);"
|
||||||
/>
|
/>
|
||||||
|
<path *ngIf="input.zeroValue"
|
||||||
|
[attr.d]="input.path"
|
||||||
|
class="line {{input.class}} zerovalue"
|
||||||
|
[class.highlight]="inputIndex != null && inputData[i].index === inputIndex"
|
||||||
|
[class.zerovalue]="input.zeroValue"
|
||||||
|
[style]="input.style"
|
||||||
|
(pointerover)="onHover($event, 'input', i);"
|
||||||
|
(pointerout)="onBlur($event, 'input', i);"
|
||||||
|
(click)="onClick($event, 'input', inputData[i].index);"
|
||||||
|
/>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngFor="let output of outputs; let i = index">
|
<ng-container *ngFor="let output of outputs; let i = index">
|
||||||
<path *ngIf="connectors && outspends[outputData[i].index]?.spent"
|
<path *ngIf="connectors && outspends[outputData[i].index]?.spent"
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
stroke: url(#output-highlight-gradient);
|
stroke: url(#output-highlight-gradient);
|
||||||
}
|
}
|
||||||
&.zerovalue {
|
&.zerovalue {
|
||||||
stroke: #1bd8f4;
|
stroke: var(--info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||||||
outspends: Outspend[] = [];
|
outspends: Outspend[] = [];
|
||||||
zeroValueWidth = 60;
|
zeroValueWidth = 60;
|
||||||
zeroValueThickness = 20;
|
zeroValueThickness = 20;
|
||||||
|
hasLine: boolean;
|
||||||
|
|
||||||
outspendsSubscription: Subscription;
|
outspendsSubscription: Subscription;
|
||||||
refreshOutspends$: ReplaySubject<string> = new ReplaySubject();
|
refreshOutspends$: ReplaySubject<string> = new ReplaySubject();
|
||||||
@@ -162,7 +163,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||||||
let truncatedInputs = this.tx.vin.map((v, i) => {
|
let truncatedInputs = this.tx.vin.map((v, i) => {
|
||||||
return {
|
return {
|
||||||
type: 'input',
|
type: 'input',
|
||||||
value: v?.prevout?.value,
|
value: v?.prevout?.value || (v?.is_coinbase && !totalValue ? 0 : undefined),
|
||||||
txid: v.txid,
|
txid: v.txid,
|
||||||
vout: v.vout,
|
vout: v.vout,
|
||||||
address: v?.prevout?.scriptpubkey_address || v?.prevout?.scriptpubkey_type?.toUpperCase(),
|
address: v?.prevout?.scriptpubkey_address || v?.prevout?.scriptpubkey_type?.toUpperCase(),
|
||||||
@@ -198,6 +199,9 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||||||
path: `M ${(this.width / 2) - this.midWidth} ${(this.height / 2) + 0.5} L ${(this.width / 2) + this.midWidth} ${(this.height / 2) + 0.5}`,
|
path: `M ${(this.width / 2) - this.midWidth} ${(this.height / 2) + 0.5} L ${(this.width / 2) + this.midWidth} ${(this.height / 2) + 0.5}`,
|
||||||
style: `stroke-width: ${this.combinedWeight + 1}; stroke: ${this.gradient[1]}`
|
style: `stroke-width: ${this.combinedWeight + 1}; stroke: ${this.gradient[1]}`
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.hasLine = this.inputs.reduce((line, put) => line || !put.zeroValue, false)
|
||||||
|
&& this.outputs.reduce((line, put) => line || !put.zeroValue, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
calcTotalValue(tx: Transaction): number {
|
calcTotalValue(tx: Transaction): number {
|
||||||
@@ -278,6 +282,9 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||||||
lineParams.forEach((line, i) => {
|
lineParams.forEach((line, i) => {
|
||||||
if (xputs[i].value === 0) {
|
if (xputs[i].value === 0) {
|
||||||
line.outerY = lastOuter + (this.zeroValueThickness / 2);
|
line.outerY = lastOuter + (this.zeroValueThickness / 2);
|
||||||
|
if (xputs.length === 1) {
|
||||||
|
line.outerY = (this.height / 2);
|
||||||
|
}
|
||||||
lastOuter += this.zeroValueThickness + spacing;
|
lastOuter += this.zeroValueThickness + spacing;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@
|
|||||||
<a class="title-link" href="" [routerLink]="['/blocks' | relativeUrl]">
|
<a class="title-link" href="" [routerLink]="['/blocks' | relativeUrl]">
|
||||||
<h5 class="card-title d-inline" i18n="dashboard.latest-blocks">Latest blocks</h5>
|
<h5 class="card-title d-inline" i18n="dashboard.latest-blocks">Latest blocks</h5>
|
||||||
<span> </span>
|
<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>
|
</a>
|
||||||
<table class="table lastest-blocks-table">
|
<table class="table lastest-blocks-table">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -140,7 +140,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="terms-of-service">
|
||||||
<a [routerLink]="['/terms-of-service']" i18n="shared.terms-of-service|Terms of Service">Terms of Service</a>
|
<a [routerLink]="['/terms-of-service']" i18n="shared.terms-of-service|Terms of Service">Terms of Service</a>
|
||||||
|
|||||||
@@ -11,12 +11,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
background-color: #1d1f31;
|
background-color: var(--bg);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-title {
|
.card-title {
|
||||||
color: #4a68b9;
|
color: var(--title-fg);
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
.progress {
|
.progress {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: #2d3348;
|
background-color: var(--secondary);
|
||||||
height: 1.1rem;
|
height: 1.1rem;
|
||||||
max-width: 180px;
|
max-width: 180px;
|
||||||
}
|
}
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
.card-text {
|
.card-text {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
span {
|
span {
|
||||||
color: #ffffff66;
|
color: var(--transparent-fg);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -323,4 +323,11 @@
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pref-selectors {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
p {
|
p {
|
||||||
color: #4a68b9;
|
color: var(--title-fg);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
margin: 15px 0 10px 0;
|
margin: 15px 0 10px 0;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
background-color: #1d1f31;
|
background-color: var(--bg);
|
||||||
font-family: Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New;
|
font-family: Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ li.nav-item {
|
|||||||
.nav-tabs .nav-link.active {
|
.nav-tabs .nav-link.active {
|
||||||
border-bottom: 1px solid #fff;
|
border-bottom: 1px solid #fff;
|
||||||
@media (min-width: 676px){
|
@media (min-width: 676px){
|
||||||
border-bottom: 1px solid #11131f;
|
border-bottom: 1px solid var(--active-bg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ li.nav-item {
|
|||||||
padding: 0px;
|
padding: 0px;
|
||||||
}
|
}
|
||||||
.nav-tabs .nav-link.active {
|
.nav-tabs .nav-link.active {
|
||||||
border-bottom: 1px solid #11131f;
|
border-bottom: 1px solid var(--active-bg);
|
||||||
}
|
}
|
||||||
.subtitle {
|
.subtitle {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -110,17 +110,17 @@ li.nav-item {
|
|||||||
top: 20px;
|
top: 20px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
height: calc(100vh - 50px);
|
height: calc(100vh - 50px);
|
||||||
scrollbar-color: #2d3348 #11131f;
|
scrollbar-color: var(--secondary) var(--active-bg);
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
}
|
}
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 3px;
|
width: 3px;
|
||||||
}
|
}
|
||||||
::-webkit-scrollbar-track {
|
::-webkit-scrollbar-track {
|
||||||
background: #11131f;
|
background: var(--active-bg);
|
||||||
}
|
}
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
background-color: #2d3348;
|
background-color: var(--secondary);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
@@ -148,8 +148,8 @@ h3 {
|
|||||||
|
|
||||||
.endpoint-container .section-header {
|
.endpoint-container .section-header {
|
||||||
display: block;
|
display: block;
|
||||||
background-color: #2d3348;
|
background-color: var(--secondary);
|
||||||
color: #1bd8f4;
|
color: var(--info);
|
||||||
padding: 1rem 1.3rem 1rem 1.3rem;
|
padding: 1rem 1.3rem 1rem 1.3rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
@@ -163,7 +163,7 @@ h3 {
|
|||||||
|
|
||||||
.endpoint-container .section-header span {
|
.endpoint-container .section-header span {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: #653b9c;
|
background-color: var(--tertiary);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
@@ -195,7 +195,7 @@ h3 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#doc-nav-mobile > div {
|
#doc-nav-mobile > div {
|
||||||
background-color: #2d3348;
|
background-color: var(--secondary);
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
border-radius: 0 0 0.5rem 0.5rem;
|
border-radius: 0 0 0.5rem 0.5rem;
|
||||||
height: 55vh;
|
height: 55vh;
|
||||||
@@ -204,9 +204,9 @@ h3 {
|
|||||||
|
|
||||||
#doc-nav-mobile button {
|
#doc-nav-mobile button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: #105fb0;
|
background-color: var(--primary);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border-color: #105fb0;
|
border-color: var(--primary);
|
||||||
border-radius: 0.5rem 0.5rem 0 0;
|
border-radius: 0.5rem 0.5rem 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,7 +221,7 @@ h3 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#disclaimer {
|
#disclaimer {
|
||||||
background-color: #1d1f31;
|
background-color: var(--bg);
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
margin: 24px 0;
|
margin: 24px 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
background-color: #1d1f31;
|
background-color: var(--bg);
|
||||||
font-family: Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New;
|
font-family: Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ li.nav-item {
|
|||||||
.nav-tabs .nav-link.active {
|
.nav-tabs .nav-link.active {
|
||||||
border-bottom: 1px solid #fff;
|
border-bottom: 1px solid #fff;
|
||||||
@media (min-width: 676px){
|
@media (min-width: 676px){
|
||||||
border-bottom: 1px solid #11131f;
|
border-bottom: 1px solid var(--active-bg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ li.nav-item {
|
|||||||
padding: 0px;
|
padding: 0px;
|
||||||
}
|
}
|
||||||
.nav-tabs .nav-link.active {
|
.nav-tabs .nav-link.active {
|
||||||
border-bottom: 1px solid #11131f;
|
border-bottom: 1px solid var(--active-bg);
|
||||||
}
|
}
|
||||||
.subtitle {
|
.subtitle {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -87,7 +87,7 @@ pre {
|
|||||||
display: block;
|
display: block;
|
||||||
font-size: 87.5%;
|
font-size: 87.5%;
|
||||||
color: #f18920;
|
color: #f18920;
|
||||||
background-color: #1d1f31;
|
background-color: var(--bg);
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
code{
|
code{
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
.green-color {
|
.green-color {
|
||||||
color: #3bcc49;
|
color: var(--green);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.shared-block {
|
.shared-block {
|
||||||
color: #ffffff66;
|
color: var(--transparent-fg);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.row.nodes {
|
.row.nodes {
|
||||||
background: #181b2d;
|
background: var(--stat-box-bg);
|
||||||
margin: 15px 0 0;
|
margin: 15px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
width: 470px;
|
width: 470px;
|
||||||
min-width: 470px;
|
min-width: 470px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background: #181b2d;
|
background: var(--stat-box-bg);
|
||||||
max-height: 350px;
|
max-height: 350px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<div class="container-xl" *ngIf="(channel$ | async) as channel; else skeletonLoader">
|
<div class="container-xl" *ngIf="(channel$ | async) as channel; else skeletonLoader">
|
||||||
<h5 class="mb-0" style="color: #ffffff66" i18n="lightning.channel">Lightning channel</h5>
|
<h5 class="mb-0" style="color: var(--transparent-fg)" i18n="lightning.channel">Lightning channel</h5>
|
||||||
<div class="title-container">
|
<div class="title-container">
|
||||||
<h1 class="mb-0">{{ channel.short_id }}</h1>
|
<h1 class="mb-0">{{ channel.short_id }}</h1>
|
||||||
<span class="tx-link">
|
<span class="tx-link">
|
||||||
@@ -114,7 +114,7 @@
|
|||||||
|
|
||||||
<ng-template #skeletonLoader>
|
<ng-template #skeletonLoader>
|
||||||
<div class="container-xl">
|
<div class="container-xl">
|
||||||
<h5 class="mb-0" style="color: #ffffff66" i18n="lightning.channel">Lightning channel</h5>
|
<h5 class="mb-0" style="color: var(--transparent-fg)" i18n="lightning.channel">Lightning channel</h5>
|
||||||
<div class="title-container">
|
<div class="title-container">
|
||||||
<h1 class="mb-0"><span class="skeleton-loader" style="width: 275px; height: 25px;"></span></h1>
|
<h1 class="mb-0"><span class="skeleton-loader" style="width: 275px; height: 25px;"></span></h1>
|
||||||
<span class="tx-link">
|
<span class="tx-link">
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sats {
|
.sats {
|
||||||
color: #ffffff66;
|
color: var(--transparent-fg);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<div class="widget-toggler">
|
<div class="widget-toggler">
|
||||||
<a href="" (click)="switchMode('avg')" class="toggler-option"
|
<a href="" (click)="switchMode('avg')" class="toggler-option"
|
||||||
[ngClass]="{'inactive': mode === 'avg'}"><small>avg</small></a>
|
[ngClass]="{'inactive': mode === 'avg'}"><small>avg</small></a>
|
||||||
<span style="color: #ffffff66; font-size: 8px"> | </span>
|
<span style="color: var(--transparent-fg); font-size: 8px"> | </span>
|
||||||
<a href="" (click)="switchMode('med')" class="toggler-option"
|
<a href="" (click)="switchMode('med')" class="toggler-option"
|
||||||
[ngClass]="{'inactive': mode === 'med'}"><small>med</small></a>
|
[ngClass]="{'inactive': mode === 'med'}"><small>med</small></a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
.card-title {
|
.card-title {
|
||||||
color: #4a68b9;
|
color: var(--title-fg);
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
.card-text span {
|
.card-text span {
|
||||||
color: #ffffff66;
|
color: var(--transparent-fg);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
}
|
}
|
||||||
@@ -107,5 +107,5 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.inactive {
|
.inactive {
|
||||||
color: #ffffff66;
|
color: var(--transparent-fg);
|
||||||
}
|
}
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 16px 0 0;
|
margin: 16px 0 0;
|
||||||
padding: 20px 12px;
|
padding: 20px 12px;
|
||||||
background: #181b2d;
|
background: var(--stat-box-bg);
|
||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
min-height: 272px;
|
min-height: 272px;
|
||||||
max-height: 272px;
|
max-height: 272px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background: #181b2d;
|
background: var(--stat-box-bg);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<div class="container-xl full-height" style="min-height: 335px">
|
<div class="container-xl full-height" style="min-height: 335px">
|
||||||
<h5 class="mb-1" style="color: #ffffff66" i18n="lightning.node-group">Lightning node group</h5>
|
<h5 class="mb-1" style="color: var(--transparent-fg)" i18n="lightning.node-group">Lightning node group</h5>
|
||||||
|
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="logo-container">
|
<div class="logo-container">
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-6 p-3 p-md-0 pr-md-3">
|
<div class="col-12 col-md-6 p-3 p-md-0 pr-md-3">
|
||||||
<div style="background-color: #181b2d">
|
<div style="background-color: var(--stat-box-bg)">
|
||||||
<app-nodes-map [widget]="true" [nodes]="nodes.nodes" type="isp"></app-nodes-map>
|
<app-nodes-map [widget]="true" [nodes]="nodes.nodes" type="isp"></app-nodes-map>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user