Compare commits

..

101 Commits

Author SHA1 Message Date
wiz
bb8649bc81 Merge pull request #747 from knorrium/be_dockerfile_fixes
Fix non-deterministic TypeScript version on Dockerfile
2021-08-27 17:08:04 +09:00
Felipe Knorr Kuhn
777e3d58b7 Fix non-deterministic TypeScript version on Dockerfile 2021-08-27 00:28:05 -07:00
wiz
c552f1aab6 Pull from transifex 2021-08-27 14:28:51 +09:00
softsimon
c0f2fa3042 Merge pull request #746 from MiguelMedeiros/bugfix-difficulty-adjustment-calc
Bugfix: difficulty adjustment calculation.
2021-08-26 03:27:59 +03:00
Miguel Medeiros
05936f82bd Refactor getDifficultyChange endpoint. 2021-08-25 21:14:01 -03:00
Miguel Medeiros
c7db81c97c Refactor difficulty adjustment calculation. 2021-08-25 17:18:30 -03:00
softsimon
bd1a37b8ef Correcting keypress arrow left test. 2021-08-25 18:41:35 +03:00
softsimon
efc4e6a8ed Fix for multiple arrow navigation bugs. (#741)
* Fix for multiple arrow navigation bugs.

fixes #731

* Updating test to handle left arrow navigation.

* Updating test to handle left arrow navigation.

* Arrow right click fix.

* Update frontend/cypress/integration/mainnet/mainnet.spec.ts

Co-authored-by: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com>

Co-authored-by: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com>
2021-08-25 15:44:55 +03:00
Felipe Knorr Kuhn
dd5d87e91e Add a test for not showing unblinding errors on regular TXs (#737) 2021-08-22 00:34:40 +03:00
wiz
ca6df488c5 Fix missing fetch command in upgrade script 2021-08-21 21:56:42 +09:00
wiz
1e018a6aa5 Fix typo/bug in mempool-logger script 2021-08-21 15:39:15 +09:00
wiz
0a627f96be Tweak production syslog configuration 2021-08-21 15:19:29 +09:00
wiz
17a8e67d8a Modify restart script to log restart events, don't restart services 2021-08-21 15:06:50 +09:00
wiz
815c2c5ad5 Modify upgrade script for PR branch deployment, tweak logging 2021-08-21 14:51:19 +09:00
softsimon
4376de85ff Liquid unblinding: Don't throw error as default for regular liquid transactions. 2021-08-21 03:13:39 +03:00
wiz
7e89de4612 Send deployment notifications to mempool.dev keybase group 2021-08-20 21:52:54 +09:00
wiz
4b72a14706 Merge pull request #735 from mempool/simon/bisq-tx-redirect-fix
Redirect unconfirmed bisq transactions to main website for tracking.
2021-08-20 21:25:13 +09:00
softsimon
b34f6fedb6 Redirect unconfirmed bisq transactions to main website for tracking. 2021-08-20 14:56:12 +03:00
Miguel Medeiros
58af0d78af Bugfix: Fix bisq dashboard tables overflow. (#733)
* Fix bisq dashboard tables overflow.

* Fix nav-item mobile margin.
2021-08-19 17:51:08 +03:00
Priyansh
ca13d9109c Updated mobile view for dropdown (#717)
Dropdown menu with dynamic positioning
2021-08-18 19:18:12 +03:00
Priyansh
e103fb5876 Updated significant digits of transaction fee (#722) 2021-08-18 16:27:35 +03:00
softsimon
58178f4563 Arrow bugfix: Unsubscribe all the observables when leaving view. 2021-08-18 15:59:18 +03:00
wiz
04f1879fd1 Add Unchained Capital as Enterprise Sponsor (#711) 2021-08-18 15:15:31 +03:00
softsimon
f5bc9ced0a Liquid unblinding: Replacing async/await with observable. 2021-08-18 14:05:40 +03:00
softsimon
7fe9993f91 Optimize performance of next/previous block. (#729) 2021-08-18 12:49:42 +03:00
softsimon
7c95339324 Merge pull request #728 from mempool/simon/unblinding-refactor-fix
Unblinding refactor fix
2021-08-18 04:09:53 +03:00
softsimon
006442f9de Merge pull request #727 from knorrium/improve_multisite_testing
Improve multisite testing
2021-08-18 03:35:43 +03:00
softsimon
e20100e437 Unblinding refactor fix 2021-08-18 03:34:17 +03:00
Felipe Knorr Kuhn
d7cf2b37d5 Update Liquid test data 2021-08-17 16:49:34 -07:00
Felipe Knorr Kuhn
278c2b9aae Fix typo on liquid setup 2021-08-17 16:38:51 -07:00
Felipe Knorr Kuhn
944246fcf5 Allow test jobs to run regardless of previous failures 2021-08-17 16:26:19 -07:00
Felipe Knorr Kuhn
03d87f4993 Add the Cypress env vars to the new test configs 2021-08-17 16:14:19 -07:00
Felipe Knorr Kuhn
cf8cab5f77 Temporarily enable cypress debug logs on GHA 2021-08-17 16:08:47 -07:00
Felipe Knorr Kuhn
49d1376647 Update cypress test config 2021-08-17 15:55:00 -07:00
Felipe Knorr Kuhn
de5518d262 update test specs to handle the new multisite setup on CI 2021-08-17 15:53:57 -07:00
Felipe Knorr Kuhn
7e4c51f47f Add new config targets for the multisite setup 2021-08-17 15:49:58 -07:00
softsimon
fe1d153632 Merge pull request #710 from MiguelMedeiros/feature-next-previous-block-arrows
UI/UX: `Next` and `Previous` button arrows for block navigation.
2021-08-18 00:57:39 +03:00
softsimon
a98f9ab80e Updating from transifex. 2021-08-18 00:54:34 +03:00
softsimon
867afaf265 Updating package lock file. 2021-08-18 00:50:18 +03:00
Rishabh
3d2ec64b14 Only display "Load all" if there are at least 3 more items to load (#724)
* Only display Load more if there are at least 3 more items to load

* Load more only if at least 3 more inputs
2021-08-18 00:49:08 +03:00
softsimon
bb407c0b42 Merge pull request #725 from mempool/simon/liquid-unblinding-refactor
Refactored liquid unblinding code into a new file.
2021-08-17 23:52:33 +03:00
softsimon
83c3d901c7 Merge pull request #726 from knorrium/update_devproxy
Update test infra to add initial support for the new multi-site setup.
2021-08-17 23:52:21 +03:00
Felipe Knorr Kuhn
901cee903c Update bisq tests to use the menu navigation 2021-08-17 13:02:24 -07:00
Felipe Knorr Kuhn
250ea09c7e Run generate config before running e2e tests locally 2021-08-17 13:01:36 -07:00
Felipe Knorr Kuhn
648d59631b Update local-prod target to use the new JS dev proxy 2021-08-17 13:01:04 -07:00
Felipe Knorr Kuhn
ed06e3c491 Add a new JS-based dev proxy for the new split up sites 2021-08-17 13:00:46 -07:00
Felipe Knorr Kuhn
3e8d646edd Fix update-config script to parse string values 2021-08-17 12:03:03 -07:00
softsimon
9c2c698575 Refactored liquid unblinding code into a new file. 2021-08-17 20:20:25 +03:00
softsimon
e2b0a286a4 Adding v2.2.1 dashboard screen shot. 2021-08-16 00:31:14 +03:00
softsimon
154809f0f9 Fix navigate to sponsor when base module is not mempool 2021-08-15 20:05:49 +03:00
softsimon
8d9a51a7c4 Readding search field, and fixing search for non-mempool base module sites. 2021-08-14 14:58:58 +03:00
softsimon
b3294369d4 Restoring Bisq Dao nav bar on Bisq module. 2021-08-14 13:25:54 +03:00
softsimon
53730920e3 Bugfix: Mempool block sizes were mixing up vsize and weight. 2021-08-14 03:24:31 +03:00
softsimon
d73b814277 Merge pull request #703 from priyanshiiit/median_fee
Get Median Fee on page reload
2021-08-14 02:25:35 +03:00
softsimon
dd0050c066 Merge pull request #669 from mempool/simon/configurable-main-module
Make base module and index.html file configurable with BASE_MODULE. A…
2021-08-14 02:25:07 +03:00
softsimon
ae51ee3e26 Removing issuance id test and fixing asset id test. 2021-08-14 02:21:38 +03:00
wiz
4b16e5d65f Add some basic title/descriptions for bisq.markets and liquid.network 2021-08-14 07:44:22 +09:00
softsimon
4f73bba132 Hide Mempool project description from non-mempool base module sites. 2021-08-14 01:37:28 +03:00
softsimon
3c229602e4 Use local about page component instead of external link. 2021-08-14 00:42:52 +03:00
softsimon
c74c902ebc Adding new preview images for Liquid and Bisq 2021-08-14 00:36:46 +03:00
wiz
8bfd315ba3 Rewrite production upgrade script to handle all 3 sites 2021-08-14 06:03:30 +09:00
softsimon
9d75c47792 Merge branch 'master' into simon/configurable-main-module
# Conflicts:
#	frontend/src/app/components/api-docs/api-docs.component.ts
#	frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.ts
#	frontend/src/app/components/master-page/master-page.component.html
#	frontend/src/app/components/mempool-blocks/mempool-blocks.component.html
#	frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts
#	frontend/src/app/dashboard/dashboard.component.html
2021-08-13 17:16:14 +03:00
wiz
e183be1a5c Update GitHub sponsor link to mempool.space/sponsor 2021-08-13 08:41:35 +09:00
wiz
7e273ce63d Bump version tags to v2.2.2-dev 2021-08-13 07:56:14 +09:00
Miguel Medeiros
6d070e75b0 Add next and previous arrows to blocks. 2021-08-12 19:49:39 -03:00
wiz
f4f96fd18e Fix typos in site.webmanifest for liquid.network 2021-08-12 20:57:33 +09:00
softsimon
ddd6420d9b Updating Bisq markets logo and favicons. 2021-08-11 20:31:05 +03:00
Miguel Medeiros
d76f42296a Add new documentation for Liquid and Bisq. 2021-08-12 01:19:40 +09:00
github2k20
47a6118ffb Get Median Fee on page reload 2021-08-11 00:17:25 +05:30
softsimon
dbd205b73f Fix: Block fee data wasn't visible unless at least 2 transactions. 2021-08-08 14:17:18 +03:00
softsimon
7ef4be26ed Use BLOCK_WEIGHT_UNITS instead of hard coded block size limit in all occations. 2021-08-08 04:19:54 +03:00
softsimon
c6b1979391 Support Liquid 0.1 sat/vB fees on blocks. 2021-08-07 04:24:46 +03:00
softsimon
0f390e65a4 Assets page design improvements. 2021-08-07 03:25:35 +03:00
softsimon
5dc0f4e270 Use actual MEMPOOL_BLOCKS_AMOUNT value for amount of mempool blocks. 2021-08-07 03:07:32 +03:00
softsimon
223288cc52 Removing extra character. 2021-08-07 03:05:54 +03:00
softsimon
e1f07884b9 Correcting favicon paths and color. 2021-08-05 19:25:13 +03:00
softsimon
e00e61edfa Updating liquid favicons. 2021-08-05 19:14:48 +03:00
softsimon
4f988e186a Updating liquid network logo. 2021-08-05 18:58:39 +03:00
wiz
1aa54faa35 Fix liquid.network and bisq.markets page titles in SEO services 2021-08-05 22:45:03 +09:00
softsimon
0bb9247609 Handle 0.1 sat/vB base fee on the dashboard and backend. 2021-08-05 02:03:52 +03:00
softsimon
d841933b21 Sync blockstream asset registry when in liquid base module mode. 2021-08-05 01:31:22 +03:00
wiz
bc8b78a01b Update page titles in index.*.html and SEO service 2021-08-05 02:41:52 +09:00
softsimon
c6e72be483 Liquid proxy fix to work locally. 2021-08-04 14:21:15 +03:00
softsimon
ef7dd6c8fb Merge branch 'master' into simon/configurable-main-module 2021-08-03 18:41:43 +03:00
wiz
e6b90385b2 Further tweak index.liquid.html metadata text 2021-08-02 21:42:02 +09:00
wiz
61181c6791 Set index.html metadata and SEO service title for liquid.network 2021-08-02 21:30:29 +09:00
wiz
d2cccd2422 Fix liquid favicons 2021-08-02 21:07:48 +09:00
wiz
b05ebe1598 Add missing route for /privacy-policy to liquid explorer routing 2021-08-02 20:59:31 +09:00
wiz
d061f7589c Update our Terms of Service, split Privacy Policy into its own page 2021-08-02 20:48:10 +09:00
softsimon
15903faf49 Merge branch 'master' into simon/configurable-main-module
# Conflicts:
#	frontend/src/app/components/blockchain/blockchain.component.ts
2021-08-02 00:24:09 +03:00
softsimon
1908b1a5a6 Adding configuration for blocks and mempool blocks amount. 2021-07-31 17:56:10 +03:00
softsimon
037f472f8c Make Block Weight Unit configurable in frontend+backend. 2021-07-31 17:30:35 +03:00
softsimon
a32c1f40b1 Restoring About page. Fix for KEEP_BLOCKS_AMOUNT setting. Show up to 2 mempool blocks. 2021-07-30 23:14:45 +03:00
softsimon
a00aa27ae4 Fix API doc page for Bisq. 2021-07-29 16:14:36 +03:00
softsimon
544be77bdc Liquid asset search bug fix. 2021-07-29 13:10:06 +03:00
softsimon
b8a110a772 Liquid fixes for latest transactions and API Doc. 2021-07-29 13:06:08 +03:00
wiz
7788a2d6bd Add other domain names to Terms of Service header 2021-07-29 18:38:49 +09:00
softsimon
da17fd16fa Liquid blockchain container position. 2021-07-29 12:36:00 +03:00
softsimon
e670f80fed Liquid dashboard updates. About page link. 2021-07-29 12:32:54 +03:00
softsimon
2de28b9926 Dynamic dropdown. 2021-07-28 22:32:13 +03:00
softsimon
d7586af392 Make base module and index.html file configurable with BASE_MODULE. Adding bare Liquid module. 2021-07-27 17:10:38 +03:00
127 changed files with 6400 additions and 2753 deletions

2
.github/FUNDING.yml vendored
View File

@@ -9,4 +9,4 @@ community_bridge: # Replace with a single Community Bridge project-name e.g., cl
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: ['https://mempool.space/about'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
custom: ['https://mempool.space/sponsor'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -15,17 +15,60 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
- name: ${{ matrix.browser }} browser tests
- name: ${{ matrix.browser }} browser tests (Mempool)
uses: cypress-io/github-action@v2
with:
working-directory: frontend
build: npm run config:defaults
build: npm run config:defaults:mempool
start: npm run start:local-prod
wait-on: 'http://localhost:4200'
wait-on-timeout: 120
record: true
parallel: true
group: Tests on ${{ matrix.browser }}
env: BASE_MODULE=mempool
group: Tests on ${{ matrix.browser }} (Mempool)
browser: ${{ matrix.browser }}
ci-build-id: '${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}'
env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }}
- name: ${{ matrix.browser }} browser tests (Liquid)
uses: cypress-io/github-action@v2
if: always()
with:
working-directory: frontend
build: npm run config:defaults:liquid
start: npm run start:local-prod
wait-on: 'http://localhost:4200'
wait-on-timeout: 120
record: true
parallel: true
spec: cypress/integration/liquid/liquid.spec.ts
env: BASE_MODULE=liquid
group: Tests on ${{ matrix.browser }} (Liquid)
browser: ${{ matrix.browser }}
ci-build-id: '${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}'
env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }}
- name: ${{ matrix.browser }} browser tests (Bisq)
uses: cypress-io/github-action@v2
if: always()
with:
working-directory: frontend
build: npm run config:defaults:bisq
start: npm run start:local-prod
wait-on: 'http://localhost:4200'
wait-on-timeout: 120
record: true
parallel: true
spec: cypress/integration/bisq/bisq.spec.ts
env: BASE_MODULE=bisq
group: Tests on ${{ matrix.browser }} (Bisq)
browser: ${{ matrix.browser }}
ci-build-id: '${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}'
env:

View File

@@ -8,7 +8,10 @@
"POLL_RATE_MS": 2000,
"CACHE_DIR": "./cache",
"CLEAR_PROTECTION_MINUTES": 20,
"RECOMMENDED_FEE_PERCENTILE": 50
"RECOMMENDED_FEE_PERCENTILE": 50,
"BLOCK_WEIGHT_UNITS": 4000000,
"INITIAL_BLOCKS_AMOUNT": 8,
"MEMPOOL_BLOCKS_AMOUNT": 8
},
"CORE_RPC": {
"HOST": "127.0.0.1",

View File

@@ -1,12 +1,12 @@
{
"name": "mempool-backend",
"version": "2.2.1",
"version": "2.2.2-dev",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "mempool-backend",
"version": "2.2.1",
"version": "2.2.2-dev",
"license": "GNU Affero General Public License v3.0",
"dependencies": {
"@mempool/bitcoin": "^3.0.3",

View File

@@ -1,6 +1,6 @@
{
"name": "mempool-backend",
"version": "2.2.1",
"version": "2.2.2-dev",
"description": "Bitcoin mempool visualizer and blockchain explorer backend",
"license": "GNU Affero General Public License v3.0",
"homepage": "https://mempool.space",

View File

@@ -9,7 +9,6 @@ import transactionUtils from './transaction-utils';
import bitcoinBaseApi from './bitcoin/bitcoin-base.api';
class Blocks {
private static INITIAL_BLOCK_AMOUNT = 8;
private blocks: BlockExtended[] = [];
private currentBlockHeight = 0;
private currentDifficulty = 0;
@@ -35,14 +34,14 @@ class Blocks {
const blockHeightTip = await bitcoinApi.$getBlockHeightTip();
if (this.blocks.length === 0) {
this.currentBlockHeight = blockHeightTip - Blocks.INITIAL_BLOCK_AMOUNT;
this.currentBlockHeight = blockHeightTip - config.MEMPOOL.INITIAL_BLOCKS_AMOUNT;
} else {
this.currentBlockHeight = this.blocks[this.blocks.length - 1].height;
}
if (blockHeightTip - this.currentBlockHeight > Blocks.INITIAL_BLOCK_AMOUNT * 2) {
logger.info(`${blockHeightTip - this.currentBlockHeight} blocks since tip. Fast forwarding to the ${Blocks.INITIAL_BLOCK_AMOUNT} recent blocks`);
this.currentBlockHeight = blockHeightTip - Blocks.INITIAL_BLOCK_AMOUNT;
if (blockHeightTip - this.currentBlockHeight > config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 2) {
logger.info(`${blockHeightTip - this.currentBlockHeight} blocks since tip. Fast forwarding to the ${config.MEMPOOL.INITIAL_BLOCKS_AMOUNT} recent blocks`);
this.currentBlockHeight = blockHeightTip - config.MEMPOOL.INITIAL_BLOCKS_AMOUNT;
}
if (!this.lastDifficultyAdjustmentTime) {
@@ -111,8 +110,8 @@ class Blocks {
blockExtended.coinbaseTx = transactionUtils.stripCoinbaseTransaction(transactions[0]);
transactions.shift();
transactions.sort((a, b) => b.effectiveFeePerVsize - a.effectiveFeePerVsize);
blockExtended.medianFee = transactions.length > 1 ? Common.median(transactions.map((tx) => tx.effectiveFeePerVsize)) : 0;
blockExtended.feeRange = transactions.length > 1 ? Common.getFeesInRange(transactions, 8) : [0, 0];
blockExtended.medianFee = transactions.length > 0 ? Common.median(transactions.map((tx) => tx.effectiveFeePerVsize)) : 0;
blockExtended.feeRange = transactions.length > 0 ? Common.getFeesInRange(transactions, 8) : [0, 0];
if (block.height % 2016 === 0) {
this.previousDifficultyRetarget = (block.difficulty - this.currentDifficulty) / this.currentDifficulty * 100;
@@ -121,8 +120,8 @@ class Blocks {
}
this.blocks.push(blockExtended);
if (this.blocks.length > Blocks.INITIAL_BLOCK_AMOUNT * 4) {
this.blocks = this.blocks.slice(-Blocks.INITIAL_BLOCK_AMOUNT * 4);
if (this.blocks.length > config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 4) {
this.blocks = this.blocks.slice(-config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 4);
}
if (this.newBlockCallbacks.length) {

View File

@@ -1,5 +1,5 @@
import { CpfpInfo, TransactionExtended, TransactionStripped } from '../mempool.interfaces';
import config from '../config';
export class Common {
static median(numbers: number[]) {
let medianNr = 0;
@@ -105,7 +105,7 @@ export class Common {
totalFees += tx.bestDescendant.fee;
}
tx.effectiveFeePerVsize = Math.max(1, totalFees / (totalWeight / 4));
tx.effectiveFeePerVsize = Math.max(config.MEMPOOL.NETWORK === 'liquid' ? 0.1 : 1, totalFees / (totalWeight / 4));
tx.cpfpChecked = true;
return {

View File

@@ -4,7 +4,6 @@ import { Common } from './common';
import config from '../config';
class MempoolBlocks {
private static DEFAULT_PROJECTED_BLOCKS_AMOUNT = 8;
private mempoolBlocks: MempoolBlockWithTransactions[] = [];
constructor() {}
@@ -72,29 +71,29 @@ class MempoolBlocks {
private calculateMempoolBlocks(transactionsSorted: TransactionExtended[]): MempoolBlockWithTransactions[] {
const mempoolBlocks: MempoolBlockWithTransactions[] = [];
let blockVSize = 0;
let blockWeight = 0;
let blockSize = 0;
let transactions: TransactionExtended[] = [];
transactionsSorted.forEach((tx) => {
if (blockVSize + tx.vsize <= 1000000 || mempoolBlocks.length === MempoolBlocks.DEFAULT_PROJECTED_BLOCKS_AMOUNT - 1) {
blockVSize += tx.vsize;
if (blockWeight + tx.weight <= config.MEMPOOL.BLOCK_WEIGHT_UNITS || mempoolBlocks.length === config.MEMPOOL.MEMPOOL_BLOCKS_AMOUNT) {
blockWeight += tx.weight;
blockSize += tx.size;
transactions.push(tx);
} else {
mempoolBlocks.push(this.dataToMempoolBlocks(transactions, blockSize, blockVSize, mempoolBlocks.length));
blockVSize = tx.vsize;
mempoolBlocks.push(this.dataToMempoolBlocks(transactions, blockSize, blockWeight, mempoolBlocks.length));
blockWeight = tx.weight;
blockSize = tx.size;
transactions = [tx];
}
});
if (transactions.length) {
mempoolBlocks.push(this.dataToMempoolBlocks(transactions, blockSize, blockVSize, mempoolBlocks.length));
mempoolBlocks.push(this.dataToMempoolBlocks(transactions, blockSize, blockWeight, mempoolBlocks.length));
}
return mempoolBlocks;
}
private dataToMempoolBlocks(transactions: TransactionExtended[],
blockSize: number, blockVSize: number, blocksIndex: number): MempoolBlockWithTransactions {
blockSize: number, blockWeight: number, blocksIndex: number): MempoolBlockWithTransactions {
let rangeLength = 4;
if (blocksIndex === 0) {
rangeLength = 8;
@@ -106,7 +105,7 @@ class MempoolBlocks {
}
return {
blockSize: blockSize,
blockVSize: blockVSize,
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),

View File

@@ -1,7 +1,7 @@
import bitcoinApi from './bitcoin/bitcoin-api-factory';
import logger from '../logger';
import { TransactionExtended, TransactionMinerInfo } from '../mempool.interfaces';
import { IEsploraApi } from './bitcoin/esplora-api.interface';
import config from '../config';
class TransactionUtils {
constructor() { }
@@ -31,7 +31,7 @@ class TransactionUtils {
// @ts-ignore
return transaction;
}
const feePerVbytes = Math.max(1, (transaction.fee || 0) / (transaction.weight / 4));
const feePerVbytes = Math.max(config.MEMPOOL.NETWORK === 'liquid' ? 0.1 : 1, (transaction.fee || 0) / (transaction.weight / 4));
const transactionExtended: TransactionExtended = Object.assign({
vsize: Math.round(transaction.weight / 4),
feePerVsize: feePerVbytes,

View File

@@ -97,7 +97,7 @@ class WebsocketHandler {
}
if (parsedMessage.action === 'init') {
const _blocks = blocks.getBlocks().slice(-8);
const _blocks = blocks.getBlocks().slice(-config.MEMPOOL.INITIAL_BLOCKS_AMOUNT);
if (!_blocks) {
return;
}
@@ -173,7 +173,7 @@ class WebsocketHandler {
getInitData(_blocks?: BlockExtended[]) {
if (!_blocks) {
_blocks = blocks.getBlocks().slice(-8);
_blocks = blocks.getBlocks().slice(-config.MEMPOOL.INITIAL_BLOCKS_AMOUNT);
}
return {
'mempoolInfo': memPool.getMempoolInfo(),

View File

@@ -11,6 +11,9 @@ interface IConfig {
CACHE_DIR: string;
CLEAR_PROTECTION_MINUTES: number;
RECOMMENDED_FEE_PERCENTILE: number;
BLOCK_WEIGHT_UNITS: number;
INITIAL_BLOCKS_AMOUNT: number;
MEMPOOL_BLOCKS_AMOUNT: number;
};
ESPLORA: {
REST_API_URL: string;
@@ -69,6 +72,9 @@ const defaults: IConfig = {
'CACHE_DIR': './cache',
'CLEAR_PROTECTION_MINUTES': 20,
'RECOMMENDED_FEE_PERCENTILE': 50,
'BLOCK_WEIGHT_UNITS': 4000000,
'INITIAL_BLOCKS_AMOUNT': 8,
'MEMPOOL_BLOCKS_AMOUNT': 8,
},
'ESPLORA': {
'REST_API_URL': 'http://127.0.0.1:3000',

View File

@@ -693,37 +693,50 @@ class Routes {
public getDifficultyChange(req: Request, res: Response) {
try {
const now = new Date().getTime() / 1000;
const DATime = blocks.getLastDifficultyAdjustmentTime();
const previousRetarget = blocks.getPreviousDifficultyRetarget();
const diff = now - DATime;
const blockHeight = blocks.getCurrentBlockHeight();
const now = new Date().getTime() / 1000;
const diff = now - DATime;
const blocksInEpoch = blockHeight % 2016;
const difficultyChange = (600 / (diff / blocksInEpoch) - 1) * 100;
const progressPercent = (blocksInEpoch >= 0) ? blocksInEpoch / 2016 * 100 : 100;
const remainingBlocks = 2016 - blocksInEpoch;
const nextRetargetHeight = blockHeight + remainingBlocks;
let difficultyChange = 0;
if (blocksInEpoch > 0) {
difficultyChange = (600 / (diff / blocksInEpoch ) - 1) * 100;
}
if (difficultyChange > 300) {
difficultyChange = 300;
}
if (difficultyChange < -75) {
difficultyChange = -75;
}
const timeAvgDiff = difficultyChange * 0.1;
let timeAvgMins = 10;
if (timeAvgDiff > 0 ){
if (timeAvgDiff > 0) {
timeAvgMins -= Math.abs(timeAvgDiff);
} else {
timeAvgMins += Math.abs(timeAvgDiff);
}
const remainingBlocks = 2016 - blocksInEpoch;
const timeAvgSeconds = timeAvgMins * 60;
const remainingTime = remainingBlocks * timeAvgSeconds;
const estimatedRetargetDate = (remainingTime + now);
const totalTime = estimatedRetargetDate-DATime;
const progressPercent = 100 - ((remainingTime * 100) / totalTime);
const result={
const timeAvg = timeAvgMins * 60;
const remainingTime = remainingBlocks * timeAvg;
const estimatedRetargetDate = remainingTime + now;
const result = {
progressPercent,
difficultyChange,
estimatedRetargetDate,
remainingBlocks,
remainingTime,
previousRetarget,
nextRetargetHeight,
timeAvg,
}
res.json(result);

View File

@@ -5,8 +5,7 @@ COPY . .
RUN apt-get update
RUN apt-get install -y build-essential python3 pkg-config
RUN npm ci --production
RUN npm i typescript
RUN npm install
RUN npm run build
FROM node:12-buster-slim

3
frontend/.gitignore vendored
View File

@@ -59,3 +59,6 @@ generated-config.js
cypress/videos
cypress/screenshots
# Base index
src/index.html

View File

@@ -200,7 +200,7 @@
"verbose": true
},
"local-prod": {
"proxyConfig": "proxy.prod.conf.json",
"proxyConfig": "proxy.conf.js",
"disableHostCheck": true,
"host": "0.0.0.0",
"verbose": false

View File

@@ -1,5 +1,8 @@
describe('Bisq', () => {
let baseModule;
beforeEach(() => {
baseModule = (Cypress.env('BASE_MODULE') && Cypress.env('BASE_MODULE') === 'bisq') ? '' : '/bisq';
cy.intercept('/sockjs-node/info*').as('socket');
cy.intercept('/bisq/api/markets/hloc?market=btc_usd&interval=day').as('hloc');
cy.intercept('/bisq/api/markets/ticker').as('ticker');
@@ -20,62 +23,66 @@ describe('Bisq', () => {
});
});
it('loads the dashboard', () => {
cy.visit('/bisq');
cy.waitForSkeletonGone();
});
if (Cypress.env("BASE_MODULE") === '' || Cypress.env("BASE_MODULE") !== 'liquid') {
it('loads the transactions screen', () => {
cy.visit('/bisq');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(2) > a').click().then(() => {
cy.get('.table > tr').should('have.length', 50);
it('loads the dashboard', () => {
cy.visit(`${baseModule}`);
cy.waitForSkeletonGone();
});
});
it('loads the blocks screen', () => {
cy.visit('/bisq');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(3) > a').click().then(() => {
cy.wait('@blocks');
it('loads the transactions screen', () => {
cy.visit(`${baseModule}`);
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(2) > a').click().then(() => {
cy.get('.table > tr').should('have.length', 50);
});
});
it('loads the blocks screen', () => {
cy.visit(`${baseModule}`);
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(3) > a').click().then(() => {
cy.wait('@blocks');
cy.get('tbody tr').should('have.length', 10);
});
});
it('loads the stats screen', () => {
cy.visit(`${baseModule}`);
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(4) > a').click().then(() => {
cy.wait('@stats');
});
});
it('loads the api screen', () => {
cy.visit(`${baseModule}`);
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(5) > a').click().then(() => {
cy.get('.card').should('have.length.at.least', 1);
cy.get('.card').first().click();
cy.get('.card-body');
});
});
it('shows blocks pagination with 5 pages (desktop)', () => {
cy.viewport(760, 800);
cy.visit(`${baseModule}/blocks`);
cy.waitForSkeletonGone();
cy.get('tbody tr').should('have.length', 10);
// 5 pages + 4 buttons = 9 buttons
cy.get('.pagination-container ul.pagination').first().children().should('have.length', 9);
});
});
it('loads the stats screen', () => {
cy.visit('/bisq');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(4) > a').click().then(() => {
cy.wait('@stats');
it('shows blocks pagination with 3 pages (mobile)', () => {
cy.viewport(669, 800);
cy.visit(`${baseModule}/blocks`);
cy.waitForSkeletonGone();
cy.get('tbody tr').should('have.length', 10);
// 3 pages + 4 buttons = 7 buttons
cy.get('.pagination-container ul.pagination').first().children().should('have.length', 7);
});
});
it('loads the api screen', () => {
cy.visit('/bisq');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(5) > a').click().then(() => {
cy.get('.card').should('have.length.at.least', 1);
cy.get('.card').first().click();
cy.get('.card-body');
});
});
it('shows blocks pagination with 5 pages (desktop)', () => {
cy.viewport(760, 800);
cy.visit('/bisq/blocks');
cy.waitForSkeletonGone();
cy.get('tbody tr').should('have.length', 10);
// 5 pages + 4 buttons = 9 buttons
cy.get('.pagination-container ul.pagination').first().children().should('have.length', 9);
});
it('shows blocks pagination with 3 pages (mobile)', () => {
cy.viewport(669, 800);
cy.visit('/bisq/blocks');
cy.waitForSkeletonGone();
cy.get('tbody tr').should('have.length', 10);
// 3 pages + 4 buttons = 7 buttons
cy.get('.pagination-container ul.pagination').first().children().should('have.length', 7);
});
} else {
it.skip("Tests cannot be run on the selected BASE_MODULE");
}
});

View File

@@ -1,5 +1,8 @@
describe('Liquid', () => {
let baseModule;
beforeEach(() => {
baseModule = (Cypress.env('BASE_MODULE') && Cypress.env('BASE_MODULE') === 'liquid') ? '' : '/liquid';
cy.intercept('/liquid/api/block/**').as('block');
cy.intercept('/liquid/api/blocks/').as('blocks');
cy.intercept('/liquid/api/tx/**/outspends').as('outspends');
@@ -13,137 +16,132 @@ describe('Liquid', () => {
});
});
it('loads the dashboard', () => {
cy.visit('/liquid');
cy.waitForSkeletonGone();
});
if (Cypress.env("BASE_MODULE") === '' || Cypress.env("BASE_MODULE") !== 'bisq') {
it('loads the blocks page', () => {
cy.visit('/liquid/blocks');
cy.waitForSkeletonGone();
});
it('loads a specific block page', () => {
cy.visit('/liquid/block/7e1369a23a5ab861e7bdede2aadcccae4ea873ffd9caf11c7c5541eb5bcdff54');
cy.waitForSkeletonGone();
});
it('loads the graphs page', () => {
cy.visit('/liquid/graphs');
cy.waitForSkeletonGone();
});
it('loads the tv page - desktop', () => {
cy.visit('/liquid');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(3) > a').click().then(() => {
cy.wait(1000);
});
});
it('loads the graphs page - mobile', () => {
cy.visit('/liquid');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(3) > a').click().then(() => {
cy.viewport('iphone-6');
cy.wait(1000);
cy.get('.tv-only').should('not.exist');
});
});
describe('assets', () => {
it('shows the assets screen', () => {
cy.visit('/liquid');
it('loads the dashboard', () => {
cy.visit(`${baseModule}`);
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(5) > a').click().then(() => {
cy.get('table tr').should('have.length', 5);
});
it('loads the blocks page', () => {
cy.visit(`${baseModule}/blocks`);
cy.waitForSkeletonGone();
});
it('loads a specific block page', () => {
cy.visit(`${baseModule}/block/7e1369a23a5ab861e7bdede2aadcccae4ea873ffd9caf11c7c5541eb5bcdff54`);
cy.waitForSkeletonGone();
});
it('loads the graphs page', () => {
cy.visit(`${baseModule}/graphs`);
cy.waitForSkeletonGone();
});
it('loads the tv page - desktop', () => {
cy.visit(`${baseModule}`);
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(3) > a').click().then(() => {
cy.wait(1000);
});
});
it('allows searching assets', () => {
cy.visit('/liquid');
it('loads the graphs page - mobile', () => {
cy.visit(`${baseModule}`)
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(5) > a').click().then(() => {
cy.get('li:nth-of-type(3) > a').click().then(() => {
cy.viewport('iphone-6');
cy.wait(1000);
cy.get('.tv-only').should('not.exist');
});
});
describe('assets', () => {
it('shows the assets screen', () => {
cy.visit(`${baseModule}/assets`);
cy.waitForSkeletonGone();
cy.get('table tr').should('have.length.at.least', 5);
});
it('allows searching assets', () => {
cy.visit(`${baseModule}/assets`);
cy.waitForSkeletonGone();
cy.get('.container-xl input').click().type('Liquid Bitcoin').then(() => {
cy.get('table tr').should('have.length', 1);
});
});
});
it('shows a specific asset ID', () => {
cy.visit('/liquid');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(5) > a').click().then(() => {
cy.get('.container-xl input').click().type('Liquid CAD').then(() => {
cy.get('table tr td:nth-of-type(4) a').click();
it('shows a specific asset ID', () => {
cy.visit(`${baseModule}/assets`);
cy.waitForSkeletonGone();
cy.get('.container-xl input').click().type('Liquid AUD').then(() => {
cy.get('table tr td:nth-of-type(1) a').click();
});
});
});
it('shows a specific asset issuance TX', () => {
cy.visit('/liquid');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(5) > a').click().then(() => {
cy.get('.container-xl input').click().type('Liquid CAD').then(() => {
cy.get('table tr td:nth-of-type(5) a').click();
});
describe('unblinded TX', () => {
it('should not show an unblinding error message for regular txs', () => {
cy.visit(`${baseModule}/tx/82a479043ec3841e0d3f829afc8df4f0e2bbd675a13f013ea611b2fde0027d45`);
cy.waitForSkeletonGone();
cy.get('.error-unblinded' ).should('not.exist');
});
it('show unblinded TX', () => {
cy.visit(`${baseModule}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc,2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3a`);
cy.waitForSkeletonGone();
cy.get('#table-tx-vin tr').should('have.class', 'assetBox');
cy.get('#table-tx-vout tr').should('have.class', 'assetBox');
});
it('show empty unblinded TX', () => {
cy.visit(`${baseModule}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=`);
cy.waitForSkeletonGone();
cy.get('#table-tx-vin tr').should('have.class', '');
cy.get('#table-tx-vout tr').should('have.class', '');
});
it('show invalid unblinded TX hex', () => {
cy.visit(`${baseModule}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=123`);
cy.waitForSkeletonGone();
cy.get('#table-tx-vin tr').should('have.class', '');
cy.get('#table-tx-vout tr').should('have.class', '');
cy.get('.error-unblinded' ).contains('Error: Invalid blinding data (invalid hex)');
});
it('show first unblinded vout', () => {
cy.visit(`${baseModule}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc`);
cy.waitForSkeletonGone();
cy.get('#table-tx-vout tr:first-child()').should('have.class', 'assetBox');
});
it('show second unblinded vout', () => {
cy.visit(`${baseModule}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3a`);
cy.get('#table-tx-vout tr').should('have.class', 'assetBox');
});
it('show invalid error unblinded TX', () => {
cy.visit(`${baseModule}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc,2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3c`);
cy.waitForSkeletonGone();
cy.get('#table-tx-vout tr').should('have.class', 'assetBox');
cy.get('.error-unblinded' ).contains('Error: Invalid blinding data.');
});
it('shows asset peg in/out and burn transactions', () => {
cy.visit(`${baseModule}/asset/6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`);
cy.waitForSkeletonGone();
cy.get('#table-tx-vout tr').not('.assetBox');
cy.get('#table-tx-vin tr').not('.assetBox');
});
it('prevents regressing issue #644', () => {
cy.visit(`${baseModule}/tx/393b890966f305e7c440fcfb12a13f51a7a9011cc59ff5f14f6f93214261bd82`);
cy.waitForSkeletonGone();
});
});
});
describe('unblinded TX', () => {
it('show unblinded TX', () => {
cy.visit('/liquid/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc,2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3a');
cy.waitForSkeletonGone();
cy.get('#table-tx-vin tr').should('have.class', 'assetBox');
cy.get('#table-tx-vout tr').should('have.class', 'assetBox');
});
it('show empty unblinded TX', () => {
cy.visit('/liquid/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=');
cy.waitForSkeletonGone();
cy.get('#table-tx-vin tr').should('have.class', '');
cy.get('#table-tx-vout tr').should('have.class', '');
});
it('show invalid unblinded TX hex', () => {
cy.visit('/liquid/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=123');
cy.waitForSkeletonGone();
cy.get('#table-tx-vin tr').should('have.class', '');
cy.get('#table-tx-vout tr').should('have.class', '');
cy.get('.error-unblinded' ).contains('Error: Invalid blinding data (invalid hex)');
});
it('show first unblinded vout', () => {
cy.visit('/liquid/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc');
cy.waitForSkeletonGone();
cy.get('#table-tx-vout tr:first-child()').should('have.class', 'assetBox');
});
it('show second unblinded vout', () => {
cy.visit('/liquid/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3a');
cy.get('#table-tx-vout tr').should('have.class', 'assetBox');
});
it('show invalid error unblinded TX', () => {
cy.visit('/liquid/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc,2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3c');
cy.waitForSkeletonGone();
cy.get('#table-tx-vout tr').should('have.class', 'assetBox');
cy.get('.error-unblinded' ).contains('Error: Invalid blinding data.');
});
it('shows asset peg in/out and burn transactions', () => {
cy.visit('/liquid/asset/6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d');
cy.waitForSkeletonGone();
cy.get('#table-tx-vout tr').not('.assetBox');
cy.get('#table-tx-vin tr').not('.assetBox');
});
it('prevents regressing issue #644', () => {
cy.visit('/liquid/tx/393b890966f305e7c440fcfb12a13f51a7a9011cc59ff5f14f6f93214261bd82');
cy.waitForSkeletonGone();
});
});
} else {
it.skip("Tests cannot be run on the selected BASE_MODULE");
}
});

View File

@@ -15,184 +15,309 @@ describe('Mainnet', () => {
});
});
it('loads the status screen', () => {
cy.visit('/status');
cy.get('#mempool-block-0').should('be.visible');
cy.get('[id^="bitcoin-block-"]').should('have.length', 8);
cy.get('.footer').should('be.visible');
cy.get('.row > :nth-child(1)').invoke('text').then((text) => {
expect(text).to.match(/Tx vBytes per second:.* vB\/s/);
});
cy.get('.row > :nth-child(2)').invoke('text').then((text) => {
expect(text).to.match(/Unconfirmed:(.*)/);
});
cy.get('.row > :nth-child(3)').invoke('text').then((text) => {
expect(text).to.match(/Mempool size:(.*) (kB|MB) \((\d+) (block|blocks)\)/);
});
});
if (Cypress.env("BASE_MODULE") === '' || Cypress.env("BASE_MODULE") === 'mempool') {
it('loads dashboard, drop websocket and reconnect', () => {
cy.viewport('macbook-16');
cy.mockMempoolSocket();
cy.visit('/');
cy.get('.badge').should('not.exist');
dropWebSocket();
cy.get('.badge').should('be.visible');
cy.get('.badge', {timeout: 25000}).should('not.exist');
emitMempoolInfo({
'params': {
loaded: true
}
});
cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist');
cy.get(':nth-child(2) > #bitcoin-block-0').should('not.exist');
cy.get(':nth-child(3) > #bitcoin-block-0').should('not.exist');
});
it('loads the dashboard', () => {
cy.visit('/');
cy.waitForSkeletonGone();
});
it('loads skeleton when changes between networks', () => {
cy.visit('/');
cy.waitForSkeletonGone();
cy.changeNetwork("testnet");
cy.changeNetwork("signet");
cy.changeNetwork("liquid");
cy.changeNetwork("mainnet");
cy.changeNetwork("bisq");
});
it('loads the dashboard with the skeleton blocks', () => {
cy.mockMempoolSocket();
cy.visit("/");
cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible');
cy.get(':nth-child(2) > #bitcoin-block-0').should('be.visible');
cy.get(':nth-child(3) > #bitcoin-block-0').should('be.visible');
cy.get('#mempool-block-0').should('be.visible');
cy.get('#mempool-block-1').should('be.visible');
cy.get('#mempool-block-2').should('be.visible');
emitMempoolInfo({
'params': {
loaded: true
}
});
cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist');
cy.get(':nth-child(2) > #bitcoin-block-0').should('not.exist');
cy.get(':nth-child(3) > #bitcoin-block-0').should('not.exist');
});
it('loads the blocks screen', () => {
cy.visit('/');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(2) > a').click().then(() => {
cy.waitForPageIdle();
});
});
it('loads the graphs screen', () => {
cy.visit('/');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(3) > a').click().then(() => {
cy.wait(1000);
});
});
it('loads the tv screen - desktop', () => {
cy.viewport('macbook-16');
cy.visit('/');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(4) > a').click().then(() => {
cy.viewport('macbook-16');
cy.get('.chart-holder');
cy.get('.blockchain-wrapper').should('be.visible');
it('loads the status screen', () => {
cy.visit('/status');
cy.get('#mempool-block-0').should('be.visible');
});
});
it('loads the tv screen - mobile', () => {
cy.viewport('iphone-6');
cy.visit('/tv');
cy.waitForSkeletonGone();
cy.get('.chart-holder');
cy.get('.blockchain-wrapper').should('be.visible');
});
it('loads the api screen', () => {
cy.visit('/');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(5) > a').click().then(() => {
cy.wait(1000);
});
});
describe('blocks', () => {
it('shows empty blocks properly', () => {
cy.visit('/block/0000000000000000000bd14f744ef2e006e61c32214670de7eb891a5732ee775');
cy.waitForSkeletonGone();
cy.waitForPageIdle();
cy.get('h2').invoke('text').should('equal', '1 transaction');
});
it('expands and collapses the block details', () => {
cy.visit('/block/0');
cy.waitForSkeletonGone();
cy.waitForPageIdle();
cy.get('.btn.btn-outline-info').click().then(() => {
cy.get('#details').should('be.visible');
cy.get('[id^="bitcoin-block-"]').should('have.length', 8);
cy.get('.footer').should('be.visible');
cy.get('.row > :nth-child(1)').invoke('text').then((text) => {
expect(text).to.match(/Tx vBytes per second:.* vB\/s/);
});
cy.get('.btn.btn-outline-info').click().then(() => {
cy.get('#details').should('not.be.visible');
cy.get('.row > :nth-child(2)').invoke('text').then((text) => {
expect(text).to.match(/Unconfirmed:(.*)/);
});
cy.get('.row > :nth-child(3)').invoke('text').then((text) => {
expect(text).to.match(/Mempool size:(.*) (kB|MB) \((\d+) (block|blocks)\)/);
});
});
it('shows blocks with no pagination', () => {
cy.visit('/block/00000000000000000001ba40caf1ad4cec0ceb77692662315c151953bfd7c4c4');
cy.waitForSkeletonGone();
cy.waitForPageIdle();
cy.get('.block-tx-title h2').invoke('text').should('equal', '19 transactions');
cy.get('.pagination-container ul.pagination').first().children().should('have.length', 5);
it('loads dashboard, drop websocket and reconnect', () => {
cy.viewport('macbook-16');
cy.mockMempoolSocket();
cy.visit('/');
cy.get('.badge').should('not.exist');
dropWebSocket();
cy.get('.badge').should('be.visible');
cy.get('.badge', {timeout: 25000}).should('not.exist');
emitMempoolInfo({
'params': {
loaded: true
}
});
cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist');
cy.get(':nth-child(2) > #bitcoin-block-0').should('not.exist');
cy.get(':nth-child(3) > #bitcoin-block-0').should('not.exist');
});
it('supports pagination on the block screen', () => {
// 41 txs
cy.visit('/block/00000000000000000009f9b7b0f63ad50053ad12ec3b7f5ca951332f134f83d8');
it('loads the dashboard', () => {
cy.visit('/');
cy.waitForSkeletonGone();
cy.get('.pagination-container a').invoke('text').then((text1) => {
cy.get('.active + li').first().click().then(() => {
});
describe('blocks navigation', () => {
describe('keyboard events', () => {
it('loads first blockchain blocks visible and keypress arrow right', () => {
cy.viewport('macbook-16');
cy.visit('/');
cy.waitForSkeletonGone();
cy.get('.blockchain-blocks-0 > a').click().then(() => {
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('not.exist');
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
cy.waitForPageIdle();
cy.document().right();
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
});
});
it('loads first blockchain blocks visible and keypress arrow left', () => {
cy.viewport('macbook-16');
cy.visit('/');
cy.waitForSkeletonGone();
cy.get('.blockchain-blocks-0 > a').click().then(() => {
cy.waitForPageIdle();
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('not.exist');
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
cy.document().left();
cy.get('.title-block h1').invoke('text').should('equal', 'Next block');
});
});
it('loads last blockchain blocks and keypress arrow right', () => {
cy.viewport('macbook-16');
cy.visit('/');
cy.waitForSkeletonGone();
cy.get('.blockchain-blocks-4 > a').click().then(() => {
cy.waitForPageIdle();
// block 6
cy.document().right();
cy.wait(5000);
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
// block 7
cy.document().right();
cy.wait(5000);
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
// block 8 - last visible block
cy.document().right();
cy.wait(5000);
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
// block 9 - not visible at the blochchain blocks visible block
cy.document().right();
cy.wait(5000);
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
});
});
it('loads genesis block and keypress arrow right', () => {
cy.viewport('macbook-16');
cy.visit('/block/0');
cy.waitForSkeletonGone();
cy.waitForPageIdle();
cy.get('.header-bg.box > a').invoke('text').then((text2) => {
expect(text1).not.to.eq(text2);
cy.document().right();
cy.wait(5000);
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('not.exist');
});
it('loads genesis block and keypress arrow left', () => {
cy.viewport('macbook-16');
cy.visit('/block/0');
cy.waitForSkeletonGone();
cy.waitForPageIdle();
cy.document().left();
cy.wait(5000);
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
});
});
describe('mouse events', () => {
it('loads first blockchain blocks visible and click on the arrow right', () => {
cy.viewport('macbook-16');
cy.visit('/');
cy.waitForSkeletonGone();
cy.get('.blockchain-blocks-0 > a').click().then(() => {
cy.waitForPageIdle();
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('not.exist');
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').click().then(() => {
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
});
});
});
it('loads genesis block and click on the arrow left', () => {
cy.viewport('macbook-16');
cy.visit('/block/0');
cy.waitForSkeletonGone();
cy.waitForPageIdle();
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('not.exist');
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').click().then(() => {
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
});
});
});
});
it('shows blocks pagination with 5 pages (desktop)', () => {
cy.viewport(760, 800);
cy.visit('/block/000000000000000000049281946d26fcba7d99fdabc1feac524bc3a7003d69b3').then(() => {
cy.waitForSkeletonGone();
cy.waitForPageIdle();
});
// 5 pages + 4 buttons = 9 buttons
cy.get('.pagination-container ul.pagination').first().children().should('have.length', 9);
it('loads skeleton when changes between networks', () => {
cy.visit('/');
cy.waitForSkeletonGone();
cy.changeNetwork("testnet");
cy.changeNetwork("signet");
cy.changeNetwork("liquid");
cy.changeNetwork("mainnet");
cy.changeNetwork("bisq");
});
it('shows blocks pagination with 3 pages (mobile)', () => {
cy.viewport(669, 800);
cy.visit('/block/000000000000000000049281946d26fcba7d99fdabc1feac524bc3a7003d69b3').then(() => {
cy.waitForSkeletonGone();
it('loads the dashboard with the skeleton blocks', () => {
cy.mockMempoolSocket();
cy.visit("/");
cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible');
cy.get(':nth-child(2) > #bitcoin-block-0').should('be.visible');
cy.get(':nth-child(3) > #bitcoin-block-0').should('be.visible');
cy.get('#mempool-block-0').should('be.visible');
cy.get('#mempool-block-1').should('be.visible');
cy.get('#mempool-block-2').should('be.visible');
emitMempoolInfo({
'params': {
loaded: true
}
});
cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist');
cy.get(':nth-child(2) > #bitcoin-block-0').should('not.exist');
cy.get(':nth-child(3) > #bitcoin-block-0').should('not.exist');
});
it('loads the blocks screen', () => {
cy.visit('/');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(2) > a').click().then(() => {
cy.waitForPageIdle();
});
// 3 pages + 4 buttons = 7 buttons
cy.get('.pagination-container ul.pagination').first().children().should('have.length', 7);
});
});
it('loads the graphs screen', () => {
cy.visit('/');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(3) > a').click().then(() => {
cy.wait(1000);
});
});
it('loads the tv screen - desktop', () => {
cy.viewport('macbook-16');
cy.visit('/');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(4) > a').click().then(() => {
cy.viewport('macbook-16');
cy.get('.chart-holder');
cy.get('.blockchain-wrapper').should('be.visible');
cy.get('#mempool-block-0').should('be.visible');
});
});
it('loads the tv screen - mobile', () => {
cy.viewport('iphone-6');
cy.visit('/tv');
cy.waitForSkeletonGone();
cy.get('.chart-holder');
cy.get('.blockchain-wrapper').should('be.visible');
});
it('loads the api screen', () => {
cy.visit('/');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(5) > a').click().then(() => {
cy.wait(1000);
});
});
describe('blocks', () => {
it('shows empty blocks properly', () => {
cy.visit('/block/0000000000000000000bd14f744ef2e006e61c32214670de7eb891a5732ee775');
cy.waitForSkeletonGone();
cy.waitForPageIdle();
cy.get('h2').invoke('text').should('equal', '1 transaction');
});
it('expands and collapses the block details', () => {
cy.visit('/block/0');
cy.waitForSkeletonGone();
cy.waitForPageIdle();
cy.get('.btn.btn-outline-info').click().then(() => {
cy.get('#details').should('be.visible');
});
cy.get('.btn.btn-outline-info').click().then(() => {
cy.get('#details').should('not.be.visible');
});
});
it('shows blocks with no pagination', () => {
cy.visit('/block/00000000000000000001ba40caf1ad4cec0ceb77692662315c151953bfd7c4c4');
cy.waitForSkeletonGone();
cy.waitForPageIdle();
cy.get('.block-tx-title h2').invoke('text').should('equal', '19 transactions');
cy.get('.pagination-container ul.pagination').first().children().should('have.length', 5);
});
it('supports pagination on the block screen', () => {
// 41 txs
cy.visit('/block/00000000000000000009f9b7b0f63ad50053ad12ec3b7f5ca951332f134f83d8');
cy.waitForSkeletonGone();
cy.get('.pagination-container a').invoke('text').then((text1) => {
cy.get('.active + li').first().click().then(() => {
cy.waitForSkeletonGone();
cy.waitForPageIdle();
cy.get('.header-bg.box > a').invoke('text').then((text2) => {
expect(text1).not.to.eq(text2);
});
});
});
});
it('shows blocks pagination with 5 pages (desktop)', () => {
cy.viewport(760, 800);
cy.visit('/block/000000000000000000049281946d26fcba7d99fdabc1feac524bc3a7003d69b3').then(() => {
cy.waitForSkeletonGone();
cy.waitForPageIdle();
});
// 5 pages + 4 buttons = 9 buttons
cy.get('.pagination-container ul.pagination').first().children().should('have.length', 9);
});
it('shows blocks pagination with 3 pages (mobile)', () => {
cy.viewport(669, 800);
cy.visit('/block/000000000000000000049281946d26fcba7d99fdabc1feac524bc3a7003d69b3').then(() => {
cy.waitForSkeletonGone();
cy.waitForPageIdle();
});
// 3 pages + 4 buttons = 7 buttons
cy.get('.pagination-container ul.pagination').first().children().should('have.length', 7);
});
});
} else {
it.skip("Tests cannot be run on the selected BASE_MODULE");
}
});

View File

@@ -8,119 +8,124 @@ describe('Signet', () => {
cy.intercept('/api/tx/*/outspends').as('tx-outspends');
});
it('loads the dashboard', () => {
cy.visit('/signet');
cy.waitForSkeletonGone();
});
it('loads the dashboard with the skeleton blocks', () => {
cy.mockMempoolSocket();
cy.visit("/signet");
cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible');
cy.get(':nth-child(2) > #bitcoin-block-0').should('be.visible');
cy.get(':nth-child(3) > #bitcoin-block-0').should('be.visible');
cy.get('#mempool-block-0').should('be.visible');
cy.get('#mempool-block-1').should('be.visible');
cy.get('#mempool-block-2').should('be.visible');
emitMempoolInfo({
'params': {
"network": "signet"
}
if (Cypress.env("BASE_MODULE") === '' || Cypress.env("BASE_MODULE") === 'mempool') {
it('loads the dashboard', () => {
cy.visit('/signet');
cy.waitForSkeletonGone();
});
cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist');
cy.get(':nth-child(2) > #bitcoin-block-0').should('not.exist');
cy.get(':nth-child(3) > #bitcoin-block-0').should('not.exist');
});
it('loads the blocks screen', () => {
cy.visit('/signet');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(2) > a').click().then(() => {
cy.wait(1000);
});
});
it('loads the graphs screen', () => {
cy.visit('/signet');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(3) > a').click().then(() => {
cy.wait(1000);
});
});
describe('tv mode', () => {
it('loads the tv screen - desktop', () => {
cy.viewport('macbook-16');
cy.visit('/signet');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(4) > a').click().then(() => {
cy.get('.chart-holder').should('be.visible');
cy.get('#mempool-block-0').should('be.visible');
cy.get('.tv-only').should('not.exist');
});
});
it('loads the tv screen - mobile', () => {
cy.visit('/signet');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(4) > a').click().then(() => {
cy.viewport('iphone-8');
cy.get('.chart-holder').should('be.visible');
//TODO: Remove comment when the bug is fixed
//cy.get('#mempool-block-0').should('be.visible');
cy.get('.tv-only').should('not.exist');
});
});
});
it('loads the api screen', () => {
cy.visit('/signet');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(5) > a').click().then(() => {
cy.wait(1000);
});
});
describe('blocks', () => {
it('shows empty blocks properly', () => {
cy.visit('/signet/block/00000133d54e4589f6436703b067ec23209e0a21b8a9b12f57d0592fd85f7a42');
cy.waitForSkeletonGone();
cy.get('h2').invoke('text').should('equal', '1 transaction');
});
it('expands and collapses the block details', () => {
cy.visit('/signet/block/0');
cy.waitForSkeletonGone();
cy.get('.btn.btn-outline-info').click().then(() => {
cy.get('#details').should('be.visible');
});
cy.get('.btn.btn-outline-info').click().then(() => {
cy.get('#details').should('not.be.visible');
});
});
it('shows blocks with no pagination', () => {
cy.visit('/signet/block/00000078f920a96a69089877b934ce7fd009ab55e3170920a021262cb258e7cc');
cy.waitForSkeletonGone();
cy.get('h2').invoke('text').should('equal', '13 transactions');
cy.get('ul.pagination').first().children().should('have.length', 5);
});
it('supports pagination on the block screen', () => {
// 43 txs
cy.visit('/signet/block/00000094bd52f73bdbfc4bece3a94c21fec2dc968cd54210496e69e4059d66a6');
cy.waitForSkeletonGone();
cy.get('.header-bg.box > a').invoke('text').then((text1) => {
cy.get('.active + li').first().click().then(() => {
cy.get('.header-bg.box > a').invoke('text').then((text2) => {
expect(text1).not.to.eq(text2);
});
});
});
});
});
it('loads the dashboard with the skeleton blocks', () => {
cy.mockMempoolSocket();
cy.visit("/signet");
cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible');
cy.get(':nth-child(2) > #bitcoin-block-0').should('be.visible');
cy.get(':nth-child(3) > #bitcoin-block-0').should('be.visible');
cy.get('#mempool-block-0').should('be.visible');
cy.get('#mempool-block-1').should('be.visible');
cy.get('#mempool-block-2').should('be.visible');
emitMempoolInfo({
'params': {
"network": "signet"
}
});
cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist');
cy.get(':nth-child(2) > #bitcoin-block-0').should('not.exist');
cy.get(':nth-child(3) > #bitcoin-block-0').should('not.exist');
});
it('loads the blocks screen', () => {
cy.visit('/signet');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(2) > a').click().then(() => {
cy.wait(1000);
});
});
it('loads the graphs screen', () => {
cy.visit('/signet');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(3) > a').click().then(() => {
cy.wait(1000);
});
});
describe('tv mode', () => {
it('loads the tv screen - desktop', () => {
cy.viewport('macbook-16');
cy.visit('/signet');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(4) > a').click().then(() => {
cy.get('.chart-holder').should('be.visible');
cy.get('#mempool-block-0').should('be.visible');
cy.get('.tv-only').should('not.exist');
});
});
it('loads the tv screen - mobile', () => {
cy.visit('/signet');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(4) > a').click().then(() => {
cy.viewport('iphone-8');
cy.get('.chart-holder').should('be.visible');
//TODO: Remove comment when the bug is fixed
//cy.get('#mempool-block-0').should('be.visible');
cy.get('.tv-only').should('not.exist');
});
});
});
it('loads the api screen', () => {
cy.visit('/signet');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(5) > a').click().then(() => {
cy.wait(1000);
});
});
describe('blocks', () => {
it('shows empty blocks properly', () => {
cy.visit('/signet/block/00000133d54e4589f6436703b067ec23209e0a21b8a9b12f57d0592fd85f7a42');
cy.waitForSkeletonGone();
cy.get('h2').invoke('text').should('equal', '1 transaction');
});
it('expands and collapses the block details', () => {
cy.visit('/signet/block/0');
cy.waitForSkeletonGone();
cy.get('.btn.btn-outline-info').click().then(() => {
cy.get('#details').should('be.visible');
});
cy.get('.btn.btn-outline-info').click().then(() => {
cy.get('#details').should('not.be.visible');
});
});
it('shows blocks with no pagination', () => {
cy.visit('/signet/block/00000078f920a96a69089877b934ce7fd009ab55e3170920a021262cb258e7cc');
cy.waitForSkeletonGone();
cy.get('h2').invoke('text').should('equal', '13 transactions');
cy.get('ul.pagination').first().children().should('have.length', 5);
});
it('supports pagination on the block screen', () => {
// 43 txs
cy.visit('/signet/block/00000094bd52f73bdbfc4bece3a94c21fec2dc968cd54210496e69e4059d66a6');
cy.waitForSkeletonGone();
cy.get('.header-bg.box > a').invoke('text').then((text1) => {
cy.get('.active + li').first().click().then(() => {
cy.get('.header-bg.box > a').invoke('text').then((text2) => {
expect(text1).not.to.eq(text2);
});
});
});
});
});
} else {
it.skip("Tests cannot be run on the selected BASE_MODULE");
}
});

View File

@@ -1,4 +1,4 @@
import { emitMempoolInfo } from "../../support/websocket";
import { confirmAddress, emitMempoolInfo, sendWsMock, showNewTx, startTrackingAddress } from "../../support/websocket";
describe('Testnet', () => {
beforeEach(() => {
@@ -8,115 +8,121 @@ describe('Testnet', () => {
cy.intercept('/api/tx/*/outspends').as('tx-outspends');
});
it('loads the dashboard', () => {
cy.visit('/testnet');
cy.waitForSkeletonGone();
});
if (Cypress.env("BASE_MODULE") === '' || Cypress.env("BASE_MODULE") === 'mempool') {
it('loads the dashboard with the skeleton blocks', () => {
cy.mockMempoolSocket();
cy.visit("/signet");
cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible');
cy.get(':nth-child(2) > #bitcoin-block-0').should('be.visible');
cy.get(':nth-child(3) > #bitcoin-block-0').should('be.visible');
cy.get('#mempool-block-0').should('be.visible');
cy.get('#mempool-block-1').should('be.visible');
cy.get('#mempool-block-2').should('be.visible');
emitMempoolInfo({
'params': {
loaded: true
}
it('loads the dashboard', () => {
cy.visit('/testnet');
cy.waitForSkeletonGone();
});
cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist');
cy.get(':nth-child(2) > #bitcoin-block-0').should('not.exist');
cy.get(':nth-child(3) > #bitcoin-block-0').should('not.exist');
});
it('loads the blocks screen', () => {
cy.visit('/testnet');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(2) > a').click().then(() => {
cy.wait(1000);
});
});
it('loads the dashboard with the skeleton blocks', () => {
cy.mockMempoolSocket();
cy.visit("/testnet");
cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible');
cy.get(':nth-child(2) > #bitcoin-block-0').should('be.visible');
cy.get(':nth-child(3) > #bitcoin-block-0').should('be.visible');
cy.get('#mempool-block-0').should('be.visible');
cy.get('#mempool-block-1').should('be.visible');
cy.get('#mempool-block-2').should('be.visible');
it('loads the graphs screen', () => {
cy.visit('/testnet');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(3) > a').click().then(() => {
cy.wait(1000);
});
});
emitMempoolInfo({
'params': {
loaded: true
}
});
describe('tv mode', () => {
it('loads the tv screen - desktop', () => {
cy.viewport('macbook-16');
cy.visit('/testnet');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(4) > a').click().then(() => {
cy.wait(1000);
cy.get('.tv-only').should('not.exist');
});
});
it('loads the tv screen - mobile', () => {
cy.visit('/testnet');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(4) > a').click().then(() => {
cy.viewport('iphone-6');
cy.wait(1000);
cy.get('.tv-only').should('not.exist');
});
});
});
it('loads the api screen', () => {
cy.visit('/testnet');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(5) > a').click().then(() => {
cy.wait(1000);
});
});
describe('blocks', () => {
it('shows empty blocks properly', () => {
cy.visit('/testnet/block/0');
cy.waitForSkeletonGone();
cy.get('h2').invoke('text').should('equal', '1 transaction');
});
it('expands and collapses the block details', () => {
cy.visit('/testnet/block/0');
cy.waitForSkeletonGone();
cy.get('.btn.btn-outline-info').click().then(() => {
cy.get('#details').should('be.visible');
});
cy.get('.btn.btn-outline-info').click().then(() => {
cy.get('#details').should('not.be.visible');
});
});
it('shows blocks with no pagination', () => {
cy.visit('/testnet/block/000000000000002f8ce27716e74ecc7ad9f7b5101fed12d09e28bb721b9460ea');
cy.waitForSkeletonGone();
cy.get('h2').invoke('text').should('equal', '11 transactions');
cy.get('ul.pagination').first().children().should('have.length', 5);
});
it('supports pagination on the block screen', () => {
// 48 txs
cy.visit('/testnet/block/000000000000002ca3878ebd98b313a1c2d531f2e70a6575d232ca7564dea7a9');
cy.waitForSkeletonGone();
cy.get('.header-bg.box > a').invoke('text').then((text1) => {
cy.get('.active + li').first().click().then(() => {
cy.get('.header-bg.box > a').invoke('text').then((text2) => {
expect(text1).not.to.eq(text2);
});
});
});
});
cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist');
cy.get(':nth-child(2) > #bitcoin-block-0').should('not.exist');
cy.get(':nth-child(3) > #bitcoin-block-0').should('not.exist');
});
it('loads the blocks screen', () => {
cy.visit('/testnet');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(2) > a').click().then(() => {
cy.wait(1000);
});
});
it('loads the graphs screen', () => {
cy.visit('/testnet');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(3) > a').click().then(() => {
cy.wait(1000);
});
});
describe('tv mode', () => {
it('loads the tv screen - desktop', () => {
cy.viewport('macbook-16');
cy.visit('/testnet');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(4) > a').click().then(() => {
cy.wait(1000);
cy.get('.tv-only').should('not.exist');
});
});
it('loads the tv screen - mobile', () => {
cy.visit('/testnet');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(4) > a').click().then(() => {
cy.viewport('iphone-6');
cy.wait(1000);
cy.get('.tv-only').should('not.exist');
});
});
});
it('loads the api screen', () => {
cy.visit('/testnet');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(5) > a').click().then(() => {
cy.wait(1000);
});
});
describe('blocks', () => {
it('shows empty blocks properly', () => {
cy.visit('/testnet/block/0');
cy.waitForSkeletonGone();
cy.get('h2').invoke('text').should('equal', '1 transaction');
});
it('expands and collapses the block details', () => {
cy.visit('/testnet/block/0');
cy.waitForSkeletonGone();
cy.get('.btn.btn-outline-info').click().then(() => {
cy.get('#details').should('be.visible');
});
cy.get('.btn.btn-outline-info').click().then(() => {
cy.get('#details').should('not.be.visible');
});
});
it('shows blocks with no pagination', () => {
cy.visit('/testnet/block/000000000000002f8ce27716e74ecc7ad9f7b5101fed12d09e28bb721b9460ea');
cy.waitForSkeletonGone();
cy.get('h2').invoke('text').should('equal', '11 transactions');
cy.get('ul.pagination').first().children().should('have.length', 5);
});
it('supports pagination on the block screen', () => {
// 48 txs
cy.visit('/testnet/block/000000000000002ca3878ebd98b313a1c2d531f2e70a6575d232ca7564dea7a9');
cy.waitForSkeletonGone();
cy.get('.header-bg.box > a').invoke('text').then((text1) => {
cy.get('.active + li').first().click().then(() => {
cy.get('.header-bg.box > a').invoke('text').then((text2) => {
expect(text1).not.to.eq(text2);
});
});
});
});
});
} else {
it.skip("Tests cannot be run on the selected BASE_MODULE");
}
});

