diff --git a/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts b/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts index 53c731e1f..71269da31 100644 --- a/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts +++ b/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts @@ -2,7 +2,7 @@ import { IEsploraApi } from './esplora-api.interface'; export interface AbstractBitcoinApi { $getRawMempool(): Promise; - $getRawTransaction(txId: string, skipConversion?: boolean, addPrevout?: boolean): Promise; + $getRawTransaction(txId: string, skipConversion?: boolean, addPrevout?: boolean, lazyPrevouts?: boolean): Promise; $getBlockHeightTip(): Promise; $getTxIdsForBlock(hash: string): Promise; $getBlockHash(height: number): Promise; diff --git a/backend/src/api/bitcoin/bitcoin-api.ts b/backend/src/api/bitcoin/bitcoin-api.ts index 48368a128..41671ede1 100644 --- a/backend/src/api/bitcoin/bitcoin-api.ts +++ b/backend/src/api/bitcoin/bitcoin-api.ts @@ -31,7 +31,8 @@ class BitcoinApi implements AbstractBitcoinApi { }; } - $getRawTransaction(txId: string, skipConversion = false, addPrevout = false): Promise { + + $getRawTransaction(txId: string, skipConversion = false, addPrevout = false, lazyPrevouts = false): Promise { // If the transaction is in the mempool we already converted and fetched the fee. Only prevouts are missing const txInMempool = mempool.getMempool()[txId]; if (txInMempool && addPrevout) { @@ -46,7 +47,7 @@ class BitcoinApi implements AbstractBitcoinApi { }); return transaction; } - return this.$convertTransaction(transaction, addPrevout); + return this.$convertTransaction(transaction, addPrevout, lazyPrevouts); }) .catch((e: Error) => { if (e.message.startsWith('The genesis block coinbase')) { @@ -126,7 +127,7 @@ class BitcoinApi implements AbstractBitcoinApi { const outSpends: IEsploraApi.Outspend[] = []; const tx = await this.$getRawTransaction(txId, true, false); for (let i = 0; i < tx.vout.length; i++) { - if (tx.status && tx.status.block_height == 0) { + if (tx.status && tx.status.block_height === 0) { outSpends.push({ spent: false }); @@ -145,7 +146,7 @@ class BitcoinApi implements AbstractBitcoinApi { return this.bitcoindClient.getNetworkHashPs(120, blockHeight); } - protected async $convertTransaction(transaction: IBitcoinApi.Transaction, addPrevout: boolean): Promise { + protected async $convertTransaction(transaction: IBitcoinApi.Transaction, addPrevout: boolean, lazyPrevouts = false): Promise { let esploraTransaction: IEsploraApi.Transaction = { txid: transaction.txid, version: transaction.version, @@ -192,12 +193,9 @@ class BitcoinApi implements AbstractBitcoinApi { } if (addPrevout) { - if (transaction.confirmations) { - esploraTransaction = await this.$calculateFeeFromInputs(esploraTransaction); - } else { - esploraTransaction = await this.$appendMempoolFeeData(esploraTransaction); - esploraTransaction = await this.$calculateFeeFromInputs(esploraTransaction); - } + esploraTransaction = await this.$calculateFeeFromInputs(esploraTransaction, false, lazyPrevouts); + } else if (!transaction.confirmations) { + esploraTransaction = await this.$appendMempoolFeeData(esploraTransaction); } return esploraTransaction; @@ -271,20 +269,30 @@ class BitcoinApi implements AbstractBitcoinApi { return this.bitcoindClient.getRawMemPool(true); } - private async $calculateFeeFromInputs(transaction: IEsploraApi.Transaction): Promise { + + private async $calculateFeeFromInputs(transaction: IEsploraApi.Transaction, addPrevout: boolean, lazyPrevouts: boolean): Promise { if (transaction.vin[0].is_coinbase) { transaction.fee = 0; return transaction; } let totalIn = 0; - for (const vin of transaction.vin) { - const innerTx = await this.$getRawTransaction(vin.txid, false, false); - vin.prevout = innerTx.vout[vin.vout]; - this.addInnerScriptsToVin(vin); - totalIn += innerTx.vout[vin.vout].value; + + for (let i = 0; i < transaction.vin.length; i++) { + if (lazyPrevouts && i > 12) { + transaction.vin[i].lazy = true; + continue; + } + const innerTx = await this.$getRawTransaction(transaction.vin[i].txid, false, false); + transaction.vin[i].prevout = innerTx.vout[transaction.vin[i].vout]; + this.addInnerScriptsToVin(transaction.vin[i]); + totalIn += innerTx.vout[transaction.vin[i].vout].value; + } + if (lazyPrevouts && transaction.vin.length > 12) { + transaction.fee = -1; + } else { + const totalOut = transaction.vout.reduce((p, output) => p + output.value, 0); + transaction.fee = parseFloat((totalIn - totalOut).toFixed(8)); } - const totalOut = transaction.vout.reduce((p, output) => p + output.value, 0); - transaction.fee = parseFloat((totalIn - totalOut).toFixed(8)); return transaction; } diff --git a/backend/src/api/bitcoin/esplora-api.interface.ts b/backend/src/api/bitcoin/esplora-api.interface.ts index 73ccbd88f..f825c60f9 100644 --- a/backend/src/api/bitcoin/esplora-api.interface.ts +++ b/backend/src/api/bitcoin/esplora-api.interface.ts @@ -33,6 +33,8 @@ export namespace IEsploraApi { // Elements is_pegin?: boolean; issuance?: Issuance; + // Custom + lazy?: boolean; } interface Issuance { diff --git a/backend/src/api/mining.ts b/backend/src/api/mining.ts index 592ac34f1..8d11abfa7 100644 --- a/backend/src/api/mining.ts +++ b/backend/src/api/mining.ts @@ -6,6 +6,7 @@ import bitcoinClient from './bitcoin/bitcoin-client'; import logger from '../logger'; import { Common } from './common'; import loadingIndicators from './loading-indicators'; +import { escape } from 'mysql2'; class Mining { constructor() { @@ -106,7 +107,7 @@ class Mining { public async $getPoolStat(slug: string): Promise { const pool = await PoolsRepository.$getPool(slug); if (!pool) { - throw new Error(`This mining pool does not exist`); + throw new Error('This mining pool does not exist ' + escape(slug)); } const blockCount: number = await BlocksRepository.$blockCount(pool.id); diff --git a/backend/src/api/transaction-utils.ts b/backend/src/api/transaction-utils.ts index 2e669d709..5b92cea5f 100644 --- a/backend/src/api/transaction-utils.ts +++ b/backend/src/api/transaction-utils.ts @@ -21,8 +21,8 @@ class TransactionUtils { }; } - public async $getTransactionExtended(txId: string, addPrevouts = false): Promise { - const transaction: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(txId, false, addPrevouts); + public async $getTransactionExtended(txId: string, addPrevouts = false, lazyPrevouts = false): Promise { + const transaction: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(txId, false, addPrevouts, lazyPrevouts); return this.extendTransaction(transaction); } diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index 47a18fc9b..a34f32e83 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -5,6 +5,7 @@ import { Common } from '../api/common'; import { prepareBlock } from '../utils/blocks-utils'; import PoolsRepository from './PoolsRepository'; import HashratesRepository from './HashratesRepository'; +import { escape } from 'mysql2'; class BlocksRepository { /** @@ -235,7 +236,7 @@ class BlocksRepository { public async $getBlocksByPool(slug: string, startHeight?: number): Promise { const pool = await PoolsRepository.$getPool(slug); if (!pool) { - throw new Error(`This mining pool does not exist`); + throw new Error('This mining pool does not exist ' + escape(slug)); } const params: any[] = []; diff --git a/backend/src/repositories/HashratesRepository.ts b/backend/src/repositories/HashratesRepository.ts index 661535aa3..531b6cdcf 100644 --- a/backend/src/repositories/HashratesRepository.ts +++ b/backend/src/repositories/HashratesRepository.ts @@ -1,3 +1,4 @@ +import { escape } from 'mysql2'; import { Common } from '../api/common'; import DB from '../database'; import logger from '../logger'; @@ -105,7 +106,7 @@ class HashratesRepository { public async $getPoolWeeklyHashrate(slug: string): Promise { const pool = await PoolsRepository.$getPool(slug); if (!pool) { - throw new Error(`This mining pool does not exist`); + throw new Error('This mining pool does not exist ' + escape(slug)); } // Find hashrate boundaries diff --git a/backend/src/repositories/PoolsRepository.ts b/backend/src/repositories/PoolsRepository.ts index 037a6250a..c7cc6cba3 100644 --- a/backend/src/repositories/PoolsRepository.ts +++ b/backend/src/repositories/PoolsRepository.ts @@ -78,7 +78,6 @@ class PoolsRepository { const [rows]: any[] = await DB.query(query, [slug]); if (rows.length < 1) { - logger.debug(`This slug does not match any known pool`); return null; } diff --git a/backend/src/routes.ts b/backend/src/routes.ts index c39d3ec56..cf28dd71d 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -645,7 +645,7 @@ class Routes { res.header('Pragma', 'public'); res.header('Cache-control', 'public'); res.header('X-total-count', blockCount.toString()); - res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString()); + res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); res.json(blockFees); } catch (e) { res.status(500).send(e instanceof Error ? e.message : e); @@ -659,7 +659,7 @@ class Routes { res.header('Pragma', 'public'); res.header('Cache-control', 'public'); res.header('X-total-count', blockCount.toString()); - res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString()); + res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); res.json(blockRewards); } catch (e) { res.status(500).send(e instanceof Error ? e.message : e); @@ -672,7 +672,7 @@ class Routes { const oldestIndexedBlockTimestamp = await BlocksRepository.$oldestBlockTimestamp(); res.header('Pragma', 'public'); res.header('Cache-control', 'public'); - res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString()); + res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); res.json({ oldestIndexedBlockTimestamp: oldestIndexedBlockTimestamp, blockFeeRates: blockFeeRates, @@ -690,7 +690,7 @@ class Routes { res.header('Pragma', 'public'); res.header('Cache-control', 'public'); res.header('X-total-count', blockCount.toString()); - res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString()); + res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); res.json({ sizes: blockSizes, weights: blockWeights @@ -778,9 +778,9 @@ class Routes { const endIndex = Math.min(startingIndex + 10, txIds.length); for (let i = startingIndex; i < endIndex; i++) { try { - const transaction = await transactionUtils.$getTransactionExtended(txIds[i], true); + const transaction = await transactionUtils.$getTransactionExtended(txIds[i], true, true); transactions.push(transaction); - loadingIndicators.setProgress('blocktxs-' + req.params.hash, (i + 1) / endIndex * 100); + loadingIndicators.setProgress('blocktxs-' + req.params.hash, (i - startingIndex + 1) / (endIndex - startingIndex) * 100); } catch (e) { logger.debug('getBlockTransactions error: ' + (e instanceof Error ? e.message : e)); } diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 2eaf7e277..672d68686 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -1,43 +1,211 @@ import { NgModule } from '@angular/core'; -import { Routes, RouterModule } from '@angular/router'; +import { Routes, RouterModule, PreloadAllModules } from '@angular/router'; import { StartComponent } from './components/start/start.component'; import { TransactionComponent } from './components/transaction/transaction.component'; import { BlockComponent } from './components/block/block.component'; import { AddressComponent } from './components/address/address.component'; import { MasterPageComponent } from './components/master-page/master-page.component'; import { AboutComponent } from './components/about/about.component'; -import { TelevisionComponent } from './components/television/television.component'; -import { StatisticsComponent } from './components/statistics/statistics.component'; -import { MempoolBlockComponent } from './components/mempool-block/mempool-block.component'; -import { AssetComponent } from './components/asset/asset.component'; -import { AssetsNavComponent } from './components/assets/assets-nav/assets-nav.component'; import { StatusViewComponent } from './components/status-view/status-view.component'; -import { DashboardComponent } from './dashboard/dashboard.component'; import { LatestBlocksComponent } from './components/latest-blocks/latest-blocks.component'; import { TermsOfServiceComponent } from './components/terms-of-service/terms-of-service.component'; import { PrivacyPolicyComponent } from './components/privacy-policy/privacy-policy.component'; import { TrademarkPolicyComponent } from './components/trademark-policy/trademark-policy.component'; import { BisqMasterPageComponent } from './components/bisq-master-page/bisq-master-page.component'; import { SponsorComponent } from './components/sponsor/sponsor.component'; -import { LiquidMasterPageComponent } from './components/liquid-master-page/liquid-master-page.component'; import { PushTransactionComponent } from './components/push-transaction/push-transaction.component'; -import { PoolRankingComponent } from './components/pool-ranking/pool-ranking.component'; +import { BlocksList } from './components/blocks-list/blocks-list.component'; +import { LiquidMasterPageComponent } from './components/liquid-master-page/liquid-master-page.component'; import { AssetGroupComponent } from './components/assets/asset-group/asset-group.component'; import { AssetsFeaturedComponent } from './components/assets/assets-featured/assets-featured.component'; import { AssetsComponent } from './components/assets/assets.component'; -import { PoolComponent } from './components/pool/pool.component'; -import { MiningDashboardComponent } from './components/mining-dashboard/mining-dashboard.component'; -import { HashrateChartComponent } from './components/hashrate-chart/hashrate-chart.component'; -import { HashrateChartPoolsComponent } from './components/hashrates-chart-pools/hashrate-chart-pools.component'; -import { MiningStartComponent } from './components/mining-start/mining-start.component'; -import { GraphsComponent } from './components/graphs/graphs.component'; -import { BlocksList } from './components/blocks-list/blocks-list.component'; -import { BlockFeesGraphComponent } from './components/block-fees-graph/block-fees-graph.component'; -import { BlockRewardsGraphComponent } from './components/block-rewards-graph/block-rewards-graph.component'; -import { BlockFeeRatesGraphComponent } from './components/block-fee-rates-graph/block-fee-rates-graph.component'; -import { BlockSizesWeightsGraphComponent } from './components/block-sizes-weights-graph/block-sizes-weights-graph.component'; +import { AssetComponent } from './components/asset/asset.component'; +import { AssetsNavComponent } from './components/assets/assets-nav/assets-nav.component'; let routes: Routes = [ + { + path: 'testnet', + children: [ + { + path: '', + pathMatch: 'full', + loadChildren: () => import('./graphs/graphs.module').then(m => m.GraphsModule) + }, + { + path: '', + component: MasterPageComponent, + children: [ + { + path: 'tx/push', + component: PushTransactionComponent, + }, + { + path: 'blocks', + component: LatestBlocksComponent, + }, + { + path: 'about', + component: AboutComponent, + }, + { + path: 'mining/blocks', + component: BlocksList, + }, + { + path: 'terms-of-service', + component: TermsOfServiceComponent + }, + { + path: 'privacy-policy', + component: PrivacyPolicyComponent + }, + { + path: 'trademark-policy', + component: TrademarkPolicyComponent + }, + { + path: 'address/:id', + children: [], + component: AddressComponent + }, + { + path: 'tx', + component: StartComponent, + children: [ + { + path: ':id', + component: TransactionComponent + }, + ], + }, + { + path: 'block', + component: StartComponent, + children: [ + { + path: ':id', + component: BlockComponent + }, + ], + }, + { + path: 'docs', + loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule) + }, + { + path: 'api', + loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule) + }, + ], + }, + { + path: 'status', + component: StatusViewComponent + }, + { + path: '', + loadChildren: () => import('./graphs/graphs.module').then(m => m.GraphsModule) + }, + { + path: '**', + redirectTo: '/testnet' + }, + ] + }, + { + path: 'signet', + children: [ + { + path: '', + pathMatch: 'full', + loadChildren: () => import('./graphs/graphs.module').then(m => m.GraphsModule) + }, + { + path: '', + component: MasterPageComponent, + children: [ + { + path: 'tx/push', + component: PushTransactionComponent, + }, + { + path: 'blocks', + component: LatestBlocksComponent, + }, + { + path: 'about', + component: AboutComponent, + }, + { + path: 'mining/blocks', + component: BlocksList, + }, + { + path: 'terms-of-service', + component: TermsOfServiceComponent + }, + { + path: 'privacy-policy', + component: PrivacyPolicyComponent + }, + { + path: 'trademark-policy', + component: TrademarkPolicyComponent + }, + { + path: 'address/:id', + children: [], + component: AddressComponent + }, + { + path: 'tx', + component: StartComponent, + children: [ + { + path: ':id', + component: TransactionComponent + }, + ], + }, + { + path: 'block', + component: StartComponent, + children: [ + { + path: ':id', + component: BlockComponent + }, + ], + }, + { + path: 'docs', + loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule) + }, + { + path: 'api', + loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule) + }, + ], + }, + { + path: 'status', + component: StatusViewComponent + }, + { + path: '', + loadChildren: () => import('./graphs/graphs.module').then(m => m.GraphsModule) + }, + { + path: '**', + redirectTo: '/signet' + }, + ] + }, + { + path: '', + pathMatch: 'full', + loadChildren: () => import('./graphs/graphs.module').then(m => m.GraphsModule) + }, { path: '', component: MasterPageComponent, @@ -46,109 +214,17 @@ let routes: Routes = [ path: 'tx/push', component: PushTransactionComponent, }, - { - path: '', - component: StartComponent, - children: [ - { - path: '', - component: DashboardComponent, - }, - { - path: 'tx/:id', - component: TransactionComponent - }, - { - path: 'block/:id', - component: BlockComponent - }, - { - path: 'mempool-block/:id', - component: MempoolBlockComponent - }, - { - path: 'mining', - component: MiningDashboardComponent, - }, - ], - }, { path: 'blocks', component: LatestBlocksComponent, }, - { - path: 'mining', - component: MiningStartComponent, - children: [ - { - path: 'blocks', - component: BlocksList, - }, - { - path: 'pool', - children: [ - { - path: ':slug', - component: PoolComponent, - }, - ] - }, - ] - }, - { - path: 'graphs', - component: GraphsComponent, - children: [ - { - path: '', - pathMatch: 'full', - redirectTo: 'mempool', - }, - { - path: 'mempool', - component: StatisticsComponent, - }, - { - path: 'mining/hashrate-difficulty', - component: HashrateChartComponent, - }, - { - path: 'mining/pools-dominance', - component: HashrateChartPoolsComponent, - }, - { - path: 'mining/pools', - component: PoolRankingComponent, - }, - { - path: 'mining/block-fees', - component: BlockFeesGraphComponent, - }, - { - path: 'mining/block-rewards', - component: BlockRewardsGraphComponent, - }, - { - path: 'mining/block-fee-rates', - component: BlockFeeRatesGraphComponent, - }, - { - path: 'mining/block-sizes-weights', - component: BlockSizesWeightsGraphComponent, - }, - ], - }, { path: 'about', component: AboutComponent, }, { - path: 'docs', - loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule) - }, - { - path: 'api', - loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule) + path: 'mining/blocks', + component: BlocksList, }, { path: 'terms-of-service', @@ -167,276 +243,36 @@ let routes: Routes = [ children: [], component: AddressComponent }, + { + path: 'tx', + component: StartComponent, + children: [ + { + path: ':id', + component: TransactionComponent + }, + ], + }, + { + path: 'block', + component: StartComponent, + children: [ + { + path: ':id', + component: BlockComponent + }, + ], + }, + { + path: 'docs', + loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule) + }, + { + path: 'api', + loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule) + }, ], }, - { - path: 'testnet', - children: [ - { - path: '', - component: MasterPageComponent, - children: [ - { - path: 'tx/push', - component: PushTransactionComponent, - }, - { - path: '', - component: StartComponent, - children: [ - { - path: '', - component: DashboardComponent - }, - { - path: 'tx/:id', - component: TransactionComponent - }, - { - path: 'block/:id', - component: BlockComponent - }, - { - path: 'mempool-block/:id', - component: MempoolBlockComponent - }, - { - path: 'mining', - component: MiningDashboardComponent, - }, - ], - }, - { - path: 'blocks', - component: LatestBlocksComponent, - }, - { - path: 'mining', - component: MiningStartComponent, - children: [ - { - path: 'blocks', - component: BlocksList, - }, - { - path: 'pool', - children: [ - { - path: ':slug', - component: PoolComponent, - }, - ] - }, - ] - }, - { - path: 'graphs', - component: GraphsComponent, - children: [ - { - path: '', - pathMatch: 'full', - redirectTo: 'mempool', - }, - { - path: 'mempool', - component: StatisticsComponent, - }, - { - path: 'mining/hashrate-difficulty', - component: HashrateChartComponent, - }, - { - path: 'mining/pools-dominance', - component: HashrateChartPoolsComponent, - }, - { - path: 'mining/pools', - component: PoolRankingComponent, - }, - { - path: 'mining/block-fees', - component: BlockFeesGraphComponent, - }, - { - path: 'mining/block-rewards', - component: BlockRewardsGraphComponent, - }, - { - path: 'mining/block-fee-rates', - component: BlockFeeRatesGraphComponent, - }, - { - path: 'mining/block-sizes-weights', - component: BlockSizesWeightsGraphComponent, - }, - ] - }, - { - path: 'address/:id', - children: [], - component: AddressComponent - }, - { - path: 'docs', - loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule) - }, - { - path: 'api', - loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule) - }, - ], - }, - { - path: 'tv', - component: TelevisionComponent - }, - { - path: 'status', - component: StatusViewComponent - }, - { - path: '**', - redirectTo: '/testnet' - }, - ] - }, - { - path: 'signet', - children: [ - { - path: '', - component: MasterPageComponent, - children: [ - { - path: 'tx/push', - component: PushTransactionComponent, - }, - { - path: '', - component: StartComponent, - children: [ - { - path: '', - component: DashboardComponent - }, - { - path: 'tx/:id', - component: TransactionComponent - }, - { - path: 'block/:id', - component: BlockComponent - }, - { - path: 'mempool-block/:id', - component: MempoolBlockComponent - }, - { - path: 'mining', - component: MiningDashboardComponent, - }, - ], - }, - { - path: 'blocks', - component: LatestBlocksComponent, - }, - { - path: 'mining', - component: MiningStartComponent, - children: [ - { - path: 'blocks', - component: BlocksList, - }, - { - path: 'pool', - children: [ - { - path: ':slug', - component: PoolComponent, - }, - ] - }, - ] - }, - { - path: 'graphs', - component: GraphsComponent, - children: [ - { - path: '', - pathMatch: 'full', - redirectTo: 'mempool', - }, - { - path: 'mempool', - component: StatisticsComponent, - }, - { - path: 'mining/hashrate-difficulty', - component: HashrateChartComponent, - }, - { - path: 'mining/pools-dominance', - component: HashrateChartPoolsComponent, - }, - { - path: 'mining/pools', - component: PoolRankingComponent, - }, - { - path: 'mining/block-fees', - component: BlockFeesGraphComponent, - }, - { - path: 'mining/block-rewards', - component: BlockRewardsGraphComponent, - }, - { - path: 'mining/block-fee-rates', - component: BlockFeeRatesGraphComponent, - }, - { - path: 'mining/block-sizes-weights', - component: BlockSizesWeightsGraphComponent, - }, - ] - }, - { - path: 'address/:id', - children: [], - component: AddressComponent - }, - { - path: 'docs', - loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule) - }, - { - path: 'api', - loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule) - }, - ], - }, - { - path: 'tv', - component: TelevisionComponent - }, - { - path: 'status', - component: StatusViewComponent - }, - { - path: '**', - redirectTo: '/signet' - }, - ] - }, - { - path: 'tv', - component: TelevisionComponent, - }, { path: 'status', component: StatusViewComponent @@ -445,6 +281,10 @@ let routes: Routes = [ path: 'sponsor', component: SponsorComponent, }, + { + path: '', + loadChildren: () => import('./graphs/graphs.module').then(m => m.GraphsModule) + }, { path: '**', redirectTo: '' @@ -464,244 +304,241 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'bisq') { } if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') { - routes = [{ - path: '', - component: LiquidMasterPageComponent, - children: [ - { - path: '', - component: StartComponent, - children: [ - { - path: '', - component: DashboardComponent - }, - { - path: 'tx/push', - component: PushTransactionComponent, - }, - { - path: 'tx/:id', - component: TransactionComponent - }, - { - path: 'block/:id', - component: BlockComponent - }, - { - path: 'mempool-block/:id', - component: MempoolBlockComponent - }, - ], - }, - { - path: 'blocks', - component: LatestBlocksComponent, - }, - { - path: 'graphs', - component: GraphsComponent, - children: [ - { - path: '', - pathMatch: 'full', - redirectTo: 'mempool', - }, - { - path: 'mempool', - component: StatisticsComponent, - } - ] - }, - { - path: 'address/:id', - component: AddressComponent - }, - { - path: 'assets', - component: AssetsNavComponent, - children: [ - { - path: 'featured', - component: AssetsFeaturedComponent, - }, - { - path: 'all', - component: AssetsComponent, - }, - { - path: 'asset/:id', - component: AssetComponent - }, - { - path: 'group/:id', - component: AssetGroupComponent - }, - { - path: '**', - redirectTo: 'featured' - } - ] - }, - { - path: 'docs', - loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule) - }, - { - path: 'api', - loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule) - }, - { - path: 'about', - component: AboutComponent, - }, - { - path: 'terms-of-service', - component: TermsOfServiceComponent - }, - { - path: 'privacy-policy', - component: PrivacyPolicyComponent - }, - { - path: 'trademark-policy', - component: TrademarkPolicyComponent - }, - ], - }, - { - path: 'testnet', - children: [ - { - path: '', - component: LiquidMasterPageComponent, - children: [ - { - path: '', - component: StartComponent, + routes = [ + { + path: 'testnet', + children: [ + { + path: '', + pathMatch: 'full', + loadChildren: () => import('./graphs/graphs.module').then(m => m.GraphsModule) + }, + { + path: '', + component: LiquidMasterPageComponent, + children: [ + { + path: 'tx/push', + component: PushTransactionComponent, + }, + { + path: 'blocks', + component: LatestBlocksComponent, + }, + { + path: 'about', + component: AboutComponent, + }, + { + path: 'mining/blocks', + component: BlocksList, + }, + { + path: 'terms-of-service', + component: TermsOfServiceComponent + }, + { + path: 'privacy-policy', + component: PrivacyPolicyComponent + }, + { + path: 'trademark-policy', + component: TrademarkPolicyComponent + }, + { + path: 'address/:id', + children: [], + component: AddressComponent + }, + { + path: 'tx', + component: StartComponent, + children: [ + { + path: ':id', + component: TransactionComponent + }, + ], + }, + { + path: 'block', + component: StartComponent, + children: [ + { + path: ':id', + component: BlockComponent + }, + ], + }, + { + path: 'assets', + component: AssetsNavComponent, + children: [ + { + path: 'all', + component: AssetsComponent, + }, + { + path: 'asset/:id', + component: AssetComponent + }, + { + path: 'group/:id', + component: AssetGroupComponent + }, + { + path: '**', + redirectTo: 'all' + } + ] + }, + { + path: 'docs', + loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule) + }, + { + path: 'api', + loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule) + }, + ], + }, + { + path: 'status', + component: StatusViewComponent + }, + { + path: '', + loadChildren: () => import('./graphs/graphs.module').then(m => m.GraphsModule) + }, + { + path: '**', + redirectTo: '/signet' + }, + ] + }, + { + path: '', + pathMatch: 'full', + loadChildren: () => import('./graphs/graphs.module').then(m => m.GraphsModule) + }, + { + path: '', + component: LiquidMasterPageComponent, + children: [ + { + path: 'tx/push', + component: PushTransactionComponent, + }, + { + path: 'blocks', + component: LatestBlocksComponent, + }, + { + path: 'about', + component: AboutComponent, + }, + { + path: 'mining/blocks', + component: BlocksList, + }, + { + path: 'terms-of-service', + component: TermsOfServiceComponent + }, + { + path: 'privacy-policy', + component: PrivacyPolicyComponent + }, + { + path: 'trademark-policy', + component: TrademarkPolicyComponent + }, + { + path: 'address/:id', + children: [], + component: AddressComponent + }, + { + path: 'tx', + component: StartComponent, + children: [ + { + path: ':id', + component: TransactionComponent + }, + ], + }, + { + path: 'block', + component: StartComponent, children: [ - { - path: '', - component: DashboardComponent - }, - { - path: 'tx/push', - component: PushTransactionComponent, - }, - { - path: 'tx/:id', - component: TransactionComponent - }, - { - path: 'block/:id', - component: BlockComponent - }, - { - path: 'mempool-block/:id', - component: MempoolBlockComponent - }, - ], - }, - { - path: 'blocks', - component: LatestBlocksComponent, - }, - { - path: 'graphs', - component: GraphsComponent, - children: [ - { - path: '', - pathMatch: 'full', - redirectTo: 'mempool', - }, - { - path: 'mempool', - component: StatisticsComponent, - } - ] - }, - { - path: 'address/:id', - component: AddressComponent - }, - { - path: 'assets', - component: AssetsNavComponent, - children: [ - { - path: 'all', - component: AssetsComponent, - }, - { - path: 'asset/:id', - component: AssetComponent - }, - { - path: 'group/:id', - component: AssetGroupComponent - }, - { - path: '**', - redirectTo: 'all' - } - ] - }, - { - path: 'docs', - loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule) - }, - { - path: 'api', - loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule) - }, - { - path: 'about', - component: AboutComponent, - }, - { - path: 'terms-of-service', - component: TermsOfServiceComponent - }, - { - path: 'privacy-policy', - component: PrivacyPolicyComponent - }, - { - path: 'trademark-policy', - component: TrademarkPolicyComponent - }, - ], - }, - { - path: 'tv', - component: TelevisionComponent - }, - { - path: 'status', - component: StatusViewComponent - }, - ] - }, - { - path: 'tv', - component: TelevisionComponent - }, - { - path: 'status', - component: StatusViewComponent - }, - { - path: '**', - redirectTo: '/testnet' - }]; + { + path: ':id', + component: BlockComponent + }, + ], + }, + { + path: 'assets', + component: AssetsNavComponent, + children: [ + { + path: 'featured', + component: AssetsFeaturedComponent, + }, + { + path: 'all', + component: AssetsComponent, + }, + { + path: 'asset/:id', + component: AssetComponent + }, + { + path: 'group/:id', + component: AssetGroupComponent + }, + { + path: '**', + redirectTo: 'featured' + } + ] + }, + { + path: 'docs', + loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule) + }, + { + path: 'api', + loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule) + }, + ], + }, + { + path: 'status', + component: StatusViewComponent + }, + { + path: 'sponsor', + component: SponsorComponent, + }, + { + path: '', + loadChildren: () => import('./graphs/graphs.module').then(m => m.GraphsModule) + }, + { + path: '**', + redirectTo: '' + }, + ]; } @NgModule({ imports: [RouterModule.forRoot(routes, { initialNavigation: 'enabled', scrollPositionRestoration: 'enabled', - anchorScrolling: 'enabled' + anchorScrolling: 'enabled', + preloadingStrategy: PreloadAllModules })], }) export class AppRoutingModule { } diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 4b4e9eba9..8845f4255 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -2,141 +2,23 @@ import { BrowserModule, BrowserTransferStateModule } from '@angular/platform-bro import { NgModule } from '@angular/core'; import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { InfiniteScrollModule } from 'ngx-infinite-scroll'; -import { NgxEchartsModule } from 'ngx-echarts'; - import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './components/app/app.component'; - -import { StartComponent } from './components/start/start.component'; import { ElectrsApiService } from './services/electrs-api.service'; -import { TransactionComponent } from './components/transaction/transaction.component'; -import { TransactionsListComponent } from './components/transactions-list/transactions-list.component'; import { StateService } from './services/state.service'; -import { BlockComponent } from './components/block/block.component'; -import { AddressComponent } from './components/address/address.component'; -import { SearchFormComponent } from './components/search-form/search-form.component'; -import { LatestBlocksComponent } from './components/latest-blocks/latest-blocks.component'; import { WebsocketService } from './services/websocket.service'; -import { AddressLabelsComponent } from './components/address-labels/address-labels.component'; -import { MasterPageComponent } from './components/master-page/master-page.component'; -import { BisqMasterPageComponent } from './components/bisq-master-page/bisq-master-page.component'; -import { LiquidMasterPageComponent } from './components/liquid-master-page/liquid-master-page.component'; -import { AboutComponent } from './components/about/about.component'; -import { TelevisionComponent } from './components/television/television.component'; -import { StatisticsComponent } from './components/statistics/statistics.component'; -import { FooterComponent } from './components/footer/footer.component'; import { AudioService } from './services/audio.service'; -import { MempoolBlockComponent } from './components/mempool-block/mempool-block.component'; -import { FeeDistributionGraphComponent } from './components/fee-distribution-graph/fee-distribution-graph.component'; -import { IncomingTransactionsGraphComponent } from './components/incoming-transactions-graph/incoming-transactions-graph.component'; -import { TimeSpanComponent } from './components/time-span/time-span.component'; import { SeoService } from './services/seo.service'; -import { MempoolGraphComponent } from './components/mempool-graph/mempool-graph.component'; -import { PoolRankingComponent } from './components/pool-ranking/pool-ranking.component'; -import { PoolComponent } from './components/pool/pool.component'; -import { LbtcPegsGraphComponent } from './components/lbtc-pegs-graph/lbtc-pegs-graph.component'; -import { AssetComponent } from './components/asset/asset.component'; -import { AssetsComponent } from './components/assets/assets.component'; -import { AssetsNavComponent } from './components/assets/assets-nav/assets-nav.component'; -import { StatusViewComponent } from './components/status-view/status-view.component'; import { SharedModule } from './shared/shared.module'; -import { NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap'; -import { FeesBoxComponent } from './components/fees-box/fees-box.component'; -import { DashboardComponent } from './dashboard/dashboard.component'; -import { DifficultyComponent } from './components/difficulty/difficulty.component'; -import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome'; -import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faHammer, faDatabase, faExchangeAlt, faInfoCircle, - faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown, - faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload } from '@fortawesome/free-solid-svg-icons'; -import { TermsOfServiceComponent } from './components/terms-of-service/terms-of-service.component'; -import { PrivacyPolicyComponent } from './components/privacy-policy/privacy-policy.component'; -import { TrademarkPolicyComponent } from './components/trademark-policy/trademark-policy.component'; import { StorageService } from './services/storage.service'; import { HttpCacheInterceptor } from './services/http-cache.interceptor'; import { LanguageService } from './services/language.service'; -import { SponsorComponent } from './components/sponsor/sponsor.component'; -import { PushTransactionComponent } from './components/push-transaction/push-transaction.component'; -import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; -import { AssetsFeaturedComponent } from './components/assets/assets-featured/assets-featured.component'; -import { AssetGroupComponent } from './components/assets/asset-group/asset-group.component'; -import { AssetCirculationComponent } from './components/asset-circulation/asset-circulation.component'; -import { MiningDashboardComponent } from './components/mining-dashboard/mining-dashboard.component'; -import { HashrateChartComponent } from './components/hashrate-chart/hashrate-chart.component'; -import { HashrateChartPoolsComponent } from './components/hashrates-chart-pools/hashrate-chart-pools.component'; -import { MiningStartComponent } from './components/mining-start/mining-start.component'; -import { AmountShortenerPipe } from './shared/pipes/amount-shortener.pipe'; import { ShortenStringPipe } from './shared/pipes/shorten-string-pipe/shorten-string.pipe'; import { CapAddressPipe } from './shared/pipes/cap-address-pipe/cap-address-pipe'; -import { GraphsComponent } from './components/graphs/graphs.component'; -import { DifficultyAdjustmentsTable } from './components/difficulty-adjustments-table/difficulty-adjustments-table.components'; -import { BlocksList } from './components/blocks-list/blocks-list.component'; -import { RewardStatsComponent } from './components/reward-stats/reward-stats.component'; -import { DataCyDirective } from './data-cy.directive'; -import { BlockFeesGraphComponent } from './components/block-fees-graph/block-fees-graph.component'; -import { BlockRewardsGraphComponent } from './components/block-rewards-graph/block-rewards-graph.component'; -import { BlockFeeRatesGraphComponent } from './components/block-fee-rates-graph/block-fee-rates-graph.component'; -import { LoadingIndicatorComponent } from './components/loading-indicator/loading-indicator.component'; -import { IndexingProgressComponent } from './components/indexing-progress/indexing-progress.component'; -import { BlockSizesWeightsGraphComponent } from './components/block-sizes-weights-graph/block-sizes-weights-graph.component'; @NgModule({ declarations: [ AppComponent, - AboutComponent, - MasterPageComponent, - BisqMasterPageComponent, - LiquidMasterPageComponent, - TelevisionComponent, - StartComponent, - StatisticsComponent, - TransactionComponent, - BlockComponent, - TransactionsListComponent, - AddressComponent, - LatestBlocksComponent, - SearchFormComponent, - TimeSpanComponent, - AddressLabelsComponent, - FooterComponent, - MempoolBlockComponent, - FeeDistributionGraphComponent, - IncomingTransactionsGraphComponent, - MempoolGraphComponent, - PoolRankingComponent, - PoolComponent, - LbtcPegsGraphComponent, - AssetComponent, - AssetsComponent, - StatusViewComponent, - FeesBoxComponent, - DashboardComponent, - DifficultyComponent, - TermsOfServiceComponent, - PrivacyPolicyComponent, - TrademarkPolicyComponent, - SponsorComponent, - PushTransactionComponent, - AssetsNavComponent, - AssetsFeaturedComponent, - AssetGroupComponent, - AssetCirculationComponent, - MiningDashboardComponent, - HashrateChartComponent, - HashrateChartPoolsComponent, - MiningStartComponent, - AmountShortenerPipe, - GraphsComponent, - DifficultyAdjustmentsTable, - BlocksList, - DataCyDirective, - RewardStatsComponent, - BlockFeesGraphComponent, - BlockRewardsGraphComponent, - BlockFeeRatesGraphComponent, - LoadingIndicatorComponent, - IndexingProgressComponent, - BlockSizesWeightsGraphComponent ], imports: [ BrowserModule.withServerTransition({ appId: 'serverApp' }), @@ -144,14 +26,7 @@ import { BlockSizesWeightsGraphComponent } from './components/block-sizes-weight AppRoutingModule, HttpClientModule, BrowserAnimationsModule, - InfiniteScrollModule, - NgbTypeaheadModule, - NgbModule, - FontAwesomeModule, SharedModule, - NgxEchartsModule.forRoot({ - echarts: () => import('echarts') - }) ], providers: [ ElectrsApiService, @@ -167,41 +42,4 @@ import { BlockSizesWeightsGraphComponent } from './components/block-sizes-weight ], bootstrap: [AppComponent] }) -export class AppModule { - constructor(library: FaIconLibrary) { - library.addIcons(faInfoCircle); - library.addIcons(faChartArea); - library.addIcons(faTv); - library.addIcons(faTachometerAlt); - library.addIcons(faCubes); - library.addIcons(faHammer); - library.addIcons(faCogs); - library.addIcons(faThList); - library.addIcons(faList); - library.addIcons(faTachometerAlt); - library.addIcons(faDatabase); - library.addIcons(faSearch); - library.addIcons(faLink); - library.addIcons(faBolt); - library.addIcons(faTint); - library.addIcons(faFilter); - library.addIcons(faAngleDown); - library.addIcons(faAngleUp); - library.addIcons(faExchangeAlt); - library.addIcons(faAngleDoubleUp); - library.addIcons(faAngleDoubleDown); - library.addIcons(faChevronDown); - library.addIcons(faFileAlt); - library.addIcons(faRedoAlt); - library.addIcons(faArrowAltCircleRight); - library.addIcons(faExternalLinkAlt); - library.addIcons(faSortUp); - library.addIcons(faCaretUp); - library.addIcons(faCaretDown); - library.addIcons(faAngleRight); - library.addIcons(faAngleLeft); - library.addIcons(faBook); - library.addIcons(faListUl); - library.addIcons(faDownload); - } -} +export class AppModule { } diff --git a/frontend/src/app/bisq/bisq.module.ts b/frontend/src/app/bisq/bisq.module.ts index 77b0a26e8..34c09f971 100644 --- a/frontend/src/app/bisq/bisq.module.ts +++ b/frontend/src/app/bisq/bisq.module.ts @@ -24,6 +24,7 @@ import { BisqAddressComponent } from './bisq-address/bisq-address.component'; import { BisqStatsComponent } from './bisq-stats/bisq-stats.component'; import { BsqAmountComponent } from './bsq-amount/bsq-amount.component'; import { BisqTradesComponent } from './bisq-trades/bisq-trades.component'; +import { CommonModule } from '@angular/common'; @NgModule({ declarations: [ @@ -46,6 +47,7 @@ import { BisqTradesComponent } from './bisq-trades/bisq-trades.component'; BisqMainDashboardComponent, ], imports: [ + CommonModule, BisqRoutingModule, SharedModule, NgbPaginationModule, diff --git a/frontend/src/app/components/address/address.component.html b/frontend/src/app/components/address/address.component.html index fa9e14651..6111075e4 100644 --- a/frontend/src/app/components/address/address.component.html +++ b/frontend/src/app/components/address/address.component.html @@ -66,9 +66,14 @@
-
- -
+ + +
+
+
+
+
+
@@ -81,13 +86,6 @@
- -
-
-
-
-
-
@@ -155,3 +153,9 @@ Confidential + + +
+ +
+
diff --git a/frontend/src/app/components/block/block.component.html b/frontend/src/app/components/block/block.component.html index 1450fe1d3..c0ff29889 100644 --- a/frontend/src/app/components/block/block.component.html +++ b/frontend/src/app/components/block/block.component.html @@ -200,9 +200,13 @@
-
- -
+ +
+
+
+
+
+
@@ -215,14 +219,6 @@
- - -
-
-
-
-
-
@@ -281,6 +277,12 @@ + +
+ +
+
+
diff --git a/frontend/src/app/components/master-page/master-page.component.html b/frontend/src/app/components/master-page/master-page.component.html index 73543663b..c3c9b7ae3 100644 --- a/frontend/src/app/components/master-page/master-page.component.html +++ b/frontend/src/app/components/master-page/master-page.component.html @@ -38,7 +38,7 @@