Compare commits

..

130 Commits

Author SHA1 Message Date
orangesurf
b640202c63 Changes following feedback 2023-12-08 13:44:22 +00:00
orangesurf
b3258b0be7 Update FAQ to add Accelerator details 2023-12-06 20:57:41 +00:00
wiz
4169e1053f Merge pull request #4482 from mempool/mononaut/fix-negative-accel-rate
Fix negative accelerated fee rate, simplify fee rate table
2023-12-07 01:50:12 +09:00
wiz
a46e779d0c Merge branch 'master' into mononaut/fix-negative-accel-rate 2023-12-06 23:56:12 +09:00
Mononaut
51102004bb Fix negative accelerated fee rate, simplify fee rate table 2023-12-06 14:50:26 +00:00
wiz
08e9142d57 Merge pull request #4481 from mempool/mononaut/improve-acceleration-tracking
Improve acceleration tracking
2023-12-06 22:42:00 +09:00
Mononaut
b7c4cd43fc Better error handling for accelerations update request 2023-12-06 12:09:46 +00:00
Mononaut
08a35b85a8 Fix current accelerations update race condition 2023-12-06 12:09:28 +00:00
wiz
51a28b2e01 Merge pull request #4480 from mempool/mononaut/mined-acceleration-info
Show accelerated fee rates for mined transactions
2023-12-05 23:00:15 +09:00
Mononaut
966adf5963 Show accelerated fee rates on mined tx pages 2023-12-05 13:48:38 +00:00
wiz
ae7b17c4fd Merge pull request #4478 from mempool/mononaut/accelerated-badge
Accelerated tx audit badge
2023-12-05 17:36:52 +09:00
Mononaut
5b4bc9fe19 Accelerated tx audit badge 2023-12-05 08:33:57 +00:00
wiz
89eb7ec90b ops: Enable accelerator in production config 2023-12-05 05:29:18 +09:00
wiz
a646c5f22f Merge pull request #4390 from ncois/tooltip-overflow-bug
Fix tooltip overflow by increasing width
2023-12-04 23:44:44 +09:00
wiz
c345f2164b Merge pull request #4462 from ncois/search-block-datetime
Search a block by date and timestamp
2023-12-04 23:44:19 +09:00
wiz
9791ee018d Merge pull request #4466 from ncois/pool-hashrate-format
Pool chart hashrate format in power of ten
2023-12-04 23:44:06 +09:00
wiz
7c83b85e3e Merge pull request #4476 from mempool/nymkappa/fix-waitlist-message
[accel preview] only show waitlist message if logged in
2023-12-04 23:43:50 +09:00
nymkappa
63942af720 wait list -> waitlist @softsimon 2023-12-04 23:30:22 +09:00
nymkappa
4da5910e0b [accel preview] only show waitlist message if logged in 2023-12-04 23:23:51 +09:00
wiz
025788e78c Merge pull request #4471 from mempool/nymkappa/fix-one-bug-add-two-more
Various fixes and improvements
2023-12-04 22:51:41 +09:00
nymkappa
88a9524064 Merge branch 'master' into pool-hashrate-format 2023-12-04 21:34:43 +09:00
ncois
c216e849e6 Add more precision to hashrate graphs 2023-12-04 09:56:56 +01:00
nymkappa
c86768edc9 [about] fix merge conflict #4425 2023-12-04 16:59:29 +09:00
nymkappa
4283d30ce0 Merge branch 'master' into search-block-datetime 2023-12-04 13:09:58 +09:00
nymkappa
e2eeaebfc7 Merge branch 'master' into nymkappa/fix-one-bug-add-two-more 2023-12-04 10:47:49 +09:00
softsimon
90d87aaaca Merge pull request #4472 from mempool/simon/upgrading-fontawesome
Upgrading fontawesome
2023-12-03 18:17:33 +09:00
softsimon
b2e9275ad9 Upgrading fontawesome 2023-12-03 17:34:31 +09:00
nymkappa
181e816c1a Merge branch 'nymkappa/about-page-empty-anchor' into nymkappa/fix-one-bug-add-two-more 2023-12-03 15:17:04 +09:00
softsimon
758ca4e93a Merge pull request #4233 from mempool/hunicus/enterprise-abs
Make enterprise links not relative
2023-12-03 14:09:25 +09:00
softsimon
a33f915c7a Merge pull request #4438 from mempool/mononaut/refactor-difficulty-reindexing
Refactor difficulty reindexing to process blocks in height order
2023-12-03 13:15:01 +09:00
softsimon
75c6d006ff Merge pull request #4433 from mempool/mononaut/dismiss-acceleration-preview
Make the acceleration preview dismissable
2023-12-02 18:19:46 +09:00
softsimon
1fe983a299 Merge pull request #4468 from mempool/mononaut/fix-liquid-fee-rounding
Fix liquid recommended fee rounding
2023-12-02 15:05:13 +09:00
Mononaut
cd8e3e2604 Fix liquid fee rounding 2023-12-02 03:20:09 +00:00
wiz
dfc4309d9e Merge pull request #4465 from mempool/mononaut/fix-liquid-minfee
Fix Liquid minfee defaults
2023-12-01 21:52:25 +09:00
ncois
0a70273456 Pool chart: show power of ten of hashrate on desktop 2023-12-01 12:57:30 +01:00
softsimon
62c9a88235 Merge pull request #4448 from mempool/nymkappa/accel-preview-logged-in
[accelerator] show wait list message in preview when logged in with no access
2023-12-01 18:37:05 +09:00
Mononaut
851e07b50b Fix liquid minfee defaults 2023-12-01 18:12:59 +09:00
wiz
e1bbec074b Merge pull request #4457 from mempool/mononaut/matomo-path
matomo path
2023-12-01 17:54:06 +09:00
Mononaut
9fcafeeeb0 Switch to toggle-style acceleration dismiss button 2023-12-01 14:55:19 +09:00
Mononaut
e986cfd30b Make the acceleration preview dismissable 2023-12-01 14:21:05 +09:00
ncois
2584a1f2b0 Add date and timestamp search option 2023-11-30 20:05:25 +01:00
softsimon
1c92394563 Merge pull request #4449 from mempool/nymkappa/translation-typo
Fix some SEO strings
2023-11-30 18:33:05 +09:00
softsimon
36398ca57a Updating messages.xlf 2023-11-30 18:29:06 +09:00
softsimon
6b978f9262 Merge pull request #3706 from mempool/mononaut/reconnect-dead-websocket
reconnect websocket after closed by server
2023-11-30 18:26:18 +09:00
softsimon
12cf130c00 Merge branch 'master' into mononaut/reconnect-dead-websocket 2023-11-30 17:57:19 +09:00
softsimon
ba933a81c3 Merge pull request #3704 from mempool/mononaut/handle-websocket-errors
Disconnect websocket clients on error
2023-11-30 17:56:58 +09:00
Mononaut
7de7081d67 matomo path 2023-11-30 17:35:26 +09:00
nymkappa
6b933c202f Update nodes-rankings-dashboard.component.ts 2023-11-30 17:22:22 +09:00
softsimon
f1525b7df5 Merge pull request #4445 from mempool/dependabot/npm_and_yarn/frontend/fortawesome/fontawesome-svg-core-6.5.0
Bump @fortawesome/fontawesome-svg-core from 6.4.0 to 6.5.0 in /frontend
2023-11-30 17:05:19 +09:00
wiz
62c4af1a0c Merge pull request #4455 from mempool/mononaut/fix-matomo-fix
Fix matomo again
2023-11-30 15:49:00 +09:00
Mononaut
4ac98bc483 Fix matomo again 2023-11-30 15:46:58 +09:00
softsimon
ed07daebca Merge branch 'master' into mononaut/handle-websocket-errors 2023-11-30 15:18:16 +09:00
wiz
f8bbf7783c Merge pull request #4443 from mempool/nymkappa/debug-add-context
[log] create more general error
2023-11-30 14:53:51 +09:00
softsimon
fac2fab16a Merge pull request #4437 from mempool/simon/remove-audit-beta
Remove block audit beta-tag
2023-11-30 14:50:57 +09:00
wiz
76de4e34d8 Merge pull request #4425 from mempool/mononaut/fix-matomo
fix matomo bug
2023-11-30 14:40:26 +09:00
softsimon
6109f25aa9 Merge pull request #4452 from ncois/tx-ui-overflow
Fix overflow on transaction page
2023-11-30 14:36:15 +09:00
softsimon
86303175e9 Merge pull request #4451 from ncois/format-difficulty-chart
Difficulty adjustment chart: format the hashrate and difficulty
2023-11-30 14:27:39 +09:00
nymkappa
a670131f40 Merge branch 'master' into tooltip-overflow-bug 2023-11-30 11:54:16 +09:00
ncois
c5ce3167f3 Fix overflow on transaction page 2023-11-29 15:27:45 +01:00
ncois
9913254a5c Difficulty value: remove rounding 2023-11-29 11:16:48 +01:00
ncois
fbb4ba39c2 Mining chart: show hashrate power of ten on desktop 2023-11-29 11:08:38 +01:00
nymkappa
22bf0fe928 Re-apply fix from https://github.com/mempool/mempool/pull/4419/files#diff-9daf43654b4e0bb2c925b6396c6e3a7e1b117bd854fe9c46290f025bfe0a762eR285 2023-11-29 18:03:23 +09:00
nymkappa
3b30765070 Revert unnecessary changes 2023-11-29 18:00:07 +09:00
nymkappa
63fde7d0b6 Merge branch 'master' into nymkappa/about-page-sponsors-component 2023-11-29 17:57:54 +09:00
softsimon
cf338970bf Merge pull request #4450 from mempool/translations_frontend-src-locale-messages-xlf--master_fr
Updates for file frontend/src/locale/messages.xlf in fr
2023-11-29 16:35:27 +09:00
Mononaut
08e046ea2a fix matomo bug 2023-11-29 16:17:07 +09:00
transifex-integration[bot]
5697710c23 Translate frontend/src/locale/messages.xlf in fr
100% reviewed source file: 'frontend/src/locale/messages.xlf'
on 'fr'.
2023-11-29 07:00:03 +00:00
nymkappa
b9e050e24b Fix small typo in lightning node ranking dashboard seo 2023-11-29 15:59:15 +09:00
nymkappa
ff819673e5 Fix "and" chain in lightning dashboard seo 2023-11-29 15:56:35 +09:00
nymkappa
2e2801a4ab Add missing txid in seo tx preview 2023-11-29 15:51:35 +09:00
nymkappa
f52b17ca7a Add missing space in seo address preview 2023-11-29 15:28:14 +09:00
softsimon
3d25235705 Merge pull request #4439 from mempool/mononaut/fix-ln-search-suggestions
Fix spurious ln channel typeahead matches
2023-11-29 15:24:34 +09:00
nymkappa
2cbc6783a4 [accelerator] show wait list message in preview when logged in with no access 2023-11-29 15:02:48 +09:00
dependabot[bot]
406bf5d3b7 Bump @fortawesome/fontawesome-svg-core from 6.4.0 to 6.5.0 in /frontend
Bumps [@fortawesome/fontawesome-svg-core](https://github.com/FortAwesome/Font-Awesome) from 6.4.0 to 6.5.0.
- [Release notes](https://github.com/FortAwesome/Font-Awesome/releases)
- [Changelog](https://github.com/FortAwesome/Font-Awesome/blob/6.x/CHANGELOG.md)
- [Commits](https://github.com/FortAwesome/Font-Awesome/compare/6.4.0...6.5.0)

---
updated-dependencies:
- dependency-name: "@fortawesome/fontawesome-svg-core"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-29 02:48:50 +00:00
nymkappa
f5bf883de9 [about] fix auto scroll to chad/whale 2023-11-28 18:05:18 +09:00
nymkappa
c06cade1ca [log] create more general error 2023-11-28 17:13:52 +09:00
softsimon
c23a872887 Updating german i18n 2023-11-28 14:42:25 +09:00
softsimon
cd5a51098b Merge pull request #4419 from mempool/nymkappa/fix-about-page-ctas
[about page] fix wrapping perk
2023-11-26 18:24:58 +09:00
Mononaut
0b449347c1 Fix spurious ln channel typeahead matches 2023-11-26 09:22:44 +00:00
softsimon
d3f8876818 Merge pull request #4424 from mempool/nymkappa/channel-map-larger
[lightning] enlarge channel map
2023-11-26 18:09:31 +09:00
softsimon
d2da81e039 Remove block audit beta-tag 2023-11-26 17:21:13 +09:00
Mononaut
66d88abdc5 Refactor difficulty reindexing to process blocks in height order 2023-11-26 08:17:30 +00:00
softsimon
be2b9a9c2e Merge pull request #4436 from mempool/simon/pull-from-transifex-11-25
Pull from transifex 11/25
2023-11-26 17:01:10 +09:00
softsimon
63ba273dbe Pull from transifex 11/25 2023-11-26 17:00:46 +09:00
softsimon
8ae4c75c1a Fixing one last i18n string 2023-11-26 16:59:45 +09:00
softsimon
1cece1037a Disclaimer i18n fix 2023-11-26 16:29:20 +09:00
softsimon
478873302c Merging another duplicate i18n string 2023-11-26 16:20:57 +09:00
softsimon
3cff01b21e Merging another duplicate i18n string 2023-11-26 16:20:46 +09:00
softsimon
f67848b043 Merging another duplicate i18n string 2023-11-26 16:10:18 +09:00
softsimon
f0c88ff6cc Fix duplicate recent block i18n 2023-11-26 16:03:53 +09:00
softsimon
da5adc3e4e Merge pull request #4435 from mempool/simon/more-i18n-fixes3
More i18n fixes (3)
2023-11-26 15:49:02 +09:00
softsimon
bebd2ea028 More i18n fixes (3) 2023-11-26 15:48:44 +09:00
softsimon
e7e25e1632 Merge pull request #4432 from mempool/simon/more-i18n-fixes-etc
RBF i18n fixes
2023-11-25 20:46:19 +09:00
softsimon
c4130fd5b9 RBF i18n fixes 2023-11-25 20:45:59 +09:00
softsimon
dcad18b297 Merge pull request #4431 from mempool/simon/seo-description-fixes
Fixing titles and merging more i18n duplicates
2023-11-25 20:31:56 +09:00
softsimon
d380aad98c Fixing titles and merging more i18n duplicates 2023-11-25 20:31:29 +09:00
Mononaut
85c9c79699 reconnect websocket after closed by server 2023-11-25 11:25:07 +00:00
softsimon
107e0be59f Merge pull request #4428 from mempool/simon/i18n-fixes-231125
Updating and correcting i18n strings
2023-11-25 18:12:17 +09:00
softsimon
e654170d0b Updating and correcting i18n strings 2023-11-25 18:05:08 +09:00
Mononaut
652100f774 More verbose websocket error logs 2023-11-25 09:02:27 +00:00
Mononaut
0f04f751e1 Disconnect websocket clients on error 2023-11-25 08:27:17 +00:00
wiz
d3055dab54 Merge pull request #4157 from mempool/hunicus/bimi-svg
Add bimi svg
2023-11-25 16:29:29 +09:00
ncois
643402c046 Merge branch 'mempool:master' into tooltip-overflow-bug 2023-11-24 13:28:42 +01:00
nymkappa
e95d5a7982 [lightning] enlarge channel map 2023-11-24 18:14:16 +09:00
nymkappa
3103ef15e5 [about page] move CTA sponsors box into a sub component 2023-11-24 11:13:12 +09:00
wiz
5d5e9e8219 Legal: update Privacy Policy 20231123 2023-11-23 22:23:06 +09:00
wiz
99fd4500e4 Fix broken sponsor links on About page 2023-11-23 21:50:27 +09:00
nymkappa
69081ed647 [about page] fix wrapping perk 2023-11-23 21:07:21 +09:00
softsimon
c8bcd4f04f Merge pull request #4415 from mempool/nymkappa/fix-css-testnet
[ui] only load empty sidebar in foss mainnet
2023-11-23 18:56:43 +09:00
nymkappa
7f4fd83ad2 [ui] fix testnets padding 2023-11-23 18:40:25 +09:00
softsimon
70722dfc9c Merge pull request #4414 from mempool/nymkappa/fix-waitlist-message-preview
[accelerator] fix preview UX on mobile when there is an error/warning
2023-11-23 17:42:19 +09:00
softsimon
407b4c53a6 Merge pull request #4403 from mempool/mononaut/empty-mempool-block-health
Fix health score when mempool was empty
2023-11-23 17:25:06 +09:00
nymkappa
c11551de7b [accelerator] fix preview UX on mobile when there is an error/warning 2023-11-23 17:00:57 +09:00
softsimon
2d5964b81e Merge pull request #4413 from mempool/nymkappa/polish-accel-preview
[accelerator] small align tweak
2023-11-23 15:51:07 +09:00
nymkappa
98e3c7b9cf [accelerator] small align tweak 2023-11-23 15:47:28 +09:00
softsimon
2de57a8074 Merge pull request #4412 from mempool/nymkappa/polish-accel-preview
[accelerator] fix preview text wrap not working on safari/firefox + polish accelerator preview
2023-11-23 15:39:24 +09:00
nymkappa
8badacf123 [accelerator] fix preview text wrap not working on safari/firefox
[accelerator] polish accelerator preview
2023-11-23 14:54:50 +09:00
softsimon
40b387a1e0 Merge pull request #4411 from mempool/nymkappa/fix-menu-css
[menu] force background color when menu is open
2023-11-23 13:31:11 +09:00
nymkappa
e5d2788736 [menu] force background color when menu is open 2023-11-23 10:45:16 +09:00
ncois
f57436f511 Merge branch 'master' into tooltip-overflow-bug 2023-11-22 12:14:03 +01:00
Mononaut
75cc844676 actually fix empty mempool health score 2023-11-22 19:53:22 +09:00
wiz
5d05dd7089 Merge pull request #4410 from mempool/simon/accelerate-button-height
Accelerate button height align fix
2023-11-22 19:51:49 +09:00
softsimon
35e108aa1c Accelerate button height fix 2023-11-22 19:37:44 +09:00
softsimon
3f06b38767 Merge pull request #4408 from mempool/mononaut/restore-calculator
Restore the calculator
2023-11-22 18:19:39 +09:00
Mononaut
9844c3d275 Restore the calculator 2023-11-22 18:13:41 +09:00
Mononaut
8dc80eadf3 Fix health score when mempool was empty 2023-11-22 13:10:53 +09:00
ncois
1eb425897e Adjust the width of the incoming tx tooltip for 2h timeframe 2023-11-19 16:42:09 +01:00
ncois
9d0bfdfa88 Increase tooltip width in mempool graphs 2023-11-17 13:47:28 +01:00
hunicus
d630e7217a Make enterprise links not relative 2023-09-06 01:42:19 +09:00
hunicus
1dd66e6695 Add bimi svg 2023-08-16 12:43:36 +09:00
116 changed files with 105064 additions and 68710 deletions

View File

@@ -9,7 +9,7 @@ class Audit {
auditBlock(transactions: MempoolTransactionExtended[], projectedBlocks: MempoolBlockWithTransactions[], mempool: { [txId: string]: MempoolTransactionExtended }, useAccelerations: boolean = false)
: { censored: string[], added: string[], fresh: string[], sigop: string[], fullrbf: string[], accelerated: string[], score: number, similarity: number } {
if (!projectedBlocks?.[0]?.transactionIds || !mempool) {
return { censored: [], added: [], fresh: [], sigop: [], fullrbf: [], accelerated: [], score: 0, similarity: 1 };
return { censored: [], added: [], fresh: [], sigop: [], fullrbf: [], accelerated: [], score: 1, similarity: 1 };
}
const matches: string[] = []; // present in both mined block and template
@@ -144,7 +144,12 @@ class Audit {
const numCensored = Object.keys(isCensored).length;
const numMatches = matches.length - 1; // adjust for coinbase tx
const score = numMatches > 0 ? (numMatches / (numMatches + numCensored)) : 0;
let score = 0;
if (numMatches <= 0 && numCensored <= 0) {
score = 1;
} else if (numMatches > 0) {
score = (numMatches / (numMatches + numCensored));
}
const similarity = projectedWeight ? matchedWeight / projectedWeight : 1;
return {

View File

@@ -80,7 +80,13 @@ class ChannelsApi {
public async $searchChannelsById(search: string): Promise<any[]> {
try {
const searchStripped = search.replace(/[^0-9x]/g, '') + '%';
// restrict search to valid id/short_id prefix formats
let searchStripped = search.match(/[0-9]+[0-9x]*/)?.[0] || '';
if (!searchStripped.length) {
return [];
}
// add wildcard to search by prefix
searchStripped += '%';
const query = `SELECT id, short_id, capacity, status FROM channels WHERE id LIKE ? OR short_id LIKE ? LIMIT 10`;
const [rows]: any = await DB.query(query, [searchStripped, searchStripped]);
return rows;

View File

@@ -15,11 +15,12 @@ class FeeApi {
constructor() { }
defaultFee = Common.isLiquid() ? 0.1 : 1;
minimumIncrement = Common.isLiquid() ? 0.1 : 1;
public getRecommendedFee(): RecommendedFees {
const pBlocks = projectedBlocks.getMempoolBlocks();
const mPool = mempool.getMempoolInfo();
const minimumFee = Math.ceil(mPool.mempoolminfee * 100000);
const minimumFee = this.roundUpToNearest(mPool.mempoolminfee * 100000, this.minimumIncrement);
const defaultMinFee = Math.max(minimumFee, this.defaultFee);
if (!pBlocks.length) {
@@ -58,7 +59,11 @@ class FeeApi {
const multiplier = (pBlock.blockVSize - 500000) / 500000;
return Math.max(Math.round(useFee * multiplier), this.defaultFee);
}
return Math.ceil(useFee);
return this.roundUpToNearest(useFee, this.minimumIncrement);
}
private roundUpToNearest(value: number, nearest: number): number {
return Math.ceil(value / nearest) * nearest;
}
}

View File

@@ -9,7 +9,7 @@ import loadingIndicators from './loading-indicators';
import bitcoinClient from './bitcoin/bitcoin-client';
import bitcoinSecondClient from './bitcoin/bitcoin-second-client';
import rbfCache from './rbf-cache';
import accelerationApi, { Acceleration } from './services/acceleration';
import { Acceleration } from './services/acceleration';
import redisCache from './redis-cache';
class Mempool {
@@ -18,7 +18,7 @@ class Mempool {
private mempoolCache: { [txId: string]: MempoolTransactionExtended } = {};
private spendMap = new Map<string, MempoolTransactionExtended>();
private mempoolInfo: IBitcoinApi.MempoolInfo = { loaded: false, size: 0, bytes: 0, usage: 0, total_fee: 0,
maxmempool: 300000000, mempoolminfee: 0.00001000, minrelaytxfee: 0.00001000 };
maxmempool: 300000000, mempoolminfee: Common.isLiquid() ? 0.00000100 : 0.00001000, minrelaytxfee: Common.isLiquid() ? 0.00000100 : 0.00001000 };
private mempoolChangedCallback: ((newMempool: {[txId: string]: MempoolTransactionExtended; }, newTransactions: MempoolTransactionExtended[],
deletedTransactions: MempoolTransactionExtended[], accelerationDelta: string[]) => void) | undefined;
private $asyncMempoolChangedCallback: ((newMempool: {[txId: string]: MempoolTransactionExtended; }, mempoolSize: number, newTransactions: MempoolTransactionExtended[],
@@ -185,7 +185,7 @@ class Mempool {
return txTimes;
}
public async $updateMempool(transactions: string[], pollRate: number): Promise<void> {
public async $updateMempool(transactions: string[], accelerations: Acceleration[] | null, pollRate: number): Promise<void> {
logger.debug(`Updating mempool...`);
// warn if this run stalls the main loop for more than 2 minutes
@@ -330,7 +330,7 @@ class Mempool {
const newTransactionsStripped = newTransactions.map((tx) => Common.stripTransaction(tx));
this.latestTransactions = newTransactionsStripped.concat(this.latestTransactions).slice(0, 6);
const accelerationDelta = await this.$updateAccelerations();
const accelerationDelta = accelerations != null ? await this.$updateAccelerations(accelerations) : [];
if (accelerationDelta.length) {
hasChange = true;
}
@@ -370,14 +370,12 @@ class Mempool {
return this.accelerations;
}
public async $updateAccelerations(): Promise<string[]> {
public $updateAccelerations(newAccelerations: Acceleration[]): string[] {
if (!config.MEMPOOL_SERVICES.ACCELERATIONS) {
return [];
}
try {
const newAccelerations = await accelerationApi.$fetchAccelerations();
const changed: string[] = [];
const newAccelerationMap: { [txid: string]: Acceleration } = {};

View File

@@ -15,6 +15,13 @@ import bitcoinApi from '../bitcoin/bitcoin-api-factory';
import { IEsploraApi } from '../bitcoin/esplora-api.interface';
import database from '../../database';
interface DifficultyBlock {
timestamp: number,
height: number,
bits: number,
difficulty: number,
}
class Mining {
private blocksPriceIndexingRunning = false;
public lastHashrateIndexingDate: number | null = null;
@@ -421,6 +428,7 @@ class Mining {
indexedHeights[height] = true;
}
// gets {time, height, difficulty, bits} of blocks in ascending order of height
const blocks: any = await BlocksRepository.$getBlocksDifficulty();
const genesisBlock: IEsploraApi.Block = await bitcoinApi.$getBlock(await bitcoinApi.$getBlockHash(0));
let currentDifficulty = genesisBlock.difficulty;
@@ -436,41 +444,45 @@ class Mining {
});
}
const oldestConsecutiveBlock = await BlocksRepository.$getOldestConsecutiveBlock();
if (config.MEMPOOL.INDEXING_BLOCKS_AMOUNT !== -1) {
currentBits = oldestConsecutiveBlock.bits;
currentDifficulty = oldestConsecutiveBlock.difficulty;
if (!blocks?.length) {
// no blocks in database yet
return;
}
const oldestConsecutiveBlock = this.getOldestConsecutiveBlock(blocks);
currentBits = oldestConsecutiveBlock.bits;
currentDifficulty = oldestConsecutiveBlock.difficulty;
let totalBlockChecked = 0;
let timer = new Date().getTime() / 1000;
for (const block of blocks) {
// skip until the first block after the oldest consecutive block
if (block.height <= oldestConsecutiveBlock.height) {
continue;
}
// difficulty has changed between two consecutive blocks!
if (block.bits !== currentBits) {
if (indexedHeights[block.height] === true) { // Already indexed
if (block.height >= oldestConsecutiveBlock.height) {
currentDifficulty = block.difficulty;
currentBits = block.bits;
}
continue;
// skip if already indexed
if (indexedHeights[block.height] !== true) {
let adjustment = block.difficulty / currentDifficulty;
adjustment = Math.round(adjustment * 1000000) / 1000000; // Remove float point noise
await DifficultyAdjustmentsRepository.$saveAdjustments({
time: block.time,
height: block.height,
difficulty: block.difficulty,
adjustment: adjustment,
});
totalIndexed++;
}
let adjustment = block.difficulty / currentDifficulty;
adjustment = Math.round(adjustment * 1000000) / 1000000; // Remove float point noise
await DifficultyAdjustmentsRepository.$saveAdjustments({
time: block.time,
height: block.height,
difficulty: block.difficulty,
adjustment: adjustment,
});
totalIndexed++;
if (block.height >= oldestConsecutiveBlock.height) {
currentDifficulty = block.difficulty;
currentBits = block.bits;
}
}
// update the current difficulty
currentDifficulty = block.difficulty;
currentBits = block.bits;
}
totalBlockChecked++;
const elapsedSeconds = Math.max(1, Math.round((new Date().getTime() / 1000) - timer));
@@ -633,6 +645,17 @@ class Mining {
default: return 86400 * scale;
}
}
// Finds the oldest block in a consecutive chain back from the tip
// assumes `blocks` is sorted in ascending height order
private getOldestConsecutiveBlock(blocks: DifficultyBlock[]): DifficultyBlock {
for (let i = blocks.length - 1; i > 0; i--) {
if ((blocks[i].height - blocks[i - 1].height) > 1) {
return blocks[i];
}
}
return blocks[0];
}
}
export default new Mining();

View File

@@ -1,6 +1,7 @@
import { query } from '../../utils/axios-query';
import config from '../../config';
import logger from '../../logger';
import { BlockExtended, PoolTag } from '../../mempool.interfaces';
import axios from 'axios';
export interface Acceleration {
txid: string,
@@ -9,10 +10,15 @@ export interface Acceleration {
}
class AccelerationApi {
public async $fetchAccelerations(): Promise<Acceleration[]> {
public async $fetchAccelerations(): Promise<Acceleration[] | null> {
if (config.MEMPOOL_SERVICES.ACCELERATIONS) {
const response = await query(`${config.MEMPOOL_SERVICES.API}/accelerator/accelerations`);
return (response as Acceleration[]) || [];
try {
const response = await axios.get(`${config.MEMPOOL_SERVICES.API}/accelerator/accelerations`, { responseType: 'json', timeout: 10000 });
return response.data as Acceleration[];
} catch (e) {
logger.warn('Failed to fetch current accelerations from the mempool services backend: ' + (e instanceof Error ? e.message : e));
return null;
}
} else {
return [];
}

View File

@@ -94,9 +94,13 @@ class WebsocketHandler {
throw new Error('WebSocket.Server is not set');
}
this.wss.on('connection', (client: WebSocket) => {
this.wss.on('connection', (client: WebSocket, req) => {
this.numConnected++;
client.on('error', logger.info);
client['remoteAddress'] = req.headers['x-forwarded-for'] || req.socket?.remoteAddress || 'unknown';
client.on('error', (e) => {
logger.info(`websocket client error from ${client['remoteAddress']}: ` + (e instanceof Error ? e.message : e));
client.close();
});
client.on('close', () => {
this.numDisconnected++;
});
@@ -282,7 +286,8 @@ class WebsocketHandler {
client.send(serializedResponse);
}
} catch (e) {
logger.debug('Error parsing websocket message: ' + (e instanceof Error ? e.message : e));
logger.debug(`Error parsing websocket message from ${client['remoteAddress']}: ` + (e instanceof Error ? e.message : e));
client.close();
}
});
});

View File

@@ -43,6 +43,7 @@ import { AxiosError } from 'axios';
import v8 from 'v8';
import { formatBytes, getBytesUnit } from './utils/format';
import redisCache from './api/redis-cache';
import accelerationApi from './api/services/acceleration';
class Server {
private wss: WebSocket.Server | undefined;
@@ -205,10 +206,11 @@ class Server {
}
}
const newMempool = await bitcoinApi.$getRawMempool();
const newAccelerations = await accelerationApi.$fetchAccelerations();
const numHandledBlocks = await blocks.$updateBlocks();
const pollRate = config.MEMPOOL.POLL_RATE_MS * (indexer.indexerIsRunning() ? 10 : 1);
if (numHandledBlocks === 0) {
await memPool.$updateMempool(newMempool, pollRate);
await memPool.$updateMempool(newMempool, newAccelerations, pollRate);
}
indexer.$run();
priceUpdater.$run();

View File

@@ -541,7 +541,7 @@ class BlocksRepository {
*/
public async $getBlocksDifficulty(): Promise<object[]> {
try {
const [rows]: any[] = await DB.query(`SELECT UNIX_TIMESTAMP(blockTimestamp) as time, height, difficulty, bits FROM blocks`);
const [rows]: any[] = await DB.query(`SELECT UNIX_TIMESTAMP(blockTimestamp) as time, height, difficulty, bits FROM blocks ORDER BY height ASC`);
return rows;
} catch (e) {
logger.err('Cannot get blocks difficulty list from the db. Reason: ' + (e instanceof Error ? e.message : e));

File diff suppressed because it is too large Load Diff

View File

@@ -74,9 +74,9 @@
"@angular/platform-server": "^16.2.2",
"@angular/router": "^16.2.2",
"@fortawesome/angular-fontawesome": "~0.13.0",
"@fortawesome/fontawesome-common-types": "~6.4.0",
"@fortawesome/fontawesome-svg-core": "~6.4.0",
"@fortawesome/free-solid-svg-icons": "~6.4.0",
"@fortawesome/fontawesome-common-types": "~6.5.1",
"@fortawesome/fontawesome-svg-core": "~6.5.1",
"@fortawesome/free-solid-svg-icons": "~6.5.1",
"@mempool/mempool.js": "2.3.0",
"@ng-bootstrap/ng-bootstrap": "^15.1.0",
"@types/qrcode": "~1.5.0",
@@ -90,7 +90,7 @@
"ngx-infinite-scroll": "^16.0.0",
"qrcode": "1.5.1",
"rxjs": "~7.8.1",
"tinyify": "^4.0.0",
"tinyify": "^3.1.0",
"tlite": "^0.1.9",
"tslib": "~2.6.0",
"zone.js": "~0.13.1"

View File

@@ -30,7 +30,7 @@ export class BisqDashboardComponent implements OnInit {
ngOnInit(): void {
this.seoService.setTitle($localize`:@@meta.title.bisq.markets:Markets`);
this.seoService.setDescription($localize`:@@meta.description.bisq.markets:Explore the full Bitcoin ecosystem with The Mempool Open Project™. See Bisq market prices, trading activity, and more.`);
this.seoService.setDescription($localize`:@@meta.description.bisq.markets:Explore the full Bitcoin ecosystem with The Mempool Open Source Project™. See Bisq market prices, trading activity, and more.`);
this.websocketService.want(['blocks']);
this.volumes$ = this.bisqApiService.getAllVolumesDay$()

View File

@@ -225,7 +225,7 @@ const witnessSize = (vin: Vin) => vin.witness ? vin.witness.reduce((S, w) => S +
const scriptSigSize = (vin: Vin) => vin.scriptsig ? vin.scriptsig.length / 2 : 0;
// Power of ten wrapper
export function selectPowerOfTen(val: number): { divider: number, unit: string } {
export function selectPowerOfTen(val: number, multiplier = 1): { divider: number, unit: string } {
const powerOfTen = {
exa: Math.pow(10, 18),
peta: Math.pow(10, 15),
@@ -236,17 +236,17 @@ export function selectPowerOfTen(val: number): { divider: number, unit: string }
};
let selectedPowerOfTen: { divider: number, unit: string };
if (val < powerOfTen.kilo) {
if (val < powerOfTen.kilo * multiplier) {
selectedPowerOfTen = { divider: 1, unit: '' }; // no scaling
} else if (val < powerOfTen.mega) {
} else if (val < powerOfTen.mega * multiplier) {
selectedPowerOfTen = { divider: powerOfTen.kilo, unit: 'k' };
} else if (val < powerOfTen.giga) {
} else if (val < powerOfTen.giga * multiplier) {
selectedPowerOfTen = { divider: powerOfTen.mega, unit: 'M' };
} else if (val < powerOfTen.tera) {
} else if (val < powerOfTen.tera * multiplier) {
selectedPowerOfTen = { divider: powerOfTen.giga, unit: 'G' };
} else if (val < powerOfTen.peta) {
} else if (val < powerOfTen.peta * multiplier) {
selectedPowerOfTen = { divider: powerOfTen.tera, unit: 'T' };
} else if (val < powerOfTen.exa) {
} else if (val < powerOfTen.exa * multiplier) {
selectedPowerOfTen = { divider: powerOfTen.peta, unit: 'P' };
} else {
selectedPowerOfTen = { divider: powerOfTen.exa, unit: 'E' };

View File

@@ -0,0 +1,16 @@
<div id="become-sponsor-container">
<div class="become-sponsor community">
<p style="font-weight: 700; font-size: 18px;">If you're an individual...</p>
<a href="https://mempool.space/sponsor" class="btn" style="background-color: rgba(152, 88, 255, 0.75); box-shadow: 0px 0px 50px 5px rgba(152, 88, 255, 0.75)" i18n="about.community-sponsor-button" (click)="onSponsorClick($event)">Become a Community Sponsor</a>
<p class="sponsor-feature"><fa-icon [icon]="['fas', 'check']"></fa-icon> Exclusive swag</p>
<p class="sponsor-feature"><fa-icon [icon]="['fas', 'check']"></fa-icon> Your avatar on the About page</p>
<p class="sponsor-feature"><fa-icon [icon]="['fas', 'check']"></fa-icon> And more coming soon :)</p>
</div>
<div class="become-sponsor enterprise">
<p style="font-weight: 700; font-size: 18px;">If you're a business...</p>
<a href="https://mempool.space/enterprise" class="btn" style="background-color: rgba(152, 88, 255, 0.75); box-shadow: 0px 0px 50px 5px rgba(152, 88, 255, 0.75)" i18n="about.enterprise-sponsor-button" (click)="onEnterpriseClick($event)">Become an Enterprise Sponsor</a>
<p class="sponsor-feature"><fa-icon [icon]="['fas', 'check']"></fa-icon> Increased API limits</p>
<p class="sponsor-feature"><fa-icon [icon]="['fas', 'check']"></fa-icon> Co-branded instance</p>
<p class="sponsor-feature"><fa-icon [icon]="['fas', 'check']"></fa-icon> 99% service-level agreement</p>
</div>
</div>

View File

@@ -0,0 +1,45 @@
#become-sponsor-container {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: center;
align-items: center;
gap: 20px;
margin: 68px auto;
}
.become-sponsor {
background-color: #1d1f31;
border-radius: 16px;
padding: 12px 20px;
width: 400px;
padding: 40px 20px;
}
.become-sponsor a {
margin-top: 10px;
}
#become-sponsor-container .btn {
margin-bottom: 24px;
}
#become-sponsor-container .ng-fa-icon {
color: #2ecc71;
margin-right: 5px;
}
#become-sponsor-container .sponsor-feature {
text-align: left;
width: 250px;
margin: 12px auto;
white-space: nowrap;
}
@media (max-width: 992px) {
#become-sponsor-container {
flex-wrap: wrap;
}
}

View File

@@ -0,0 +1,22 @@
import { Component } from '@angular/core';
import { EnterpriseService } from '../../services/enterprise.service';
@Component({
selector: 'app-about-sponsors',
templateUrl: './about-sponsors.component.html',
styleUrls: ['./about-sponsors.component.scss'],
})
export class AboutSponsorsComponent {
constructor(private enterpriseService: EnterpriseService) {
}
onSponsorClick(e): boolean {
this.enterpriseService.goal(5);
return true;
}
onEnterpriseClick(e): boolean {
this.enterpriseService.goal(6);
return true;
}
}

View File

@@ -33,22 +33,7 @@
</video>
<ng-container *ngIf="officialMempoolSpace">
<div id="become-sponsor-container">
<div class="become-sponsor community">
<p style="font-weight: 700; font-size: 18px;">If you're an individual...</p>
<a href="/sponsor" class="btn" style="background-color: rgba(152, 88, 255, 0.75); box-shadow: 0px 0px 50px 5px rgba(152, 88, 255, 0.75)" i18n="about.community-sponsor-button">Become a Community Sponsor</a>
<p class="sponsor-feature"><fa-icon [icon]="['fas', 'check']"></fa-icon> Exclusive swag</p>
<p class="sponsor-feature"><fa-icon [icon]="['fas', 'check']"></fa-icon> Your avatar on the About page</p>
<p class="sponsor-feature"><fa-icon [icon]="['fas', 'check']"></fa-icon> And more coming soon :)</p>
</div>
<div class="become-sponsor enterprise">
<p style="font-weight: 700; font-size: 18px;">If you're a business...</p>
<a href="/enterprise" class="btn" style="background-color: rgba(152, 88, 255, 0.75); box-shadow: 0px 0px 50px 5px rgba(152, 88, 255, 0.75)" i18n="about.enterprise-sponsor-button">Become an Enterprise Sponsor</a>
<p class="sponsor-feature"><fa-icon [icon]="['fas', 'check']"></fa-icon> Increased API limits</p>
<p class="sponsor-feature"><fa-icon [icon]="['fas', 'check']"></fa-icon> Co-branded instance</p>
<p class="sponsor-feature"><fa-icon [icon]="['fas', 'check']"></fa-icon> 99% service-level agreement</p>
</div>
</div>
<app-about-sponsors></app-about-sponsors>
</ng-container>
<div class="enterprise-sponsor" id="enterprise-sponsors">
@@ -197,7 +182,7 @@
</div>
<ng-container *ngIf="officialMempoolSpace">
<div *ngIf="profiles$ | async as profiles" id="community-sponsors">
<div *ngIf="profiles$ | async as profiles" id="community-sponsors-anchor">
<div class="community-sponsor" style="margin-bottom: 68px" *ngIf="profiles.whales.length > 0">
<h3 i18n="about.sponsors.withHeart">Whale Sponsors</h3>
<div class="wrapper">

View File

@@ -246,48 +246,3 @@
width: 64px;
height: 64px;
}
#become-sponsor-container {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: center;
align-items: center;
gap: 20px;
margin: 68px auto;
}
.become-sponsor {
background-color: #1d1f31;
border-radius: 16px;
padding: 12px 20px;
width: 400px;
padding: 40px 20px;
}
.become-sponsor a {
margin-top: 10px;
}
#become-sponsor-container .btn {
margin-bottom: 24px;
}
#become-sponsor-container .ng-fa-icon {
color: #2ecc71;
margin-right: 5px;
}
#become-sponsor-container .sponsor-feature {
text-align: left;
width: 250px;
margin: 12px auto;
}
@media (max-width: 992px) {
#become-sponsor-container {
flex-wrap: wrap;
}
}

View File

@@ -9,6 +9,7 @@ import { Router, ActivatedRoute } from '@angular/router';
import { map, share, tap } from 'rxjs/operators';
import { ITranslators } from '../../interfaces/node-api.interface';
import { DOCUMENT } from '@angular/common';
import { EnterpriseService } from '../../services/enterprise.service';
@Component({
selector: 'app-about',
@@ -33,6 +34,7 @@ export class AboutComponent implements OnInit {
private websocketService: WebsocketService,
private seoService: SeoService,
public stateService: StateService,
private enterpriseService: EnterpriseService,
private apiService: ApiService,
private router: Router,
private route: ActivatedRoute,
@@ -47,8 +49,13 @@ export class AboutComponent implements OnInit {
this.websocketService.want(['blocks']);
this.profiles$ = this.apiService.getAboutPageProfiles$().pipe(
tap(() => {
this.goToAnchor()
tap((profiles: any) => {
const scrollToSponsors = this.route.snapshot.fragment === 'community-sponsors';
if (scrollToSponsors && !profiles?.whales?.length && !profiles?.chads?.length) {
return;
} else {
this.goToAnchor(scrollToSponsors)
}
}),
share(),
)
@@ -83,11 +90,19 @@ export class AboutComponent implements OnInit {
this.goToAnchor();
}
goToAnchor() {
goToAnchor(scrollToSponsor = false) {
if (!scrollToSponsor) {
return;
}
setTimeout(() => {
if (this.route.snapshot.fragment) {
if (this.document.getElementById(this.route.snapshot.fragment)) {
this.document.getElementById(this.route.snapshot.fragment).scrollIntoView({behavior: 'smooth'});
const el = scrollToSponsor ? this.document.getElementById('community-sponsors-anchor') : this.document.getElementById(this.route.snapshot.fragment);
if (el) {
if (scrollToSponsor) {
el.scrollIntoView({behavior: 'smooth', block: 'center', inline: 'center'});
} else {
el.scrollIntoView({behavior: 'smooth'});
}
}
}
}, 1);
@@ -108,4 +123,14 @@ export class AboutComponent implements OnInit {
unmutePromoVideo(): void {
this.promoVideo.nativeElement.muted = false;
}
onSponsorClick(e): boolean {
this.enterpriseService.goal(5);
return true;
}
onEnterpriseClick(e): boolean {
this.enterpriseService.goal(6);
return true;
}
}

View File

@@ -2,6 +2,7 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Routes, RouterModule } from '@angular/router';
import { AboutComponent } from './about.component';
import { AboutSponsorsComponent } from './about-sponsors.component';
import { SharedModule } from '../../shared/shared.module';
const routes: Routes = [
@@ -29,6 +30,10 @@ export class AboutRoutingModule { }
],
declarations: [
AboutComponent,
AboutSponsorsComponent,
],
exports: [
AboutSponsorsComponent,
]
})
export class AboutModule { }

View File

@@ -1,14 +1,16 @@
<span id="successAlert" class="m-0 p-0 d-block" style="height: 1px;"></span>
<div class="row" *ngIf="showSuccess">
<div class="col" id="successAlert">
<div class="col">
<div class="alert alert-success">
Transaction has now been <a class="alert-link" routerLink="/services/accelerator/history">submitted</a> to mining pools for acceleration.
</div>
</div>
</div>
<span id="mempoolError" class="m-0 p-0 d-block" style="height: 1px;"></span>
<div class="row" *ngIf="error">
<div class="col" id="mempoolError">
<app-mempool-error [error]="error"></app-mempool-error>
<div class="col">
<app-mempool-error [error]="error" [alertClass]="error === 'waitlisted' ? 'alert-mempool' : 'alert-danger'"></app-mempool-error>
</div>
</div>
@@ -25,6 +27,11 @@
<ng-container *ngIf="estimate">
<div [class]="{estimateDisabled: error}">
<div *ngIf="user && !estimate.hasAccess">
<div class="alert alert-mempool">You are currently on the waitlist</div>
</div>
<h5>Your transaction</h5>
<div class="row">
<div class="col">
@@ -37,10 +44,10 @@
<td class="item">
Virtual size
</td>
<td class="units" [innerHTML]="'&lrm;' + (estimate.txSummary.effectiveVsize | vbytes: 2)"></td>
<td style="text-align: end;" [innerHTML]="'&lrm;' + (estimate.txSummary.effectiveVsize | vbytes: 2)"></td>
</tr>
<tr class="info">
<td class="info">
<td class="info" colspan=3>
<i><small>Size in vbytes of this transaction<span *ngIf="hasAncestors"> and its unconfirmed ancestors</span></small></i>
</td>
</tr>
@@ -48,12 +55,12 @@
<td class="item">
In-band fees
</td>
<td class="units">
{{ estimate.txSummary.effectiveFee | number : '1.0-0' }} <span class="symbol" i18n="shared.sats|sats">sats</span>
<td style="text-align: end;">
{{ estimate.txSummary.effectiveFee | number : '1.0-0' }} <span class="symbol" i18n="shared.sats">sats</span>
</td>
</tr>
<tr class="info group-last">
<td class="info">
<td class="info" colspan=3>
<i><small>Fees already paid by this transaction<span *ngIf="hasAncestors"> and its unconfirmed ancestors</span></small></i>
</td>
</tr>
@@ -74,7 +81,7 @@
<div class="d-flex mb-0">
<ng-container *ngFor="let option of maxRateOptions">
<button type="button" class="btn btn-primary flex-grow-1 btn-border btn-sm feerate" [class]="{active: selectFeeRateIndex === option.index}" (click)="setUserBid(option)">
<span class="fee">{{ option.fee + estimate.mempoolBaseFee + estimate.vsizeFee | number }} <span class="symbol" i18n="shared.sats|sats">sats</span></span>
<span class="fee">{{ option.fee + estimate.mempoolBaseFee + estimate.vsizeFee | number }} <span class="symbol" i18n="shared.sats">sats</span></span>
<span class="rate">~<app-fee-rate [fee]="option.rate" rounding="1.0-0"></app-fee-rate></span>
</button>
</ng-container>
@@ -95,7 +102,7 @@
<td class="item">
Next block market rate
</td>
<td class="amt" style="font-size: 20px">
<td class="amt" style="font-size: 16px">
{{ estimate.targetFeeRate | number : '1.0-0' }}
</td>
<td class="units"><span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></td>
@@ -108,8 +115,8 @@
{{ math.max(0, estimate.nextBlockFee - estimate.txSummary.effectiveFee) | number }}
</td>
<td class="units">
<span class="symbol" i18n="shared.sats|sats">sats</span>
<span class="fiat"><app-fiat [value]="math.max(0, estimate.nextBlockFee - estimate.txSummary.effectiveFee)"></app-fiat></span>
<span class="symbol" i18n="shared.sats">sats</span>
<span class="fiat ml-1"><app-fiat [value]="math.max(0, estimate.nextBlockFee - estimate.txSummary.effectiveFee)"></app-fiat></span>
</td>
</tr>
</ng-container>
@@ -128,8 +135,8 @@
+{{ estimate.mempoolBaseFee | number }}
</td>
<td class="units">
<span class="symbol" i18n="shared.sats|sats">sats</span>
<span class="fiat"><app-fiat [value]="estimate.mempoolBaseFee"></app-fiat></span>
<span class="symbol" i18n="shared.sats">sats</span>
<span class="fiat ml-1"><app-fiat [value]="estimate.mempoolBaseFee"></app-fiat></span>
</td>
</tr>
<tr class="info group-last">
@@ -140,8 +147,8 @@
+{{ estimate.vsizeFee | number }}
</td>
<td class="units">
<span class="symbol" i18n="shared.sats|sats">sats</span>
<span class="fiat"><app-fiat [value]="estimate.vsizeFee"></app-fiat></span>
<span class="symbol" i18n="shared.sats">sats</span>
<span class="fiat ml-1"><app-fiat [value]="estimate.vsizeFee"></app-fiat></span>
</td>
</tr>
@@ -157,12 +164,12 @@
</span>
</td>
<td class="units">
<span class="symbol" i18n="shared.sats|sats">sats</span>
<span class="fiat"><app-fiat [value]="estimate.cost + estimate.mempoolBaseFee + estimate.vsizeFee"></app-fiat></span>
<span class="symbol" i18n="shared.sats">sats</span>
<span class="fiat ml-1"><app-fiat [value]="estimate.cost + estimate.mempoolBaseFee + estimate.vsizeFee"></app-fiat></span>
</td>
</tr>
<tr class="info group-last" style="border-bottom: 1px solid lightgrey">
<td class="info">
<td class="info" colspan=3>
<i><small>If your tx is accelerated to </small><small>{{ estimate.targetFeeRate | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></small></i>
</td>
</tr>
@@ -180,14 +187,14 @@
</span>
</td>
<td class="units">
<span class="symbol" i18n="shared.sats|sats">sats</span>
<span class="fiat">
<span class="symbol" i18n="shared.sats">sats</span>
<span class="fiat ml-1">
<app-fiat [value]="maxCost" [colorClass]="estimate.userBalance < maxCost ? 'red-color' : 'green-color'"></app-fiat>
</span>
</td>
</tr>
<tr class="info group-last">
<td class="info">
<td class="info" colspan=3>
<i><small>If your tx is accelerated to </small><small>~{{ ((estimate.txSummary.effectiveFee + userBid) / estimate.txSummary.effectiveVsize) | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></small></i>
</td>
</tr>
@@ -203,8 +210,8 @@
{{ estimate.userBalance | number }}
</td>
<td class="units">
<span class="symbol" i18n="shared.sats|sats">sats</span>
<span class="fiat">
<span class="symbol" i18n="shared.sats">sats</span>
<span class="fiat ml-1">
<app-fiat [value]="estimate.userBalance" [colorClass]="estimate.userBalance < maxCost ? 'red-color' : 'green-color'"></app-fiat>
</span>
</td>
@@ -228,7 +235,7 @@
<div class="row mb-3" *ngIf="isLoggedIn()">
<div class="col">
<div class="d-flex justify-content-end">
<div class="d-flex justify-content-end" *ngIf="user && estimate.hasAccess">
<button class="btn btn-sm btn-primary btn-success" style="width: 150px" (click)="accelerate()">Accelerate</button>
</div>
</div>

View File

@@ -8,9 +8,6 @@
align-items: center;
justify-content: center;
.fee {
font-size: 1.2em;
}
.rate {
font-size: 0.9em;
.symbol {
@@ -64,8 +61,6 @@
.table-accelerator {
tr {
text-wrap: wrap;
td {
padding-top: 0;
padding-bottom: 0;
@@ -89,6 +84,7 @@
}
&.info {
color: #6c757d;
white-space: initial;
}
&.amt {
text-align: right;
@@ -97,6 +93,9 @@
&.units {
padding-left: 0.2em;
white-space: nowrap;
display: flex;
justify-content: space-between;
align-items: center;
}
}
}
@@ -106,4 +105,8 @@
flex-direction: row;
align-items: stretch;
margin-top: 1em;
}
.item {
white-space: initial;
}

View File

@@ -1,5 +1,4 @@
import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChanges, HostListener } from '@angular/core';
import { Router } from '@angular/router';
import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChanges, HostListener, ChangeDetectorRef } from '@angular/core';
import { ApiService } from '../../services/api.service';
import { Subscription, catchError, of, tap } from 'rxjs';
import { StorageService } from '../../services/storage.service';
@@ -57,13 +56,14 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
userBid = 0;
selectFeeRateIndex = 1;
isMobile: boolean = window.innerWidth <= 767.98;
user: any = undefined;
maxRateOptions: RateOption[] = [];
constructor(
private apiService: ApiService,
private storageService: StorageService,
private router: Router,
private cd: ChangeDetectorRef
) { }
ngOnDestroy(): void {
@@ -79,6 +79,8 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
}
ngOnInit() {
this.user = this.storageService.getAuth()?.user ?? null;
this.estimateSubscription = this.apiService.estimate$(this.tx.txid).pipe(
tap((response) => {
if (response.status === 204) {
@@ -94,7 +96,7 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
this.estimateSubscription.unsubscribe();
}
if (this.estimate.userBalance <= 0) {
if (this.estimate.hasAccess === true && this.estimate.userBalance <= 0) {
if (this.isLoggedIn()) {
this.error = `not_enough_balance`;
this.scrollToPreviewWithTimeout('mempoolError', 'center');
@@ -163,13 +165,14 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
scrollToPreview(id: string, position: ScrollLogicalPosition) {
const acceleratePreviewAnchor = document.getElementById(id);
if (acceleratePreviewAnchor) {
this.cd.markForCheck();
acceleratePreviewAnchor.scrollIntoView({
behavior: 'smooth',
inline: position,
block: position,
});
}
}
}
/**
* Send acceleration request

View File

@@ -69,7 +69,7 @@ export class AddressPreviewComponent implements OnInit, OnDestroy {
this.addressString = this.addressString.toLowerCase();
}
this.seoService.setTitle($localize`:@@address.component.browser-title:Address: ${this.addressString}:INTERPOLATION:`);
this.seoService.setDescription($localize`:@@meta.description.bitcoin.address:See mempool transactions, confirmed transactions, balance, and more for ${this.stateService.network==='liquid'||this.stateService.network==='liquidtestnet'?'Liquid':'Bitcoin'}${seoDescriptionNetwork(this.stateService.network)} address ${this.addressString}:INTERPOLATION:.`);
this.seoService.setDescription($localize`:@@meta.description.bitcoin.address:See mempool transactions, confirmed transactions, balance, and more for ${this.stateService.network==='liquid'||this.stateService.network==='liquidtestnet'?'Liquid':'Bitcoin'} ${seoDescriptionNetwork(this.stateService.network)} address ${this.addressString}:INTERPOLATION:.`);
return (this.addressString.match(/04[a-fA-F0-9]{128}|(02|03)[a-fA-F0-9]{64}/)
? this.electrsApiService.getPubKeyAddress$(this.addressString)

View File

@@ -55,7 +55,7 @@
<td *ngSwitchCase="'added'"><span class="badge badge-warning" i18n="transaction.audit.added">Added</span></td>
<td *ngSwitchCase="'selected'"><span class="badge badge-warning" i18n="transaction.audit.marginal">Marginal fee rate</span></td>
<td *ngSwitchCase="'rbf'"><span class="badge badge-warning" i18n="transaction.audit.conflicting">Conflicting</span></td>
<td *ngSwitchCase="'accelerated'"><span class="badge badge-success" i18n="transaction.audit.accelerated">Accelerated</span></td>
<td *ngSwitchCase="'accelerated'"><span class="badge badge-accelerated" i18n="transaction.audit.accelerated">Accelerated</span></td>
</ng-container>
</tr>
</tbody>

View File

@@ -19,4 +19,17 @@
.td-width {
padding-right: 10px;
}
.badge.badge-accelerated {
background-color: #653b9c;
box-shadow: #ad7de57f 0px 0px 12px -2px;
color: white;
animation: acceleratePulse 1s infinite;
}
@keyframes acceleratePulse {
0% { background-color: #653b9c; box-shadow: #ad7de57f 0px 0px 12px -2px; }
50% { background-color: #8457bb; box-shadow: #ad7de5 0px 0px 18px -2px;}
100% { background-color: #653b9c; box-shadow: #ad7de57f 0px 0px 12px -2px; }
}

View File

@@ -219,13 +219,13 @@
<div class="box" *ngIf="!error && webGlEnabled && showAudit">
<div class="nav nav-tabs" *ngIf="isMobile && showAudit">
<a class="nav-link" [class.active]="mode === 'projected'"
fragment="projected" (click)="changeMode('projected')"><ng-container i18n="block.expected">Expected</ng-container>&nbsp;&nbsp;<span class="badge badge-pill badge-warning" i18n="beta">beta</span></a>
fragment="projected" (click)="changeMode('projected')"><ng-container i18n="block.expected">Expected</ng-container></a>
<a class="nav-link" [class.active]="mode === 'actual'" i18n="block.actual"
fragment="actual" (click)="changeMode('actual')">Actual</a>
</div>
<div class="row">
<div class="col-sm">
<h3 class="block-subtitle" *ngIf="!isMobile"><ng-container i18n="block.expected-block">Expected Block</ng-container> <span class="badge badge-pill badge-warning beta" i18n="beta">beta</span></h3>
<h3 class="block-subtitle" *ngIf="!isMobile"><ng-container i18n="block.expected-block">Expected Block</ng-container></h3>
<div class="block-graph-wrapper">
<app-block-overview-graph #blockGraphProjected [isLoading]="isLoadingOverview" [resolution]="86"
[blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx" [auditHighlighting]="showAudit"
@@ -262,7 +262,7 @@
<tbody>
<tr>
<td class="td-width" i18n="transaction.version">Version</td>
<td>{{ block.version | decimal2hex }} <span *ngIf="displayTaprootStatus() && hasTaproot(block.version)" class="badge badge-success ml-1" >Taproot</span></td>
<td>{{ block.version | decimal2hex }} <span *ngIf="displayTaprootStatus() && hasTaproot(block.version)" class="badge badge-success ml-1" i18n="tx-features.tag.taproot|Taproot">Taproot</span></td>
</tr>
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
<td i18n="block.bits">Bits</td>

View File

@@ -55,7 +55,9 @@ export class BlocksList implements OnInit {
this.skeletonLines = this.widget === true ? [...Array(6).keys()] : [...Array(15).keys()];
this.paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 3 : 5;
this.seoService.setTitle($localize`:@@meta.title.blocks-list:Blocks`);
if (!this.widget) {
this.seoService.setTitle($localize`:@@m8a7b4bd44c0ac71b2e72de0398b303257f7d2f54:Blocks`);
}
if( this.stateService.network==='liquid'||this.stateService.network==='liquidtestnet' ) {
this.seoService.setDescription($localize`:@@meta.description.liquid.blocks:See the most recent Liquid${seoDescriptionNetwork(this.stateService.network)} blocks along with basic stats such as block height, block size, and more.`);
} else {

View File

@@ -1,6 +1,6 @@
<div class="container-xl">
<div class="text-center">
<h2>Calculator</h2>
<h2 i18n="shared.calculator">Calculator</h2>
</div>
<ng-container *ngIf="price$ | async; else loading">
@@ -26,7 +26,7 @@
<div class="input-group input-group-lg mb-1">
<div class="input-group-prepend">
<span class="input-group-text">sats</span>
<span class="input-group-text" i18n="shared.sats">sats</span>
</div>
<input type="text" class="form-control" formControlName="satoshis" (input)="transformInput('satoshis')" (click)="selectAll($event)">
<app-clipboard [button]="true" [text]="form.get('satoshis').value" [class]="'btn btn-lg btn-secondary ml-1'"></app-clipboard>
@@ -41,7 +41,7 @@
<div class="bitcoin-satoshis-text">
<span [innerHTML]="form.get('bitcoin').value | bitcoinsatoshis"></span>
<span class="sats"> sats</span>
<span class="sats" i18n="shared.sats">sats</span>
</div>
</div>

View File

@@ -38,36 +38,35 @@
</div>
<ng-container *ngIf="!hideStats">
<div class="stats top left">
<p class="label" i18n="clock.fiat-price">fiat price</p>
<p class="label" i18n>Price</p>
<p>
<app-fiat [value]="100000000" digitsInfo="1.2-2" colorClass="white-color"></app-fiat>
</p>
</div>
<div class="stats top right">
<p class="label" i18n="clock.priority-rate|priority fee rate">priority rate</p>
<p class="label" i18n="fees-box.high-priority">High Priority</p>
<p *ngIf="recommendedFees$ | async as recommendedFees;">
<app-fee-rate [fee]="recommendedFees.fastestFee" unitClass="" rounding="1.0-0"></app-fee-rate>
</p>
</div>
<div *ngIf="mode !== 'mempool' && blocks?.length" class="stats bottom left">
<p [innerHTML]="blocks[blockIndex].size | bytes: 2"></p>
<p class="label" i18n="clock.block-size">block size</p>
<p class="label" i18n="block.size">Size</p>
</div>
<div *ngIf="mode !== 'mempool' && blocks?.length" class="stats bottom right">
<p class="force-wrap">
<ng-container *ngTemplateOutlet="blocks[blockIndex].tx_count === 1 ? transactionsSingular : transactionsPlural; context: {$implicit: blocks[blockIndex].tx_count | number}"></ng-container>
<ng-template #transactionsSingular let-i i18n="shared.transaction-count.singular">{{ i }} <span class="label">transaction</span></ng-template>
<ng-template #transactionsPlural let-i i18n="shared.transaction-count.plural">{{ i }} <span class="label">transactions</span></ng-template>
{{ blocks[blockIndex].tx_count | number }}
<span class="label" i18n="dashboard.txs">Transactions</span>
</p>
</div>
<ng-container *ngIf="mempoolInfo$ | async as mempoolInfo;">
<div *ngIf="mode === 'mempool'" class="stats bottom left">
<p [innerHTML]="mempoolInfo.usage | bytes: 0"></p>
<p class="label" i18n="dashboard.memory-usage|Memory usage">memory usage</p>
<p class="label" i18n="dashboard.memory-usage|Memory usage">Memory Usage</p>
</div>
<div *ngIf="mode === 'mempool'" class="stats bottom right">
<p>{{ mempoolInfo.size | number }}</p>
<p class="label" i18n="dashboard.unconfirmed|Unconfirmed count">unconfirmed</p>
<p class="label" i18n="dashboard.unconfirmed|Unconfirmed count">Unconfirmed</p>
</div>
</ng-container>
</ng-container>

View File

@@ -63,6 +63,7 @@
.label {
font-size: calc(0.04 * var(--clock-width));
line-height: calc(0.05 * var(--clock-width));
text-transform: lowercase;
}
&.top {

View File

@@ -11,7 +11,7 @@
<div class="progress inc-tx-progress-bar">
<div class="progress-bar" role="progressbar" [ngStyle]="{'width': mempoolInfoData.progressWidth, 'background-color': mempoolInfoData.progressColor}">&nbsp;</div>
<div class="progress-text" *only-vsize>&lrm;{{ mempoolInfoData.vBytesPerSecond | ceil | number }} <ng-container i18n="shared.vbytes-per-second|vB/s">vB/s</ng-container></div>
<div class="progress-text" *only-weight>&lrm;{{ mempoolInfoData.vBytesPerSecond * 4 | ceil | number }} <ng-container i18n="shared.weight-units-per-second|vB/s">WU/s</ng-container></div>
<div class="progress-text" *only-weight>&lrm;{{ mempoolInfoData.vBytesPerSecond * 4 | ceil | number }} <ng-container i18n="shared.weight-per-second|WU/s">WU/s</ng-container></div>
</div>
</ng-template>
</ng-template>

View File

@@ -22,7 +22,7 @@
<a class="dropdown-item" routerLinkActive="active"
[routerLink]="['/graphs/mining/block-sizes-weights' | relativeUrl]" i18n="mining.block-sizes-weights">Block Sizes and Weights</a>
<a *ngIf="stateService.env.AUDIT" class="dropdown-item" routerLinkActive="active"
[routerLink]="['/graphs/mining/block-health' | relativeUrl]" i18n="mining.block-health">Block Health</a>
[routerLink]="['/graphs/mining/block-health' | relativeUrl]" i18n="mining.blocks-health">Block Health</a>
</div>
</div>

View File

@@ -249,10 +249,8 @@ export class HashrateChartComponent implements OnInit {
for (const tick of ticks) {
if (tick.seriesIndex === 0) { // Hashrate
let hashrate = tick.data[1];
if (this.isMobile()) {
hashratePowerOfTen = selectPowerOfTen(tick.data[1]);
hashrate = Math.round(tick.data[1] / hashratePowerOfTen.divider);
}
hashratePowerOfTen = selectPowerOfTen(tick.data[1], 10);
hashrate = tick.data[1] / hashratePowerOfTen.divider;
hashrateString = `${tick.marker} ${tick.seriesName}: ${formatNumber(hashrate, this.locale, '1.0-0')} ${hashratePowerOfTen.unit}H/s<br>`;
} else if (tick.seriesIndex === 1) { // Difficulty
let difficultyPowerOfTen = hashratePowerOfTen;
@@ -260,18 +258,14 @@ export class HashrateChartComponent implements OnInit {
if (difficulty === null) {
difficultyString = `${tick.marker} ${tick.seriesName}: No data<br>`;
} else {
if (this.isMobile()) {
difficultyPowerOfTen = selectPowerOfTen(tick.data[1]);
difficulty = Math.round(tick.data[1] / difficultyPowerOfTen.divider);
}
difficultyPowerOfTen = selectPowerOfTen(tick.data[1]);
difficulty = tick.data[1] / difficultyPowerOfTen.divider;
difficultyString = `${tick.marker} ${tick.seriesName}: ${formatNumber(difficulty, this.locale, '1.2-2')} ${difficultyPowerOfTen.unit}<br>`;
}
} else if (tick.seriesIndex === 2) { // Hashrate MA
let hashrate = tick.data[1];
if (this.isMobile()) {
hashratePowerOfTen = selectPowerOfTen(tick.data[1]);
hashrate = Math.round(tick.data[1] / hashratePowerOfTen.divider);
}
hashratePowerOfTen = selectPowerOfTen(tick.data[1], 10);
hashrate = tick.data[1] / hashratePowerOfTen.divider;
hashrateStringMA = `${tick.marker} ${tick.seriesName}: ${formatNumber(hashrate, this.locale, '1.0-0')} ${hashratePowerOfTen.unit}H/s`;
}
}

View File

@@ -209,8 +209,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On
obj[['left', 'right'][+(pos[0] < size.viewSize[0] / 2)]] = 80;
return obj;
},
extraCssText: `width: ${(['2h', '24h'].includes(this.windowPreference) || this.template === 'widget') ? '125px' : '135px'};
background: transparent;
extraCssText: `background: transparent;
border: none;
box-shadow: none;`,
axisPointer: {
@@ -233,7 +232,8 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On
}
}
});
return `<div class="tx-wrapper-tooltip-chart ${(this.template === 'advanced') ? 'tx-wrapper-tooltip-chart-advanced' : ''}">${itemFormatted}</div>`;
return `<div class="tx-wrapper-tooltip-chart ${(this.template === 'advanced') ? 'tx-wrapper-tooltip-chart-advanced' : ''}"
style="width: ${(this.windowPreference === '2h' || this.template === 'widget') ? '125px' : '215px'}">${itemFormatted}</div>`;
}
},
xAxis: [

View File

@@ -76,7 +76,7 @@
</nav>
</header>
<div class="d-flex">
<div class="d-flex" style="overflow: clip">
<app-menu *ngIf="servicesEnabled" [navOpen]="menuOpen" (loggedOut)="onLoggedOut()" (menuToggled)="menuToggled($event)"></app-menu>
<div *ngIf="!servicesEnabled" class="sidenav"><!-- empty sidenav needed to push footer down the screen --></div>

View File

@@ -244,10 +244,9 @@ main {
.sidenav {
z-index: 1;
background-color: transparent;
width: 225px;
width: 0px;
height: calc(100vh - 65px);
position: sticky;
top: 65px;
margin-left: -225px;
padding-bottom: 20px;
}

View File

@@ -7,7 +7,7 @@
</span>
<a *ngIf="!userAuth" class="d-flex justify-content-center align-items-center nav-link m-0 menu-click" routerLink="/login" role="tab" (click)="onLinkClick('/login')">
<fa-icon class="menu-click" [icon]="['fas', 'user-circle']" [fixedWidth]="true" style="font-size: 25px;margin-right: 15px;"></fa-icon>
<span class="menu-click" style="font-size: 20px;">Sign in</span>
<span class="menu-click" style="font-size: 20px;" i18n="shared.sign-in">Sign in</span>
</a>
<ng-container *ngIf="userMenuGroups$ | async as menuGroups">

View File

@@ -5,7 +5,7 @@
height: calc(100vh - 65px);
position: sticky;
top: 65px;
transition: width 0.25s, left 0.25s, margin 0.25s;
transition: 0.25s;
margin-left: -250px;
box-shadow: 5px 0px 30px 0px #000;
padding-bottom: 20px;
@@ -29,6 +29,7 @@
margin-left: 0px;
left: 0px;
display: block;
background-color: #1d1f31;
}
.sidenav a, button{

View File

@@ -44,12 +44,12 @@
</div>
</div>
<!-- Latest blocks -->
<!-- Recent blocks -->
<div class="col">
<div class="card">
<div class="card-body">
<a class="title-link" href="" [routerLink]="['/blocks' | relativeUrl]">
<h5 class="card-title d-inline" i18n="dashboard.latest-blocks">Latest blocks</h5>
<h5 class="card-title d-inline" i18n="dashboard.recent-blocks">Recent Blocks</h5>
<span>&nbsp;</span>
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: text-top; font-size: 13px; color: #4a68b9"></fa-icon>
</a>

View File

@@ -6,7 +6,7 @@
<div class="pool-distribution" *ngIf="(miningStatsObservable$ | async) as miningStats; else loadingReward">
<div class="item">
<h5 class="card-title d-inline-block" i18n="mining.miners-luck" i18n-ngbTooltip="mining.miners-luck-1w"
ngbTooltip="Pools luck (1 week)" placement="bottom" #minersluck [disableTooltip]="!isEllipsisActive(minersluck)">Pools luck</h5>
ngbTooltip="Pools luck (1 week)" placement="bottom" #minersluck [disableTooltip]="!isEllipsisActive(minersluck)">Pools Luck</h5>
<p class="card-text" i18n-ngbTooltip="mining.pools-luck-desc"
ngbTooltip="The overall luck of all mining pools over the past week. A luck bigger than 100% means the average block time for the current epoch is less than 10 minutes." placement="bottom">
{{ miningStats['minersLuck'] }}%
@@ -14,14 +14,14 @@
</div>
<div class="item">
<h5 class="card-title d-inline-block" i18n="mining.miners-count" i18n-ngbTooltip="mining.miners-count-1w"
ngbTooltip="Pools count (1w)" placement="bottom" #poolscount [disableTooltip]="!isEllipsisActive(poolscount)">Pools count</h5>
ngbTooltip="Pools count (1w)" placement="bottom" #poolscount [disableTooltip]="!isEllipsisActive(poolscount)">Pools Count</h5>
<p class="card-text" i18n-ngbTooltip="mining.pools-count-desc"
ngbTooltip="How many unique pools found at least one block over the past week." placement="bottom">
{{ miningStats.pools.length }}
</p>
</div>
<div class="item">
<h5 class="card-title d-inline-block" i18n="master-page.blocks" i18n-ngbTooltip="master-page.blocks"
<h5 class="card-title d-inline-block" i18n="shared.blocks-1w" i18n-ngbTooltip="master-page.blocks"
ngbTooltip="Blocks (1w)" placement="bottom" #blockscount [disableTooltip]="!isEllipsisActive(blockscount)">Blocks (1w)</h5>
<p class="card-text" i18n-ngbTooltip="mining.blocks-count-desc"
ngbTooltip="The number of blocks found over the past week." placement="bottom">
@@ -95,7 +95,7 @@
<th *ngIf="auditAvailable" class="health text-right widget" i18n="latest-blocks.avg_health"
i18n-ngbTooltip="latest-blocks.avg_health" ngbTooltip="Avg Health" placement="bottom" #health [disableTooltip]="!isEllipsisActive(health)">Avg Health</th>
<th *ngIf="auditAvailable" class="d-none d-sm-table-cell" i18n="mining.fees-per-block">Avg Block Fees</th>
<th class="d-none d-lg-table-cell" i18n="mining.empty-blocks">Empty blocks</th>
<th class="d-none d-lg-table-cell" i18n="mining.empty-blocks">Empty Blocks</th>
</tr>
</thead>
<tbody [attr.data-cy]="'pools-table'" *ngIf="(miningStatsObservable$ | async) as miningStats">
@@ -153,19 +153,19 @@
<ng-template #loadingReward>
<div class="pool-distribution">
<div class="item">
<h5 class="card-title" i18n="mining.miners-luck">Pools Luck (1w)</h5>
<h5 class="card-title" i18n="mining.miners-luck">Pools Luck</h5>
<p class="card-text">
<span class="skeleton-loader skeleton-loader-big"></span>
</p>
</div>
<div class="item">
<h5 class="card-title" i18n="master-page.blocks">Blocks (1w)</h5>
<h5 class="card-title" i18n="mining.miners-count" >Pools Count</h5>
<p class="card-text">
<span class="skeleton-loader skeleton-loader-big"></span>
</p>
</div>
<div class="item">
<h5 class="card-title" i18n="mining.miners-count">Pools Count (1w)</h5>
<h5 class="card-title" i18n="shared.blocks-1w">Blocks (1w)</h5>
<p class="card-text">
<span class="skeleton-loader skeleton-loader-big"></span>
</p>

View File

@@ -143,7 +143,7 @@
<table class="table table-xs table-data">
<thead>
<tr>
<th scope="col" class="data-title text-center" style="width: 33%" i18n="24h">Blocks 24h</th>
<th scope="col" class="data-title text-center" style="width: 33%" i18n="24h">Blocks (24h)</th>
<th scope="col" class="data-title text-center" style="width: 33%" i18n="1w">1w</th>
<th scope="col" class="data-title text-center" style="width: 33%" i18n="all">All</th>
</tr>
@@ -165,7 +165,7 @@
<table class="table table-xs table-data">
<thead>
<tr>
<th scope="col" class="data-title text-center" style="width: 33%" i18n="24h">Blocks 24h</th>
<th scope="col" class="data-title text-center" style="width: 33%" i18n="24h">Blocks (24h)</th>
<th scope="col" class="data-title text-center" style="width: 33%" i18n="1w">1w</th>
<th scope="col" class="data-title text-center" style="width: 33%" i18n="all">All</th>
</tr>
@@ -433,7 +433,7 @@
<table class="table table-xs table-data text-center">
<thead>
<tr>
<th scope="col" class="data-title text-center" style="width: 33%" i18n="24h">Blocks 24h</th>
<th scope="col" class="data-title text-center" style="width: 33%" i18n="24h">Blocks (24h)</th>
<th scope="col" class="data-title text-center" style="width: 33%" i18n="1w">1w</th>
<th scope="col" class="data-title text-center" style="width: 33%" i18n="all">All</th>
</tr>
@@ -458,7 +458,7 @@
<table class="table table-xs table-data text-center">
<thead>
<tr>
<th scope="col" class="data-title text-center" style="width: 33%" i18n="24h">Blocks 24h</th>
<th scope="col" class="data-title text-center" style="width: 33%" i18n="24h">Blocks (24h)</th>
<th scope="col" class="data-title text-center" style="width: 33%" i18n="1w">1w</th>
<th scope="col" class="data-title text-center" style="width: 33%" i18n="all">All</th>
</tr>

View File

@@ -163,10 +163,8 @@ export class PoolComponent implements OnInit {
let hashratePowerOfTen: any = selectPowerOfTen(1);
let hashrate = ticks[0].data[1];
if (this.isMobile()) {
hashratePowerOfTen = selectPowerOfTen(ticks[0].data[1]);
hashrate = Math.round(ticks[0].data[1] / hashratePowerOfTen.divider);
}
hashratePowerOfTen = selectPowerOfTen(ticks[0].data[1], 10);
hashrate = ticks[0].data[1] / hashratePowerOfTen.divider;
return `
<b style="color: white; margin-left: 18px">${ticks[0].axisValueLabel}</b><br>

View File

@@ -5,7 +5,7 @@
<br><br>
<h2>Privacy Policy</h2>
<h6>Updated: November 18, 2021</h6>
<h6>Updated: November 23, 2023</h6>
<br><br>
@@ -59,7 +59,7 @@
<ol>
<li>If you provide your e-mail address, we may contact you regarding your account, billing purposess, or to update you about our services. We will not share this with any third-party.</li>
<li>If you provide your name, country, and/or e-mail address, we may use this information to manage your user account, for billing purposes, or to update you about our services. We will not share this with any third-party, except as detailed below if you sponsor The Mempool Open Source Project®, purchase a subscription to Mempool Enterprise®, or accelerate transactions using Mempool Accelerator™.</li>
<li>If you connect your Twitter account, we may store your Twitter identity, e-mail address, and profile photo. We may publicly display your profile photo or link to your profile on our website, if you sponsor The Mempool Open Source Project, claim your Lightning node, or other such use cases.</li>
@@ -67,7 +67,7 @@
<li>If you make a Bitcoin or Liquid payment, we will process your payment using our self-hosted BTCPay Server instance and not share these details with any third-party.</li>
<li>If you accelerate transactions using Mempool Accelerator(tm), we will store the TXID of your transactions you accelerate with us. We share this information with our mining pool partners, as well as publicly display accelerated transaction details on our website and APIs.</li>
<li>If you accelerate transactions using Mempool Accelerator, we will store the TXID of your transactions you accelerate with us. We share this information with our mining pool partners, as well as publicly display accelerated transaction details on our website and APIs.</li>
</ol>

View File

@@ -6,11 +6,9 @@
<form class="formRadioGroup">
<div class="btn-group btn-group-toggle" name="radioBasic">
<label class="btn btn-primary btn-sm" [class.active]="!fullRbf">
<input type="radio" [value]="'All'" fragment="" [routerLink]="[]"> All
</label>
<input type="radio" [value]="'All'" fragment="" [routerLink]="[]"><span i18n="all">All</span></label>
<label class="btn btn-primary btn-sm" [class.active]="fullRbf">
<input type="radio" [value]="'Full RBF'" fragment="fullrbf" [routerLink]="[]"> Full RBF
</label>
<input type="radio" [value]="'Full RBF'" fragment="fullrbf" [routerLink]="[]" i18n="transaction.full-rbf">Full RBF</label>
</div>
</form>
</div>
@@ -33,7 +31,7 @@
</div>
<div class="no-replacements" *ngIf="!trees?.length">
<p i18n="rbf.no-replacements-yet">there are no replacements in the mempool yet!</p>
<p i18n="rbf.no-replacements-yet">There are no replacements in the mempool yet!</p>
</div>
</ng-container>
</div>

View File

@@ -55,7 +55,7 @@ export class RbfList implements OnInit, OnDestroy {
})
);
this.seoService.setTitle($localize`:@@meta.title.rbf-list:RBF Replacements`);
this.seoService.setTitle($localize`:@@5e3d5a82750902f159122fcca487b07f1af3141f:RBF Replacements`);
this.seoService.setDescription($localize`:@@meta.description.rbf-list:See the most recent RBF replacements on the Bitcoin${seoDescriptionNetwork(this.stateService.network)} network, updated in real-time.`);
}

View File

@@ -32,9 +32,9 @@
<tr>
<td class="td-width" i18n="transaction.status|Transaction Status">Status</td>
<td>
<span *ngIf="rbfInfo.tx.fullRbf" class="badge badge-info" i18n="rbfInfo-features.tag.full-rbf|Full RBF">Full RBF</span>
<span *ngIf="rbfInfo.tx.fullRbf" class="badge badge-info" i18n="transaction.full-rbf">Full RBF</span>
<span *ngIf="rbfInfo.tx.rbf; else rbfDisabled" class="badge badge-success" i18n="rbfInfo-features.tag.rbf|RBF">RBF</span>
<ng-template #rbfDisabled><span class="badge badge-danger mr-1"><del i18n="rbfInfo-features.tag.rbf|RBF">RBF</del></span></ng-template>
<ng-template #rbfDisabled><span class="badge badge-danger mr-1"><del i18n="tx-features.tag.rbf|RBF">RBF</del></span></ng-template>
<span *ngIf="rbfInfo.tx.mined" class="badge badge-success" i18n="transaction.rbf.mined">Mined</span>
</td>
</tr>

View File

@@ -40,6 +40,8 @@ export class SearchFormComponent implements OnInit {
regexBlockhash = /^[0]{8}[a-fA-F0-9]{56}$/;
regexTransaction = /^([a-fA-F0-9]{64})(:\d+)?$/;
regexBlockheight = /^[0-9]{1,9}$/;
regexDate = /^(?:\d{4}[-/]\d{1,2}[-/]\d{1,2}(?: \d{1,2}:\d{2})?)$/;
regexUnixTimestamp = /^\d{10}$/;
focus$ = new Subject<string>();
click$ = new Subject<string>();
@@ -173,6 +175,8 @@ export class SearchFormComponent implements OnInit {
const lightningResults = result[1];
const matchesBlockHeight = this.regexBlockheight.test(searchText);
const matchesDateTime = this.regexDate.test(searchText) && new Date(searchText).toString() !== 'Invalid Date';
const matchesUnixTimestamp = this.regexUnixTimestamp.test(searchText);
const matchesTxId = this.regexTransaction.test(searchText) && !this.regexBlockhash.test(searchText);
const matchesBlockHash = this.regexBlockhash.test(searchText);
const matchesAddress = !matchesTxId && this.regexAddress.test(searchText);
@@ -181,10 +185,16 @@ export class SearchFormComponent implements OnInit {
searchText = 'B' + searchText;
}
if (matchesDateTime && searchText.indexOf('/') !== -1) {
searchText = searchText.replace(/\//g, '-');
}
return {
searchText: searchText,
hashQuickMatch: +(matchesBlockHeight || matchesBlockHash || matchesTxId || matchesAddress),
hashQuickMatch: +(matchesBlockHeight || matchesBlockHash || matchesTxId || matchesAddress || matchesUnixTimestamp || matchesDateTime),
blockHeight: matchesBlockHeight,
dateTime: matchesDateTime,
unixTimestamp: matchesUnixTimestamp,
txId: matchesTxId,
blockHash: matchesBlockHash,
address: matchesAddress,
@@ -243,6 +253,13 @@ export class SearchFormComponent implements OnInit {
} else {
this.navigate('/tx/', matches[0]);
}
} else if (this.regexDate.test(searchText) || this.regexUnixTimestamp.test(searchText)) {
let timestamp: number;
this.regexDate.test(searchText) ? timestamp = Math.floor(new Date(searchText).getTime() / 1000) : timestamp = searchText;
this.apiService.getBlockDataFromTimestamp$(timestamp).subscribe(
(data) => { this.navigate('/block/', data.hash); },
(error) => { console.log(error); this.isSearching = false; }
);
} else {
this.searchResults.searchButtonClick();
this.isSearching = false;

View File

@@ -5,6 +5,18 @@
<ng-container *ngTemplateOutlet="goTo; context: { $implicit: results.searchText }"></ng-container>
</button>
</ng-template>
<ng-template [ngIf]="results.dateTime">
<div class="card-title" i18n="search.bitcoin-block-date">Date</div>
<button (click)="clickItem(0)" [class.active]="0 === activeIdx" type="button" role="option" class="dropdown-item">
<ng-container *ngTemplateOutlet="goTo; context: { $implicit: results.searchText }"></ng-container>
</button>
</ng-template>
<ng-template [ngIf]="results.unixTimestamp">
<div class="card-title" i18n="search.bitcoin-block-timestamp">Timestamp</div>
<button (click)="clickItem(0)" [class.active]="0 === activeIdx" type="button" role="option" class="dropdown-item">
<ng-container *ngTemplateOutlet="goTo; context: { $implicit: results.searchText }"></ng-container>
</button>
</ng-template>
<ng-template [ngIf]="results.txId">
<div class="card-title" i18n="search.bitcoin-transaction">Bitcoin Transaction</div>
<button (click)="clickItem(0)" [class.active]="0 === activeIdx" type="button" role="option" class="dropdown-item">

View File

@@ -14,7 +14,7 @@
[class]="(stateService.env.MINING_DASHBOARD || stateService.env.LIGHTNING) ? 'mining' : 'no-menu'" (click)="saveGraphPreference()">
<div class="small-buttons">
<a class="btn btn-primary btn-sm mb-0" [routerLink]="['/clock/mempool/0' | relativeUrl]" style="color: white" id="btn-clock">
<fa-icon [icon]="['fas', 'clock']" [fixedWidth]="true" i18n-title="master-page.clockview" title="Clock view"></fa-icon>
<fa-icon [icon]="['fas', 'clock']" [fixedWidth]="true" i18n-title="master-page.clockview" i18n-title="footer.clock-mempool" title="Clock (Mempool)"></fa-icon>
</a>
<a *ngIf="!isMobile()" class="btn btn-primary btn-sm mb-0" [routerLink]="['/tv' | relativeUrl]" style="color: white" id="btn-tv">
<fa-icon [icon]="['fas', 'tv']" [fixedWidth]="true" i18n-title="master-page.tvview" title="TV view"></fa-icon>

View File

@@ -40,7 +40,7 @@
<ng-container [ngSwitch]="extraData">
<div class="opreturns" *ngSwitchCase="'coinbase'">
<div class="opreturn-row">
<span class="label">Coinbase</span>
<span class="label" i18n="transactions-list.coinbase">Coinbase</span>
<span class="message">{{ tx.vin[0].scriptsig | hex2ascii }}</span>
</div>
</div>

View File

@@ -88,7 +88,7 @@ export class TransactionPreviewComponent implements OnInit, OnDestroy {
this.seoService.setTitle(
$localize`:@@bisq.transaction.browser-title:Transaction: ${this.txId}:INTERPOLATION:`
);
this.seoService.setDescription($localize`:@@meta.description.bitcoin.transaction:Get real-time status, addresses, fees, script info, and more for ${this.stateService.network==='liquid'||this.stateService.network==='liquidtestnet'?'Liquid':'Bitcoin'}${seoDescriptionNetwork(this.stateService.network)} transaction with txid {txid}.`);
this.seoService.setDescription($localize`:@@meta.description.bitcoin.transaction:Get real-time status, addresses, fees, script info, and more for ${this.stateService.network==='liquid'||this.stateService.network==='liquidtestnet'?'Liquid':'Bitcoin'}${seoDescriptionNetwork(this.stateService.network)} transaction with txid ${this.txId}.`);
this.resetTransaction();
return merge(
of(true),

View File

@@ -83,9 +83,13 @@
<!-- Accelerator -->
<ng-container *ngIf="!tx?.status?.confirmed && showAccelerationSummary">
<div class="title mt-3">
<br>
<div class="title float-left">
<h2>Accelerate</h2>
</div>
<button type="button" class="btn btn-outline-info accelerator-toggle btn-sm float-right" (click)="showAccelerationSummary = false" i18n="hide-accelerator">Hide accelerator</button>
<div class="clearfix"></div>
<div class="box">
<app-accelerate-preview [tx]="tx" [scrollEvent]="scrollIntoAccelPreview"></app-accelerate-preview>
</div>
@@ -120,7 +124,7 @@
<ng-template [ngIf]="this.mempoolPosition.block >= 7" [ngIfElse]="belowBlockLimit">
<span [class]="(acceleratorAvailable && accelerateCtaType === 'button') ? 'etaDeepMempool d-flex justify-content-end align-items-center' : ''">
<span i18n="transaction.eta.in-several-hours|Transaction ETA in several hours or more">In several hours (or more)</span>
<a *ngIf="!tx.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !tx?.acceleration" [href]="'/services/accelerator/accelerate?txid=' + tx.txid" class="btn btn-sm accelerateDeepMempool" i18n="transaction.accelerate|Accelerate button label" (click)="onAccelerateClicked()">Accelerate</a>
<a *ngIf="!tx.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !tx?.acceleration" [href]="'/services/accelerator/accelerate?txid=' + tx.txid" class="btn btn-sm accelerateDeepMempool btn-small-height" i18n="transaction.accelerate|Accelerate button label" (click)="onAccelerateClicked()">Accelerate</a>
</span>
</ng-template>
<ng-template #belowBlockLimit>
@@ -130,7 +134,7 @@
<ng-template #timeEstimateDefault>
<span class="eta justify-content-end" [class]="(acceleratorAvailable && accelerateCtaType === 'button') ? 'd-flex align-items-center' : ''">
<app-time kind="until" *ngIf="(da$ | async) as da;" [time]="da.timeAvg * (this.mempoolPosition.block + 1) + now + da.timeOffset" [fastRender]="false" [fixedRender]="true"></app-time>
<a *ngIf="!tx.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !tx?.acceleration" [href]="'/services/accelerator/accelerate?txid=' + tx.txid" class="btn btn-sm accelerate" i18n="transaction.accelerate|Accelerate button label" (click)="onAccelerateClicked()">Accelerate</a>
<a *ngIf="!tx.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !tx?.acceleration" [href]="'/services/accelerator/accelerate?txid=' + tx.txid" class="btn btn-sm accelerate btn-small-height" i18n="transaction.accelerate|Accelerate button label" (click)="onAccelerateClicked()">Accelerate</a>
</span>
</ng-template>
</ng-template>
@@ -508,18 +512,24 @@
<app-fee-rate [fee]="tx.feePerVsize"></app-fee-rate>
<ng-template [ngIf]="tx?.status?.confirmed">
&nbsp;
<app-tx-fee-rating *ngIf="tx.fee && !hasEffectiveFeeRate" [tx]="tx"></app-tx-fee-rating>
<app-tx-fee-rating *ngIf="tx.fee && !hasEffectiveFeeRate && !accelerationInfo" [tx]="tx"></app-tx-fee-rating>
</ng-template>
</td>
</tr>
<tr *ngIf="cpfpInfo && hasEffectiveFeeRate">
<td *ngIf="tx.acceleration" i18n="transaction.accelerated-fee-rate|Accelerated transaction fee rate">Accelerated fee rate</td>
<td *ngIf="!tx.acceleration" i18n="transaction.effective-fee-rate|Effective transaction fee rate">Effective fee rate</td>
<tr *ngIf="(cpfpInfo && hasEffectiveFeeRate) || accelerationInfo">
<td *ngIf="tx.acceleration || accelerationInfo" i18n="transaction.accelerated-fee-rate|Accelerated transaction fee rate">Accelerated fee rate</td>
<td *ngIf="!(tx.acceleration || accelerationInfo)" i18n="transaction.effective-fee-rate|Effective transaction fee rate">Effective fee rate</td>
<td>
<div class="effective-fee-container">
<app-fee-rate [fee]="tx.effectiveFeePerVsize"></app-fee-rate>
<ng-template [ngIf]="tx?.status?.confirmed">
<app-tx-fee-rating class="ml-2 mr-2" *ngIf="tx.fee || tx.effectiveFeePerVsize" [tx]="tx"></app-tx-fee-rating>
<app-fee-rate *ngIf="accelerationInfo" [fee]="accelerationInfo.actualFeeDelta" [weight]="accelerationInfo.effectiveVsize * 4"></app-fee-rate>
<app-fee-rate *ngIf="!accelerationInfo" [fee]="tx.effectiveFeePerVsize"></app-fee-rate>
<ng-template [ngIf]="tx?.status?.confirmed || tx.acceleration || accelerationInfo">
<app-tx-fee-rating *ngIf="!(tx.acceleration || accelerationInfo) && (tx.fee || tx.effectiveFeePerVsize)" class="ml-2 mr-2 effective-fee-rating" [tx]="tx"></app-tx-fee-rating>
<ng-container *ngIf="accelerationInfo || tx.acceleration">
&nbsp;
<span class="badge badge-accelerated" i18n="transaction.audit.accelerated">Accelerated</span>
</ng-container>
</ng-template>
</div>
<button *ngIf="cpfpInfo.bestDescendant || cpfpInfo.descendants?.length || cpfpInfo.ancestors?.length" type="button" class="btn btn-outline-info btn-sm btn-small-height float-right" (click)="showCpfpDetails = !showCpfpDetails">CPFP <fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon></button>

View File

@@ -60,8 +60,13 @@
top: -1px;
}
.badge.badge-accelerated {
background-color: #653b9c;
color: white;
}
.btn-small-height {
line-height: 1.1;
line-height: 1;
}
.arrow-green {
@@ -152,6 +157,16 @@
@media (min-width: 768px){
display: inline-block;
}
@media (max-width: 425px){
display: flex;
flex-direction: column;
}
}
.effective-fee-rating {
@media (max-width: 767px){
margin-right: 0px !important;
}
}
.title {
@@ -169,7 +184,7 @@
}
}
.details-button, .flow-toggle {
.details-button, .flow-toggle, .accelerator-toggle {
margin-top: -5px;
margin-left: 10px;
@media (min-width: 768px){
@@ -259,7 +274,6 @@
.accelerate {
display: flex !important;
align-self: auto;
margin-top: 3px;
margin-left: auto;
background-color: #653b9c;
@media (max-width: 849px) {

View File

@@ -21,7 +21,7 @@ import { ApiService } from '../../services/api.service';
import { SeoService } from '../../services/seo.service';
import { StorageService } from '../../services/storage.service';
import { seoDescriptionNetwork } from '../../shared/common.utils';
import { BlockExtended, CpfpInfo, RbfTree, MempoolPosition, DifficultyAdjustment } from '../../interfaces/node-api.interface';
import { BlockExtended, CpfpInfo, RbfTree, MempoolPosition, DifficultyAdjustment, Acceleration } from '../../interfaces/node-api.interface';
import { LiquidUnblinding } from './liquid-ublinding';
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
import { Price, PriceService } from '../../services/price.service';
@@ -49,6 +49,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
fetchCpfpSubscription: Subscription;
fetchRbfSubscription: Subscription;
fetchCachedTxSubscription: Subscription;
fetchAccelerationSubscription: Subscription;
txReplacedSubscription: Subscription;
txRbfInfoSubscription: Subscription;
mempoolPositionSubscription: Subscription;
@@ -62,12 +63,14 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
rbfReplaces: string[];
rbfInfo: RbfTree;
cpfpInfo: CpfpInfo | null;
accelerationInfo: Acceleration | null = null;
sigops: number | null;
adjustedVsize: number | null;
showCpfpDetails = false;
fetchCpfp$ = new Subject<string>();
fetchRbfHistory$ = new Subject<string>();
fetchCachedTx$ = new Subject<string>();
fetchAcceleration$ = new Subject<string>();
isCached: boolean = false;
now = Date.now();
da$: Observable<DifficultyAdjustment>;
@@ -238,6 +241,25 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
}
});
this.fetchAccelerationSubscription = this.fetchAcceleration$.pipe(
tap(() => {
this.accelerationInfo = null;
}),
switchMap((blockHash: string) => {
return this.apiService.getAccelerationHistory$({ blockHash });
}),
catchError(() => {
return of(null);
})
).subscribe((accelerationHistory) => {
for (const acceleration of accelerationHistory) {
if (acceleration.txid === this.txId && (acceleration.status === 'completed' || acceleration.status === 'mined') && acceleration.feePaid > 0) {
acceleration.actualFeeDelta = Math.max(acceleration.effectiveFee, acceleration.effectiveFee + acceleration.feePaid - acceleration.baseFee - acceleration.vsizeFee);
this.accelerationInfo = acceleration;
}
}
});
this.mempoolPositionSubscription = this.stateService.mempoolTxPosition$.subscribe(txPosition => {
this.now = Date.now();
if (txPosition && txPosition.txid === this.txId && txPosition.position) {
@@ -365,6 +387,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
this.getTransactionTime();
}
} else {
this.fetchAcceleration$.next(tx.status.block_hash);
this.transactionTime = 0;
}
@@ -417,6 +440,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
};
this.stateService.markBlock$.next({ blockHeight: block.height });
this.audioService.playSound('magic');
this.fetchAcceleration$.next(block.id);
}
});
@@ -585,6 +609,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
this.rbfInfo = null;
this.rbfReplaces = [];
this.showCpfpDetails = false;
this.accelerationInfo = null;
this.txInBlockIndex = null;
this.mempoolPosition = null;
document.body.scrollTo(0, 0);
@@ -664,6 +689,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
this.fetchCpfpSubscription.unsubscribe();
this.fetchRbfSubscription.unsubscribe();
this.fetchCachedTxSubscription.unsubscribe();
this.fetchAccelerationSubscription.unsubscribe();
this.txReplacedSubscription.unsubscribe();
this.txRbfInfoSubscription.unsubscribe();
this.queryParamsSubscription.unsubscribe();

View File

@@ -16,7 +16,7 @@
<ng-template #coinbase>
<ng-container *ngIf="line.coinbase; else pegin">
<p>Coinbase</p>
<p i18n="transactions-list.coinbase">Coinbase</p>
</ng-container>
</ng-template>

View File

@@ -76,7 +76,7 @@
<div class="card" *ngIf="(network$ | async) !== 'liquid' && (network$ | async) !== 'liquidtestnet'; else latestBlocks">
<div class="card-body">
<a class="title-link" href="" [routerLink]="['/rbf' | relativeUrl]">
<h5 class="card-title d-inline" i18n="dashboard.latest-rbf-replacements">Latest replacements</h5>
<h5 class="card-title d-inline" i18n="dashboard.recent-rbf-replacements">Recent Replacements</h5>
<span>&nbsp;</span>
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: text-top; font-size: 13px; color: #4a68b9"></fa-icon>
</a>
@@ -99,7 +99,7 @@
<td class="table-cell-badges">
<span *ngIf="replacement.mined" class="badge badge-success" i18n="transaction.rbf.mined">Mined</span>
<span *ngIf="replacement.fullRbf" class="badge badge-info" i18n="transaction.full-rbf">Full RBF</span>
<span *ngIf="!replacement.fullRbf" class="badge badge-success" i18n="transaction.rbf">RBF</span>
<span *ngIf="!replacement.fullRbf" class="badge badge-success" i18n="tx-features.tag.rbf|RBF">RBF</span>
</td>
</tr>
</tbody>
@@ -110,7 +110,7 @@
<div class="card">
<div class="card-body">
<a class="title-link" href="" [routerLink]="['/blocks' | relativeUrl]">
<h5 class="card-title d-inline" i18n="dashboard.latest-blocks">Latest blocks</h5>
<h5 class="card-title d-inline" i18n="dashboard.recent-blocks">Recent Blocks</h5>
<span>&nbsp;</span>
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: text-top; font-size: 13px; color: #4a68b9"></fa-icon>
</a>
@@ -150,7 +150,7 @@
<div class="col" style="max-height: 410px">
<div class="card">
<div class="card-body">
<h5 class="card-title" i18n="dashboard.latest-transactions">Latest transactions</h5>
<h5 class="card-title" i18n="dashboard.recent-transactions">Recent Transactions</h5>
<table class="table latest-transactions">
<thead>
<th class="table-cell-txid" i18n="dashboard.latest-transactions.txid">TXID</th>
@@ -233,7 +233,7 @@
</p>
</div>
<div class="item bar">
<h5 class="card-title" i18n="dashboard.memory-usage|Memory usage">Memory usage</h5>
<h5 class="card-title" i18n="dashboard.memory-usage|Memory usage">Memory Usage</h5>
<div class="card-text" *ngIf="(isLoadingWebSocket$ | async) === false && mempoolInfoData.value; else loadingbig">
<div class="progress">
<div class="progress-bar {{ mempoolInfoData.value.mempoolSizeProgress }}" role="progressbar" [ngStyle]="{'width': (mempoolInfoData.value.memPoolInfo.usage / mempoolInfoData.value.memPoolInfo.maxmempool * 100) + '%' }">&nbsp;</div>
@@ -256,7 +256,7 @@
</ng-template>
<ng-template #txPerSecond let-mempoolInfoData>
<h5 class="card-title" i18n="dashboard.incoming-transactions">Incoming transactions</h5>
<h5 class="card-title" i18n="dashboard.incoming-transactions">Incoming Transactions</h5>
<ng-template [ngIf]="(isLoadingWebSocket$ | async) === false && mempoolInfoData.value" [ngIfElse]="loadingTransactions">
<span *ngIf="(mempoolLoadingStatus$ | async) !== 100; else inSync">
&nbsp;<span class="badge badge-pill badge-warning"><ng-container i18n="dashboard.backend-is-synchronizing">Backend is synchronizing</ng-container> ({{ mempoolLoadingStatus$ | async }}%)</span>

View File

@@ -2922,7 +2922,7 @@ export const restApiDocsData = [
fragment: "get-blocks-bulk",
title: "GET Blocks (Bulk)",
description: {
default: "<p>Returns details on the range of blocks between <code>:minHeight</code> and <code>:maxHeight</code>, inclusive, up to 10 blocks. If <code>:maxHeight</code> is not specified, it defaults to the current tip.</p><p>To return data for more than 10 blocks, consider becoming an <a href='/enterprise'>enterprise sponsor</a>.</p>"
default: "<p>Returns details on the range of blocks between <code>:minHeight</code> and <code>:maxHeight</code>, inclusive, up to 10 blocks. If <code>:maxHeight</code> is not specified, it defaults to the current tip.</p><p>To return data for more than 10 blocks, consider becoming an <a href='https://mempool.space/enterprise'>enterprise sponsor</a>.</p>"
},
urlString: "/v1/blocks-bulk/:minHeight[/:maxHeight]",
showConditions: bitcoinNetworks,
@@ -8775,6 +8775,83 @@ export const faqData = [
fragment: "what-is-svb",
title: "What is sat/vB?",
},
{
type: "category",
category: "accelerator",
fragment: "mempool-accelerator",
title: "Mempool Accelerator™",
showConditions: bitcoinNetworks
},
{
type: "endpoint",
category: "accelerator",
showConditions: bitcoinNetworks,
fragment: "what-is-mempool-accelerator",
title: "What is Mempool Accelerator™?",
},
{
type: "endpoint",
category: "accelerator",
showConditions: bitcoinNetworks,
fragment: "how-does-mempool-accelerator-work",
title: "How does Mempool Accelerator™ work?",
},
{
type: "endpoint",
category: "accelerator",
showConditions: bitcoinNetworks,
fragment: "join-accelerator-waitlist",
title: "How can I join the Waitlist for the Mempool Accelerator™?",
},
{
type: "endpoint",
category: "accelerator",
showConditions: bitcoinNetworks,
fragment: "who-has-access-to-mempool-accelerator",
title: "Who has access to Mempool Accelerator™?",
},
{
type: "endpoint",
category: "accelerator",
showConditions: bitcoinNetworks,
fragment: "account-requirement",
title: "Why is an account required for Mempool Accelerator™?",
},
{
type: "endpoint",
category: "accelerator",
showConditions: bitcoinNetworks,
fragment: "mempool-accelerator-balance",
title: "How do I deposit into Mempool Accelerator™?",
},
{
type: "endpoint",
category: "accelerator",
showConditions: bitcoinNetworks,
fragment: "how-to-request-an-acceleration",
title: "How do I request an acceleration?",
},
{
type: "endpoint",
category: "accelerator",
showConditions: bitcoinNetworks,
fragment: "cancelling-an-acceleration",
title: "How do I cancel an acceleration?",
},
{
type: "endpoint",
category: "accelerator",
showConditions: bitcoinNetworks,
fragment: "mempool-accelerator-improves-transparency",
title: "Does this break fee estimation algorithms?",
},
{
type: "endpoint",
category: "accelerator",
showConditions: bitcoinNetworks,
fragment: "will-mining-pools-underpay-miners",
title: "Will mining pools underpay miners?",
},
{
type: "category",
category: "help",

View File

@@ -10,9 +10,9 @@
<div class="doc-content">
<div id="disclaimer">
<table *ngIf="!mobileViewport"><tr><td><app-svg-images name="warning" class="disclaimer-warning" viewBox="0 0 304 304" fill="#ffc107" width="50" height="50"></app-svg-images></td><td><p i18n="faq.big-disclaimer"><b>mempool.space merely provides data about the Bitcoin network.</b> It cannot help you with retrieving funds, wallet issues, etc.</p><p>For any such requests, you need to get in touch with the entity that helped make the transaction (wallet software, exchange company, etc).</p></td></tr></table>
<div *ngIf="mobileViewport"><app-svg-images name="warning" class="disclaimer-warning" viewBox="0 0 304 304" fill="#ffc107" width="50" height="50"></app-svg-images><p i18n="faq.big-disclaimer"><b>mempool.space merely provides data about the Bitcoin network.</b> It cannot help you with retrieving funds, wallet issues, etc.</p><p>For any such requests, you need to get in touch with the entity that helped make the transaction (wallet software, exchange company, etc).</p></div>
<table *ngIf="!mobileViewport"><tr><td><app-svg-images name="warning" class="disclaimer-warning" viewBox="0 0 304 304" fill="#ffc107" width="50" height="50"></app-svg-images></td><td> <ng-container *ngTemplateOutlet="faqDisclaimer"></ng-container></td></tr></table>
<div *ngIf="mobileViewport"><app-svg-images name="warning" class="disclaimer-warning" viewBox="0 0 304 304" fill="#ffc107" width="50" height="50"></app-svg-images><ng-container *ngTemplateOutlet="faqDisclaimer"></ng-container></div>
<ng-template #faqDisclaimer i18n="faq.big-disclaimer"><p><b>mempool.space merely provides data about the Bitcoin network.</b> It cannot help you with retrieving funds, wallet issues, etc.</p><p>For any such requests, you need to get in touch with the entity that helped make the transaction (wallet software, exchange company, etc).</p></ng-template>
</div>
<div class="doc-item-container" *ngFor="let item of faq">
@@ -40,7 +40,7 @@
<div class="doc-content">
<p class="doc-welcome-note">Below is a reference for the {{ network.val === '' ? 'Bitcoin' : network.val.charAt(0).toUpperCase() + network.val.slice(1) }} <ng-container i18n="api-docs.title">REST API service</ng-container>.</p>
<p class="doc-welcome-note api-note" *ngIf="officialMempoolInstance">Note that we enforce rate limits. If you exceed these limits, you will get an HTTP 429 error. If you repeatedly exceed the limits, you may be banned from accessing the service altogether. Consider an <a href="/enterprise">enterprise sponsorship</a> if you need higher API limits.</p>
<p class="doc-welcome-note api-note" *ngIf="officialMempoolInstance">Note that we enforce rate limits. If you exceed these limits, you will get an HTTP 429 error. If you repeatedly exceed the limits, you may be banned from accessing the service altogether. Consider an <a href="https://mempool.space/enterprise">enterprise sponsorship</a> if you need higher API limits.</p>
<div class="doc-item-container" *ngFor="let item of restDocs">
<h3 *ngIf="( item.type === 'category' ) && ( item.showConditions.indexOf(network.val) > -1 )">{{ item.title }}</h3>
@@ -123,7 +123,7 @@
<p>{{electrsPort}}</p>
<p class="subtitle">SSL</p>
<p>Enabled</p>
<p class="note" *ngIf="network.val !== 'signet'">Electrum RPC interface for Bitcoin Signet is <a href="/signet/docs/api/electrs">publicly available</a>. Electrum RPC interface for all other networks is available to <a href='/enterprise'>sponsors</a> only—whitelisting is required.</p>
<p class="note" *ngIf="network.val !== 'signet'">Electrum RPC interface for Bitcoin Signet is <a href="/signet/docs/api/electrs">publicly available</a>. Electrum RPC interface for all other networks is available to <a href='https://mempool.space/enterprise'>sponsors</a> only—whitelisting is required.</p>
</div>
</div>
</div>
@@ -180,15 +180,37 @@
</ng-template>
<ng-template type="why-is-transaction-stuck-in-mempool">
<p>If it's been a while and your transaction hasn't confirmed, your transaction is probably using a lower feerate relative to other transactions currently in the mempool. Depending on how you made your transaction, there may be <a [routerLink]="['/docs/faq' | relativeUrl]" fragment="how-to-get-transaction-confirmed-quickly">ways to accelerate the process</a>.</p><p>There's no need to panic—a Bitcoin transaction will always either confirm completely (or not at all) at some point. As long as you have your transaction's ID, you can always see where your funds are.</p><p style='font-weight:700'>This site only provides data about the Bitcoin network—it cannot help you get your transaction confirmed quicker.</p>
<p>If it's been a while and your transaction hasn't confirmed, your transaction is probably using a lower feerate relative to other transactions currently in the mempool.</p>
<p style='font-weight:700'> You can accelerate your transaction with <a [routerLink]="['/docs/faq' | relativeUrl]" fragment="what-is-mempool-accelerator">Mempool Accelerator™</a></p>
<p>Depending on how you made your transaction, there may be <a [routerLink]="['/docs/faq' | relativeUrl]" fragment="how-to-get-transaction-confirmed-quickly">other ways</a> to increase the fee.</p>
<p>There's no need to panic—a Bitcoin transaction will always either confirm completely (or not at all) at some point. As long as you have your transaction's ID, you can always see where your funds are.</p>
</ng-template>
<ng-template type="how-to-get-transaction-confirmed-quickly">
<p>To get your transaction confirmed quicker, you will need to increase its effective feerate.</p><p>If your transaction was created with RBF enabled, your stuck transaction can simply be replaced with a new one that has a higher fee.</p><p>Otherwise, if you control any of the stuck transaction's outputs, you can use CPFP to increase your stuck transaction's effective feerate.</p><p>If you are not sure how to do RBF or CPFP, work with the tool you used to make the transaction (wallet software, exchange company, etc). This website only provides data about the Bitcoin network, so there is nothing it can do to help you get your transaction confirmed quicker.</p>
<p>There are 3 ways to increase the incentive for a miner to include your transaction</p>
<ol>
<li>
<p><b>Replace By Fee (RBF)</b></p>
<p>If you are the sender in a bitcoin transaction you can create a new transaction which spends the same inputs but with a higher fee.</p>
<p>Full RBF miners will mine blocks containing your replacement even if you don't flag RBF, but you may have issues propagating the transaction. </p>
</li>
<li>
<p><b>Child Pays for Parent (CPFP)</b></p>
<p>If you are the recipient of an unconfirmed transaction output you can spend it and increase the effective fee (the weighted average of both the original unconfirmed transaction and your CPFP transaction).</p>
<p>A miner can't confirm a CPFP transaction without also confirming the original, making it impossible for the miner to take the fee from your CPFP unless your unconfirmed original transaction is included in the same (or a previous) block.</p>
</li>
<li>
<p><b>Out of Band</b></p>
<p>The <a [routerLink]="['/docs/faq' | relativeUrl]" fragment="what-is-mempool-accelerator">Mempool Accelerator™</a> is a transaction acceleration marketplace which enables acceleration offers to be made to mining partners, where payment is made via an alternate method (not in the onchain transaction fee).</p>
<p>Unlike RBF and CPFP a new onchain transaction does not need to be signed for each fee increase and depending on the payment method used may not be required at all, but trust is required by one or more parties involved (miner or user). </p>
</li>
</ol>
</ng-template>
<ng-template type="how-prevent-stuck-transaction">
<p>You must use an adequate transaction fee commensurate with how quickly you need the transaction to be confirmed. See Mempool's fee estimates on the <a [routerLink]="['/' | relativeUrl]">front page</a>.</p><p>Also consider using RBF (if your wallet supports it) so that you can bump the feerate on your transaction if it does end up getting stuck.</p>
<p>You must use an adequate transaction fee commensurate with how quickly you need the transaction to be confirmed. See Mempool's fee estimates on the <a [routerLink]="['/' | relativeUrl]">front page</a>.</p>
<p>If you need your transaction to be confirmed quickly but don't want to overpay you can use the <a href="https://mempool.space/accelerator" target="_blank">Mempool Accelerator™ </a>.</p>
<p>You may be able to increase your transaction bid manually <a [routerLink]="['/docs/faq' | relativeUrl]" fragment="how-to-get-transaction-confirmed-quickly">using RBF / CPFP</a>, if your wallet supports these features.</p>
</ng-template>
<ng-template type="looking-up-transactions">
@@ -288,7 +310,7 @@
</ng-template>
<ng-template type="host-my-own-instance-server">
<p>You can manually install Mempool on your own server, but this requires advanced sysadmin skills since you will be manually configuring everything. You could also use our <a href="https://github.com/mempool/mempool/tree/master/docker" target="_blank">Docker images</a>.</p><p>In any case, we only provide support for manual deployments to <a href="/enterprise">enterprise sponsors</a>.</p>
<p>You can manually install Mempool on your own server, but this requires advanced sysadmin skills since you will be manually configuring everything. You could also use our <a href="https://github.com/mempool/mempool/tree/master/docker" target="_blank">Docker images</a>.</p><p>In any case, we only provide support for manual deployments to <a href="https://mempool.space/enterprise">enterprise sponsors</a>.</p>
<p>For casual users, we strongly suggest installing Mempool using one of the <a href="https://github.com/mempool/mempool#one-click-installation" target="_blank">1-click install methods</a>.</p>
</ng-template>
@@ -299,3 +321,67 @@
<ng-template type="address-lookup-issues">
<p>If you're getting errors when doing address lookups, it's probably because of your Electrum server backend.</p><p>Mempool uses an Electrum server to do address lookups. There are several implementations of the Electrum server protocol, and Mempool can use any of them, but the implementation you use affects performance:</p><ol><li><a href="https://github.com/romanz/electrs" target="_blank">romanz/electrs</a>. This is a common choice for its low resource requirements, and most full-node distros use it. But while this implementation works great for basic queries, it will struggle with heavier ones (e.g. looking up addresses with many transactions)—especially when running on low-power hardware like a Raspberry Pi.</li><li><a href="https://github.com/cculianu/Fulcrum" target="_blank">Fulcrum</a>. Fulcrum requires more resources than romanz/electrs but it can still run on a Raspberry Pi, and it handles heavy queries much more efficiently. If you're having issues with romanz/electrs, Fulcrum is worth a try.</li><li><a href="https://github.com/Blockstream/electrs" target="_blank">blockstream/electrs</a>. If you have stronger hardware, consider running Blockstream's electrs implementation. It's the backend mempool.space uses, and is also what powers blockstream.info.</li></ol>
</ng-template>
<ng-template type="what-is-mempool-accelerator">
<p><a href="https://mempool.space/accelerator" target="_blank">Mempool Accelerator™ </a> is a bitcoin transaction prioritisation service offered by mempool.space in collaboration bitcoin mining partners - Using the accelerator you can get your transaction confirmed quickly and easily.</p>
</ng-template>
<ng-template type="how-does-mempool-accelerator-work">
<p>When a user requests an acceleration the following actions are taken:</p>
<ol>
<li>A hold of an amount equal to the "Maximum Acceleration Bid" is placed on the users account balance.</li>
<li>The transaction is monitored to detect ejection from the accelerator mempool. If at any time the transaction is replaced or otherwise ejected from the accelerator mempool the transaction acceleration will be cancelled.</li>
<li>The acceleration request is sent to our mining partners, who apply a fee delta equal to the "Maximum Acceleration Bid" to the transaction.</li>
<li>The transaction is included in the next block produced by our mining partners.</li>
<li>mempool.space calculates the acceleration fee retrospectively (taking into account regular and accelerated transactions) and debits the user balance appropriately. </li>
<li>Any user balance still on hold is released for future accelerations.</li>
</ol>
</ng-template>
<ng-template type="join-accelerator-waitlist">
<p>If you <a href="https://mempool.space/signup" target="_blank">sign up for an account</a> you will automatically join the Mempool Accelerator™ waitlist.</p>
<p>You will get notified once you are granted access, although this will not happy until internal testing is completed and the service is rolled out. See <a [routerLink]="['/docs/faq' | relativeUrl]" fragment="who-has-access-to-mempool-accelerator">here</a> for more details.</p>
</ng-template>
<ng-template type="who-has-access-to-mempool-accelerator">
<p>Currently access to Mempool Accelerator™ is limited to internal testers.</p>
<p>Anyone can <a href="https://mempool.space/signup" target="_blank">sign up</a> to join the waitlist, though their account will not be authorized immediately.
<p>Users who have signed up for the waitlist will see a banner in their account accelerator overview tab stating "You are currently on the waitlist. You will get notified once you are granted access". No user action is required and access will be granted as soon as possible.</p>
<p>Once the service is ready access will be rolled out to Mempool Enterprise® Sponsors and those on the <a [routerLink]="['/docs/faq' | relativeUrl]" fragment="join-accelerator-waitlist">waitlist</a> for Mempool Accelerator™. </p>
</ng-template>
<ng-template type="account-requirement">
<p>The blockspace market will change between the time when a user requests a transaction acceleration and the time that one of our mining partners finds a block. At the time of making an acceleration request it is not possible to know what bid will be sufficient to get into the next block mined by our mining partners, therefore it is not possible to know how much the user should pay.</p>
<p>For this reason, at launch is is necessary to <a href="https://mempool.space/signup" target="_blank">sign up for an account</a> to use the Mempool Accelerator™. Having an account makes it possible to place a hold on user balance while awaiting an acceleration, and retrospectively charge customers the appropriate market rate, leaving the remaining balance for subsequent transaction accelerations. This makes the service very convenient for high volume users who can make many accelerations with a single deposit into their Mempool account.</p>
<p>We know that some users would prefer to pay per acceleration without using an account, and we hope to develop this functionality in the future.</p>
</ng-template>
<ng-template type="mempool-accelerator-balance">
<p>Mempool Accelerator™ will initially use an accounts model in which <a [routerLink]="['/docs/faq' | relativeUrl]" fragment="who-has-access-to-mempool-accelerator">authorized users</a> can top-up their account balance by depositing bitcoin (on chain or lightning) or L-BTC, and draw down on their credit when making an acceleration. </p>
<p>After <a [routerLink]="['/docs/faq' | relativeUrl]" fragment="how-to-request-an-acceleration">requesting an acceleration</a> a hold will be placed on the user account with a value equal to the maximum acceleration bid. Once the acceleration is completed the appropriate market price will be computed retrospectively and only this amount will be debited from the user balance, with the remaining held balance being released.</p>
<p>If the transaction is not accelerated the user will not be charged, and the hold will be released. If the transaction is ejected from the Mempool Accelerator™ mempool we reserve the right to charge you. </p>
</ng-template>
<ng-template type="how-to-request-an-acceleration">
<p> A user of mempool.space can browse to an unconfirmed transaction and click the accelerate button in the ETA field. They will then be shown the Acceleration details, including a breakdown of the fees associated with the service.</p>
<p>Provided the user has an <a [routerLink]="['/docs/faq' | relativeUrl]" fragment="who-has-access-to-mempool-accelerator">authorized account</a> and a <a [routerLink]="['/docs/faq' | relativeUrl]" fragment="who-has-access-to-mempool-accelerator">sufficient balance</a> they can click the Accelerate button.</p>
<p> Optionally, prior to clicking the Accelerate button the user may adjust the maximum acceleration bid.</p>
<p>Once a transaction acceleration has been requested it cannot be cancelled (<a [routerLink]="['/docs/faq' | relativeUrl]" fragment="cancelling-an-acceleration">read more</a>).</p>
</ng-template>
<ng-template type="cancelling-an-acceleration">
<p> Once an acceleration is requested it cannot be cancelled by the user. To give the user control over how much they may pay for the acceleration the user can select the maximum acceleration bid when submitting their acceleration request. If the required fee for acceleration exceeds the maximum bid set by the user the acceleration will be cancelled automatically.</p>
</ng-template>
<ng-template type="mempool-accelerator-improves-transparency">
<p>Transaction prioritisation services have existed for much of bitcoin's history, and miners have always been able to choose which transactions are included in their blocks. By running this service with a commitment to transparency we hope to improve public access to blockspace market data, including out of band offers. This transparency is novel, current transaction prioritisation services are private and although transactions which were included as a result of out of band payments can be identified, the amount paid is not public data. Furthermore, these transactions can only be identified in retrospect, after a block has been mined wheres acceleration bids are shown public on Mempool Space when they are made.</p>
<p>Mempool Accelerator™ publishes data on all accelerated transactions for public access. This information is available both on the mempool.space accelerator website and via an API. By making this information public other entities within the bitcoin ecosystem can better understand current and historic blockspace market dynamics. </p>
<p>"Fee estimation" algorithms can only utilize data which they have access to, and historically "Fee Estimation" algorithms have been blind to transaction prioritisations where out of band payments are made to miners or mining pools.</p>
<p>By making this data public Mempool Accelerator™ will ensure that "Fee Estimation" algorithms can take into account more transaction bid data (both in band and out of band).</p>
</ng-template>
<ng-template type="will-mining-pools-underpay-miners">
<p>If mining pools do not distribute out of band income to miners the incentives of each group are misaligned - mining pools earn more and miners earn less for each accelerated transaction.</p>
<p> To help miners hold mining pools to account we developed the <a [routerLink]="['/docs/faq' | relativeUrl]" fragment="how-do-block-audits-work">Mempool Block Audit</a> feature which highlights transactions which were likely included due to out of band payments (those which were included in blocks but which were not expected when sorting by effective fee rate). </p>
<p> Mempool Accelerator™ <a [routerLink]="['/docs/faq' | relativeUrl]" fragment="mempool-accelerator-improves-transparency">improves transparency</a>, giving miners the data required to inform their decision as to which pool to mine with. For example Mempool Accelerator fees paid to mining pools will be displayed on the block overview pages, making it easy for miners to determine whether this income is shared with miners.</p>
</ng-template>

View File

@@ -64,10 +64,9 @@ export class DocsComponent implements OnInit {
}
} else {
this.activeTab = 3;
this.seoService.setTitle($localize`:@@meta.title.docs.websocket:Electrum RPC`);
this.seoService.setTitle($localize`:@@meta.title.docs.electrum:Electrum RPC`);
this.seoService.setDescription($localize`:@@meta.description.docs.electrumrpc:Documentation for our Electrum RPC interface: get instant, convenient, and reliable access to an Esplora instance.`);
}
}
ngOnDestroy(): void {

View File

@@ -302,3 +302,28 @@ export interface INode {
funding_balance?: number;
closing_balance?: number;
}
export interface Acceleration {
txid: string;
status: 'requested' | 'accelerating' | 'mined' | 'completed' | 'failed';
pools: number[];
feePaid: number;
added: number; // timestamp
lastUpdated: number; // timestamp
baseFee: number;
vsizeFee: number;
effectiveFee: number;
effectiveVsize: number;
feeDelta: number;
blockHash: string;
blockHeight: number;
actualFeeDelta?: number;
}
export interface AccelerationHistoryParams {
timeframe?: string,
status?: string,
pool?: string,
blockHash?: string,
}

View File

@@ -37,7 +37,7 @@
<thead>
<th class="alias text-left" i18n="lightning.alias">Alias</th>
<th class="nodedetails text-left">&nbsp;</th>
<th class="status text-left" i18n="status">Status</th>
<th class="status text-left" i18n="transaction.status|Transaction Status">Status</th>
<th class="feerate text-left" *ngIf="status !== 'closed'" i18n="transaction.fee-rate|Transaction fee rate">Fee rate</th>
<th class="feerate text-left" *ngIf="status === 'closed'" i18n="channels.closing_date">Closing date</th>
<th class="liquidity text-right" i18n="lightning.capacity">Capacity</th>

View File

@@ -14,7 +14,7 @@
<table class="table table-borderless table-striped">
<tbody>
<tr>
<td>Description</td>
<td i18n>Description</td>
<td><div class="description-text">These are the Lightning nodes operated by The Mempool Open Source Project that provide data for the mempool.space website. Connect to us!
</div>
</td>
@@ -70,7 +70,7 @@
<table class="table table-borderless">
<thead>
<th class="alias text-left" i18n="lightning.alias">Alias</th>
<th class="text-left">Connect</th>
<th class="text-left" i18n="lightning.connect-to-node|Connect">Connect</th>
<th class="city text-right d-none d-md-table-cell" i18n="lightning.location">Location</th>
</thead>
<tbody *ngIf="nodes$ | async as response; else skeleton">

View File

@@ -25,7 +25,7 @@ export class LightningDashboardComponent implements OnInit, AfterViewInit {
ngOnInit(): void {
this.seoService.setTitle($localize`:@@142e923d3b04186ac6ba23387265d22a2fa404e0:Lightning Explorer`);
this.seoService.setDescription($localize`:@@meta.description.lightning.dashboard:Get stats on the Lightning network (aggregate capacity, connectivity, etc) and Lightning nodes (channels, liquidity, etc) and Lightning channels (status, fees, etc).`);
this.seoService.setDescription($localize`:@@meta.description.lightning.dashboard:Get stats on the Lightning network (aggregate capacity, connectivity, etc), Lightning nodes (channels, liquidity, etc) and Lightning channels (status, fees, etc).`);
this.nodesRanking$ = this.lightningApiService.getNodesRanking$().pipe(share());
this.statistics$ = this.lightningApiService.getLatestStatistics$().pipe(share());

View File

@@ -49,7 +49,7 @@
<tr *ngIf="!node.city && !node.country">
<td i18n="lightning.location">Location</td>
<td>
<span>unknown</span>
<span i18n="unknown">Unknown</span>
</td>
</tr>
</tbody>

View File

@@ -119,7 +119,7 @@
</div>
<ng-template #featurebits let-bits="bits">
<td i18n="lightning.features" class="text-truncate label">Features</td>
<td i18n="transaction.features|Transaction features" class="text-truncate label">Features</td>
<td class="d-flex justify-content-between">
<span class="text-truncate w-90">{{ bits }}</span>
<button type="button" class="btn btn-outline-info btn-xs" (click)="toggleFeatures()" i18n="transaction.details|Transaction Details">Details</button>
@@ -133,11 +133,11 @@
<h5>Raw bits</h5>
<span class="text-wrap w-100"><small>{{ node.featuresBits }}</small></span>
</div>
<h5>Decoded</h5>
<h5 i18n="lightning.decoded|Decoded">Decoded</h5>
<table class="table table-borderless table-striped table-fixed">
<thead>
<th style="width: 13%">Bit</th>
<th>Name</th>
<th i18n="lightning.as-name">Name</th>
<th style="width: 25%; text-align: right">Required</th>
</thead>
<tbody>

View File

@@ -1,22 +1,28 @@
<div class="map-wrapper" [class]="style">
<div class="map-wrapper" [class]="style" *ngIf="style !== 'graph'">
<ng-container *ngIf="channelsObservable | async">
<div *ngIf="chartOptions" [class]="'full-container ' + style + (fitContainer ? ' fit-container' : '')">
<div *ngIf="style === 'graph'" class="card-header">
<div class="d-flex d-md-block align-items-baseline" style="margin-bottom: -5px">
<span i18n="lightning.nodes-channels-world-map">Lightning Nodes Channels World Map</span>
</div>
<small style="color: #ffffff66" i18n="lightning.tor-nodes-excluded">(Tor nodes excluded)</small>
</div>
<div class="chart" [class]="style" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
(chartInit)="onChartInit($event)" (chartFinished)="onChartFinished($event)">
</div>
<div *ngIf="!chartOptions && style === 'nodepage'" style="padding-top: 30px"></div>
</div>
<div class="text-center loading-spinner" [class]="style" *ngIf="isLoading && !disableSpinner">
<div class="spinner-border text-light"></div>
</div>
</ng-container>
</div>
<div class="full-container-graph" *ngIf="style === 'graph'">
<div class="card-header">
<div class="d-flex d-md-block align-items-baseline" style="margin-bottom: -5px">
<span i18n="lightning.nodes-channels-world-map">Lightning Nodes Channels World Map</span>
</div>
<small style="color: #ffffff66" i18n="lightning.tor-nodes-excluded">(Tor nodes excluded)</small>
</div>
<div *ngIf="channelsObservable | async" class="chart-graph" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
(chartInit)="onChartInit($event)" (chartFinished)="onChartFinished($event)">
</div>
</div>

View File

@@ -143,3 +143,55 @@
text-align: center;
margin-top: 100px;
}
.full-container-graph {
display: flex;
flex-direction: column;
padding: 0px 15px;
width: 100%;
height: calc(100vh - 225px);
min-height: 400px;
@media (min-width: 992px) {
height: calc(100vh - 150px);
}
}
.full-container-graph.widget {
min-height: 240px;
height: 240px;
padding: 0px;
}
.full-container-graph.fit-container {
margin: 0;
padding: 0;
height: 100%;
min-height: 100px;
.chart {
padding: 0;
min-height: 100px;
}
}
.chart-graph {
display: flex;
flex: 1;
height: 100%;
padding-top: 30px;
padding-bottom: 20px;
padding-right: 10px;
@media (max-width: 992px) {
padding-bottom: 25px;
}
@media (max-width: 829px) {
padding-bottom: 50px;
}
@media (max-width: 767px) {
padding-bottom: 25px;
}
@media (max-width: 629px) {
padding-bottom: 55px;
}
@media (max-width: 567px) {
padding-bottom: 55px;
}
}

View File

@@ -65,6 +65,7 @@ export class NodesChannelsMap implements OnInit {
}
if (this.style === 'graph') {
this.center = [0, 5];
this.seoService.setTitle($localize`Lightning Nodes Channels World Map`);
this.seoService.setDescription($localize`:@@meta.description.lightning.node-map:See the channels of non-Tor Lightning network nodes visualized on a world map. Hover/tap on points on the map for node names and details.`);
}
@@ -238,7 +239,6 @@ export class NodesChannelsMap implements OnInit {
title: title ?? undefined,
tooltip: {},
geo: {
top: 75,
animation: false,
silent: true,
center: this.center,

View File

@@ -20,7 +20,7 @@ export class NodesRankingsDashboard implements OnInit {
ngOnInit(): void {
this.seoService.setTitle($localize`Top lightning nodes`);
this.seoService.setDescription($localize`:@@meta.description.lightning.rankings-dashboard:See top the Lightning network nodes ranked by liquidity, connectivity, and age.`);
this.seoService.setDescription($localize`:@@meta.description.lightning.rankings-dashboard:See the top Lightning network nodes ranked by liquidity, connectivity, and age.`);
this.nodesRanking$ = this.lightningApiService.getNodesRanking$().pipe(share());
}
}

View File

@@ -7,6 +7,7 @@ import { SharedModule } from './shared/shared.module';
import { StartComponent } from './components/start/start.component';
import { AddressComponent } from './components/address/address.component';
import { PushTransactionComponent } from './components/push-transaction/push-transaction.component';
import { CalculatorComponent } from './components/calculator/calculator.component';
import { BlocksList } from './components/blocks-list/blocks-list.component';
import { RbfList } from './components/rbf-list/rbf-list.component';
@@ -87,6 +88,10 @@ const routes: Routes = [
loadChildren: () => import('./lightning/lightning.module').then(m => m.LightningModule),
data: { preload: browserWindowEnv && browserWindowEnv.LIGHTNING === true, networks: ['bitcoin'] },
},
{
path: 'tools/calculator',
component: CalculatorComponent
},
],
}
];

View File

@@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http';
import { CpfpInfo, OptimizedMempoolStats, AddressInformation, LiquidPegs, ITranslators,
PoolStat, BlockExtended, TransactionStripped, RewardStats, AuditScore, BlockSizesAndWeights, RbfTree, BlockAudit } from '../interfaces/node-api.interface';
PoolStat, BlockExtended, TransactionStripped, RewardStats, AuditScore, BlockSizesAndWeights, RbfTree, BlockAudit, Acceleration, AccelerationHistoryParams } from '../interfaces/node-api.interface';
import { BehaviorSubject, Observable, catchError, filter, of, shareReplay, take, tap } from 'rxjs';
import { StateService } from './state.service';
import { IBackendInfo, WebsocketResponse } from '../interfaces/websocket.interface';
@@ -227,6 +227,10 @@ export class ApiService {
return this.httpClient.get<BlockExtended>(this.apiBaseUrl + this.apiBasePath + '/api/v1/block/' + hash);
}
getBlockDataFromTimestamp$(timestamp: number): Observable<any> {
return this.httpClient.get<number>(this.apiBaseUrl + this.apiBasePath + '/api/v1/mining/blocks/timestamp/' + timestamp);
}
getStrippedBlockTransactions$(hash: string): Observable<TransactionStripped[]> {
return this.httpClient.get<TransactionStripped[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/block/' + hash + '/summary');
}
@@ -424,4 +428,12 @@ export class ApiService {
accelerate$(txInput: string, userBid: number) {
return this.httpClient.post<any>(`${SERVICES_API_PREFIX}/accelerator/accelerate`, { txInput: txInput, userBid: userBid });
}
getAccelerations$(): Observable<Acceleration[]> {
return this.httpClient.get<Acceleration[]>(`${SERVICES_API_PREFIX}/accelerator/accelerations`);
}
getAccelerationHistory$(params: AccelerationHistoryParams): Observable<Acceleration[]> {
return this.httpClient.get<Acceleration[]>(`${SERVICES_API_PREFIX}/accelerator/accelerations/history`, { params: { ...params } });
}
}

View File

@@ -3,6 +3,7 @@ import { Inject, Injectable } from '@angular/core';
import { ApiService } from './api.service';
import { SeoService } from './seo.service';
import { StateService } from './state.service';
import { ActivatedRoute } from '@angular/router';
@Injectable({
providedIn: 'root'
@@ -11,12 +12,15 @@ export class EnterpriseService {
exclusiveHostName = '.mempool.space';
subdomain: string | null = null;
info: object = {};
statsUrl: string;
siteId: number;
constructor(
@Inject(DOCUMENT) private document: Document,
private apiService: ApiService,
private seoService: SeoService,
private stateService: StateService,
private activatedRoute: ActivatedRoute,
) {
const subdomain = this.document.location.hostname.indexOf(this.exclusiveHostName) > -1
&& this.document.location.hostname.split(this.exclusiveHostName)[0] || false;
@@ -56,7 +60,7 @@ export class EnterpriseService {
insertMatomo(siteId?: number): void {
let statsUrl = '//stats.mempool.space/';
if (!siteId) {
switch (this.document.location.hostname) {
case 'mempool.space':
@@ -88,16 +92,63 @@ export class EnterpriseService {
}
}
this.statsUrl = statsUrl;
this.siteId = siteId;
// @ts-ignore
const _paq = window._paq = window._paq || [];
_paq.push(['disableCookies']);
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
_paq.push(['setTrackerUrl', statsUrl+'m.php']);
_paq.push(['setSiteId', siteId.toString()]);
const d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.type='text/javascript'; g.async=true; g.src=statsUrl+'m.js'; s.parentNode.insertBefore(g,s);
})();
if (window._paq && window['Matomo']) {
window['Matomo'].addTracker(statsUrl+'m.php', siteId.toString());
const matomo = this.getMatomo();
matomo.setDocumentTitle(this.seoService.getTitle());
matomo.setCustomUrl(this.getCustomUrl());
matomo.disableCookies();
matomo.trackPageView();
matomo.enableLinkTracking();
} else {
// @ts-ignore
const alreadyInitialized = !!window._paq;
// @ts-ignore
const _paq = window._paq = window._paq || [];
_paq.push(['setDocumentTitle', this.seoService.getTitle()]);
_paq.push(['setCustomUrl', this.getCustomUrl()]);
_paq.push(['disableCookies']);
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
if (alreadyInitialized) {
_paq.push(['addTracker', statsUrl+'m.php', siteId.toString()]);
} else {
(function() {
_paq.push(['setTrackerUrl', statsUrl+'m.php']);
_paq.push(['setSiteId', siteId.toString()]);
const d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
// @ts-ignore
g.type='text/javascript'; g.async=true; g.src=statsUrl+'m.js'; s.parentNode.insertBefore(g,s);
})();
}
}
}
private getMatomo() {
if (this.siteId != null) {
return window['Matomo']?.getTracker(this.statsUrl+'m.php', this.siteId);
}
}
goal(id: number) {
// @ts-ignore
this.getMatomo()?.trackGoal(id);
}
private getCustomUrl(): string {
let url = window.location.origin + '/';
let route = this.activatedRoute;
while (route) {
const segment = route?.routeConfig?.path;
if (segment && segment.length) {
url += segment + '/';
}
route = route.firstChild;
}
return url;
}
}

View File

@@ -10,7 +10,7 @@ import { StateService } from './state.service';
export class SeoService {
network = '';
baseTitle = 'mempool';
baseDescription = 'Explore the full Bitcoin ecosystem with The Mempool Open Project™.';
baseDescription = 'Explore the full Bitcoin ecosystem with The Mempool Open Source Project™.';
canonicalLink: HTMLElement = document.getElementById('canonical');

View File

@@ -76,17 +76,22 @@ export class WebsocketService {
this.stateService.resetChainTip();
this.websocketSubject.complete();
this.subscription.unsubscribe();
this.websocketSubject = webSocket<WebsocketResponse>(
this.webSocketUrl.replace('{network}', this.network ? '/' + this.network : '')
);
this.startSubscription();
this.reconnectWebsocket();
});
}
}
reconnectWebsocket(retrying = false, hasInitData = false) {
console.log('reconnecting websocket');
this.websocketSubject.complete();
this.subscription.unsubscribe();
this.websocketSubject = webSocket<WebsocketResponse>(
this.webSocketUrl.replace('{network}', this.network ? '/' + this.network : '')
);
this.startSubscription(retrying, hasInitData);
}
startSubscription(retrying = false, hasInitData = false) {
if (!hasInitData) {
this.stateService.isLoadingWebSocket$.next(true);
@@ -237,7 +242,7 @@ export class WebsocketService {
this.goneOffline = true;
this.stateService.connectionState$.next(0);
window.setTimeout(() => {
this.startSubscription(true);
this.reconnectWebsocket(true);
}, retryDelay);
}

View File

@@ -1,4 +1,4 @@
<ng-container *ngIf="rateUnits$ | async as units">
<ng-container *ngIf="units !== 'wu'">{{ fee / (weight / 4) | feeRounding:rounding }} <span *ngIf="showUnit" [class]="unitClass" [style]="unitStyle">sat/vB</span></ng-container>
<ng-container *ngIf="units === 'wu'">{{ fee / weight | feeRounding:rounding }} <span *ngIf="showUnit" [class]="unitClass" [style]="unitStyle">sat/WU</span></ng-container>
<ng-container *ngIf="units !== 'wu'">{{ fee / (weight / 4) | feeRounding:rounding }} <span *ngIf="showUnit" [class]="unitClass" [style]="unitStyle" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></ng-container>
<ng-container *ngIf="units === 'wu'">{{ fee / weight | feeRounding:rounding }} <span *ngIf="showUnit" [class]="unitClass" [style]="unitStyle" i18n="shared.sat-weight-units|sat/WU">sat/WU</span></ng-container>
</ng-container>

View File

@@ -38,45 +38,45 @@
</div>
<div class="row col-md-12 link-tree" [class]="{'services': isServicesPage}">
<div class="links">
<p class="category">Explore</p>
<p><a [routerLink]="['/mining' | relativeUrl]">Mining Dashboard</a></p>
<p><a *ngIf="env.LIGHTNING" [routerLink]="['/lightning' | relativeUrl]">Lightning Dashboard</a></p>
<p><a [routerLink]="['/blocks' | relativeUrl]">Recent Blocks</a></p>
<p class="category" i18n="footer.explore">Explore</p>
<p><a [routerLink]="['/mining' | relativeUrl]" i18n="mining.mining-dashboard">Mining Dashboard</a></p>
<p><a *ngIf="env.LIGHTNING" [routerLink]="['/lightning' | relativeUrl]" i18n="master-page.lightning">Lightning Explorer</a></p>
<p><a [routerLink]="['/blocks' | relativeUrl]" i18n="dashboard.recent-blocks">Recent Blocks</a></p>
<p><a [routerLink]="['/tx/push' | relativeUrl]" i18n="shared.broadcast-transaction|Broadcast Transaction">Broadcast Transaction</a></p>
<p *ngIf="officialMempoolSpace"><a [routerLink]="['/lightning/group/the-mempool-open-source-project' | relativeUrl]" i18n="footer.connect-to-our-nodes">Connect to our Nodes</a></p>
<p><a [routerLink]="['/docs/api' | relativeUrl]">API Documentation</a></p>
<p><a [routerLink]="['/docs/api' | relativeUrl]" i18n="footer.api-documentation">API Documentation</a></p>
</div>
<div class="links">
<p class="category">Learn</p>
<p><a [routerLink]="['/docs/faq']" fragment="what-is-a-mempool">What is a mempool?</a></p>
<p><a [routerLink]="['/docs/faq']" fragment="what-is-a-block-explorer">What is a block explorer?</a></p>
<p><a [routerLink]="['/docs/faq']" fragment="what-is-a-mempool-explorer">What is a mempool explorer?</a></p>
<p><a [routerLink]="['/docs/faq']" fragment="why-is-transaction-stuck-in-mempool">Why isn't my transaction confirming?</a></p>
<p><a [routerLink]="['/docs/faq' | relativeUrl]">More FAQs </a></p>
<p class="category" i18n="footer.learn">Learn</p>
<p><a [routerLink]="['/docs/faq']" fragment="what-is-a-mempool" i18n="faq.what-is-a-mempool">What is a mempool?</a></p>
<p><a [routerLink]="['/docs/faq']" fragment="what-is-a-block-explorer" i18n="faq.what-is-a-block-exlorer">What is a block explorer?</a></p>
<p><a [routerLink]="['/docs/faq']" fragment="what-is-a-mempool-explorer" i18n="faq.what-is-a-mempool-exlorer">What is a mempool explorer?</a></p>
<p><a [routerLink]="['/docs/faq']" fragment="why-is-transaction-stuck-in-mempool" i18n="faq.why-isnt-my-transaction-confirming">Why isn't my transaction confirming?</a></p>
<p><a [routerLink]="['/docs/faq' | relativeUrl]" i18n="faq.more-faq">More FAQs &raquo;</a></p>
</div>
<div class="links" *ngIf="officialMempoolSpace || env.TESTNET_ENABLED || env.SIGNET_ENABLED || env.LIQUID_ENABLED || env.LIQUID_TESTNET_ENABLED else toolBox" >
<p class="category">Networks</p>
<p *ngIf="(officialMempoolSpace || (env.BASE_MODULE === 'mempool')) && (currentNetwork !== '') && (currentNetwork !== 'mainnet')"><a [href]="networkLink('mainnet')">Mainnet Explorer</a></p>
<p *ngIf="(officialMempoolSpace || (env.BASE_MODULE === 'mempool')) && (currentNetwork !== 'testnet') && env.TESTNET_ENABLED"><a [href]="networkLink('testnet')">Testnet Explorer</a></p>
<p *ngIf="(officialMempoolSpace || (env.BASE_MODULE === 'mempool')) && (currentNetwork !== 'signet') && env.SIGNET_ENABLED"><a [href]="networkLink('signet')">Signet Explorer</a></p>
<p *ngIf="(officialMempoolSpace || env.LIQUID_ENABLED) && (currentNetwork !== 'liquidtestnet')"><a [href]="networkLink('liquidtestnet')">Liquid Testnet Explorer</a></p>
<p *ngIf="(officialMempoolSpace || env.LIQUID_ENABLED) && (currentNetwork !== 'liquid')"><a [href]="networkLink('liquid')">Liquid Explorer</a></p>
<p *ngIf="(officialMempoolSpace && (currentNetwork !== 'bisq'))"><a [href]="networkLink('bisq')">Bisq Explorer</a></p>
<p class="category" i18n="footer.networks">Networks</p>
<p *ngIf="(officialMempoolSpace || (env.BASE_MODULE === 'mempool')) && (currentNetwork !== '') && (currentNetwork !== 'mainnet')"><a [href]="networkLink('mainnet')" i18n="footer.mainnet-explorer">Mainnet Explorer</a></p>
<p *ngIf="(officialMempoolSpace || (env.BASE_MODULE === 'mempool')) && (currentNetwork !== 'testnet') && env.TESTNET_ENABLED"><a [href]="networkLink('testnet')" i18n="footer.testnet-explorer">Testnet Explorer</a></p>
<p *ngIf="(officialMempoolSpace || (env.BASE_MODULE === 'mempool')) && (currentNetwork !== 'signet') && env.SIGNET_ENABLED"><a [href]="networkLink('signet')" i18n="footer.signet-explorer">Signet Explorer</a></p>
<p *ngIf="(officialMempoolSpace || env.LIQUID_ENABLED) && (currentNetwork !== 'liquidtestnet')"><a [href]="networkLink('liquidtestnet')" i18n="footer.liquid-testnet-explorer">Liquid Testnet Explorer</a></p>
<p *ngIf="(officialMempoolSpace || env.LIQUID_ENABLED) && (currentNetwork !== 'liquid')"><a [href]="networkLink('liquid')" i18n="footer.liquid-explorer">Liquid Explorer</a></p>
<p *ngIf="(officialMempoolSpace && (currentNetwork !== 'bisq'))"><a [href]="networkLink('bisq')" i18n="footer.bisq-explorer">Bisq Explorer</a></p>
</div>
<ng-template #toolBox>
<div class="links">
<p class="category">Tools</p>
<p><a [routerLink]="['/clock/mempool/0']">Clock (Mempool)</a></p>
<p><a [routerLink]="['/clock/mined/0']">Clock (Mined)</a></p>
<p><a [routerLink]="['/tools/calculator']">BTC/Fiat Converter</a></p>
<p class="category" i18n="footer.tools">Tools</p>
<p><a [routerLink]="['/clock/mempool/0']" i18n="footer.clock-mempool">Clock (Mempool)</a></p>
<p><a [routerLink]="['/clock/mined/0']" i18n="footer.clock-mined">Clock (Mined)</a></p>
<p><a [routerLink]="['/tools/calculator']" i18n="shared.calculator">Calculator</a></p>
</div>
</ng-template>
<div class="links">
<p class="category">Legal</p>
<p class="category" i18n="footer.legal">Legal</p>
<p><a [routerLink]="['/terms-of-service']" i18n="shared.terms-of-service|Terms of Service">Terms of Service</a></p>
<p><a [routerLink]="['/privacy-policy']" i18n="shared.privacy-policy|Privacy Policy">Privacy Policy</a></p>
<p><a [routerLink]="['/trademark-policy']">Trademark Policy</a></p>
<p><a [routerLink]="['/trademark-policy']" i18n="shared.trademark-policy|Trademark Policy">Trademark Policy</a></p>
</div>
</div>
<div class="row social-links">

View File

@@ -1,2 +1,2 @@
<div class="alert alert-danger" [innerHTML]="errorContent">
<div class="alert" [class]="alertClass" [innerHTML]="errorContent">
</div>

View File

@@ -2,12 +2,12 @@ import { Component, Input, OnInit } from "@angular/core";
import { DomSanitizer, SafeHtml } from "@angular/platform-browser";
const MempoolErrors = {
'internal_server_error': `Something went wrong, please try again later`,
'acceleration_duplicated': `This transaction has already been accelerated.`,
'acceleration_outbid': `Your fee delta is too low.`,
'cannot_accelerate_tx': `Cannot accelerate this transaction.`,
'cannot_decode_raw_tx': `Cannot decode this raw transaction.`,
'cannot_fetch_raw_tx': `Cannot find this transaction.`,
'database_error': `Something went wrong. Please try again later.`,
'high_sigop_tx': `This transaction cannot be accelerated.`,
'invalid_acceleration_request': `This acceleration request is not valid.`,
'invalid_tx_dependencies': `This transaction dependencies are not valid.`,
@@ -33,6 +33,7 @@ export function isMempoolError(error: string) {
})
export class MempoolErrorComponent implements OnInit {
@Input() error: string;
@Input() alertClass = 'alert-danger';
errorContent: SafeHtml;
constructor(private sanitizer: DomSanitizer) { }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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