View File

@@ -42,10 +42,20 @@
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
'use strict'
import 'cypress-wait-until';
import { PageIdleDetector } from './PageIdleDetector';
import { mockWebSocket } from './websocket';
/* global Cypress */
const codes = {
ArrowLeft: 37,
ArrowUp: 38,
ArrowRight: 39,
ArrowDown: 40
}
Cypress.Commands.add('waitForSkeletonGone', () => {
cy.waitUntil(() => {
return Cypress.$('.skeleton-loader').length === 0;
@@ -75,3 +85,63 @@ Cypress.Commands.add('changeNetwork', (network: "testnet"|"signet"|"liquid"|"bis
});
});
});
// https://github.com/bahmutov/cypress-arrows/blob/8f0303842a343550fbeaf01528d01d1ff213b70c/src/index.js
function keydownCommand ($el, key) {
const message = `sending the "${key}" keydown event`
const log = Cypress.log({
name: `keydown: ${key}`,
message: message,
consoleProps: function () {
return {
Subject: $el
}
}
})
const e = $el.createEvent('KeyboardEvent')
Object.defineProperty(e, 'key', {
get: function () {
return key
}
})
Object.defineProperty(e, 'keyCode', {
get: function () {
return this.keyCodeVal
}
})
Object.defineProperty(e, 'which', {
get: function () {
return this.keyCodeVal
}
})
var metaKey = false
Object.defineProperty(e, 'metaKey', {
get: function () {
return metaKey
}
})
Object.defineProperty(e, 'shiftKey', {
get: function () {
return false
}
})
e.keyCodeVal = codes[key]
e.initKeyboardEvent('keydown', true, true,
$el.defaultView, false, false, false, false, e.keyCodeVal, e.keyCodeVal)
$el.dispatchEvent(e)
log.snapshot().end()
return $el
}
Cypress.Commands.add('keydown', { prevSubject: "dom" }, keydownCommand)
Cypress.Commands.add('left', { prevSubject: "dom" }, $el => keydownCommand($el, 'ArrowLeft'))
Cypress.Commands.add('right', { prevSubject: "dom" }, $el => keydownCommand($el, 'ArrowRight'))
Cypress.Commands.add('up', { prevSubject: "dom" }, $el => keydownCommand($el, 'ArrowUp'))
Cypress.Commands.add('down', { prevSubject: "dom" }, $el => keydownCommand($el, 'ArrowDown'))

View File

@@ -21,6 +21,16 @@ try {
}
}
const indexFilePath = configContent.BASE_MODULE ? 'src/index.' + configContent.BASE_MODULE + '.html' : 'src/index.mempool.html';
try {
fs.copyFileSync(indexFilePath, 'src/index.html');
console.log('Copied ' + indexFilePath + ' to src/index.html');
} catch (e) {
console.log('Error copying the index file');
throw new Error(e);
}
try {
const packageJson = fs.readFileSync('package.json');
packetJsonVersion = JSON.parse(packageJson).version;

View File

@@ -8,5 +8,8 @@
"KEEP_BLOCKS_AMOUNT": 8,
"NGINX_PROTOCOL": "http",
"NGINX_HOSTNAME": "127.0.0.1",
"NGINX_PORT": "80"
"NGINX_PORT": "80",
"MEMPOOL_BLOCKS_AMOUNT": 8,
"BLOCK_WEIGHT_UNITS": 4000000,
"BASE_MODULE": "mempool"
}

View File

@@ -1,12 +1,12 @@
{
"name": "mempool-frontend",
"version": "2.2.1",
"version": "2.2.2-dev",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "mempool-frontend",
"version": "2.2.1",
"version": "2.2.2-dev",
"license": "GNU Affero General Public License v3.0",
"dependencies": {
"@angular/animations": "~11.2.8",
@@ -192,40 +192,6 @@
"node": ">= 10.13.0",
"npm": "^6.11.0 || ^7.5.6",
"yarn": ">= 1.13.0"
},
"peerDependencies": {
"@angular/compiler-cli": "^11.0.0 || ^11.2.0-next",
"@angular/localize": "^11.0.0 || ^11.2.0-next",
"@angular/service-worker": "^11.0.0 || ^11.2.0-next",
"karma": "^5.2.0 || ^6.0.0",
"ng-packagr": "^11.0.0 || ^11.2.0-next",
"protractor": "^7.0.0",
"tailwindcss": "^2.0.0",
"tslint": "^6.1.0",
"typescript": "~4.0.0 || ~4.1.0"
},
"peerDependenciesMeta": {
"@angular/localize": {
"optional": true
},
"@angular/service-worker": {
"optional": true
},
"karma": {
"optional": true
},
"ng-packagr": {
"optional": true
},
"protractor": {
"optional": true
},
"tailwindcss": {
"optional": true
},
"tslint": {
"optional": true
}
}
},
"node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/architect": {
@@ -360,10 +326,6 @@
"node": ">= 10.13.0",
"npm": "^6.11.0 || ^7.5.6",
"yarn": ">= 1.13.0"
},
"peerDependencies": {
"webpack": "^4.6.0",
"webpack-dev-server": "^3.1.4"
}
},
"node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/architect": {
@@ -2339,11 +2301,6 @@
"node": ">= 10.13.0",
"npm": "^6.11.0 || ^7.5.6",
"yarn": ">= 1.13.0"
},
"peerDependencies": {
"@angular/compiler-cli": "^11.0.0 || ^11.2.0-next",
"typescript": "~4.0.0 || ~4.1.0",
"webpack": "^4.0.0"
}
},
"node_modules/@ngtools/webpack/node_modules/@angular-devkit/core": {
@@ -5066,9 +5023,6 @@
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cli-width": {
@@ -5871,9 +5825,6 @@
},
"engines": {
"node": ">= 10"
},
"peerDependencies": {
"postcss": "^8.0.9"
}
},
"node_modules/css-loader": {
@@ -5933,9 +5884,6 @@
"domhandler": "^4.2.0",
"domutils": "^2.6.0",
"nth-check": "^2.0.0"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/css-selector-tokenizer": {
@@ -5977,9 +5925,6 @@
"dev": true,
"engines": {
"node": ">= 6"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/css/node_modules/source-map": {
@@ -6024,13 +5969,6 @@
},
"engines": {
"node": "^10 || ^12 || >=14.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/cssnano"
},
"peerDependencies": {
"postcss": "^8.2.1"
}
},
"node_modules/cssnano-preset-default": {
@@ -6071,9 +6009,6 @@
},
"engines": {
"node": "^10 || ^12 || >=14.0"
},
"peerDependencies": {
"postcss": "^8.2.15"
}
},
"node_modules/cssnano-utils": {
@@ -6083,9 +6018,6 @@
"dev": true,
"engines": {
"node": "^10 || ^12 || >=14.0"
},
"peerDependencies": {
"postcss": "^8.2.15"
}
},
"node_modules/csso": {
@@ -6140,7 +6072,6 @@
"version": "7.7.0",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-7.7.0.tgz",
"integrity": "sha512-uYBYXNoI5ym0UxROwhQXWTi8JbUEjpC6l/bzoGZNxoKGsLrC1SDPgIDJMgLX/MeEdPL0UInXLDUWN/rSyZUCjQ==",
"hasInstallScript": true,
"optional": true,
"dependencies": {
"@cypress/request": "^2.88.5",
@@ -6847,9 +6778,6 @@
"domelementtype": "^2.0.1",
"domhandler": "^4.2.0",
"entities": "^2.0.0"
},
"funding": {
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
}
},
"node_modules/domain-browser": {
@@ -6865,13 +6793,7 @@
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz",
"integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
]
"dev": true
},
"node_modules/domexception": {
"version": "2.0.1",
@@ -6904,9 +6826,6 @@
},
"engines": {
"node": ">= 4"
},
"funding": {
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
"node_modules/domino": {
@@ -6923,9 +6842,6 @@
"dom-serializer": "^1.0.1",
"domelementtype": "^2.2.0",
"domhandler": "^4.2.0"
},
"funding": {
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
"node_modules/duplexer": {
@@ -7187,10 +7103,7 @@
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
"integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
"dev": true,
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
"dev": true
},
"node_modules/env-paths": {
"version": "2.2.1",
@@ -10683,9 +10596,6 @@
},
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"enquirer": ">= 2.3.0 < 3"
}
},
"node_modules/listr2/node_modules/ansi-styles": {
@@ -10698,9 +10608,6 @@
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/listr2/node_modules/color-convert": {
@@ -10733,9 +10640,6 @@
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/loader-runner": {
@@ -10922,9 +10826,6 @@
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/log-update/node_modules/ansi-styles": {
@@ -10937,9 +10838,6 @@
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/log-update/node_modules/color-convert": {
@@ -10972,9 +10870,6 @@
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
}
},
"node_modules/log4js": {
@@ -12184,9 +12079,6 @@
"dev": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/npm-bundled": {
@@ -12309,9 +12201,6 @@
"dev": true,
"dependencies": {
"boolbase": "^1.0.0"
},
"funding": {
"url": "https://github.com/fb55/nth-check?sponsor=1"
}
},
"node_modules/number-is-nan": {
@@ -13193,10 +13082,6 @@
},
"engines": {
"node": "^10 || ^12 || >=14"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
}
},
"node_modules/postcss-calc": {
@@ -13207,9 +13092,6 @@
"dependencies": {
"postcss-selector-parser": "^6.0.2",
"postcss-value-parser": "^4.0.2"
},
"peerDependencies": {
"postcss": "^8.2.2"
}
},
"node_modules/postcss-colormin": {
@@ -13225,9 +13107,6 @@
},
"engines": {
"node": "^10 || ^12 || >=14.0"
},
"peerDependencies": {
"postcss": "^8.2.15"
}
},
"node_modules/postcss-convert-values": {
@@ -13240,9 +13119,6 @@
},
"engines": {
"node": "^10 || ^12 || >=14.0"
},
"peerDependencies": {
"postcss": "^8.2.15"
}
},
"node_modules/postcss-discard-comments": {
@@ -13252,9 +13128,6 @@
"dev": true,
"engines": {
"node": "^10 || ^12 || >=14.0"
},
"peerDependencies": {
"postcss": "^8.2.15"
}
},
"node_modules/postcss-discard-duplicates": {
@@ -13264,9 +13137,6 @@
"dev": true,
"engines": {
"node": "^10 || ^12 || >=14.0"
},
"peerDependencies": {
"postcss": "^8.2.15"
}
},
"node_modules/postcss-discard-empty": {
@@ -13276,9 +13146,6 @@
"dev": true,
"engines": {
"node": "^10 || ^12 || >=14.0"
},
"peerDependencies": {
"postcss": "^8.2.15"
}
},
"node_modules/postcss-discard-overridden": {
@@ -13288,9 +13155,6 @@
"dev": true,
"engines": {
"node": "^10 || ^12 || >=14.0"
},
"peerDependencies": {
"postcss": "^8.2.15"
}
},
"node_modules/postcss-import": {
@@ -13349,9 +13213,6 @@
},
"engines": {
"node": "^10 || ^12 || >=14.0"
},
"peerDependencies": {
"postcss": "^8.2.15"
}
},
"node_modules/postcss-merge-rules": {
@@ -13368,9 +13229,6 @@
},
"engines": {
"node": "^10 || ^12 || >=14.0"
},
"peerDependencies": {
"postcss": "^8.2.15"
}
},
"node_modules/postcss-minify-font-values": {
@@ -13383,9 +13241,6 @@
},
"engines": {
"node": "^10 || ^12 || >=14.0"
},
"peerDependencies": {
"postcss": "^8.2.15"
}
},
"node_modules/postcss-minify-gradients": {
@@ -13400,9 +13255,6 @@
},
"engines": {
"node": "^10 || ^12 || >=14.0"
},
"peerDependencies": {
"postcss": "^8.2.15"
}
},
"node_modules/postcss-minify-params": {
@@ -13419,9 +13271,6 @@
},
"engines": {
"node": "^10 || ^12 || >=14.0"
},
"peerDependencies": {
"postcss": "^8.2.15"
}
},
"node_modules/postcss-minify-selectors": {
@@ -13435,9 +13284,6 @@
},
"engines": {
"node": "^10 || ^12 || >=14.0"
},
"peerDependencies": {
"postcss": "^8.2.15"
}
},
"node_modules/postcss-modules-extract-imports": {
@@ -13494,9 +13340,6 @@
"dev": true,
"engines": {
"node": "^10 || ^12 || >=14.0"
},
"peerDependencies": {
"postcss": "^8.2.15"
}
},
"node_modules/postcss-normalize-display-values": {
@@ -13510,9 +13353,6 @@
},
"engines": {
"node": "^10 || ^12 || >=14.0"
},
"peerDependencies": {
"postcss": "^8.2.15"
}
},
"node_modules/postcss-normalize-positions": {
@@ -13525,9 +13365,6 @@
},
"engines": {
"node": "^10 || ^12 || >=14.0"
},
"peerDependencies": {
"postcss": "^8.2.15"
}
},
"node_modules/postcss-normalize-repeat-style": {
@@ -13541,9 +13378,6 @@
},
"engines": {
"node": "^10 || ^12 || >=14.0"
},
"peerDependencies": {
"postcss": "^8.2.15"
}
},
"node_modules/postcss-normalize-string": {
@@ -13556,9 +13390,6 @@
},
"engines": {
"node": "^10 || ^12 || >=14.0"
},
"peerDependencies": {
"postcss": "^8.2.15"
}
},
"node_modules/postcss-normalize-timing-functions": {
@@ -13572,9 +13403,6 @@
},
"engines": {
"node": "^10 || ^12 || >=14.0"
},
"peerDependencies": {
"postcss": "^8.2.15"
}
},
"node_modules/postcss-normalize-unicode": {
@@ -13588,9 +13416,6 @@
},
"engines": {
"node": "^10 || ^12 || >=14.0"
},
"peerDependencies": {
"postcss": "^8.2.15"
}
},
"node_modules/postcss-normalize-url": {
@@ -13605,9 +13430,6 @@
},
"engines": {
"node": "^10 || ^12 || >=14.0"
},
"peerDependencies": {
"postcss": "^8.2.15"
}
},
"node_modules/postcss-normalize-whitespace": {
@@ -13620,9 +13442,6 @@
},
"engines": {
"node": "^10 || ^12 || >=14.0"
},
"peerDependencies": {
"postcss": "^8.2.15"
}
},
"node_modules/postcss-ordered-values": {
@@ -13636,9 +13455,6 @@
},
"engines": {
"node": "^10 || ^12 || >=14.0"
},
"peerDependencies": {
"postcss": "^8.2.15"
}
},
"node_modules/postcss-reduce-initial": {
@@ -13652,9 +13468,6 @@
},
"engines": {
"node": "^10 || ^12 || >=14.0"
},
"peerDependencies": {
"postcss": "^8.2.15"
}
},
"node_modules/postcss-reduce-transforms": {
@@ -13668,9 +13481,6 @@
},
"engines": {
"node": "^10 || ^12 || >=14.0"
},
"peerDependencies": {
"postcss": "^8.2.15"
}
},
"node_modules/postcss-selector-parser": {
@@ -13697,9 +13507,6 @@
},
"engines": {
"node": "^10 || ^12 || >=14.0"
},
"peerDependencies": {
"postcss": "^8.2.15"
}
},
"node_modules/postcss-unique-selectors": {
@@ -13714,9 +13521,6 @@
},
"engines": {
"node": "^10 || ^12 || >=14.0"
},
"peerDependencies": {
"postcss": "^8.2.15"
}
},
"node_modules/postcss-value-parser": {
@@ -14624,18 +14428,6 @@
},
"engines": {
"node": ">=8.9"
},
"peerDependencies": {
"rework": "1.0.1",
"rework-visit": "1.0.0"
},
"peerDependenciesMeta": {
"rework": {
"optional": true
},
"rework-visit": {
"optional": true
}
}
},
"node_modules/resolve-url-loader/node_modules/postcss": {
@@ -14650,10 +14442,6 @@
},
"engines": {
"node": ">=6.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
}
},
"node_modules/resolve-url-loader/node_modules/source-map": {
@@ -15316,21 +15104,13 @@
"diff": "^4.0.2",
"nise": "^4.0.4",
"supports-color": "^7.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/sinon"
}
},
"node_modules/sinon-chai": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.7.0.tgz",
"integrity": "sha512-mf5NURdUaSdnatJx3uhoBOrY9dtL19fiOtAdT1Azxg3+lNJFiuN0uzaU3xX1LeAfL17kHQhTAJgpsfhbMJMY2g==",
"optional": true,
"peerDependencies": {
"chai": "^4.0.0",
"sinon": ">=4.0.0"
}
"optional": true
},
"node_modules/sinon/node_modules/has-flag": {
"version": "4.0.0",
@@ -15386,9 +15166,6 @@
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/slice-ansi/node_modules/color-convert": {
@@ -16113,11 +15890,6 @@
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/start-server-and-test/node_modules/execa": {
@@ -16138,9 +15910,6 @@
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sindresorhus/execa?sponsor=1"
}
},
"node_modules/start-server-and-test/node_modules/get-stream": {
@@ -16150,9 +15919,6 @@
"optional": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/start-server-and-test/node_modules/human-signals": {
@@ -16484,9 +16250,6 @@
},
"engines": {
"node": "^10 || ^12 || >=14.0"
},
"peerDependencies": {
"postcss": "^8.2.15"
}
},
"node_modules/stylus": {
@@ -16627,9 +16390,6 @@
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/svgo/node_modules/chalk": {
@@ -16643,9 +16403,6 @@
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/svgo/node_modules/color-convert": {
@@ -17822,11 +17579,7 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz",
"integrity": "sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==",
"dev": true,
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
"dev": true
},
"node_modules/verror": {
"version": "1.10.0",
@@ -19940,18 +19693,6 @@
"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
"engines": {
"node": ">=8.3.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xhr2": {
@@ -25441,8 +25182,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-2.0.1.tgz",
"integrity": "sha512-i8vLRZTnEH9ubIyfdZCAdIdgnHAUeQeByEeQ2I7oTilvP9oHO6RScpeq3GsFUVqeB8uZgOQ9pw8utofNn32hhQ==",
"dev": true,
"requires": {}
"dev": true
},
"csso": {
"version": "4.2.0",
@@ -31370,29 +31110,25 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.0.1.tgz",
"integrity": "sha512-lgZBPTDvWrbAYY1v5GYEv8fEO/WhKOu/hmZqmCYfrpD6eyDWWzAOsl2rF29lpvziKO02Gc5GJQtlpkTmakwOWg==",
"dev": true,
"requires": {}
"dev": true
},
"postcss-discard-duplicates": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.0.1.tgz",
"integrity": "sha512-svx747PWHKOGpAXXQkCc4k/DsWo+6bc5LsVrAsw+OU+Ibi7klFZCyX54gjYzX4TH+f2uzXjRviLARxkMurA2bA==",
"dev": true,
"requires": {}
"dev": true
},
"postcss-discard-empty": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.0.1.tgz",
"integrity": "sha512-vfU8CxAQ6YpMxV2SvMcMIyF2LX1ZzWpy0lqHDsOdaKKLQVQGVP1pzhrI9JlsO65s66uQTfkQBKBD/A5gp9STFw==",
"dev": true,
"requires": {}
"dev": true
},
"postcss-discard-overridden": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.0.1.tgz",
"integrity": "sha512-Y28H7y93L2BpJhrdUR2SR2fnSsT+3TVx1NmVQLbcnZWwIUpJ7mfcTC6Za9M2PG6w8j7UQRfzxqn8jU2VwFxo3Q==",
"dev": true,
"requires": {}
"dev": true
},
"postcss-import": {
"version": "14.0.0",
@@ -31537,8 +31273,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.0.1.tgz",
"integrity": "sha512-6J40l6LNYnBdPSk+BHZ8SF+HAkS4q2twe5jnocgd+xWpz/mx/5Sa32m3W1AA8uE8XaXN+eg8trIlfu8V9x61eg==",
"dev": true,
"requires": {}
"dev": true
},
"postcss-normalize-display-values": {
"version": "5.0.1",
@@ -33040,8 +32775,7 @@
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.7.0.tgz",
"integrity": "sha512-mf5NURdUaSdnatJx3uhoBOrY9dtL19fiOtAdT1Azxg3+lNJFiuN0uzaU3xX1LeAfL17kHQhTAJgpsfhbMJMY2g==",
"optional": true,
"requires": {}
"optional": true
},
"slash": {
"version": "3.0.0",
@@ -36827,8 +36561,7 @@
"ws": {
"version": "7.4.6",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
"requires": {}
"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A=="
},
"xhr2": {
"version": "0.2.0",

View File

@@ -1,6 +1,6 @@
{
"name": "mempool-frontend",
"version": "2.2.1",
"version": "2.2.2-dev",
"description": "Bitcoin mempool visualizer and blockchain explorer backend",
"license": "GNU Affero General Public License v3.0",
"homepage": "https://mempool.space",
@@ -37,9 +37,11 @@
"build-mempool.js": "tsc | browserify -p tinyify ./node_modules/@mempool/mempool.js/lib/index.js --standalone mempoolJS > ./dist/mempool/browser/en-US/mempool.js",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e",
"e2e": "npm run generate-config && ng e2e",
"e2e:ci": "npm run cypress:run:ci",
"config:defaults": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config",
"config:defaults:mempool": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 BASE_MODULE=mempool && npm run generate-config",
"config:defaults:liquid": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 BASE_MODULE=liquid && npm run generate-config",
"config:defaults:bisq": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 BASE_MODULE=bisq && npm run generate-config",
"dev:ssr": "npm run generate-config && ng run mempool:serve-ssr",
"serve:ssr": "node server.run.js",
"build:ssr": "npm run build && ng run mempool:server:production && ./node_modules/typescript/bin/tsc server.run.ts",

64
frontend/proxy.conf.js Normal file
View File

@@ -0,0 +1,64 @@
const fs = require('fs');
let PROXY_CONFIG;
let configContent;
const CONFIG_FILE_NAME = 'mempool-frontend-config.json';
try {
const rawConfig = fs.readFileSync(CONFIG_FILE_NAME);
configContent = JSON.parse(rawConfig);
console.log(`${CONFIG_FILE_NAME} file found, using provided config`);
} catch (e) {
console.log(e);
if (e.code !== 'ENOENT') {
throw new Error(e);
} else {
console.log(`${CONFIG_FILE_NAME} file not found, using default config`);
}
}
PROXY_CONFIG = [
{
context: ['*',
'/api/**', '!/api/v1/ws',
'!/bisq', '!/bisq/**', '!/bisq/',
'!/liquid', '!/liquid/**', '!/liquid/',
'/testnet/api/**', '/signet/api/**'
],
target: "https://mempool.space",
ws: true,
secure: false,
changeOrigin: true
},
{
context: ['/api/v1/ws'],
target: "https://mempool.space",
ws: true,
secure: false,
changeOrigin: true,
},
{
context: ['/api/bisq**', '/bisq/api/**'],
target: "https://bisq.markets",
pathRewrite: {
"^/api/bisq/": "/bisq/api"
},
ws: true,
secure: false,
changeOrigin: true
},
{
context: ['/api/liquid**', '/liquid/api/**'],
target: "https://liquid.network",
pathRewrite: {
"^/api/liquid/": "/liquid/api"
},
ws: true,
secure: false,
changeOrigin: true
}
];
module.exports = PROXY_CONFIG;

View File

@@ -67,11 +67,18 @@
"^/liquid/api": "/api/v1/ws"
}
},
"/liquid/api/": {
"target": "http://localhost:50001/",
"/liquid/api/v1/": {
"target": "http://localhost:8999/",
"secure": false,
"pathRewrite": {
"^/liquid/api/": ""
"^/liquid/api/": "/api/"
}
},
"/liquid/api/": {
"target": "http://localhost:8999/",
"secure": false,
"pathRewrite": {
"^/liquid/api/": "/api/v1/"
}
},
"/bisq/api/": {

View File

@@ -20,6 +20,7 @@ import { PrivacyPolicyComponent } from './components/privacy-policy/privacy-poli
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';
let routes: Routes = [
{
@@ -303,7 +304,7 @@ const browserWindow = window || {};
// @ts-ignore
const browserWindowEnv = browserWindow.__env || {};
if (browserWindowEnv && browserWindowEnv.OFFICIAL_BISQ_MARKETS) {
if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'bisq') {
routes = [{
path: '',
component: BisqMasterPageComponent,
@@ -311,6 +312,93 @@ if (browserWindowEnv && browserWindowEnv.OFFICIAL_BISQ_MARKETS) {
}];
}
if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
routes = [{
path: '',
component: LiquidMasterPageComponent,
children: [
{
path: '',
component: StartComponent,
children: [
{
path: '',
component: DashboardComponent
},
{
path: 'tx/:id',
component: TransactionComponent
},
{
path: 'block/:id',
component: BlockComponent
},
{
path: 'mempool-block/:id',
component: MempoolBlockComponent
},
],
},
{
path: 'blocks',
component: LatestBlocksComponent,
},
{
path: 'graphs',
component: StatisticsComponent,
},
{
path: 'address/:id',
component: AddressComponent
},
{
path: 'asset/:id',
component: AssetComponent
},
{
path: 'assets',
component: AssetsComponent,
},
{
path: 'api',
component: ApiDocsComponent,
},
{
path: 'about',
component: AboutComponent,
},
{
path: 'terms-of-service',
component: TermsOfServiceComponent
},
{
path: 'privacy-policy',
component: PrivacyPolicyComponent
},
{
path: 'trademark-policy',
component: TrademarkPolicyComponent
},
{
path: 'sponsor',
component: SponsorComponent,
},
],
},
{
path: 'tv',
component: TelevisionComponent
},
{
path: 'status',
component: StatusViewComponent
},
{
path: '**',
redirectTo: ''
}];
}
@NgModule({
imports: [RouterModule.forRoot(routes, {
initialNavigation: 'enabled',

View File

@@ -22,6 +22,7 @@ import { AddressLabelsComponent } from './components/address-labels/address-labe
import { MempoolBlocksComponent } from './components/mempool-blocks/mempool-blocks.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';
@@ -44,7 +45,7 @@ import { NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap';
import { FeesBoxComponent } from './components/fees-box/fees-box.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
import { faAngleDown, faAngleUp, faBolt, faChartArea, faCogs, faCubes, faDatabase, faExchangeAlt, faInfoCircle,
import { faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faDatabase, faExchangeAlt, faInfoCircle,
faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown, faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt } from '@fortawesome/free-solid-svg-icons';
import { ApiDocsComponent } from './components/api-docs/api-docs.component';
import { CodeTemplateComponent } from './components/api-docs/code-template.component';
@@ -61,6 +62,7 @@ import { SponsorComponent } from './components/sponsor/sponsor.component';
AboutComponent,
MasterPageComponent,
BisqMasterPageComponent,
LiquidMasterPageComponent,
TelevisionComponent,
BlockchainComponent,
StartComponent,
@@ -145,5 +147,7 @@ export class AppModule {
library.addIcons(faSortUp);
library.addIcons(faCaretUp);
library.addIcons(faCaretDown);
library.addIcons(faAngleRight);
library.addIcons(faAngleLeft);
}
}

View File

@@ -20,15 +20,13 @@
<th i18n="Asset ticker header">Ticker</th>
<th class="d-none d-md-block" i18n="Asset Issuer Domain header">Issuer domain</th>
<th i18n="Asset ID header">Asset ID</th>
<th class="d-none d-lg-block" i18n="Asset issuance transaction header">Issuance TX</th>
</thead>
<tbody>
<tr *ngFor="let asset of filteredAssets; trackBy: trackByAsset">
<td class="td-name">{{ asset.name }}</td>
<td class="td-name"><a [routerLink]="['/asset/' | relativeUrl, asset.asset_id]">{{ asset.name }}</a></td>
<td>{{ asset.ticker }}</td>
<td class="d-none d-md-block"><a *ngIf="asset.entity" target="_blank" href="{{ 'http://' + asset.entity.domain }}">{{ asset.entity.domain }}</a></td>
<td class="d-none d-md-block">{{ asset.entity && asset.entity.domain }}</td>
<td><a [routerLink]="['/asset/' | relativeUrl, asset.asset_id]">{{ asset.asset_id | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="asset.asset_id"></app-clipboard></td>
<td class="d-none d-lg-block"><ng-template [ngIf]="asset.issuance_txin"><a [routerLink]="['/tx/' | relativeUrl, asset.issuance_txin.txid]">{{ asset.issuance_txin.txid | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="asset.issuance_txin.txid"></app-clipboard></ng-template></td>
</tr>
</tbody>
</table>
@@ -47,15 +45,13 @@
<th i18n="Asset ticker header">Ticker</th>
<th i18n="Asset Issuer Domain header">Issuer domain</th>
<th i18n="Asset ID header">Asset ID</th>
<th i18n="Asset issuance transaction header">Issuance TX</th>
</thead>
<tbody>
<tr *ngFor="let dummy of [0,0,0]">
<tr *ngFor="let dummy of [0,0,0,0,0,0,0,0,0,0]">
<td><span class="skeleton-loader"></span></td>
<td><span class="skeleton-loader"></span></td>
<td class="d-none d-md-block"><span class="skeleton-loader"></span></td>
<td><span class="skeleton-loader"></span></td>
<td class="d-none d-lg-block"><span class="skeleton-loader"></span></td>
</tr>
</tbody>
</table>

View File

@@ -107,7 +107,7 @@ export class AssetsComponent implements OnInit {
const start = (this.page - 1) * this.itemsPerPage;
if (searchText.length ) {
const filteredAssets = this.assetsCache.filter((asset) => asset.name.toLowerCase().indexOf(searchText.toLowerCase()) > -1
|| asset.ticker.toLowerCase().indexOf(searchText.toLowerCase()) > -1);
|| (asset.ticker || '').toLowerCase().indexOf(searchText.toLowerCase()) > -1);
this.assets = filteredAssets;
return filteredAssets.slice(start, this.itemsPerPage + start);
} else {

View File

@@ -17,7 +17,7 @@
<div class="container-info">
<h1>
<ng-template [ngIf]="stateService.env.OFFICIAL_BISQ_MARKETS" [ngIfElse]="nonOfficialMarkets" i18n="Bisq All Markets">Markets</ng-template>
<ng-template [ngIf]="stateService.env.BASE_MODULE === 'bisq'" [ngIfElse]="nonOfficialMarkets" i18n="Bisq All Markets">Markets</ng-template>
<ng-template #nonOfficialMarkets i18n="Bisq Bitcoin Markets">Bitcoin Markets</ng-template>
</h1>
<ng-container *ngIf="{ value: (tickers$ | async) } as tickers">

View File

@@ -17,19 +17,6 @@
z-index: 100;
}
.table-container {
overflow: scroll;
-ms-overflow-style: none;
scrollbar-width: none;
font-size: 13px;
@media(min-width: 576px){
font-size: 16px;
}
&::-webkit-scrollbar {
display: none;
}
}
.container-info{
overflow-x: scroll;
}

View File

@@ -69,7 +69,7 @@ export class BisqDashboardComponent implements OnInit {
const newTickers = [];
for (const t in tickers) {
if (!this.stateService.env.OFFICIAL_BISQ_MARKETS) {
if (this.stateService.env.BASE_MODULE !== 'bisq') {
const pair = t.split('_');
if (pair[1] === 'btc' && this.allowCryptoCoins.indexOf(pair[0]) === -1) {
continue;
@@ -106,7 +106,7 @@ export class BisqDashboardComponent implements OnInit {
])
.pipe(
map(([trades, markets]) => {
if (!this.stateService.env.OFFICIAL_BISQ_MARKETS) {
if (this.stateService.env.BASE_MODULE !== 'bisq') {
trades = trades.filter((trade) => {
const pair = trade.market.split('_');
return !(pair[1] === 'btc' && this.allowCryptoCoins.indexOf(pair[0]) === -1);

View File

@@ -63,7 +63,7 @@
<div class="card">
<div class="card-body">
<h5 class="card-title text-center">
<ng-template [ngIf]="stateService.env.OFFICIAL_BISQ_MARKETS" [ngIfElse]="nonOfficialMarkets" i18n="Bisq All Markets">Markets</ng-template>
<ng-template [ngIf]="stateService.env.BASE_MODULE === 'bisq'" [ngIfElse]="nonOfficialMarkets" i18n="Bisq All Markets">Markets</ng-template>
<ng-template #nonOfficialMarkets i18n="Bisq Bitcoin Markets">Bitcoin Markets</ng-template>
</h5>
@@ -105,7 +105,7 @@
</ng-container>
</div>
<app-language-selector *ngIf="!stateService.env.OFFICIAL_BISQ_MARKETS"></app-language-selector>
<app-language-selector *ngIf="stateService.env.BASE_MODULE !== 'bisq'"></app-language-selector>
<div class="text-small text-center mt-3">
<a [routerLink]="['/terms-of-service']" i18n="shared.terms-of-service|Terms of Service">Terms of Service</a>

View File

@@ -18,15 +18,44 @@
}
.table-container {
font-size: 13px;
@media(min-width: 576px){
font-size: 16px;
overflow: scroll;
scrollbar-width: none;
font-size: 13px;
&::-webkit-scrollbar {
display: none;
}
@media(min-width: 576px){
font-size: 16px;
}
thead th{
text-align: right;
&:first-child {
text-align: left;
}
&::-webkit-scrollbar {
&:nth-child(3) {
display: none;
@media(min-width: 1100px){
display: table-cell;
}
}
}
tr {
td {
text-align: right;
&:first-child {
text-align: left;
}
&:nth-child(3) {
display: none;
@media(min-width: 1100px){
display: table-cell;
}
}
}
}
}
.chart-container {
height: 300px;
}
@@ -41,44 +70,43 @@
background-color: #1d1f31;
height: 100%;
}
.card-title {
color: #4a68b9;
font-size: 1rem;
}
.info-block {
float: left;
width: 350px;
line-height: 25px;
}
.progress {
display: inline-flex;
width: 100%;
background-color: #2d3348;
height: 1.1rem;
}
.bg-warning {
background-color: #b58800 !important;
}
.skeleton-loader {
max-width: 100%;
&.shorter {
max-width: 150px;
}
}
.more-padding {
padding: 1.25rem 2rem 1.25rem 2rem;
}
.graph-card {
height: 100%;
@media (min-width: 992px) {
height: 385px;
}
}

View File

@@ -77,7 +77,7 @@ export class BisqMainDashboardComponent implements OnInit {
const newTickers = [];
for (const t in tickers) {
if (!this.stateService.env.OFFICIAL_BISQ_MARKETS) {
if (this.stateService.env.BASE_MODULE !== 'bisq') {
const pair = t.split('_');
if (pair[1] === 'btc' && this.allowCryptoCoins.indexOf(pair[0]) === -1) {
continue;
@@ -114,7 +114,7 @@ export class BisqMainDashboardComponent implements OnInit {
])
.pipe(
map(([trades, markets]) => {
if (!this.stateService.env.OFFICIAL_BISQ_MARKETS) {
if (this.stateService.env.BASE_MODULE !== 'bisq') {
trades = trades.filter((trade) => {
const pair = trade.market.split('_');
return !(pair[1] === 'btc' && this.allowCryptoCoins.indexOf(pair[0]) === -1);

View File

@@ -1,12 +1,38 @@
.table-container {
overflow: scroll;
-ms-overflow-style: none;
scrollbar-width: none;
font-size: 13px;
@media(min-width: 576px){
font-size: 16px;
overflow: scroll;
scrollbar-width: none;
font-size: 13px;
&::-webkit-scrollbar {
display: none;
}
@media(min-width: 576px){
font-size: 16px;
}
thead th{
text-align: right;
&:first-child{
text-align: left;
}
&::-webkit-scrollbar {
&:nth-child(2) {
display: none;
@media(min-width: 1100px){
display: table-cell;
}
}
}
tr {
td {
text-align: right;
&:first-child{
text-align: left;
}
&:nth-child(2) {
display: none;
@media(min-width: 1100px){
display: table-cell;
}
}
}
}
}

View File

@@ -67,7 +67,7 @@
<tr>
<td i18n="transaction.fee-per-vbyte|Transaction fee">Fee per vByte</td>
<td *ngIf="!isLoadingTx; else loadingTxFee">
{{ tx.fee / (tx.weight / 4) | number : '1.1-1' }} <span class="symbol">sat/vB</span>
{{ tx.fee / (tx.weight / 4) | feeRounding }} <span class="symbol">sat/vB</span>
&nbsp;
<app-tx-fee-rating [tx]="tx"></app-tx-fee-rating>
</td>

View File

@@ -85,7 +85,11 @@ export class BisqTransactionComponent implements OnInit, OnDestroy {
}
if (tx.version) {
this.router.navigate(['/tx/', this.txId], { state: { data: tx, bsqTx: true }});
if (this.stateService.env.BASE_MODULE === 'bisq') {
window.location.replace('https://mempool.space/tx/' + this.txId);
} else {
this.router.navigate(['/tx/', this.txId], { state: { data: tx, bsqTx: true }});
}
return of(null);
}

View File

@@ -8,10 +8,13 @@
</div>
</div>
<div class="about-text">
<div class="about-text" *ngIf="stateService.env.BASE_MODULE === 'mempool'; else marginBox">
<h5><ng-container i18n="about.about-the-project">The Mempool Open Source Project</ng-container><ng-template [ngIf]="locale.substr(0, 2) === 'en'"> &trade;</ng-template></h5>
<p i18n>Building a mempool and blockchain explorer for the Bitcoin community, focusing on the transaction fee market and multi-layer ecosystem, without any advertising, altcoins, or third-party trackers.</p>
</div>
<ng-template #marginBox>
<div class="no-about-margin"></div>
</ng-template>
<div class="social-icons">
<a target="_blank" href="https://github.com/mempool/mempool">
@@ -47,6 +50,10 @@
<img class="image" src="/resources/profile/foundry.svg" />
<span>Foundry</span>
</a>
<a href="https://unchained-capital.com/" target="_blank" title="Unchained">
<img class="image" src="/resources/profile/unchained.svg" />
<span>Unchained</span>
</a>
</div>
</div>

View File

@@ -176,4 +176,8 @@
.footer-version {
font-size: 12px;
}
}
.no-about-margin {
height: 10px;
}

View File

@@ -25,7 +25,7 @@ export class AboutComponent implements OnInit {
constructor(
private websocketService: WebsocketService,
private seoService: SeoService,
private stateService: StateService,
public stateService: StateService,
private apiService: ApiService,
private router: Router,
@Inject(LOCALE_ID) public locale: string,
@@ -41,7 +41,7 @@ export class AboutComponent implements OnInit {
}
sponsor() {
if (this.officialMempoolSpace) {
if (this.officialMempoolSpace && this.stateService.env.BASE_MODULE === 'mempool') {
this.router.navigateByUrl('/sponsor');
} else {
this.showNavigateToSponsor = true;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -12,7 +12,7 @@
<ng-template ngbNavContent>
<div class="subtitle"><ng-container i18n="API Docs code example">Code Example</ng-container> <app-clipboard [text]="wrapCommonJS(code.codeSample.commonJS)"></app-clipboard></div>
<div class="links">
<a href="https://github.com/mempool/mempool.js" target="_blank">github repository</a>
<a [href]="npmGithubLink()" target="_blank">github repository</a>
</div>
<pre><code [innerText]="wrapCommonJS(code.codeSample.commonJS)"></code></pre>
</ng-template>
@@ -20,12 +20,12 @@
<li ngbNavItem>
<a ngbNavLink>ES Module</a>
<ng-template ngbNavContent>
<div class="subtitle"><ng-container i18n="API Docs install lib">Install Package</ng-container> <app-clipboard [text]="esModuleInstall"></app-clipboard></div>
<div class="subtitle"><ng-container i18n="API Docs install lib">Install Package</ng-container> <app-clipboard [text]="wrapImportTemplate()"></app-clipboard></div>
<div class="links">
<a href="https://github.com/mempool/mempool.js" target="_blank">github repository</a>
<a href="https://www.npmjs.org/package/@mempool/mempool.js" target="_blank">npm package</a>
<a [href]="npmGithubLink()" target="_blank">github repository</a>
<a [href]="npmModuleLink()" target="_blank">npm package</a>
</div>
<pre><code [innerText]="esModuleInstall"></code></pre>
<pre><code [innerText]="wrapImportTemplate()"></code></pre>
<div class="subtitle"><ng-container i18n="API Docs code example">Code Example</ng-container> <app-clipboard [text]="wrapESmodule(code.codeSample.esModule)"></app-clipboard></div>
<pre><code [innerText]="wrapESmodule(code.codeSample.esModule)"></code></pre>
</ng-template>

View File

@@ -7,6 +7,7 @@ import { Component, Input } from '@angular/core';
})
export class CodeTemplateComponent {
@Input() network: string;
@Input() layer: string;
@Input() code: {
codeSample: {
esModule: string;
@@ -16,15 +17,32 @@ export class CodeTemplateComponent {
responseSample: string;
};
hostname = document.location.hostname;
esModuleInstall = `# npm
npm install @mempool/mempool.js --save
# yarn
yarn add @mempool/mempool.js`;
constructor(
) { }
npmGithubLink(){
let npmLink = `https://github.com/mempool/mempool.js`;
if (this.layer === 'bisq') {
npmLink = `https://github.com/mempool/mempool.js/tree/main/npm-bisq-js`;
}
if (this.layer === 'liquid') {
npmLink = `https://github.com/mempool/mempool.js/tree/main/npm-liquid-js`;
}
return npmLink;
}
npmModuleLink() {
let npmLink = `https://www.npmjs.org/package/@mempool/mempool.js`;
if (this.layer === 'bisq') {
npmLink = `https://www.npmjs.org/package/@mempool/bisq.js`;
}
if (this.layer === 'liquid') {
npmLink = `https://www.npmjs.org/package/@mempool/liquid.js`;
}
return npmLink;
}
normalizeCodeHostname(code: string) {
let codeText: string;
if (this.network === 'bisq' || this.network === 'liquid'){
@@ -44,7 +62,15 @@ yarn add @mempool/mempool.js`;
});` );
}
return `import mempoolJS from "@mempool/mempool.js";
let importText = `import mempoolJS from "@mempool/mempool.js";`;
if (this.layer === 'bisq') {
importText = `import bisqJS from "@mempool/bisq.js";`;
}
if (this.layer === 'liquid') {
importText = `import liquidJS from "@mempool/liquid.js";`;
}
return `${importText}
const init = async () => {
${codeText}
@@ -60,10 +86,18 @@ init();`;
hostname: '${this.hostname}/${this.network}'
});` );
}
let importText = `<script src="https://mempool.space/mempool.js"></script>`;
if (this.layer === 'bisq') {
importText = `<script src="https://bisq.markets/bisq.js"></script>`;
}
if (this.layer === 'liquid') {
importText = `<script src="https://liquid.network/liquid.js"></script>`;
}
return `<!DOCTYPE html>
<html>
<head>
<script src="https://mempool.space/mempool.js"></script>
${importText}
<script>
const init = async () => {
${codeText}
@@ -81,4 +115,31 @@ init();`;
return code;
}
wrapImportTemplate() {
let importTemplate = `# npm
npm install @mempool/mempool.js --save
# yarn
yarn add @mempool/mempool.js`;
if (this.layer === 'bisq') {
importTemplate = `# npm
npm install @mempool/bisq.js --save
# yarn
yarn add @mempool/bisq.js`;
}
if (this.layer === 'liquid') {
importTemplate = `# npm
npm install @mempool/liquid.js --save
# yarn
yarn add @mempool/liquid.js`;
}
return importTemplate;
}
}

View File

@@ -2,7 +2,7 @@
<nav class="navbar navbar-expand-md navbar-dark bg-dark">
<a class="navbar-brand" [routerLink]="['/' | relativeUrl]" style="position: relative;">
<ng-container *ngIf="{ val: connectionState$ | async } as connectionState">
<img [src]="'./resources/bisq-markets.svg'" height="35" width="180" class="logo" [ngStyle]="{'opacity': connectionState.val === 2 ? 1 : 0.5 }">
<img src="./resources/bisq/bisq-markets-logo.png" height="35" width="140" class="logo" [ngStyle]="{'opacity': connectionState.val === 2 ? 1 : 0.5 }">
<div class="connection-badge">
<div class="badge badge-warning" *ngIf="connectionState.val === 0" i18n="master-page.offline">Offline</div>
<div class="badge badge-warning" *ngIf="connectionState.val === 1" i18n="master-page.reconnecting">Reconnecting...</div>
@@ -10,15 +10,42 @@
</ng-container>
</a>
<div ngbDropdown (window:resize)="onResize($event)" class="dropdown-container" *ngIf="env.TESTNET_ENABLED || env.SIGNET_ENABLED || env.LIQUID_ENABLED || env.BISQ_ENABLED">
<button ngbDropdownToggle type="button" class="btn btn-secondary dropdown-toggle-split" aria-haspopup="true">
<img src="./resources/bisq-logo.png" style="width: 25px; height: 25px;" class="mr-1">
</button>
<div ngbDropdownMenu [ngClass]="{'dropdown-menu-right' : isMobile}">
<a href="https://mempool.space" ngbDropdownItem class="mainnet"><img src="./resources/bitcoin-logo.png" style="width: 30px;" class="mr-1"> Mainnet</a>
<a href="https://mempool.space/signet" ngbDropdownItem *ngIf="env.SIGNET_ENABLED" class="signet"><img src="./resources/signet-logo.png" style="width: 30px;" class="mr-1"> Signet</a>
<a href="https://mempool.space/testnet" ngbDropdownItem *ngIf="env.TESTNET_ENABLED" class="testnet"><img src="./resources/testnet-logo.png" style="width: 30px;" class="mr-1"> Testnet</a>
<h6 class="dropdown-header" i18n="master-page.layer2-networks-header">Layer 2 Networks</h6>
<button ngbDropdownItem class="mainnet active" routerLink="/"><img src="./resources/bisq-logo.png" style="width: 30px;" class="mr-1"> Bisq</button>
<a href="https://liquid.network" ngbDropdownItem *ngIf="env.LIQUID_ENABLED" class="liquid"><img src="./resources/liquid-logo.png" style="width: 30px;" class="mr-1"> Liquid</a>
</div>
</div>
<div class="navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav">
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">
<a class="nav-link" [routerLink]="['/' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'tachometer-alt']" [fixedWidth]="true" i18n-title="master-page.dashboard" title="Dashboard"></fa-icon></a>
</li>
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">
<a class="nav-link" [routerLink]="['/transactions']" (click)="collapse()"><fa-icon [icon]="['fas', 'list']" [fixedWidth]="true" i18n-title="master-page.transactions" title="Transactions"></fa-icon></a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/blocks']" (click)="collapse()"><fa-icon [icon]="['fas', 'cubes']" [fixedWidth]="true" i18n-title="master-page.blocks" title="Blocks"></fa-icon></a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/stats']" (click)="collapse()"><fa-icon [icon]="['fas', 'file-alt']" [fixedWidth]="true" i18n-title="master-page.stats" title="Stats"></fa-icon></a>
</li>
<li class="nav-item mr-2" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/api' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'cogs']" [fixedWidth]="true" i18n-title="master-page.api" title="API"></fa-icon></a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/about']" (click)="collapse()"><fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true" i18n-title="master-page.about" title="About"></fa-icon></a>
</li>
</ul>
<app-search-form class="search-form-container" location="top" (searchTriggered)="collapse()"></app-search-form>
</div>
</nav>
</header>

View File

@@ -8,14 +8,16 @@ fa-icon {
.navbar {
z-index: 100;
min-height: 64px;
}
li.nav-item {
margin: auto 5px;
padding-left: 10px;
padding-right: 10px;
}
@media (min-width: 768px) {
@media (min-width: 992px) {
.navbar {
padding: 0rem 2rem;
}
@@ -26,17 +28,54 @@ li.nav-item {
margin-right: 16px;
}
li.nav-item {
margin: auto 0px;
padding: 10px;
}
}
li.nav-item a {
color: #ffffff;
.navbar-nav {
background: #212121;
bottom: 0;
box-shadow: 0px 0px 15px 0px #000;
flex-direction: row;
left: 0;
justify-content: center;
position: fixed;
width: 100%;
@media (min-width: 992px) {
background: transparent;
box-shadow: none;
position: relative;
width: auto;
}
a {
font-size: 0.8em;
@media (min-width: 375px) {
font-size: 1em;
}
}
}
.navbar-nav {
flex-direction: row;
justify-content: center;
.navbar-collapse {
flex-basis: auto;
justify-content: flex-end;
}
@media (min-width: 992px) {
.navbar-collapse {
justify-content: space-between;
}
}
.navbar-brand {
width: 60%;
}
@media (min-width: 576px) {
.navbar-brand {
width: 140px;
}
}
nav {
@@ -81,5 +120,20 @@ nav {
.dropdown-item {
display: flex;
align-items:center;
align-items: center;
}
@media (min-width: 992px) {
.search-form-container {
width: 100%;
max-width: 500px;
padding-left: 15px;
}
}
.navbar-dark .navbar-nav .nav-link {
color: #f1f1f1;
}
.navbar-brand {
margin-right: 5px;
}

View File

@@ -1,5 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { StateService } from '../../services/state.service';
import { Env, StateService } from '../../services/state.service';
import { Observable } from 'rxjs';
@Component({
@@ -10,16 +10,23 @@ import { Observable } from 'rxjs';
export class BisqMasterPageComponent implements OnInit {
connectionState$: Observable<number>;
navCollapsed = false;
env: Env;
isMobile = window.innerWidth <= 767.98;
constructor(
private stateService: StateService,
) { }
ngOnInit() {
this.env = this.stateService.env;
this.connectionState$ = this.stateService.connectionState$;
}
collapse(): void {
this.navCollapsed = !this.navCollapsed;
}
onResize(event: any) {
this.isMobile = window.innerWidth <= 767.98;
}
}

View File

@@ -1,7 +1,39 @@
<div class="container-xl" (window:resize)="onResize($event)">
<div class="title-block" id="block">
<h1><ng-template [ngIf]="blockHeight === 0" i18n="block.genesis">Genesis </ng-template><ng-template [ngIf]="blockHeight" i18n="block.block">Block <a [routerLink]="['/block/' | relativeUrl, blockHash]">{{ blockHeight }}</a></ng-template></h1>
<h1>
<ng-template [ngIf]="blockHeight === 0" i18n="block.genesis">Genesis
<div class="next-previous-blocks">
<a *ngIf="showNextBlocklink" [routerLink]="['/block/' | relativeUrl, nextBlockHeight]" (click)="navigateToNextBlock()" i18n-ngbTooltip="Next Block" ngbTooltip="Next Block" placement="bottom">
<fa-icon [icon]="['fas', 'angle-left']" [fixedWidth]="true"></fa-icon>
</a>
<a [routerLink]="['/block/' | relativeUrl, blockHash]">{{ blockHeight }}</a>
<span placement="bottom" class="disable">
<fa-icon [icon]="['fas', 'angle-right']" [fixedWidth]="true"></fa-icon>
</span>
</div>
</ng-template>
<ng-template [ngIf]="blockHeight" i18n="block.block"> Block
<div class="next-previous-blocks">
<a *ngIf="showNextBlocklink" [routerLink]="['/block/' | relativeUrl, nextBlockHeight]" (click)="navigateToNextBlock()" i18n-ngbTooltip="Next Block" ngbTooltip="Next Block" placement="bottom">
<fa-icon [icon]="['fas', 'angle-left']" [fixedWidth]="true"></fa-icon>
</a>
<span *ngIf="!showNextBlocklink" placement="bottom" class="disable">
<fa-icon [icon]="['fas', 'angle-left']" [fixedWidth]="true"></fa-icon>
</span>
<a [routerLink]="['/block/' | relativeUrl, blockHash]">{{ blockHeight }}</a>
<a *ngIf="showPreviousBlocklink && block" [routerLink]="['/block/' | relativeUrl, block.previousblockhash]" (click)="navigateToPreviousBlock()" i18n-ngbTooltip="Previous Block" ngbTooltip="Previous Block" placement="bottom">
<fa-icon [icon]="['fas', 'angle-right']" [fixedWidth]="true"></fa-icon>
</a>
<span *ngIf="!showPreviousBlocklink" placement="bottom" class="disable">
<fa-icon [icon]="['fas', 'angle-right']" [fixedWidth]="true"></fa-icon>
</span>
</div>
</ng-template>
</h1>
<div class="grow"></div>
<button [routerLink]="['/' | relativeUrl]" class="btn btn-sm">&#10005;</button>
</div>

View File

@@ -44,6 +44,11 @@ h1 {
float: left;
margin-right: 10px;
}
a {
&:hover, &:focus{
text-decoration: none;;
}
}
}
.address-link {
@@ -110,4 +115,29 @@ h1 {
line-height: 1;
}
}
}
.grow {
flex-grow: 1;
}
.next-previous-blocks {
font-size: 36px;
display: inline-block;
vertical-align: bottom;
a {
color: #1ad8f4;
&:hover, &:focus {
color: #09a3ba;
display: inline-block;
// transform: scale(1.2);
// transition: 150ms all ease-in-out;
}
}
}
.disable {
font-size: 36px;
color: #393e5c73;
}

View File

@@ -18,14 +18,15 @@ export class BlockComponent implements OnInit, OnDestroy {
network = '';
block: Block;
blockHeight: number;
nextBlockHeight: number;
blockHash: string;
isLoadingBlock = true;
latestBlock: Block;
latestBlocks: Block[] = [];
transactions: Transaction[];
isLoadingTransactions = true;
error: any;
blockSubsidy: number;
subscription: Subscription;
fees: number;
paginationMaxSize: number;
coinbaseTx: Transaction;
@@ -33,6 +34,14 @@ export class BlockComponent implements OnInit, OnDestroy {
itemsPerPage: number;
txsLoadingStatus$: Observable<number>;
showDetails = false;
showPreviousBlocklink = true;
showNextBlocklink = true;
subscription: Subscription;
keyNavigationSubscription: Subscription;
blocksSubscription: Subscription;
networkChangedSubscription: Subscription;
queryParamsSubscription: Subscription;
constructor(
private route: ActivatedRoute,
@@ -97,12 +106,22 @@ export class BlockComponent implements OnInit, OnDestroy {
})
);
}
this.stateService.blocks$.subscribe(([block]) => {
if (block.id === blockHash) {
this.block = block;
}
});
return this.electrsApiService.getBlock$(blockHash);
}
}),
tap((block: Block) => {
this.block = block;
this.blockHeight = block.height;
this.nextBlockHeight = block.height + 1;
this.setNextAndPreviousBlockLink();
this.seoService.setTitle($localize`:@@block.component.browser-title:Block ${block.height}:BLOCK_HEIGHT:: ${block.id}:BLOCK_ID:`);
this.isLoadingBlock = false;
if (block.coinbaseTx) {
@@ -140,24 +159,46 @@ export class BlockComponent implements OnInit, OnDestroy {
this.isLoadingBlock = false;
});
this.stateService.blocks$
.subscribe(([block]) => this.latestBlock = block);
this.blocksSubscription = this.stateService.blocks$
.subscribe(([block]) => {
this.latestBlock = block;
this.latestBlocks.unshift(block);
this.latestBlocks = this.latestBlocks.slice(0, this.stateService.env.KEEP_BLOCKS_AMOUNT);
this.setNextAndPreviousBlockLink();
});
this.stateService.networkChanged$
this.networkChangedSubscription = this.stateService.networkChanged$
.subscribe((network) => this.network = network);
this.route.queryParams.subscribe((params) => {
this.queryParamsSubscription = this.route.queryParams.subscribe((params) => {
if (params.showDetails === 'true') {
this.showDetails = true;
} else {
this.showDetails = false;
}
});
this.keyNavigationSubscription = this.stateService.keyNavigation$.subscribe((event) => {
if (this.showPreviousBlocklink && event.key === 'ArrowRight' && this.nextBlockHeight - 2 >= 0) {
this.navigateToPreviousBlock();
}
if (event.key === 'ArrowLeft') {
if (this.showNextBlocklink) {
this.navigateToNextBlock();
} else {
this.router.navigate([(this.network && this.stateService.env.BASE_MODULE === 'mempool' ? '/' + this.network : '') + '/mempool-block/', '0']);
}
}
});
}
ngOnDestroy() {
this.stateService.markBlock$.next({});
this.subscription.unsubscribe();
this.keyNavigationSubscription.unsubscribe();
this.blocksSubscription.unsubscribe();
this.networkChangedSubscription.unsubscribe();
this.queryParamsSubscription.unsubscribe();
}
setBlockSubsidy() {
@@ -222,4 +263,32 @@ export class BlockComponent implements OnInit, OnDestroy {
onResize(event: any) {
this.paginationMaxSize = event.target.innerWidth < 670 ? 3 : 5;
}
navigateToPreviousBlock() {
if (!this.block) {
return;
}
const block = this.latestBlocks.find((b) => b.height === this.nextBlockHeight - 2);
this.router.navigate([(this.network && this.stateService.env.BASE_MODULE === 'mempool' ? '/' + this.network : '') + '/block/', block ? block.id : this.block.previousblockhash], { state: { data: { block, blockHeight: this.nextBlockHeight - 2 } } });
}
navigateToNextBlock() {
const block = this.latestBlocks.find((b) => b.height === this.nextBlockHeight);
this.router.navigate([(this.network && this.stateService.env.BASE_MODULE === 'mempool' ? '/' + this.network : '') + '/block/', block ? block.id : this.nextBlockHeight], { state: { data: { block, blockHeight: this.nextBlockHeight } } });
}
setNextAndPreviousBlockLink(){
if (this.latestBlock && this.blockHeight) {
if (this.blockHeight === 0){
this.showPreviousBlocklink = false;
} else {
this.showPreviousBlocklink = true;
}
if (this.latestBlock.height && this.latestBlock.height === this.blockHeight) {
this.showNextBlocklink = false;
} else {
this.showNextBlocklink = true;
}
}
}
}

View File

@@ -1,16 +1,16 @@
<div class="blocks-container" *ngIf="(loadingBlocks$ | async) === false; else loadingBlocksTemplate">
<div class="blocks-container blockchain-blocks-container" *ngIf="(loadingBlocks$ | async) === false; else loadingBlocksTemplate">
<div *ngFor="let block of blocks; let i = index; trackBy: trackByBlocksFn" >
<div class="text-center bitcoin-block mined-block" id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]">
<div class="text-center bitcoin-block mined-block blockchain-blocks-{{ i }}" id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]">
<a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }" class="blockLink">&nbsp;</a>
<div class="block-height">
<a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a>
</div>
<div class="block-body">
<div class="fees">
~{{ block.medianFee | number:'1.0-0' }} <ng-container i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
~{{ block.medianFee | number:feeRounding }} <ng-container i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
</div>
<div class="fee-span">
{{ block.feeRange[1] | number:'1.0-0' }} - {{ block.feeRange[block.feeRange.length - 1] | number:'1.0-0' }} <ng-container i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
{{ block.feeRange[1] | number:feeRounding }} - {{ block.feeRange[block.feeRange.length - 1] | number:feeRounding }} <ng-container i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
</div>
<div class="block-size" [innerHTML]="block.size | bytes: 2">&lrm;</div>
<div class="transaction-count">

View File

@@ -25,7 +25,7 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
emptyBlockStyles = [];
interval: any;
tabHidden = false;
feeRounding = '1.0-0';
arrowVisible = false;
arrowLeftPx = 30;
blocksFilled = false;
@@ -46,6 +46,9 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
) { }
ngOnInit() {
if (this.stateService.network === 'liquid') {
this.feeRounding = '1.0-1';
}
this.emptyBlocks.forEach((b) => this.emptyBlockStyles.push(this.getStyleForEmptyBlock(b)));
this.loadingBlocks$ = this.stateService.isLoadingWebSocket$;
this.networkSubscription = this.stateService.networkChanged$.subscribe((network) => this.network = network);
@@ -62,7 +65,7 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
}
this.blocks.unshift(block);
this.blocks = this.blocks.slice(0, 8);
this.blocks = this.blocks.slice(0, this.stateService.env.KEEP_BLOCKS_AMOUNT);
if (this.blocksFilled && !this.tabHidden) {
block.stage = block.matchRate >= 66 ? 1 : 2;
@@ -98,28 +101,6 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
this.moveArrowToPosition(false);
this.cd.markForCheck();
});
this.stateService.keyNavigation$.subscribe((event) => {
if (!this.markHeight) {
return;
}
if (event.key === 'ArrowRight') {
const blockindex = this.blocks.findIndex((b) => b.height === this.markHeight);
if (this.blocks[blockindex + 1]) {
this.router.navigate([(this.network ? '/' + this.network : '') + '/block/',
this.blocks[blockindex + 1].id], { state: { data: { block: this.blocks[blockindex + 1] } } });
}
} else if (event.key === 'ArrowLeft') {
const blockindex = this.blocks.findIndex((b) => b.height === this.markHeight);
if (blockindex === 0) {
this.router.navigate([(this.network ? '/' + this.network : '') + '/mempool-block/', '0']);
} else {
this.router.navigate([(this.network ? '/' + this.network : '') + '/block/',
this.blocks[blockindex - 1].id], { state: { data: { block: this.blocks[blockindex - 1] }}});
}
}
});
}
ngOnDestroy() {
@@ -165,7 +146,7 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
}
getStyleForBlock(block: Block) {
const greenBackgroundHeight = 100 - (block.weight / 4000000) * 100;
const greenBackgroundHeight = 100 - (block.weight / this.stateService.env.BLOCK_WEIGHT_UNITS) * 100;
let addLeft = 0;
if (block.stage === 1) {
@@ -200,7 +181,7 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
mountEmptyBlocks() {
const emptyBlocks = [];
for (let i = 0; i < 8; i++) {
for (let i = 0; i < this.stateService.env.KEEP_BLOCKS_AMOUNT; i++) {
emptyBlocks.push({
id: '',
height: 0,

View File

@@ -1,5 +1,5 @@
<div class="text-center" class="blockchain-wrapper">
<div class="position-container">
<div class="position-container {{ network }}">
<span>
<app-mempool-blocks></app-mempool-blocks>
<app-blockchain-blocks></app-blockchain-blocks>

View File

@@ -26,10 +26,17 @@
top: 75px;
}
.position-container.liquid {
left: 420px;
}
@media (max-width: 767.98px) {
.position-container {
left: 95%;
}
.position-container.liquid {
left: 50%;
}
.position-container.loading {
left: 50%;
}

View File

@@ -1,11 +1,20 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
import { StateService } from 'src/app/services/state.service';
@Component({
selector: 'app-blockchain',
templateUrl: './blockchain.component.html',
styleUrls: ['./blockchain.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BlockchainComponent {
export class BlockchainComponent implements OnInit {
network: string;
constructor() { }
constructor(
private stateService: StateService,
) {}
ngOnInit() {
this.network = this.stateService.network;
}
}

View File

@@ -64,7 +64,7 @@ export class FooterComponent implements OnInit {
return {
size: size,
blocks: Math.ceil(vsize / 1000000)
blocks: Math.ceil(vsize / this.stateService.blockVSize)
};
})
);

View File

@@ -43,6 +43,11 @@ export class LanguageSelectorComponent implements OnInit {
try {
document.cookie = `lang=${language}; expires=Thu, 18 Dec 2050 12:00:00 UTC; path=/`;
} catch (e) { }
this.document.location.href = `/${language}/${this.stateService.network}`;
if (this.stateService.env.BASE_MODULE === 'mempool') {
this.document.location.href = `/${language}/${this.stateService.network}`;
} else {
this.document.location.href = `/${language}`;
}
}
}

View File

@@ -20,7 +20,7 @@
<td class="d-none d-lg-block">{{ block.tx_count | number }}</td>
<td>
<div class="progress">
<div class="progress-bar progress-mempool {{ network$ | async }}" role="progressbar" [ngStyle]="{'width': (block.weight / 4000000)*100 + '%' }"></div>
<div class="progress-bar progress-mempool {{ network$ | async }}" role="progressbar" [ngStyle]="{'width': (block.weight / stateService.env.BLOCK_WEIGHT_UNITS)*100 + '%' }"></div>
<div class="progress-text" [innerHTML]="block.size | bytes: 2"></div>
</div>
</td>

View File

@@ -29,7 +29,7 @@ export class LatestBlocksComponent implements OnInit, OnDestroy {
constructor(
private electrsApiService: ElectrsApiService,
private stateService: StateService,
public stateService: StateService,
private seoService: SeoService,
private websocketService: WebsocketService,
private cd: ChangeDetectorRef,

View File

@@ -0,0 +1,62 @@
<header>
<nav class="navbar navbar-expand-md navbar-dark bg-dark">
<a class="navbar-brand" [routerLink]="['/' | relativeUrl]" style="position: relative;">
<ng-container *ngIf="{ val: connectionState$ | async } as connectionState">
<img src="./resources/liquid/liquid-network-logo.png" height="35" width="140" class="logo" [ngStyle]="{'opacity': connectionState.val === 2 ? 1 : 0.5 }">
<div class="connection-badge">
<div class="badge badge-warning" *ngIf="connectionState.val === 0" i18n="master-page.offline">Offline</div>
<div class="badge badge-warning" *ngIf="connectionState.val === 1" i18n="master-page.reconnecting">Reconnecting...</div>
</div>
</ng-container>
</a>
<div ngbDropdown (window:resize)="onResize($event)" class="dropdown-container" *ngIf="env.TESTNET_ENABLED || env.SIGNET_ENABLED || env.LIQUID_ENABLED || env.BISQ_ENABLED">
<button ngbDropdownToggle type="button" class="btn btn-secondary dropdown-toggle-split" aria-haspopup="true">
<img src="./resources/liquid-logo.png" style="width: 25px; height: 25px;" class="mr-1">
</button>
<div ngbDropdownMenu [ngClass]="{'dropdown-menu-right' : isMobile}">
<a href="https://mempool.space" ngbDropdownItem class="mainnet"><img src="./resources/bitcoin-logo.png" style="width: 30px;" class="mr-1"> Mainnet</a>
<a href="https://mempool.space/signet" ngbDropdownItem *ngIf="env.SIGNET_ENABLED" class="signet"><img src="./resources/signet-logo.png" style="width: 30px;" class="mr-1"> Signet</a>
<a href="https://mempool.space/testnet" ngbDropdownItem *ngIf="env.TESTNET_ENABLED" class="testnet"><img src="./resources/testnet-logo.png" style="width: 30px;" class="mr-1"> Testnet</a>
<h6 class="dropdown-header" i18n="master-page.layer2-networks-header">Layer 2 Networks</h6>
<a href="https://bisq.markets" ngbDropdownItem class="mainnet"><img src="./resources/bisq-logo.png" style="width: 30px;" class="mr-1"> Bisq</a>
<button ngbDropdownItem class="liquid active" routerLink="/"><img src="./resources/liquid-logo.png" style="width: 30px;" class="mr-1"> Liquid</button>
</div>
</div>
<div class="navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav liquid">
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">
<a class="nav-link" [routerLink]="['/' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'tachometer-alt']" [fixedWidth]="true" i18n-title="master-page.dashboard" title="Dashboard"></fa-icon></a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/blocks' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'cubes']" [fixedWidth]="true" i18n-title="master-page.blocks" title="Blocks"></fa-icon></a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/graphs' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'chart-area']" [fixedWidth]="true" i18n-title="master-page.graphs" title="Graphs"></fa-icon></a>
</li>
<!--
<li class="nav-item d-none d-lg-block" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/tv' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'tv']" [fixedWidth]="true" i18n-title="master-page.tvview" title="TV view"></fa-icon></a>
</li>
-->
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/assets']" (click)="collapse()"><fa-icon [icon]="['fas', 'database']" [fixedWidth]="true" i18n-title="master-page.assets" title="Assets"></fa-icon></a>
</li>
<li [hidden]="isMobile" class="nav-item mr-2" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/api' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'cogs']" [fixedWidth]="true" i18n-title="master-page.api" title="API"></fa-icon></a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/about']" (click)="collapse()"><fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true" i18n-title="master-page.about" title="About"></fa-icon></a>
</li>
</ul>
<app-search-form class="search-form-container" location="top" (searchTriggered)="collapse()"></app-search-form>
</div>
</nav>
</header>
<br />
<router-outlet></router-outlet>
<br>

View File

@@ -0,0 +1,135 @@
li.nav-item.active {
background-color: #653b9c;
}
fa-icon {
font-size: 1.66em;
}
.navbar {
z-index: 100;
min-height: 64px;
}
li.nav-item {
margin: auto 10px;
padding-left: 10px;
padding-right: 10px;
}
@media (min-width: 992px) {
.navbar {
padding: 0rem 2rem;
}
fa-icon {
font-size: 1.2em;
}
.dropdown-container {
margin-right: 16px;
}
li.nav-item {
margin: auto 0px;
padding: 10px;
}
}
.navbar-nav {
background: #212121;
bottom: 0;
box-shadow: 0px 0px 15px 0px #000;
flex-direction: row;
left: 0;
justify-content: center;
position: fixed;
width: 100%;
@media (min-width: 992px) {
background: transparent;
box-shadow: none;
position: relative;
width: auto;
}
a {
font-size: 0.8em;
@media (min-width: 375px) {
font-size: 1em;
}
}
}
.navbar-collapse {
flex-basis: auto;
justify-content: flex-end;
}
@media (min-width: 992px) {
.navbar-collapse {
justify-content: space-between;
}
}
.navbar-brand {
width: 60%;
}
@media (min-width: 576px) {
.navbar-brand {
width: 130px;
}
}
nav {
box-shadow: 0px 0px 15px 0px #000;
}
.connection-badge {
position: absolute;
top: 13px;
left: 0px;
width: 130px;
}
.badge {
margin: 0 auto;
display: table;
}
.mainnet.active {
background-color: #653b9c;
}
.liquid.active {
background-color: #116761;
}
.testnet.active {
background-color: #1d486f;
}
.signet.active {
background-color: #6f1d5d;
}
.dropdown-divider {
border-top: 1px solid #121420;
}
.dropdown-toggle::after {
vertical-align: 0.1em;
}
.dropdown-item {
display: flex;
align-items: center;
}
@media (min-width: 992px) {
.search-form-container {
width: 100%;
max-width: 500px;
padding-left: 15px;
}
}
.navbar-dark .navbar-nav .nav-link {
color: #f1f1f1;
}

View File

@@ -0,0 +1,33 @@
import { Component, OnInit } from '@angular/core';
import { Env, StateService } from '../../services/state.service';
import { Observable} from 'rxjs';
@Component({
selector: 'app-liquid-master-page',
templateUrl: './liquid-master-page.component.html',
styleUrls: ['./liquid-master-page.component.scss'],
})
export class LiquidMasterPageComponent implements OnInit {
env: Env;
connectionState$: Observable<number>;
navCollapsed = false;
isMobile = window.innerWidth <= 767.98;
officialMempoolSpace = this.stateService.env.OFFICIAL_MEMPOOL_SPACE;
constructor(
private stateService: StateService,
) { }
ngOnInit() {
this.env = this.stateService.env;
this.connectionState$ = this.stateService.connectionState$;
}
collapse(): void {
this.navCollapsed = !this.navCollapsed;
}
onResize(event: any) {
this.isMobile = window.innerWidth <= 767.98;
}
}

View File

@@ -11,17 +11,19 @@
</ng-container>
</a>
<div ngbDropdown display="dynamic" class="dropdown-container" *ngIf="env.TESTNET_ENABLED || env.SIGNET_ENABLED || env.LIQUID_ENABLED || env.BISQ_ENABLED">
<div (window:resize)="onResize($event)" ngbDropdown class="dropdown-container" *ngIf="env.TESTNET_ENABLED || env.SIGNET_ENABLED || env.LIQUID_ENABLED || env.BISQ_ENABLED">
<button ngbDropdownToggle type="button" class="btn btn-secondary dropdown-toggle-split" aria-haspopup="true">
<img src="./resources/{{ network.val === '' ? 'bitcoin' : network.val }}-logo.png" style="width: 25px; height: 25px;" class="mr-1">
</button>
<div ngbDropdownMenu>
<div ngbDropdownMenu [ngClass]="{'dropdown-menu-right' : isMobile}">
<button ngbDropdownItem class="mainnet" routerLink="/"><img src="./resources/bitcoin-logo.png" style="width: 30px;" class="mr-1"> Mainnet</button>
<button ngbDropdownItem *ngIf="env.SIGNET_ENABLED" class="signet" [class.active]="network.val === 'signet'" routerLink="/signet"><img src="./resources/signet-logo.png" style="width: 30px;" class="mr-1"> Signet</button>
<button ngbDropdownItem *ngIf="env.TESTNET_ENABLED" class="testnet" [class.active]="network.val === 'testnet'" routerLink="/testnet"><img src="./resources/testnet-logo.png" style="width: 30px;" class="mr-1"> Testnet</button>
<h6 *ngIf="env.LIQUID_ENABLED || env.BISQ_ENABLED" class="dropdown-header" i18n="master-page.layer2-networks-header">Layer 2 Networks</h6>
<button ngbDropdownItem *ngIf="env.BISQ_ENABLED" class="bisq" [class.active]="network.val === 'bisq'" routerLink="/bisq"><img src="./resources/bisq-logo.png" style="width: 30px;" class="mr-1"> Bisq</button>
<button ngbDropdownItem *ngIf="env.LIQUID_ENABLED" class="liquid" [class.active]="network.val === 'liquid'" routerLink="/liquid"><img src="./resources/liquid-logo.png" style="width: 30px;" class="mr-1"> Liquid</button>
<a href="https://bisq.markets" ngbDropdownItem *ngIf="env.BISQ_ENABLED && env.OFFICIAL_MEMPOOL_SPACE" class="bisq"><img src="./resources/bisq-logo.png" style="width: 30px;" class="mr-1"> Bisq</a>
<button ngbDropdownItem *ngIf="env.BISQ_ENABLED && !env.OFFICIAL_MEMPOOL_SPACE" class="bisq" [class.active]="network.val === 'bisq'" routerLink="/bisq"><img src="./resources/bisq-logo.png" style="width: 30px;" class="mr-1"> Bisq</button>
<a href="https://liquid.network" ngbDropdownItem *ngIf="env.LIQUID_ENABLED && env.OFFICIAL_MEMPOOL_SPACE" class="liquid" [class.active]="network.val === 'liquid'"><img src="./resources/liquid-logo.png" style="width: 30px;" class="mr-1"> Liquid</a>
<button ngbDropdownItem *ngIf="env.LIQUID_ENABLED && !env.OFFICIAL_MEMPOOL_SPACE" class="liquid" [class.active]="network.val === 'liquid'" routerLink="/liquid"><img src="./resources/liquid-logo.png" style="width: 30px;" class="mr-1"> Liquid</button>
</div>
</div>

View File

@@ -28,4 +28,8 @@ export class MasterPageComponent implements OnInit {
collapse(): void {
this.navCollapsed = !this.navCollapsed;
}
onResize(event: any) {
this.isMobile = window.innerWidth <= 767.98;
}
}

View File

@@ -32,7 +32,7 @@
<td i18n="mempool-block.size">Size</td>
<td>
<div class="progress">
<div class="progress-bar progress-mempool {{ (network$ | async) }}" role="progressbar" [ngStyle]="{'width': (mempoolBlock.blockVSize / 1000000) * 100 + '%' }"></div>
<div class="progress-bar progress-mempool {{ (network$ | async) }}" role="progressbar" [ngStyle]="{'width': (mempoolBlock.blockVSize / stateService.blockVSize) * 100 + '%' }"></div>
<div class="progress-text" [innerHTML]="mempoolBlock.blockSize | bytes: 2"></div>
</div>
</td>

View File

@@ -21,7 +21,7 @@ export class MempoolBlockComponent implements OnInit, OnDestroy {
constructor(
private route: ActivatedRoute,
private stateService: StateService,
public stateService: StateService,
private seoService: SeoService,
private websocketService: WebsocketService,
) { }
@@ -66,7 +66,7 @@ export class MempoolBlockComponent implements OnInit, OnDestroy {
}
getOrdinal(mempoolBlock: MempoolBlock): string {
const blocksInBlock = Math.ceil(mempoolBlock.blockVSize / 1000000);
const blocksInBlock = Math.ceil(mempoolBlock.blockVSize / this.stateService.blockVSize);
if (this.mempoolBlockIndex === 0) {
return $localize`:@@mempool-block.next.block:Next block`;
} else if (this.mempoolBlockIndex === this.stateService.env.KEEP_BLOCKS_AMOUNT - 1 && blocksInBlock > 1) {

View File

@@ -6,10 +6,10 @@
<a [routerLink]="['/mempool-block/' | relativeUrl, i]" class="blockLink">&nbsp;</a>
<div class="block-body">
<div class="fees">
~{{ projectedBlock.medianFee | number:'1.0-0' }} <span i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
~{{ projectedBlock.medianFee | number:feeRounding }} <span i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
</div>
<div class="fee-span">
{{ projectedBlock.feeRange[0] | number:'1.0-0' }} - {{ projectedBlock.feeRange[projectedBlock.feeRange.length - 1] | number:'1.0-0' }} <span i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
{{ projectedBlock.feeRange[0] | number:feeRounding }} - {{ projectedBlock.feeRange[projectedBlock.feeRange.length - 1] | number:feeRounding }} <span i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
</div>
<div class="block-size" [innerHTML]="projectedBlock.blockSize | bytes: 2">&lrm;</div>
<div class="transaction-count">
@@ -17,7 +17,7 @@
<ng-template #transactionsSingular let-i i18n="shared.transaction-count.singular">{{ i }} transaction</ng-template>
<ng-template #transactionsPlural let-i i18n="shared.transaction-count.plural">{{ i }} transactions</ng-template>
</div>
<div class="time-difference" *ngIf="projectedBlock.blockVSize <= 1000000; else mergedBlock">
<div class="time-difference" *ngIf="projectedBlock.blockVSize <= stateService.blockVSize; else mergedBlock">
<ng-template [ngIf]="network === 'liquid'" [ngIfElse]="timeDiffMainnet">
<app-time-until [time]="(1 * i) + now + 61000" [fastRender]="false" [fixedRender]="true"></app-time-until>
</ng-template>
@@ -27,7 +27,7 @@
</div>
<ng-template #mergedBlock>
<div class="time-difference">
<b>(<ng-container *ngTemplateOutlet="blocksPlural; context: {$implicit: projectedBlock.blockVSize / 1000000 | ceil }"></ng-container>)</b>
<b>(<ng-container *ngTemplateOutlet="blocksPlural; context: {$implicit: projectedBlock.blockVSize / stateService.blockVSize | ceil }"></ng-container>)</b>
<ng-template #blocksPlural let-i i18n="shared.blocks">{{ i }} <span class="shared-block">blocks</span></ng-template>
</div>
</ng-template>

View File

@@ -34,6 +34,7 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
blockPadding = 30;
arrowVisible = false;
tabHidden = false;
feeRounding = '1.0-0';
rightPosition = 0;
transition = '2s';
@@ -47,11 +48,14 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
constructor(
private router: Router,
private stateService: StateService,
public stateService: StateService,
private cd: ChangeDetectorRef,
) { }
ngOnInit() {
if (this.stateService.network === 'liquid') {
this.feeRounding = '1.0-1';
}
this.mempoolEmptyBlocks.forEach((b) => {
this.mempoolEmptyBlockStyles.push(this.getStyleForMempoolEmptyBlock(b.index));
});
@@ -153,7 +157,7 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
this.router.navigate([(this.network ? '/' + this.network : '') + '/mempool-block/', this.markIndex - 1]);
} else {
this.stateService.blocks$
.pipe(take(8))
.pipe(take(this.stateService.env.MEMPOOL_BLOCKS_AMOUNT))
.subscribe(([block]) => {
if (this.stateService.latestBlockHeight === block.height) {
this.router.navigate([(this.network ? '/' + this.network : '') + '/block/', block.id], { state: { data: { block } }});
@@ -211,7 +215,7 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
}
getStyleForMempoolBlock(mempoolBlock: MempoolBlock, index: number) {
const emptyBackgroundSpacePercentage = Math.max(100 - mempoolBlock.blockVSize / 1000000 * 100, 0);
const emptyBackgroundSpacePercentage = Math.max(100 - mempoolBlock.blockVSize / this.stateService.blockVSize * 100, 0);
const usedBlockSpace = 100 - emptyBackgroundSpacePercentage;
const backgroundGradients = [`repeating-linear-gradient(to right, #554b45, #554b45 ${emptyBackgroundSpacePercentage}%`];
const gradientColors = [];
@@ -278,7 +282,7 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
const chunkPositionOffset = blockLocation * feeRangeChunkSize;
const feePosition = feeRangeChunkSize * feeRangeIndex + chunkPositionOffset;
const blockedFilledPercentage = (block.blockVSize > 1000000 ? 1000000 : block.blockVSize) / 1000000;
const blockedFilledPercentage = (block.blockVSize > this.stateService.blockVSize ? this.stateService.blockVSize : block.blockVSize) / this.stateService.blockVSize;
const arrowRightPosition = txInBlockIndex * (this.blockWidth + this.blockPadding)
+ ((1 - feePosition) * blockedFilledPercentage * this.blockWidth);
@@ -291,8 +295,8 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
mountEmptyBlocks() {
const emptyBlocks = [];
const numberOfBlocks = 8;
for (let i = 0; i <= numberOfBlocks; i++) {
const numberOfBlocks = this.stateService.env.MEMPOOL_BLOCKS_AMOUNT;
for (let i = 0; i < numberOfBlocks; i++) {
emptyBlocks.push({
blockSize: 0,
blockVSize: 0,

View File

@@ -74,7 +74,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
labelInterpolationFnc: (value: number): any => this.vbytesPipe.transform(value, 2, 'vB', 'MvB', true),
offset: this.showLegend ? 160 : 60,
},
plugins: this.inverted ? [Chartist.plugins.ctTargetLine({ value: 1000000 })] : []
plugins: this.inverted ? [Chartist.plugins.ctTargetLine({ value: this.stateService.blockVSize })] : []
};
if (this.showLegend) {

View File

@@ -119,7 +119,7 @@ export class SearchFormComponent implements OnInit {
}
navigate(url: string, searchText: string) {
this.router.navigate([(this.network ? '/' + this.network : '') + url, searchText]);
this.router.navigate([(this.network && this.stateService.env.BASE_MODULE === 'mempool' ? '/' + this.network : '') + url, searchText]);
this.searchTriggered.emit();
this.searchForm.setValue({
searchText: '',

View File

@@ -0,0 +1,139 @@
import { Transaction } from '../../interfaces/electrs.interface';
// Parse the blinders data from a string encoded as a comma separated list, in the following format:
// <value_in_satoshis>,<asset_tag_hex>,<amount_blinder_hex>,<asset_blinder_hex>
// This can be repeated with a comma separator to specify blinders for multiple outputs.
export class LiquidUnblinding {
commitments: Map<any, any>;
parseBlinders(str: string) {
const parts = str.split(',');
const blinders = [];
while (parts.length) {
blinders.push({
value: this.verifyNum(parts.shift()),
asset: this.verifyHex32(parts.shift()),
value_blinder: this.verifyHex32(parts.shift()),
asset_blinder: this.verifyHex32(parts.shift()),
});
}
return blinders;
}
verifyNum(num: string) {
if (!+num) {
throw new Error('Invalid blinding data (invalid number)');
}
return +num;
}
verifyHex32(str: string) {
if (!str || !/^[0-9a-f]{64}$/i.test(str)) {
throw new Error('Invalid blinding data (invalid hex)');
}
return str;
}
async makeCommitmentMap(blinders: any) {
const libwally = await import('./libwally.js');
await libwally.load();
const commitments = new Map();
blinders.forEach(b => {
const { asset_commitment, value_commitment } =
libwally.generate_commitments(b.value, b.asset, b.value_blinder, b.asset_blinder);
commitments.set(`${asset_commitment}:${value_commitment}`, {
asset: b.asset,
value: b.value,
});
});
return commitments;
}
// Look for the given output, returning an { value, asset } object
find(vout: any) {
return vout.assetcommitment && vout.valuecommitment &&
this.commitments.get(`${vout.assetcommitment}:${vout.valuecommitment}`);
}
// Lookup all transaction inputs/outputs and attach the unblinded data
tryUnblindTx(tx: Transaction) {
if (tx) {
if (tx._unblinded) { return tx; }
let matched = 0;
if (tx.vout !== undefined) {
tx.vout.forEach(vout => matched += +this.tryUnblindOut(vout));
tx.vin.filter(vin => vin.prevout).forEach(vin => matched += +this.tryUnblindOut(vin.prevout));
}
if (this.commitments !== undefined) {
tx._unblinded = { matched, total: this.commitments.size };
this.deduceBlinded(tx);
if (matched < this.commitments.size) {
throw new Error(`Invalid blinding data.`)
}
tx._deduced = false; // invalidate cache so deduction is attempted again
return tx;
}
}
}
// Look the given output and attach the unblinded data
tryUnblindOut(vout: any) {
const unblinded = this.find(vout);
if (unblinded) { Object.assign(vout, unblinded); }
return !!unblinded;
}
// Attempt to deduce the blinded input/output based on the available information
deduceBlinded(tx: any) {
if (tx._deduced) { return; }
tx._deduced = true;
// Find ins/outs with unknown amounts (blinded ant not revealed via the `#blinded` hash fragment)
const unknownIns = tx.vin.filter(vin => vin.prevout && vin.prevout.value == null);
const unknownOuts = tx.vout.filter(vout => vout.value == null);
// If the transaction has a single unknown input/output, we can deduce its asset/amount
// based on the other known inputs/outputs.
if (unknownIns.length + unknownOuts.length === 1) {
// Keep a per-asset tally of all known input amounts, minus all known output amounts
const totals = new Map();
tx.vin.filter(vin => vin.prevout && vin.prevout.value != null)
.forEach(({ prevout }) =>
totals.set(prevout.asset, (totals.get(prevout.asset) || 0) + prevout.value));
tx.vout.filter(vout => vout.value != null)
.forEach(vout =>
totals.set(vout.asset, (totals.get(vout.asset) || 0) - vout.value));
// There should only be a single asset where the inputs and outputs amounts mismatch,
// which is the asset of the blinded input/output
const remainder = Array.from(totals.entries()).filter(([ asset, value ]) => value !== 0);
if (remainder.length !== 1) { throw new Error('unexpected remainder while deducing blinded tx'); }
const [ blindedAsset, blindedValue ] = remainder[0];
// A positive remainder (when known in > known out) is the asset/amount of the unknown blinded output,
// a negative one is the input.
if (blindedValue > 0) {
if (!unknownOuts.length) { throw new Error('expected unknown output'); }
unknownOuts[0].asset = blindedAsset;
unknownOuts[0].value = blindedValue;
} else {
if (!unknownIns.length) { throw new Error('expected unknown input'); }
unknownIns[0].prevout.asset = blindedAsset;
unknownIns[0].prevout.value = blindedValue * -1;
}
}
}
async checkUnblindedTx(tx: Transaction) {
const windowLocationHash = window.location.hash.substring('#blinded='.length);
if (windowLocationHash.length > 0) {
const blinders = this.parseBlinders(windowLocationHash);
if (blinders) {
this.commitments = await this.makeCommitmentMap(blinders);
return this.tryUnblindTx(tx);
}
}
return tx;
}
}

View File

@@ -168,7 +168,7 @@
</a>
</td>
<td class="d-none d-lg-table-cell" [innerHTML]="cpfpInfo.bestDescendant.weight / 4 | vbytes: 2"></td>
<td>{{ cpfpInfo.bestDescendant.fee / (cpfpInfo.bestDescendant.weight / 4) | number : '1.1-1' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></td>
<td>{{ cpfpInfo.bestDescendant.fee / (cpfpInfo.bestDescendant.weight / 4) | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></td>
<td class="d-none d-lg-table-cell"><fa-icon class="arrow-green" [icon]="['fas', 'angle-double-up']" [fixedWidth]="true"></fa-icon></td>
</tr>
</ng-template>
@@ -181,7 +181,7 @@
</a>
</td>
<td class="d-none d-lg-table-cell" [innerHTML]="cpfpTx.weight / 4 | vbytes: 2"></td>
<td>{{ cpfpTx.fee / (cpfpTx.weight / 4) | number : '1.1-1' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></td>
<td>{{ cpfpTx.fee / (cpfpTx.weight / 4) | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></td>
<td class="d-none d-lg-table-cell"><fa-icon *ngIf="roundToOneDecimal(cpfpTx) < roundToOneDecimal(tx)" class="arrow-red" [icon]="['fas', 'angle-double-down']" [fixedWidth]="true"></fa-icon></td>
</tr>
</ng-template>
@@ -331,7 +331,7 @@
<tr>
<td i18n="transaction.fee-rate|Transaction fee rate">Fee rate</td>
<td>
{{ tx.feePerVsize | number : '1.1-1' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
{{ tx.feePerVsize | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
<ng-template [ngIf]="tx.status.confirmed">
&nbsp;
<app-tx-fee-rating *ngIf="tx.fee && ((cpfpInfo && !cpfpInfo.bestDescendant && !cpfpInfo.ancestors.length) || !cpfpInfo)" [tx]="tx"></app-tx-fee-rating>
@@ -342,7 +342,7 @@
<td i18n="transaction.effective-fee-rate|Effective transaction fee rate">Effective fee rate</td>
<td>
<div class="effective-fee-container">
{{ tx.effectiveFeePerVsize | number : '1.1-1' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
{{ tx.effectiveFeePerVsize | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
<ng-template [ngIf]="tx.status.confirmed">
<app-tx-fee-rating class="d-none d-lg-inline ml-2" *ngIf="tx.fee" [tx]="tx"></app-tx-fee-rating>
</ng-template>

View File

@@ -10,13 +10,14 @@ import {
map
} from 'rxjs/operators';
import { Transaction, Block } from '../../interfaces/electrs.interface';
import { of, merge, Subscription, Observable, Subject, timer, combineLatest, } from 'rxjs';
import { of, merge, Subscription, Observable, Subject, timer, combineLatest, from } from 'rxjs';
import { StateService } from '../../services/state.service';
import { WebsocketService } from '../../services/websocket.service';
import { AudioService } from 'src/app/services/audio.service';
import { ApiService } from 'src/app/services/api.service';
import { SeoService } from 'src/app/services/seo.service';
import { CpfpInfo } from 'src/app/interfaces/node-api.interface';
import { LiquidUnblinding } from './liquid-ublinding';
@Component({
selector: 'app-transaction',
@@ -40,9 +41,9 @@ export class TransactionComponent implements OnInit, OnDestroy {
cpfpInfo: CpfpInfo | null;
showCpfpDetails = false;
fetchCpfp$ = new Subject<string>();
commitments: Map<any, any>;
now = new Date().getTime();
timeAvg$: Observable<number>;
liquidUnblinding = new LiquidUnblinding();
constructor(
private route: ActivatedRoute,
@@ -123,10 +124,8 @@ export class TransactionComponent implements OnInit, OnDestroy {
this.subscription = this.route.paramMap
.pipe(
switchMap(async (params: ParamMap) => {
switchMap((params: ParamMap) => {
this.txId = params.get('id') || '';
await this.checkUnblindedTx();
this.seoService.setTitle(
$localize`:@@bisq.transaction.browser-title:Transaction: ${this.txId}:INTERPOLATION:`
);
@@ -155,13 +154,25 @@ export class TransactionComponent implements OnInit, OnDestroy {
transactionObservable$,
this.stateService.mempoolTransactions$
);
}),
switchMap((tx) => {
if (this.network === 'liquid') {
return from(this.liquidUnblinding.checkUnblindedTx(tx))
.pipe(
catchError((error) => {
this.errorUnblinded = error;
return of(tx);
})
)
}
return of(tx);
})
)
.subscribe(
async (tx: Transaction) => {
.subscribe((tx: Transaction) => {
if (!tx) {
return;
}
this.tx = tx;
if (tx.fee === undefined) {
this.tx.fee = 0;
@@ -199,7 +210,6 @@ export class TransactionComponent implements OnInit, OnDestroy {
this.fetchCpfp$.next(this.tx.txid);
}
}
await this.checkUnblindedTx();
},
(error) => {
this.error = error;
@@ -294,145 +304,4 @@ export class TransactionComponent implements OnInit, OnDestroy {
this.fetchCpfpSubscription.unsubscribe();
this.leaveTransaction();
}
// Parse the blinders data from a string encoded as a comma separated list, in the following format:
// <value_in_satoshis>,<asset_tag_hex>,<amount_blinder_hex>,<asset_blinder_hex>
// This can be repeated with a comma separator to specify blinders for multiple outputs.
parseBlinders(str: string) {
const parts = str.split(',');
const blinders = [];
while (parts.length) {
blinders.push({
value: this.verifyNum(parts.shift()),
asset: this.verifyHex32(parts.shift()),
value_blinder: this.verifyHex32(parts.shift()),
asset_blinder: this.verifyHex32(parts.shift()),
});
}
return blinders;
}
verifyNum(num: string) {
if (!+num) {
throw new Error('Invalid blinding data (invalid number)');
}
return +num;
}
verifyHex32(str: string) {
if (!str || !/^[0-9a-f]{64}$/i.test(str)) {
throw new Error('Invalid blinding data (invalid hex)');
}
return str;
}
async makeCommitmentMap(blinders: any) {
const libwally = await import('./libwally.js');
await libwally.load();
const commitments = new Map();
blinders.forEach(b => {
const { asset_commitment, value_commitment } =
libwally.generate_commitments(b.value, b.asset, b.value_blinder, b.asset_blinder);
commitments.set(`${asset_commitment}:${value_commitment}`, {
asset: b.asset,
value: b.value,
});
});
return commitments;
}
// Look for the given output, returning an { value, asset } object
find(vout: any) {
return vout.assetcommitment && vout.valuecommitment &&
this.commitments.get(`${vout.assetcommitment}:${vout.valuecommitment}`);
}
// Lookup all transaction inputs/outputs and attach the unblinded data
tryUnblindTx(tx: any) {
if (tx) {
if (tx._unblinded) { return tx._unblinded; }
let matched = 0;
if (tx.vout !== undefined) {
tx.vout.forEach(vout => matched += +this.tryUnblindOut(vout));
tx.vin.filter(vin => vin.prevout).forEach(vin => matched += +this.tryUnblindOut(vin.prevout));
}
if (this.commitments !== undefined) {
tx._unblinded = { matched, total: this.commitments.size };
this.deduceBlinded(tx);
if (matched < this.commitments.size) {
this.errorUnblinded = `Error: Invalid blinding data.`;
}
tx._deduced = false; // invalidate cache so deduction is attempted again
return tx._unblinded;
}
}
}
// Look the given output and attach the unblinded data
tryUnblindOut(vout: any) {
const unblinded = this.find(vout);
if (unblinded) { Object.assign(vout, unblinded); }
return !!unblinded;
}
// Attempt to deduce the blinded input/output based on the available information
deduceBlinded(tx: any) {
if (tx._deduced) { return; }
tx._deduced = true;
// Find ins/outs with unknown amounts (blinded ant not revealed via the `#blinded` hash fragment)
const unknownIns = tx.vin.filter(vin => vin.prevout && vin.prevout.value == null);
const unknownOuts = tx.vout.filter(vout => vout.value == null);
// If the transaction has a single unknown input/output, we can deduce its asset/amount
// based on the other known inputs/outputs.
if (unknownIns.length + unknownOuts.length === 1) {
// Keep a per-asset tally of all known input amounts, minus all known output amounts
const totals = new Map();
tx.vin.filter(vin => vin.prevout && vin.prevout.value != null)
.forEach(({ prevout }) =>
totals.set(prevout.asset, (totals.get(prevout.asset) || 0) + prevout.value));
tx.vout.filter(vout => vout.value != null)
.forEach(vout =>
totals.set(vout.asset, (totals.get(vout.asset) || 0) - vout.value));
// There should only be a single asset where the inputs and outputs amounts mismatch,
// which is the asset of the blinded input/output
const remainder = Array.from(totals.entries()).filter(([ asset, value ]) => value !== 0);
if (remainder.length !== 1) { throw new Error('unexpected remainder while deducing blinded tx'); }
const [ blindedAsset, blindedValue ] = remainder[0];
// A positive remainder (when known in > known out) is the asset/amount of the unknown blinded output,
// a negative one is the input.
if (blindedValue > 0) {
if (!unknownOuts.length) { throw new Error('expected unknown output'); }
unknownOuts[0].asset = blindedAsset;
unknownOuts[0].value = blindedValue;
} else {
if (!unknownIns.length) { throw new Error('expected unknown input'); }
unknownIns[0].prevout.asset = blindedAsset;
unknownIns[0].prevout.value = blindedValue * -1;
}
}
}
async checkUnblindedTx() {
try {
if (this.network === 'liquid') {
const windowLocationHash = window.location.hash.substring('#blinded='.length);
if (windowLocationHash.length > 0) {
const blinders = this.parseBlinders(windowLocationHash);
if (blinders) {
this.commitments = await this.makeCommitmentMap(blinders);
this.tryUnblindTx(this.tx);
}
}
}
} catch (error) {
this.errorUnblinded = error;
}
}
}

View File

@@ -20,7 +20,7 @@
<div class="col">
<table class="table table-borderless smaller-text table-sm" id="table-tx-vin">
<tbody>
<ng-template ngFor let-vin [ngForOf]="tx['@vinLimit'] ? tx.vin.slice(0, 10) : tx.vin" [ngForTrackBy]="trackByIndexFn">
<ng-template ngFor let-vin [ngForOf]="tx['@vinLimit'] ? ((tx.vin.length>12)?tx.vin.slice(0, 10): tx.vin.slice(0, 12)) : tx.vin" [ngForTrackBy]="trackByIndexFn">
<tr [ngClass]="assetsMinimal && vin.prevout && assetsMinimal[vin.prevout.asset] && !vin.is_coinbase && vin.prevout.scriptpubkey_address && tx._unblinded ? 'assetBox' : ''">
<td class="arrow-td">
<ng-template [ngIf]="vin.prevout === null && !vin.is_pegin" [ngIfElse]="hasPrevout">
@@ -109,7 +109,7 @@
</td>
</tr>
</ng-template>
<tr *ngIf="tx.vin.length > 10 && tx['@vinLimit']">
<tr *ngIf="tx.vin.length > 12 && tx['@vinLimit']">
<td colspan="3" class="text-center">
<button class="btn btn-sm btn-primary mt-2" (click)="tx['@vinLimit'] = false;"><span i18n="transactions-list.load-all">Load all</span> ({{ tx.vin.length - 10 }})</button>
</td>
@@ -121,7 +121,7 @@
<div class="col mobile-bottomcol">
<table class="table table-borderless smaller-text table-sm" id="table-tx-vout">
<tbody>
<ng-template ngFor let-vout let-vindex="index" [ngForOf]="tx['@voutLimit'] ? tx.vout.slice(0, 10) : tx.vout" [ngForTrackBy]="trackByIndexFn">
<ng-template ngFor let-vout let-vindex="index" [ngForOf]="tx['@voutLimit'] ?((tx.vout.length>12)?tx.vout.slice(0, 10): tx.vout.slice(0, 12)) : tx.vout" [ngForTrackBy]="trackByIndexFn">
<tr [ngClass]="assetsMinimal && assetsMinimal[vout.asset] && vout.scriptpubkey_address && tx.vin && !tx.vin[0].is_coinbase && tx._unblinded ? 'assetBox' : ''">
<td>
<a *ngIf="vout.scriptpubkey_address; else scriptpubkey_type" [routerLink]="['/address/' | relativeUrl, vout.scriptpubkey_address]" title="{{ vout.scriptpubkey_address }}">
@@ -197,7 +197,7 @@
</td>
</tr>
</ng-template>
<tr *ngIf="tx.vout.length > 10 && tx['@voutLimit']">
<tr *ngIf="tx.vout.length > 12 && tx['@voutLimit']">
<td colspan="3" class="text-center">
<button class="btn btn-sm btn-primary mt-2" (click)="tx['@voutLimit'] = false;"><span i18n="transactions-list.load-all">Load all</span> ({{ tx.vout.length - 10 }})</button>
</td>
@@ -209,7 +209,7 @@
<div>
<div class="float-left mt-2-5" *ngIf="!transactionPage && tx.fee">
{{ tx.fee / (tx.weight / 4) | number : '1.1-1' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> <span class="d-none d-sm-inline-block">&nbsp;&ndash; {{ tx.fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span> <span class="fiat"><app-fiat [value]="tx.fee"></app-fiat></span></span>
{{ tx.fee / (tx.weight / 4) | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> <span class="d-none d-sm-inline-block">&nbsp;&ndash; {{ tx.fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span> <span class="fiat"><app-fiat [value]="tx.fee"></app-fiat></span></span>
</div>
<div class="float-right">

View File

@@ -1,3 +1,3 @@
<span *ngIf="feeRating === 1" class="badge badge-success" i18n="tx-fee-rating.optimal|TX Fee Rating is Optimal">Optimal</span>
<span *ngIf="feeRating === 2" class="badge badge-warning" placement="bottom" i18n-ngbTooltip="tx-fee-rating.warning-tooltip" ngbTooltip="Only ~{{ medianFeeNeeded | number : '1.1-1' }} sat/vB was needed to get into this block" i18n="tx-fee-rating.overpaid.warning|TX Fee Rating is Warning">Overpaid {{ overpaidTimes }}x</span>
<span *ngIf="feeRating === 3" class="badge badge-danger" placement="bottom" i18n-ngbTooltip="tx-fee-rating.warning-tooltip" ngbTooltip="Only ~{{ medianFeeNeeded | number : '1.1-1' }} sat/vB was needed to get into this block" i18n="tx-fee-rating.overpaid.warning|TX Fee Rating is Warning">Overpaid {{ overpaidTimes }}x</span>
<span *ngIf="feeRating === 2" class="badge badge-warning" placement="bottom" i18n-ngbTooltip="tx-fee-rating.warning-tooltip" ngbTooltip="Only ~{{ medianFeeNeeded | feeRounding }} sat/vB was needed to get into this block" i18n="tx-fee-rating.overpaid.warning|TX Fee Rating is Warning">Overpaid {{ overpaidTimes }}x</span>
<span *ngIf="feeRating === 3" class="badge badge-danger" placement="bottom" i18n-ngbTooltip="tx-fee-rating.warning-tooltip" ngbTooltip="Only ~{{ medianFeeNeeded | feeRounding }} sat/vB was needed to get into this block" i18n="tx-fee-rating.overpaid.warning|TX Fee Rating is Warning">Overpaid {{ overpaidTimes }}x</span>

View File

@@ -56,7 +56,7 @@ export class TxFeeRatingComponent implements OnInit, OnChanges, OnDestroy {
this.medianFeeNeeded = block.medianFee;
// Block not filled
if (block.weight < 4000000 * 0.95) {
if (block.weight < this.stateService.env.BLOCK_WEIGHT_UNITS * 0.95) {
this.medianFeeNeeded = 1;
}

View File

@@ -2,7 +2,7 @@
<div class="container-xl dashboard-container">
<div class="row row-cols-1 row-cols-md-2" *ngIf="{ value: (mempoolInfoData$ | async) } as mempoolInfoData">
<ng-template [ngIf]="collapseLevel === 'three'" [ngIfElse]="expanded">
<div class="col card-wrapper">
<div class="col card-wrapper" *ngIf="(network$ | async) !== 'liquid'">
<div class="main-title" i18n="fees-box.transaction-fees">Transaction Fees</div>
<div class="card">
<div class="card-body">
@@ -10,7 +10,7 @@
</div>
</div>
</div>
<div class="col" *ngIf="(network$ | async) !== 'liquid'; else emptyBlock">
<div class="col" *ngIf="(network$ | async) !== 'liquid'">
<ng-container *ngTemplateOutlet="difficultyEpoch"></ng-container>
</div>
<div class="col">
@@ -27,10 +27,9 @@
</div>
</div>
</div>
</ng-template>
<ng-template #expanded>
<div class="col card-wrapper">
<div class="col card-wrapper" *ngIf="(network$ | async) !== 'liquid'">
<div class="main-title" i18n="fees-box.transaction-fees">Transaction Fees</div>
<div class="card">
<div class="card-body">
@@ -38,7 +37,7 @@
</div>
</div>
</div>
<div class="col" *ngIf="(network$ | async) !== 'liquid'; else emptyBlock">
<div class="col" *ngIf="(network$ | async) !== 'liquid'">
<ng-container *ngTemplateOutlet="difficultyEpoch"></ng-container>
</div>
<div class="col">
@@ -89,7 +88,7 @@
<td class="table-cell-transaction-count">{{ block.tx_count | number }}</td>
<td class="table-cell-size">
<div class="progress">
<div class="progress-bar progress-mempool {{ network$ | async }}" role="progressbar" [ngStyle]="{'width': (block.weight / 4000000)*100 + '%' }">&nbsp;</div>
<div class="progress-bar progress-mempool {{ network$ | async }}" role="progressbar" [ngStyle]="{'width': (block.weight / stateService.env.BLOCK_WEIGHT_UNITS)*100 + '%' }">&nbsp;</div>
<div class="progress-text" [innerHTML]="block.size | bytes: 2"></div>
</div>
</td>
@@ -114,9 +113,9 @@
<tbody>
<tr *ngFor="let transaction of transactions$ | async; let i = index;">
<td class="table-cell-txid"><a [routerLink]="['/tx' | relativeUrl, transaction.txid]">{{ transaction.txid | shortenString : 10 }}</a></td>
<td class="table-cell-satoshis"><app-amount [satoshis]="transaction.value" digitsInfo="1.2-4" [noFiat]="true"></app-amount></td>
<td class="table-cell-satoshis"><app-amount *ngIf="(network$ | async) !== 'liquid'; else liquidAmount" [satoshis]="transaction.value" digitsInfo="1.2-4" [noFiat]="true"></app-amount><ng-template #liquidAmount i18n="shared.confidential">Confidential</ng-template></td>
<td class="table-cell-fiat" *ngIf="(network$ | async) === ''" ><app-fiat [value]="transaction.value" digitsInfo="1.0-0"></app-fiat></td>
<td class="table-cell-fees">{{ transaction.fee / transaction.vsize | number : '1.1-1' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></td>
<td class="table-cell-fees">{{ transaction.fee / transaction.vsize | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></td>
</tr>
</tbody>
</table>
@@ -166,10 +165,10 @@
<ng-template #mempoolTable let-mempoolInfoData>
<div class="mempool-info-data">
<div class="item">
<h5 *ngIf="!mempoolInfoData.value || mempoolInfoData.value.memPoolInfo.mempoolminfee === 0.00001 else purgingText" class="card-title" i18n="dashboard.minimum-fee|Minimum mempool fee">Minimum fee</h5>
<h5 *ngIf="!mempoolInfoData.value || mempoolInfoData.value.memPoolInfo.mempoolminfee === 0.00001 || (stateService.env.BASE_MODULE === 'liquid' && mempoolInfoData.value.memPoolInfo.mempoolminfee === 0.000001) else purgingText" class="card-title" i18n="dashboard.minimum-fee|Minimum mempool fee">Minimum fee</h5>
<ng-template #purgingText><h5 class="card-title" i18n="dashboard.purging|Purgin below fee">Purging</h5></ng-template>
<p class="card-text" *ngIf="(isLoadingWebSocket$ | async) === false && mempoolInfoData.value; else loading">
<ng-template [ngIf]="mempoolInfoData.value.memPoolInfo.mempoolminfee > 0.00001">&lt; </ng-template>{{ mempoolInfoData.value.memPoolInfo.mempoolminfee * 100000 | number : '1.1-1' }} <span><ng-container i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container></span>
<ng-template [ngIf]="mempoolInfoData.value.memPoolInfo.mempoolminfee > 0.00001">&lt; </ng-template>{{ mempoolInfoData.value.memPoolInfo.mempoolminfee * 100000 | feeRounding }} <span><ng-container i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container></span>
</p>
</div>
<div class="item">

View File

@@ -66,7 +66,7 @@ export class DashboardComponent implements OnInit {
constructor(
@Inject(LOCALE_ID) private locale: string,
private stateService: StateService,
public stateService: StateService,
private apiService: ApiService,
private websocketService: WebsocketService,
private seoService: SeoService,
@@ -116,7 +116,7 @@ export class DashboardComponent implements OnInit {
};
})
);
this.difficultyEpoch$ = timer(0, 1000)
.pipe(
switchMap(() => combineLatest([
@@ -128,72 +128,65 @@ export class DashboardComponent implements OnInit {
const now = new Date().getTime() / 1000;
const diff = now - DATime;
const blocksInEpoch = block.height % 2016;
const estimatedBlocks = Math.round(diff / 60 / 10);
let difficultyChange = 0;
const progress = (blocksInEpoch >= 0) ? (blocksInEpoch / 2016 * 100).toFixed(2) : `100`;
const remainingBlocks = 2016 - blocksInEpoch;
const newDifficultyHeight = block.height + remainingBlocks;
let change = 0;
if (blocksInEpoch > 0) {
difficultyChange = (600 / (diff / blocksInEpoch ) - 1) * 100;
change = (600 / (diff / blocksInEpoch ) - 1) * 100;
}
if (change > 300) {
change = 300;
}
if (change < -75) {
change = -75;
}
let base = 0;
const timeAvgDiff = change * 0.1;
if (blocksInEpoch >= estimatedBlocks) {
base = estimatedBlocks / 2016 * 100;
let timeAvgMins = 10;
if (timeAvgDiff > 0) {
timeAvgMins -= Math.abs(timeAvgDiff);
} else {
base = blocksInEpoch / 2016 * 100;
timeAvgMins += Math.abs(timeAvgDiff);
}
let colorAdjustments = '#dc3545';
if (difficultyChange >= 0) {
const timeAvg = timeAvgMins.toFixed(0);
const remainingTime = (remainingBlocks * timeAvgMins * 60 * 1000) + (now * 1000);
let colorAdjustments = '#ffffff66';
if (change > 0) {
colorAdjustments = '#3bcc49';
}
if (change < 0) {
colorAdjustments = '#dc3545';
}
let colorPreviousAdjustments = '#dc3545';
if(previousRetarget){
if (previousRetarget){
if (previousRetarget >= 0) {
colorPreviousAdjustments = '#3bcc49';
}
if (previousRetarget === 0) {
colorPreviousAdjustments = '#ffffff66';
}
}else{
} else {
colorPreviousAdjustments = '#ffffff66';
}
const timeAvgDiff = difficultyChange * 0.1;
let timeAvgMins = 10;
if(timeAvgDiff > timeAvgDiff){
if (timeAvgDiff > 0){
timeAvgMins -= Math.abs(timeAvgDiff);
} else {
timeAvgMins += Math.abs(timeAvgDiff);
}
}
const remainingBlocks = 2016 - blocksInEpoch;
const nowMilliseconds = now * 1000;
const timeAvgMilliseconds = timeAvgMins * 60 * 1000;
const remainingBlocsMilliseconds = remainingBlocks * timeAvgMilliseconds;
if(difficultyChange > 300) {
difficultyChange = 300;
}
if(difficultyChange < -75){
difficultyChange = -75;
}
return {
base: base + '%',
change: difficultyChange,
progress: base.toFixed(2),
base: `${progress}%`,
change,
progress,
remainingBlocks,
timeAvg: timeAvgMins.toFixed(0),
timeAvg,
colorAdjustments,
colorPreviousAdjustments,
blocksInEpoch,
newDifficultyHeight: block.height + remainingBlocks,
remainingTime: remainingBlocsMilliseconds + nowMilliseconds,
previousRetarget: previousRetarget ? previousRetarget : 0
newDifficultyHeight,
remainingTime,
previousRetarget,
};
})
);
@@ -207,7 +200,7 @@ export class DashboardComponent implements OnInit {
return {
size: size,
blocks: Math.ceil(vsize / 1000000)
blocks: Math.ceil(vsize / this.stateService.blockVSize)
};
})
);

View File

@@ -27,6 +27,10 @@ export class SeoService {
}
getTitle(): string {
if (this.network === 'liquid')
return 'mempool - Liquid Network';
if (this.network === 'bisq')
return 'mempool - Bisq Markets';
return 'mempool - ' + (this.network ? this.ucfirst(this.network) : 'Bitcoin') + ' Explorer';
}

View File

@@ -24,10 +24,12 @@ export interface Env {
ITEMS_PER_PAGE: number;
KEEP_BLOCKS_AMOUNT: number;
OFFICIAL_MEMPOOL_SPACE: boolean;
OFFICIAL_BISQ_MARKETS: boolean;
BASE_MODULE: string;
NGINX_PROTOCOL?: string;
NGINX_HOSTNAME?: string;
NGINX_PORT?: string;
BLOCK_WEIGHT_UNITS: number;
MEMPOOL_BLOCKS_AMOUNT: number;
GIT_COMMIT_HASH: string;
PACKAGE_JSON_VERSION: string;
}
@@ -36,7 +38,7 @@ const defaultEnv: Env = {
'TESTNET_ENABLED': false,
'SIGNET_ENABLED': false,
'LIQUID_ENABLED': false,
'OFFICIAL_BISQ_MARKETS': false,
'BASE_MODULE': 'mempool',
'BISQ_ENABLED': false,
'BISQ_SEPARATE_BACKEND': false,
'ITEMS_PER_PAGE': 10,
@@ -45,6 +47,8 @@ const defaultEnv: Env = {
'NGINX_PROTOCOL': 'http',
'NGINX_HOSTNAME': '127.0.0.1',
'NGINX_PORT': '80',
'BLOCK_WEIGHT_UNITS': 4000000,
'MEMPOOL_BLOCKS_AMOUNT': 8,
'GIT_COMMIT_HASH': '',
'PACKAGE_JSON_VERSION': '',
};
@@ -55,6 +59,7 @@ const defaultEnv: Env = {
export class StateService {
isBrowser: boolean = isPlatformBrowser(this.platformId);
network = '';
blockVSize: number;
env: Env;
latestBlockHeight = 0;
@@ -88,11 +93,10 @@ export class StateService {
@Inject(PLATFORM_ID) private platformId: any,
private router: Router,
) {
this.router.events.subscribe((event) => {
if (event instanceof NavigationStart) {
this.setNetworkBasedonUrl(event.url);
}
});
const browserWindow = window || {};
// @ts-ignore
const browserWindowEnv = browserWindow.__env || {};
this.env = Object.assign(defaultEnv, browserWindowEnv);
if (this.isBrowser) {
this.setNetworkBasedonUrl(window.location.pathname);
@@ -102,15 +106,26 @@ export class StateService {
this.isTabHidden$ = new BehaviorSubject(false);
}
const browserWindow = window || {};
// @ts-ignore
const browserWindowEnv = browserWindow.__env || {};
this.env = Object.assign(defaultEnv, browserWindowEnv);
this.router.events.subscribe((event) => {
if (event instanceof NavigationStart) {
this.setNetworkBasedonUrl(event.url);
}
});
this.blocks$ = new ReplaySubject<[Block, boolean]>(this.env.KEEP_BLOCKS_AMOUNT);
if (this.env.BASE_MODULE !== 'mempool') {
this.network = this.env.BASE_MODULE;
this.networkChanged$.next(this.env.BASE_MODULE);
}
this.blockVSize = this.env.BLOCK_WEIGHT_UNITS / 4;
}
setNetworkBasedonUrl(url: string) {
if (this.env.BASE_MODULE !== 'mempool') {
return;
}
const networkMatches = url.match(/\/(bisq|testnet|liquid|signet)/);
switch (networkMatches && networkMatches[1]) {
case 'liquid':

View File

@@ -0,0 +1,8 @@
import { FeeRoundingPipe } from './fee-rounding.pipe';
describe('FeeRoundingPipe', () => {
it('create an instance', () => {
const pipe = new FeeRoundingPipe();
expect(pipe).toBeTruthy();
});
});

View File

@@ -0,0 +1,15 @@
import { Pipe, PipeTransform } from "@angular/core";
@Pipe({
name: "feeRounding",
})
export class FeeRoundingPipe implements PipeTransform {
transform(fee: number): string {
if (fee >= 100) {
return fee.toFixed(0);
} else if (fee < 10) {
return fee.toFixed(2);
}
return fee.toFixed(1);
}
}

View File

@@ -11,6 +11,9 @@ export class RelativeUrlPipe implements PipeTransform {
) { }
transform(value: string): string {
if (this.stateService.env.BASE_MODULE !== 'mempool') {
return '/' + value;
}
return (this.stateService.network ? '/' + this.stateService.network : '') + value;
}

View File

@@ -5,6 +5,7 @@ import { ShortenStringPipe } from './pipes/shorten-string-pipe/shorten-string.pi
import { CeilPipe } from './pipes/math-ceil/math-ceil.pipe';
import { Hex2asciiPipe } from './pipes/hex2ascii/hex2ascii.pipe';
import { Decimal2HexPipe } from './pipes/decimal2hex/decimal2hex.pipe';
import { FeeRoundingPipe } from './pipes/fee-rounding/fee-rounding.pipe';
import { AsmStylerPipe } from './pipes/asm-styler/asm-styler.pipe';
import { AbsolutePipe } from './pipes/absolute/absolute.pipe';
import { RelativeUrlPipe } from './pipes/relative-url/relative-url.pipe';
@@ -44,6 +45,7 @@ import { ColoredPriceDirective } from './directives/colored-price.directive';
CeilPipe,
ShortenStringPipe,
Decimal2HexPipe,
FeeRoundingPipe,
ColoredPriceDirective,
],
imports: [
@@ -87,6 +89,7 @@ import { ColoredPriceDirective } from './directives/colored-price.directive';
CeilPipe,
ShortenStringPipe,
Decimal2HexPipe,
FeeRoundingPipe,
ColoredPriceDirective,
]
})

View File

@@ -2,33 +2,34 @@
<html lang="en">
<head>
<meta charset="utf-8">
<title>Bisq Markets</title>
<title>mempool - Bisq Markets</title>
<base href="/">
<meta name="description" content="Bisq is an open-source desktop application that allows you to buy and sell bitcoins in exchange for national currencies, or alternative cryptocurrencies.">
<meta name="description" content="An open-source explorer developed for the Bisq community.">
<meta property="og:image" content="https://bisq.network/images/bisq-og.jpg">
<meta property="og:image" content="https://bisq.markets/resources/bisq/bisq-markets-preview.png" />
<meta property="og:image:type" content="image/jpeg" />
<meta property="twitter:card" content="summary_large_image">
<meta property="twitter:site" content="https://bisq.markets/">
<meta property="twitter:creator" content="@bisq_network">
<meta property="twitter:title" content="Bisq Markets">
<meta property="twitter:description" content="Bisq is an open-source desktop application that allows you to buy and sell bitcoins in exchange for national currencies, or alternative cryptocurrencies.">
<meta property="twitter:image:src" content="https://bisq.network/images/bisq-og.jpg">
<meta property="twitter:title" content="mempool - Bisq Markets">
<meta property="twitter:description" content="An open-source explorer developed for the Bisq community.">
<meta property="twitter:image:src" content="https://bisq.markets/resources/bisq/bisq-markets-preview.png" />
<meta property="twitter:domain" content="bisq.markets">
<meta name="msapplication-TileColor" content="#000000">
<meta name="msapplication-config" content="/resources/favicons/browserconfig.xml">
<meta name="msapplication-config" content="/resources/bisq/favicons/browserconfig.xml">
<meta name="theme-color" content="#ffffff">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="apple-touch-icon" sizes="180x180" href="https://bisq.network/images/apple-touch-icon.png">
<link rel="mask-icon" href="https://bisq.network/safari-pinned-tab.svg" color="#404040">
<link rel="icon" type="image/png" sizes="32x32" href="https://bisq.network/images/bisq-fav.png">
<link rel="icon" type="image/png" sizes="16x16" href="https://bisq.network/images/bisq-fav.png">
<link rel="icon" type="image/x-icon" href="https://bisq.network/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/resources/bisq/favicons/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/resources/bisq/favicons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/resources/bisq/favicons/favicon-16x16.png">
<link rel="manifest" href="/resources/bisq/favicons/site.webmanifest">
<link rel="mask-icon" href="/resources/bisq/favicons/safari-pinned-tab.svg" color="#5bbad5">
<link rel="shortcut icon" href="/resources/bisq/favicons/favicon.ico">
<link id="canonical" rel="canonical" href="https://bisq.markets">

View File

@@ -0,0 +1,53 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>mempool - Liquid Network</title>
<base href="/">
<meta name="description" content="An open-source explorer developed for the Liquid Network.">
<meta property="og:image" content="https://liquid.network/resources/liquid/liquid-network-preview.png" />
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1000" />
<meta property="og:image:height" content="500" />
<meta property="twitter:card" content="summary_large_image">
<meta property="twitter:site" content="@mempool">
<meta property="twitter:creator" content="@mempool">
<meta property="twitter:title" content="mempool - Liquid Network">
<meta property="twitter:description" content="An open-source explorer developed for the Liquid Network.">
<meta property="twitter:image:src" content="https://liquid.network/resources/liquid/liquid-network-preview.png" />
<meta property="twitter:domain" content="liquid.network">
<link rel="apple-touch-icon" sizes="180x180" href="/resources/liquid/favicons/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="48x48" href="/resources/liquid/favicons/favicon-48x48.png">
<link rel="icon" type="image/png" sizes="32x32" href="/resources/liquid/favicons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="24x24" href="/resources/liquid/favicons/favicon-24x24.png">
<link rel="icon" type="image/png" sizes="16x16" href="/resources/liquid/favicons/favicon-16x16.png">
<link rel="manifest" href="/resources/liquid/favicons/site.webmanifest">
<link id="canonical" rel="canonical" href="https://liquid.network">
<meta name="msapplication-TileColor" content="#000000">
<meta name="msapplication-config" content="/resources/liquid/favicons/browserconfig.xml">
<meta name="theme-color" content="#ffffff">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<app-root></app-root>
<script type="text/javascript">
if (document.location.hostname === "liquid.network")
{
var _paq = window._paq = window._paq || [];
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="//stats.liquid.network/";
_paq.push(['setTrackerUrl', u+'m.php']);
_paq.push(['setSiteId', '8']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.type='text/javascript'; g.async=true; g.src=u+'m.js'; s.parentNode.insertBefore(g,s);
})();
}
</script>
</body>
</html>

View File

@@ -3623,7 +3623,7 @@
</trans-unit>
<trans-unit datatype="html" id="071dc2ed21e40ad2199ea5dda884f48c0414a42a">
<source>sat</source>
<target>sat</target>
<target>سات</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/transaction/transaction.component.html</context>
<context context-type="linenumber">329,332</context>
@@ -3643,7 +3643,7 @@
</trans-unit>
<trans-unit datatype="html" id="7e06b8dd9f29261827018351cd71efe1c87839de">
<source>Coinbase</source>
<target>Coinbase</target>
<target>كوين بيس</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/transactions-list/transactions-list.component.html</context>
<context context-type="linenumber">44</context>
@@ -3786,7 +3786,7 @@
</trans-unit>
<trans-unit datatype="html" id="83b8b9dd1ed416447cf8438460a37b0ddeb2677c">
<source>sat</source>
<target>sat</target>
<target>سات</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/transactions-list/transactions-list.component.html</context>
<context context-type="linenumber">212</context>
@@ -3805,7 +3805,7 @@
</trans-unit>
<trans-unit datatype="html" id="0ca27703757bf9a636a0b9b61e6a0cf248781cb4">
<source>SegWit</source>
<target>SegWit</target>
<target>سيقويت</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/tx-features/tx-features.component.html</context>
<context context-type="linenumber">1</context>

View File

@@ -51,7 +51,7 @@
</trans-unit>
<trans-unit datatype="html" id="ngb.datepicker.select-month">
<source>Select month</source>
<target>Sélectionne le mois</target>
<target>Sélectionner le mois</target>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/@ng-bootstrap/ng-bootstrap/__ivy_ngcc__/fesm2015/@ng-bootstrap/ng-bootstrap/datepicker/datepicker-navigation-select.ts</context>
<context context-type="linenumber">74</context>
@@ -3270,7 +3270,7 @@
</trans-unit>
<trans-unit datatype="html" id="time-since">
<source><x equiv-text="dateStrings.i18nYear" id="DATE"/> ago</source>
<target><x equiv-text="dateStrings.i18nYear" id="DATE"/> depuis</target>
<target>Il y a <x equiv-text="dateStrings.i18nYear" id="DATE"/> </target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/time-since/time-since.component.ts</context>
<context context-type="linenumber">67</context>

View File

@@ -2375,6 +2375,7 @@
</trans-unit>
<trans-unit datatype="html" id="8f3ac0544491bf048120928d39e02a6baeb0b278">
<source>Privacy Policy</source>
<target>Personvernerklæring</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/api-docs/api-docs.component.html</context>
<context context-type="linenumber">880,885</context>
@@ -3585,6 +3586,7 @@
</trans-unit>
<trans-unit datatype="html" id="a2fbb7745c7048af923991e08ccd6975364be90d">
<source>Transaction Hex</source>
<target>Transaksjon Hex</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/transaction/transaction.component.html</context>
<context context-type="linenumber">220,221</context>
@@ -3908,6 +3910,7 @@
</trans-unit>
<trans-unit datatype="html" id="86d26b45470e43b409e589517922276109138e87">
<source>Transaction Fees</source>
<target>Transaksjonsavgift</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dashboard/dashboard.component.html</context>
<context context-type="linenumber">6,9</context>
@@ -4061,6 +4064,7 @@
</trans-unit>
<trans-unit datatype="html" id="680d5c75b7fd8d37961083608b9fcdc4167b4c43">
<source>Previous</source>
<target>Forrige</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dashboard/dashboard.component.html</context>
<context context-type="linenumber">235,236</context>

View File

@@ -1583,6 +1583,7 @@
</trans-unit>
<trans-unit datatype="html" id="42b59f1f3dfde09c4c04f711307847438e057efb">
<source>General</source>
<target>Splošno</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/api-docs/api-docs.component.html</context>
<context context-type="linenumber">9,11</context>
@@ -1933,6 +1934,7 @@
</trans-unit>
<trans-unit datatype="html" id="2761d0de651f1c4395e6e7fbc7fded09918f8dcb">
<source>Returns details about difficulty adjustment.</source>
<target>Vrne podrobnosti o prilagoditvi težavnosti.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/api-docs/api-docs.component.html</context>
<context context-type="linenumber">24,26</context>
@@ -2046,6 +2048,7 @@
</trans-unit>
<trans-unit datatype="html" id="2ca235ae8c14854eb6ea6d42fd2521204d3db01f">
<source>Returns the hex-encoded block header.</source>
<target>Vrne šestnajstiško vrednost glave bloka.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/api-docs/api-docs.component.html</context>
<context context-type="linenumber">230,233</context>
@@ -2372,6 +2375,7 @@
</trans-unit>
<trans-unit datatype="html" id="8f3ac0544491bf048120928d39e02a6baeb0b278">
<source>Privacy Policy</source>
<target>Politika zasebnosti</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/api-docs/api-docs.component.html</context>
<context context-type="linenumber">880,885</context>
@@ -2850,6 +2854,7 @@
</trans-unit>
<trans-unit datatype="html" id="cbfb79ff517493268a49acffa81ecc02336d8372">
<source>Block Header Hex</source>
<target>Glava bloka (Hex)</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/block/block.component.html</context>
<context context-type="linenumber">118,119</context>
@@ -3385,6 +3390,7 @@
</trans-unit>
<trans-unit datatype="html" id="time-until">
<source>In ~<x equiv-text="dateStrings.i18nMinute" id="DATE"/></source>
<target>Še ~<x equiv-text="dateStrings.i18nMinute" id="DATE"/></target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/time-until/time-until.component.ts</context>
<context context-type="linenumber">66</context>
@@ -3580,6 +3586,7 @@
</trans-unit>
<trans-unit datatype="html" id="a2fbb7745c7048af923991e08ccd6975364be90d">
<source>Transaction Hex</source>
<target>Šestnajstiška vrednost</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/transaction/transaction.component.html</context>
<context context-type="linenumber">220,221</context>
@@ -3903,6 +3910,7 @@
</trans-unit>
<trans-unit datatype="html" id="86d26b45470e43b409e589517922276109138e87">
<source>Transaction Fees</source>
<target>Omrežnina</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dashboard/dashboard.component.html</context>
<context context-type="linenumber">6,9</context>
@@ -4056,6 +4064,7 @@
</trans-unit>
<trans-unit datatype="html" id="680d5c75b7fd8d37961083608b9fcdc4167b4c43">
<source>Previous</source>
<target>Prejšnja</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dashboard/dashboard.component.html</context>
<context context-type="linenumber">235,236</context>
@@ -4157,6 +4166,7 @@
</trans-unit>
<trans-unit datatype="html" id="date-base.minute">
<source><x equiv-text="counter" id="DATE"/> minute</source>
<target><x equiv-text="counter" id="DATE"/> min</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/shared/i18n/dates.ts</context>
<context context-type="linenumber">13</context>

View File

@@ -1,17 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="172.649" height="33.558" viewBox="0 0 172.649 33.558">
<g id="bisq-markets" transform="translate(-158 -373.5)">
<g id="bisq_logo_green" transform="translate(158 376.985)">
<g id="bisq_mark" transform="translate(0 0)">
<path id="Combined_Shape" data-name="Combined Shape" d="M8.251,22.938A11.845,11.845,0,0,1,0,11.722,11.38,11.38,0,0,1,3.118,3.8,11.625,11.625,0,0,1,10.946,0c4.083,0,5.907,3.373,4.469,5.255-1.607,2.092-3.475,1.922-4.865,1.025,0,0-1.912-1.281-2.476-1.708-.869-.64-1.825-.64-2.172.385C5.294,6.792,4.991,7.945,4.513,9.78A12.5,12.5,0,0,0,4.166,12.6a5.734,5.734,0,0,0,2.215,4.483,23.065,23.065,0,0,0,3.084,2.305,5.649,5.649,0,0,0,2.954.9h.087a5.649,5.649,0,0,0,2.954-.9,37.207,37.207,0,0,0,4.083-2.69c1.611-1.5,3.849.178,2.286,2.187a12.238,12.238,0,0,1-9.679,4.684A12.38,12.38,0,0,1,8.251,22.938Zm3.874-4.88a.512.512,0,0,1-.334-.111c-.251-.221-.71-.664-1.128-1.034a.368.368,0,0,1-.057-.44.472.472,0,0,1,.432-.224,22.129,22.129,0,0,1,2.213,0,.446.446,0,0,1,.381.251.362.362,0,0,1-.089.413c-.417.369-.876.812-1.085,1.034a.511.511,0,0,1-.322.111Zm2.482-4.744a1.463,1.463,0,0,1,1.473-1.377c.332,0,1.368.12,1.553.561.369.8-.888,1.883-1.443,2.084a1.728,1.728,0,0,1-.575.11C14.852,14.691,14.525,14,14.607,13.314ZM8.031,14.581c-.555-.2-1.812-1.282-1.443-2.084.148-.441,1.221-.561,1.554-.561a1.463,1.463,0,0,1,1.473,1.377C9.7,14,9.37,14.691,8.606,14.691A1.728,1.728,0,0,1,8.031,14.581Zm10.457-3.794C16.284,9.8,14.689,9.1,16,7.458a16.235,16.235,0,0,1,2.164-2.219c.851-.7,1.378-1.137,2.108.328A30.626,30.626,0,0,1,21.959,9.43a1.372,1.372,0,0,1-1.431,1.894A5.117,5.117,0,0,1,18.488,10.787Z" transform="translate(0 0)" fill="#25b135"/>
</g>
<g id="Group" transform="translate(27.192 0.604)">
<path id="Fill_1" data-name="Fill 1" d="M14.5,20.545H10.918V14.184a5.512,5.512,0,0,1-1.692.958,5.913,5.913,0,0,1-1.975.354,7.037,7.037,0,0,1-5.1-2.158A7.954,7.954,0,0,1,0,7.748a7.954,7.954,0,0,1,2.153-5.59A7.037,7.037,0,0,1,7.251,0c.128,0,.26,0,.4.012.1.005.507.048.524.049a7.027,7.027,0,0,1,4.171,2.1A7.939,7.939,0,0,1,14.5,7.572c0,.013,0,12.439,0,12.968v.006ZM7.146,3.486h0c-2.682.027-3.638,2.219-3.638,4.261,0,2.061.969,4.262,3.688,4.262,2.156,0,3.781-1.832,3.781-4.262,0-.081,0-.165-.006-.249v0a4.727,4.727,0,0,0-.754-2.431A3.448,3.448,0,0,0,8.073,3.592v0c-.062-.015-.128-.03-.2-.043l-.011,0-.024,0-.034-.005-.026,0a4.62,4.62,0,0,0-.636-.05Z" transform="translate(33.235 4.834)" fill="#25b135"/>
<path id="Fill_4" data-name="Fill 4" d="M6.629,6.112,5.159,5.789c-.964-.206-1.442-.627-1.42-1.252.023-.662.766-1.15,1.726-1.135A1.817,1.817,0,0,1,7.376,4.459l2.842-1.873A5.337,5.337,0,0,0,5.585,0a6.052,6.052,0,0,0-3.6,1.1A4.308,4.308,0,0,0,.156,4.48C.076,6.743,1.579,8.46,4.279,9.19c.41.111.951.252,1.495.358,1.271.246,1.432.7,1.416,1.161-.028.8-.766,1.3-1.879,1.281a3.207,3.207,0,0,1-2.931-1.768L0,12.326a5.623,5.623,0,0,0,5.19,3.067c2.986.048,5.49-2.027,5.582-4.626.083-2.348-1.386-4-4.143-4.655" transform="translate(21.452 4.937)" fill="#25b135"/>
<path id="Fill_6" data-name="Fill 6" d="M0,14.778H3.695V0H0Z" transform="translate(16.218 5.245)" fill="#25b135"/>
<path id="Fill_8" data-name="Fill 8" d="M7.252,20.545h0c-.128,0-.26,0-.4-.012-.1-.005-.207-.014-.311-.023H6.529L6.451,20.5l-.127-.011v-.008a7.025,7.025,0,0,1-4.171-2.1A7.936,7.936,0,0,1,0,12.983a.036.036,0,0,0,0-.008.033.033,0,0,1,0-.011V0H3.585V6.362A5.512,5.512,0,0,1,5.276,5.4,5.913,5.913,0,0,1,7.251,5.05a7.037,7.037,0,0,1,5.1,2.158A7.952,7.952,0,0,1,14.5,12.8a7.952,7.952,0,0,1-2.153,5.59A7.037,7.037,0,0,1,7.252,20.545Zm-.823-3.6h0c.059.015.123.029.2.043l.012,0,.023,0L6.694,17l.026,0a4.527,4.527,0,0,0,.636.05c2.683-.027,3.638-2.219,3.638-4.261,0-2.061-.969-4.262-3.688-4.262-2.156,0-3.781,1.832-3.781,4.262a5.138,5.138,0,0,0,.111,1.051h-.01a4.351,4.351,0,0,0,.9,1.95,3.42,3.42,0,0,0,1.905,1.156v0Z" transform="translate(0 0)" fill="#25b135"/>
</g>
</g>
<path id="Path_3" data-name="Path 3" d="M.924,0H1.98V-6.4H2L4.631,0h.7L7.964-6.4h.022V0H9.042V-7.788H7.469l-2.453,5.9H4.972L2.5-7.788H.924Zm11.8,0h1.232l.77-1.925h3.707L19.217,0h1.232L17.116-7.788h-.957ZM15.1-2.849l1.474-3.575H16.6l1.452,3.575ZM24.211,0h1.056V-3.432h1.177L28.424,0h1.32L27.533-3.553A2.088,2.088,0,0,0,29.447-5.61c0-.979-.506-2.178-2.5-2.178H24.211Zm1.056-6.864h1.386c.836,0,1.672.2,1.672,1.254s-.836,1.254-1.672,1.254H25.267ZM33.682,0h1.056V-3.894h.088L38.456,0H40L35.981-4.191l3.762-3.6H38.269L34.826-4.422h-.088V-7.788H33.682ZM43.56,0h5.225V-.99H44.616v-2.5h3.7v-.99h-3.7V-6.8h3.971v-.99H43.56Zm11.1,0h1.056V-6.8h2.508v-.99H52.151v.99h2.508Zm6.8-.88A2.99,2.99,0,0,0,63.943.2a2.46,2.46,0,0,0,2.706-2.409c0-2.8-3.839-1.628-3.839-3.6,0-.484.352-1.188,1.518-1.188a1.634,1.634,0,0,1,1.386.682l.858-.781a2.693,2.693,0,0,0-2.244-.891,2.365,2.365,0,0,0-2.64,2.178c0,3.036,3.839,1.925,3.839,3.718a1.443,1.443,0,0,1-1.551,1.3,1.943,1.943,0,0,1-1.65-.836Z" transform="translate(264 394.28)" fill="#9d9da5"/>
<line id="Line_1" data-name="Line 1" y1="33.559" transform="translate(248.67 373.5)" fill="none" stroke="#3e3e50" stroke-width="1"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Some files were not shown because too many files have changed in this diff Show More