diff --git a/backend/src/api/acceleration/acceleration.routes.ts b/backend/src/api/acceleration/acceleration.routes.ts index 69b320171..ae0c3f7a8 100644 --- a/backend/src/api/acceleration/acceleration.routes.ts +++ b/backend/src/api/acceleration/acceleration.routes.ts @@ -1,12 +1,14 @@ -import { Application, Request, Response } from "express"; -import config from "../../config"; -import axios from "axios"; -import logger from "../../logger"; +import { Application, Request, Response } from 'express'; +import config from '../../config'; +import axios from 'axios'; +import logger from '../../logger'; +import mempool from '../mempool'; +import AccelerationRepository from '../../repositories/AccelerationRepository'; class AccelerationRoutes { private tag = 'Accelerator'; - public initRoutes(app: Application) { + public initRoutes(app: Application): void { app .get(config.MEMPOOL.API_URL_PREFIX + 'services/accelerator/accelerations', this.$getAcceleratorAccelerations.bind(this)) .get(config.MEMPOOL.API_URL_PREFIX + 'services/accelerator/accelerations/history', this.$getAcceleratorAccelerationsHistory.bind(this)) @@ -15,41 +17,33 @@ class AccelerationRoutes { ; } - private async $getAcceleratorAccelerations(req: Request, res: Response) { - const url = `${config.MEMPOOL_SERVICES.API}/${req.originalUrl.replace('/api/v1/services/', '')}`; - try { - const response = await axios.get(url, { responseType: 'stream', timeout: 10000 }); - for (const key in response.headers) { - res.setHeader(key, response.headers[key]); - } - response.data.pipe(res); - } catch (e) { - logger.err(`Unable to get current accelerations from ${url} in $getAcceleratorAccelerations(), ${e}`, this.tag); - res.status(500).end(); - } + private async $getAcceleratorAccelerations(req: Request, res: Response): Promise { + const accelerations = mempool.getAccelerations(); + res.status(200).send(Object.values(accelerations)); } - private async $getAcceleratorAccelerationsHistory(req: Request, res: Response) { - const url = `${config.MEMPOOL_SERVICES.API}/${req.originalUrl.replace('/api/v1/services/', '')}`; - try { - const response = await axios.get(url, { responseType: 'stream', timeout: 10000 }); - for (const key in response.headers) { - res.setHeader(key, response.headers[key]); - } - response.data.pipe(res); - } catch (e) { - logger.err(`Unable to get acceleration history from ${url} in $getAcceleratorAccelerationsHistory(), ${e}`, this.tag); - res.status(500).end(); - } + private async $getAcceleratorAccelerationsHistory(req: Request, res: Response): Promise { + const history = await AccelerationRepository.$getAccelerationInfo(null, req.query.blockHeight ? parseInt(req.query.blockHeight as string, 10) : null); + res.status(200).send(history.map(accel => ({ + txid: accel.txid, + added: accel.added, + status: 'completed', + effectiveFee: accel.effective_fee, + effectiveVsize: accel.effective_vsize, + boostRate: accel.boost_rate, + boostCost: accel.boost_cost, + blockHeight: accel.height, + pools: [accel.pool], + }))); } - private async $getAcceleratorAccelerationsHistoryAggregated(req: Request, res: Response) { + private async $getAcceleratorAccelerationsHistoryAggregated(req: Request, res: Response): Promise { const url = `${config.MEMPOOL_SERVICES.API}/${req.originalUrl.replace('/api/v1/services/', '')}`; try { const response = await axios.get(url, { responseType: 'stream', timeout: 10000 }); for (const key in response.headers) { - res.setHeader(key, response.headers[key]); - } + res.setHeader(key, response.headers[key]); + } response.data.pipe(res); } catch (e) { logger.err(`Unable to get aggregated acceleration history from ${url} in $getAcceleratorAccelerationsHistoryAggregated(), ${e}`, this.tag); @@ -57,13 +51,13 @@ class AccelerationRoutes { } } - private async $getAcceleratorAccelerationsStats(req: Request, res: Response) { + private async $getAcceleratorAccelerationsStats(req: Request, res: Response): Promise { const url = `${config.MEMPOOL_SERVICES.API}/${req.originalUrl.replace('/api/v1/services/', '')}`; try { const response = await axios.get(url, { responseType: 'stream', timeout: 10000 }); for (const key in response.headers) { - res.setHeader(key, response.headers[key]); - } + res.setHeader(key, response.headers[key]); + } response.data.pipe(res); } catch (e) { logger.err(`Unable to get acceleration stats from ${url} in $getAcceleratorAccelerationsStats(), ${e}`, this.tag); diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index 28ca38152..16ae94f66 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -29,6 +29,7 @@ import websocketHandler from './websocket-handler'; import redisCache from './redis-cache'; import rbfCache from './rbf-cache'; import { calcBitsDifference } from './difficulty-adjustment'; +import AccelerationRepository from '../repositories/AccelerationRepository'; class Blocks { private blocks: BlockExtended[] = []; @@ -872,6 +873,7 @@ class Blocks { await BlocksRepository.$deleteBlocksFrom(lastBlock.height - 10); await HashratesRepository.$deleteLastEntries(); await cpfpRepository.$deleteClustersFrom(lastBlock.height - 10); + await AccelerationRepository.$deleteAccelerationsFrom(lastBlock.height - 10); this.blocks = this.blocks.slice(0, -10); this.updateTimerProgress(timer, `rolled back chain divergence from ${this.currentBlockHeight}`); for (let i = 10; i >= 0; --i) { @@ -974,6 +976,9 @@ class Blocks { if (this.blocks.length > config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 4) { this.blocks = this.blocks.slice(-config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 4); } + blockSummary.transactions.forEach(tx => { + delete tx.acc; + }); this.blockSummaries.push(blockSummary); if (this.blockSummaries.length > config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 4) { this.blockSummaries = this.blockSummaries.slice(-config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 4); @@ -1117,6 +1122,7 @@ class Blocks { } return { txid: tx.txid, + time: tx.firstSeen, fee: tx.fee || 0, vsize: tx.vsize, value: Math.round(tx.vout.reduce((acc, vout) => acc + (vout.value ? vout.value : 0), 0)), diff --git a/backend/src/api/database-migration.ts b/backend/src/api/database-migration.ts index 81f2caa44..c8272674f 100644 --- a/backend/src/api/database-migration.ts +++ b/backend/src/api/database-migration.ts @@ -7,7 +7,7 @@ import cpfpRepository from '../repositories/CpfpRepository'; import { RowDataPacket } from 'mysql2'; class DatabaseMigration { - private static currentVersion = 76; + private static currentVersion = 77; private queryTimeout = 3600_000; private statisticsAddedIndexed = false; private uniqueLogs: string[] = []; @@ -664,6 +664,11 @@ class DatabaseMigration { await this.$executeQuery('ALTER TABLE `blocks_audits` ADD prioritized_txs JSON DEFAULT "[]"'); await this.updateToSchemaVersion(76); } + + if (databaseSchemaVersion < 77 && config.MEMPOOL.NETWORK === 'mainnet') { + await this.$executeQuery('ALTER TABLE `accelerations` ADD requested datetime DEFAULT NULL'); + await this.updateToSchemaVersion(77); + } } /** diff --git a/backend/src/api/services/acceleration.ts b/backend/src/api/services/acceleration.ts index f22959f3f..5dc5d5074 100644 --- a/backend/src/api/services/acceleration.ts +++ b/backend/src/api/services/acceleration.ts @@ -5,6 +5,9 @@ import axios from 'axios'; export interface Acceleration { txid: string, + added: number, + effectiveVsize: number, + effectiveFee: number, feeDelta: number, pools: number[], }; diff --git a/backend/src/repositories/AccelerationRepository.ts b/backend/src/repositories/AccelerationRepository.ts index 4969013c4..34df770f1 100644 --- a/backend/src/repositories/AccelerationRepository.ts +++ b/backend/src/repositories/AccelerationRepository.ts @@ -6,7 +6,7 @@ import { IEsploraApi } from '../api/bitcoin/esplora-api.interface'; import { Common } from '../api/common'; import config from '../config'; import blocks from '../api/blocks'; -import accelerationApi, { Acceleration } from '../api/services/acceleration'; +import accelerationApi, { Acceleration, AccelerationHistory } from '../api/services/acceleration'; import accelerationCosts from '../api/acceleration/acceleration'; import bitcoinApi from '../api/bitcoin/bitcoin-api-factory'; import transactionUtils from '../api/transaction-utils'; @@ -15,6 +15,7 @@ import { BlockExtended, MempoolTransactionExtended } from '../mempool.interfaces export interface PublicAcceleration { txid: string, height: number, + added: number, pool: { id: number, slug: string, @@ -29,15 +30,20 @@ export interface PublicAcceleration { class AccelerationRepository { private bidBoostV2Activated = 831580; - public async $saveAcceleration(acceleration: AccelerationInfo, block: IEsploraApi.Block, pool_id: number): Promise { + public async $saveAcceleration(acceleration: AccelerationInfo, block: IEsploraApi.Block, pool_id: number, accelerationData: Acceleration[]): Promise { + const accelerationMap: { [txid: string]: Acceleration } = {}; + for (const acc of accelerationData) { + accelerationMap[acc.txid] = acc; + } try { await DB.query(` - INSERT INTO accelerations(txid, added, height, pool, effective_vsize, effective_fee, boost_rate, boost_cost) - VALUE (?, FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?) + INSERT INTO accelerations(txid, requested, added, height, pool, effective_vsize, effective_fee, boost_rate, boost_cost) + VALUE (?, FROM_UNIXTIME(?), FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE height = ? `, [ acceleration.txSummary.txid, + accelerationMap[acceleration.txSummary.txid].added, block.timestamp, block.height, pool_id, @@ -64,7 +70,7 @@ class AccelerationRepository { } let query = ` - SELECT * FROM accelerations + SELECT *, UNIX_TIMESTAMP(requested) as requested_timestamp, UNIX_TIMESTAMP(added) as block_timestamp FROM accelerations JOIN pools on pools.unique_id = accelerations.pool `; let params: any[] = []; @@ -99,6 +105,7 @@ class AccelerationRepository { return rows.map(row => ({ txid: row.txid, height: row.height, + added: row.requested_timestamp || row.block_timestamp, pool: { id: row.id, slug: row.slug, @@ -202,7 +209,7 @@ class AccelerationRepository { const tx = blockTxs[acc.txid]; const accelerationInfo = accelerationCosts.getAccelerationInfo(tx, boostRate, transactions); accelerationInfo.cost = Math.max(0, Math.min(acc.feeDelta, accelerationInfo.cost)); - this.$saveAcceleration(accelerationInfo, block, block.extras.pool.id); + this.$saveAcceleration(accelerationInfo, block, block.extras.pool.id, successfulAccelerations); } } const lastSyncedHeight = await this.$getLastSyncedHeight(); @@ -230,7 +237,7 @@ class AccelerationRepository { logger.debug(`Fetching accelerations between block ${lastSyncedHeight} and ${currentHeight}`); // Fetch accelerations from mempool.space since the last synced block; - const accelerationsByBlock = {}; + const accelerationsByBlock: {[height: number]: AccelerationHistory[]} = {}; const blockHashes = {}; let done = false; let page = 1; @@ -297,12 +304,16 @@ class AccelerationRepository { const feeStats = Common.calcEffectiveFeeStatistics(template); boostRate = feeStats.medianFee; } + const accelerationSummaries = accelerations.map(acc => ({ + ...acc, + pools: acc.pools.map(pool => pool.pool_unique_id), + })) for (const acc of accelerations) { if (blockTxs[acc.txid]) { const tx = blockTxs[acc.txid]; const accelerationInfo = accelerationCosts.getAccelerationInfo(tx, boostRate, transactions); accelerationInfo.cost = Math.max(0, Math.min(acc.feeDelta, accelerationInfo.cost)); - await this.$saveAcceleration(accelerationInfo, block, block.extras.pool.id); + await this.$saveAcceleration(accelerationInfo, block, block.extras.pool.id, accelerationSummaries); } } await this.$setLastSyncedHeight(height); @@ -317,6 +328,26 @@ class AccelerationRepository { logger.debug(`Indexing accelerations completed`); } + + /** + * Delete accelerations from the database above blockHeight + */ + public async $deleteAccelerationsFrom(blockHeight: number): Promise { + logger.info(`Delete newer accelerations from height ${blockHeight} from the database`); + try { + const currentSyncedHeight = await this.$getLastSyncedHeight(); + if (currentSyncedHeight >= blockHeight) { + await DB.query(` + UPDATE state + SET number = ? + WHERE name = 'last_acceleration_block' + `, [blockHeight - 1]); + } + await DB.query(`DELETE FROM accelerations where height >= ${blockHeight}`); + } catch (e) { + logger.err('Cannot delete indexed accelerations. Reason: ' + (e instanceof Error ? e.message : e)); + } + } } export default new AccelerationRepository(); diff --git a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html index bb451d039..d303858a5 100644 --- a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html +++ b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html @@ -65,24 +65,26 @@
-
How much more are you willing to pay?
-
-
- Choose the maximum extra transaction fee you're willing to pay to get into the next block. -
-
-
- - - + @if (paymentType !== 'cashapp') { +
How much more are you willing to pay?
+
+
+ Choose the maximum extra transaction fee you're willing to pay to get into the next block. +
+
+
+ + + +
-
+ }
Acceleration summary
@@ -90,27 +92,51 @@ - - - - - - - - - - - - + @if (paymentType === 'cashapp') { + + + + + + + + + + + + + } @else { + + + + + + + + + + + + + } @@ -141,53 +167,76 @@ - - - - - - - - - - - + + @if (paymentType === 'cashapp') { + + + + + + + + + } @else { + + + + + + + + + + + - - - - - - - - - - - + + + + + + + + + + + + } @@ -237,14 +286,17 @@ -
-
Accelerate with
-
-
- Loading -
+ @if (!hideCashApp && paymentType === 'cashapp') { +
+
+
Accelerate for with
+
+
+ Loading +
+
-
+ }
diff --git a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.scss b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.scss index 5275bec64..1f73ea9b2 100644 --- a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.scss +++ b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.scss @@ -109,4 +109,61 @@ .item { white-space: initial; +} + +.cashapp-cta { + width: 100%; + height: 54px; + background: #653b9c; + position: relative; + bottom: initial; + top: initial; + border-radius: 3px; + font-size: 14px; + line-height: 16px; + text-align: center; + padding: 4px 6px; + cursor: pointer; + box-shadow: 0px 0px 15px 0px #000; + + &.sticky-top { + position: fixed; + width: calc(100vw - 30px - 1.5rem); + margin: auto; + z-index: 50; + left: 0; + right: 0; + top: 102px; + @media (min-width: 573px) { + top: 62px; + } + } + &.sticky-bottom { + position: fixed; + width: calc(100vw - 30px - 1.5rem); + margin: auto; + z-index: 50; + left: 0; + right: 0; + bottom: 50px; + @media (min-width: 430px) { + bottom: 56px; + } + } + + @media (max-width: 400px) { + width: calc(100% + 1.5rem); + margin: 0 -0.75rem; + &.sticky-top, &.sticky-bottom { + width: calc(100vw - 30px); + } + } +} + +.cashapp-placeholder { + height: 54px; + + &.non-stick { + height: 0px; + } } \ No newline at end of file diff --git a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts index aee0189aa..19edf9b7e 100644 --- a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts +++ b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChanges, HostListener, ChangeDetectorRef } from '@angular/core'; +import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChanges, HostListener, ChangeDetectorRef, ViewChild, ElementRef } from '@angular/core'; import { Subscription, catchError, of, tap } from 'rxjs'; import { StorageService } from '../../services/storage.service'; import { Transaction } from '../../interfaces/electrs.interface'; @@ -43,6 +43,9 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges @Input() tx: Transaction | undefined; @Input() scrollEvent: boolean; + @ViewChild('cashappCTA') + cashappCTA: ElementRef; + math = Math; error = ''; showSuccess = false; @@ -56,9 +59,11 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges defaultBid = 0; maxCost = 0; userBid = 0; + accelerationUUID: string; selectFeeRateIndex = 1; isMobile: boolean = window.innerWidth <= 767.98; user: any = undefined; + stickyCTA: string = 'non-stick'; maxRateOptions: RateOption[] = []; @@ -66,6 +71,7 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges paymentType: 'bitcoin' | 'cashapp' = 'bitcoin'; cashAppSubscription: Subscription; conversionsSubscription: Subscription; + cashappSubmit: any; payments: any; showSpinner = false; square: any; @@ -80,7 +86,10 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges private cd: ChangeDetectorRef ) { if (this.stateService.ref === 'https://cash.app/') { + this.paymentType = 'cashapp'; this.insertSquare(); + } else { + this.paymentType = 'bitcoin'; } } @@ -94,21 +103,23 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges } ngOnInit() { + this.accelerationUUID = window.crypto.randomUUID(); if (this.stateService.ref === 'https://cash.app/') { this.paymentType = 'cashapp'; - this.stateService.ref = ''; } else { this.paymentType = 'bitcoin'; } } ngOnChanges(changes: SimpleChanges): void { - if (changes.scrollEvent) { + if (changes.scrollEvent && this.paymentType !== 'cashapp' && this.stateService.ref !== 'https://cash.app/') { this.scrollToPreview('acceleratePreviewAnchor', 'start'); } } ngAfterViewInit() { + this.onScroll(); + if (this.paymentType === 'cashapp') { this.showSpinner = true; } @@ -173,10 +184,15 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges this.maxCost = this.userBid + this.estimate.mempoolBaseFee + this.estimate.vsizeFee; if (!this.error) { - this.scrollToPreview('acceleratePreviewAnchor', 'start'); if (this.paymentType === 'cashapp') { this.setupSquare(); - } + } else { + this.scrollToPreview('acceleratePreviewAnchor', 'start'); + } + + setTimeout(() => { + this.onScroll(); + }, 100); } } }), @@ -231,7 +247,8 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges } this.accelerationSubscription = this.servicesApiService.accelerate$( this.tx.txid, - this.userBid + this.userBid, + this.accelerationUUID ).subscribe({ next: () => { this.audioService.playSound('ascend-chime-cartoon'); @@ -301,6 +318,10 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges this.conversionsSubscription = this.stateService.conversions$.subscribe( async (conversions) => { + if (this.cashAppPay) { + this.cashAppPay.destroy(); + } + const maxCostUsd = this.maxCost / 100_000_000 * conversions.USD; const paymentRequest = this.payments.paymentRequest({ countryCode: 'US', @@ -310,13 +331,15 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges label: 'Total', pending: true, productUrl: `https://mempool.space/tx/${this.tx.txid}`, - } + }, + button: { shape: 'semiround', size: 'small', theme: 'light'} }); this.cashAppPay = await this.payments.cashAppPay(paymentRequest, { redirectURL: `https://mempool.space/tx/${this.tx.txid}`, referenceId: `accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`, + button: { shape: 'semiround', size: 'small', theme: 'light'} }); - await this.cashAppPay.attach('#cash-app-pay'); + const renderPromise = this.cashAppPay.CashAppPayInstance.render('#cash-app-pay', { button: { theme: 'light', size: 'small', shape: 'semiround' }, manage: false }); this.showSpinner = false; const that = this; @@ -332,7 +355,8 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges that.userBid, tokenResult.token, tokenResult.details.cashAppPay.cashtag, - tokenResult.details.cashAppPay.referenceId + tokenResult.details.cashAppPay.referenceId, + that.accelerationUUID ).subscribe({ next: () => { that.audioService.playSound('ascend-chime-cartoon'); @@ -351,13 +375,19 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges }); } }); + + this.cashappSubmit = await renderPromise; } ); } insertSquare(): void { let statsUrl = 'https://sandbox.web.squarecdn.com/v1/square.js'; - if (document.location.hostname === 'mempool-staging.tk7.mempool.space' || document.location.hostname === 'mempool.space') { + if (document.location.hostname === 'mempool-staging.fmt.mempool.space' || + document.location.hostname === 'mempool-staging.va1.mempool.space' || + document.location.hostname === 'mempool-staging.fra.mempool.space' || + document.location.hostname === 'mempool-staging.tk7.mempool.space' || + document.location.hostname === 'mempool.space') { statsUrl = 'https://web.squarecdn.com/v1/square.js'; } @@ -367,4 +397,34 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges g.type='text/javascript'; g.src=statsUrl; s.parentNode.insertBefore(g, s); })(); } + + submitCashappPay(): void { + if (this.cashappSubmit) { + this.cashappSubmit?.begin(); + } + } + + @HostListener('window:scroll', ['$event']) // for window scroll events + onScroll() { + if (this.estimate && !this.cashappCTA?.nativeElement) { + setTimeout(() => { + this.onScroll(); + }, 200); + return; + } + if (!this.cashappCTA?.nativeElement || this.paymentType !== 'cashapp' || !this.isMobile) { + return; + } + const cta = this.cashappCTA.nativeElement; + const rect = cta.getBoundingClientRect(); + const topOffset = window.innerWidth <= 572 ? 102 : 62; + const bottomOffset = window.innerWidth < 430 ? 50 : 56; + if (rect.top < topOffset) { + this.stickyCTA = 'sticky-top'; + } else if (rect.top > window.innerHeight - (bottomOffset + 54)) { + this.stickyCTA = 'sticky-bottom'; + } else { + this.stickyCTA = 'non-stick'; + } + } } diff --git a/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.html b/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.html index 74c8ed3d1..668f345f0 100644 --- a/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.html +++ b/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.html @@ -39,10 +39,10 @@ - - + + + + } @else { - } @else { - } @@ -530,7 +537,7 @@ } @else if (this.mempoolPosition.block >= 7) { In several hours (or more) - @if (!tx.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !tx?.acceleration) { + @if (!(isMobile && paymentType === 'cashapp') && !tx.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !tx?.acceleration) { Accelerate } @@ -539,7 +546,7 @@ } @else { - @if (!tx.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !tx?.acceleration) { + @if (!(isMobile && paymentType === 'cashapp') && !tx.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !tx?.acceleration) { Accelerate } @@ -554,13 +561,13 @@ @if (!isLoadingTx) { - @if (((auditStatus && auditStatus.accelerated) || accelerationInfo || (tx && tx.acceleration)) || filters.length) { + @if (isAcceleration || filters.length) { - @if (tx.acceleration || accelerationInfo) { + @if (isAcceleration) { } @else { }
Next block market rate - {{ estimate.targetFeeRate | number : '1.0-0' }} - sat/vB
- Estimated extra fee required - - {{ math.max(0, estimate.nextBlockFee - estimate.txSummary.effectiveFee) | number }} - - sats - -
Boost rate + {{ maxRateOptions[selectFeeRateIndex].rate | number : '1.0-0' }} + sat/vB
+ Boost fee + + {{ maxRateOptions[selectFeeRateIndex].fee | number }} + + sats + +
Next block market rate + {{ estimate.targetFeeRate | number : '1.0-0' }} + sat/vB
+ Estimated extra fee required + + {{ math.max(0, estimate.nextBlockFee - estimate.txSummary.effectiveFee) | number }} + + sats + +
- Estimated acceleration cost - - - {{ estimate.cost + estimate.mempoolBaseFee + estimate.vsizeFee | number }} - - - sats - -
- -
+ Total cost + + + {{ maxCost | number }} + + + sats + + + +
+ Estimated acceleration cost + + + {{ estimate.cost + estimate.mempoolBaseFee + estimate.vsizeFee | number }} + + + sats + +
+ +
- Maximum acceleration cost - - - {{ maxCost | number }} - - - sats - - - -
- -
+ Maximum acceleration cost + + + {{ maxCost | number }} + + + sats + + + +
+ +
- {{ (acceleration.boost) | number }} sat + + {{ acceleration.boost | number }} sat + ~ diff --git a/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.ts b/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.ts index 1a0aacbb6..5acd77d5d 100644 --- a/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.ts +++ b/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.ts @@ -58,7 +58,7 @@ export class AccelerationsListComponent implements OnInit { } } for (const acc of accelerations) { - acc.boost = acc.feePaid - acc.baseFee - acc.vsizeFee; + acc.boost = acc.boostCost != null ? acc.boostCost : (acc.feePaid - acc.baseFee - acc.vsizeFee); } if (this.widget) { return of(accelerations.slice(0, 6)); diff --git a/frontend/src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.ts b/frontend/src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.ts index 2330e19e9..acaff9170 100644 --- a/frontend/src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.ts +++ b/frontend/src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.ts @@ -119,15 +119,15 @@ export class AcceleratorDashboardComponent implements OnInit { switchMap(([accelerations, blocks]) => { const blockMap = {}; for (const block of blocks) { - blockMap[block.id] = block; + blockMap[block.height] = block; } - const accelerationsByBlock: { [ hash: string ]: Acceleration[] } = {}; + const accelerationsByBlock: { [ height: number ]: Acceleration[] } = {}; for (const acceleration of accelerations) { - if (['completed_provisional', 'failed_provisional', 'completed'].includes(acceleration.status) && acceleration.pools.includes(blockMap[acceleration.blockHash]?.extras.pool.id)) { - if (!accelerationsByBlock[acceleration.blockHash]) { - accelerationsByBlock[acceleration.blockHash] = []; + if (['completed_provisional', 'failed_provisional', 'completed'].includes(acceleration.status) && acceleration.pools.includes(blockMap[acceleration.blockHeight]?.extras.pool.id)) { + if (!accelerationsByBlock[acceleration.blockHeight]) { + accelerationsByBlock[acceleration.blockHeight] = []; } - accelerationsByBlock[acceleration.blockHash].push(acceleration); + accelerationsByBlock[acceleration.blockHeight].push(acceleration); } } return of(blocks.slice(0, 6).map(block => { diff --git a/frontend/src/app/components/address-graph/address-graph.component.ts b/frontend/src/app/components/address-graph/address-graph.component.ts index fc3cf24dd..4e31e663f 100644 --- a/frontend/src/app/components/address-graph/address-graph.component.ts +++ b/frontend/src/app/components/address-graph/address-graph.component.ts @@ -161,7 +161,7 @@ export class AddressGraphComponent implements OnChanges { ], series: [ { - name: $localize`Balance:Balance`, + name: $localize`:@@7e69426bd97a606d8ae6026762858e6e7c86a1fd:Balance`, showSymbol: false, symbol: 'circle', symbolSize: 8, diff --git a/frontend/src/app/components/block-overview-graph/block-scene.ts b/frontend/src/app/components/block-overview-graph/block-scene.ts index 05abddb03..cb26fab02 100644 --- a/frontend/src/app/components/block-overview-graph/block-scene.ts +++ b/frontend/src/app/components/block-overview-graph/block-scene.ts @@ -199,6 +199,7 @@ export default class BlockScene { this.txs[tx.txid].feerate = tx.rate || (this.txs[tx.txid].fee / this.txs[tx.txid].vsize); this.txs[tx.txid].rate = tx.rate; this.txs[tx.txid].dirty = true; + this.updateColor(this.txs[tx.txid], startTime, 50, true); } }); diff --git a/frontend/src/app/components/block/block-preview.component.ts b/frontend/src/app/components/block/block-preview.component.ts index 3e1d9b409..91dfef8c2 100644 --- a/frontend/src/app/components/block/block-preview.component.ts +++ b/frontend/src/app/components/block/block-preview.component.ts @@ -136,7 +136,7 @@ export class BlockPreviewComponent implements OnInit, OnDestroy { return of(transactions); }) ), - this.stateService.env.ACCELERATOR === true && block.height > 819500 ? this.servicesApiService.getAccelerationHistory$({ blockHash: block.id }) : of([]) + this.stateService.env.ACCELERATOR === true && block.height > 819500 ? this.servicesApiService.getAccelerationHistory$({ blockHeight: block.height }) : of([]) ]); } ), diff --git a/frontend/src/app/components/block/block.component.ts b/frontend/src/app/components/block/block.component.ts index 13b0ecd76..40d451d59 100644 --- a/frontend/src/app/components/block/block.component.ts +++ b/frontend/src/app/components/block/block.component.ts @@ -345,7 +345,7 @@ export class BlockComponent implements OnInit, OnDestroy { return of(null); }) ), - this.stateService.env.ACCELERATOR === true && block.height > 819500 ? this.servicesApiService.getAccelerationHistory$({ blockHash: block.id }) : of([]) + this.stateService.env.ACCELERATOR === true && block.height > 819500 ? this.servicesApiService.getAccelerationHistory$({ blockHeight: block.height }) : of([]) ]); }) ) diff --git a/frontend/src/app/components/liquid-master-page/liquid-master-page.component.scss b/frontend/src/app/components/liquid-master-page/liquid-master-page.component.scss index 1577b5c88..12d0930d4 100644 --- a/frontend/src/app/components/liquid-master-page/liquid-master-page.component.scss +++ b/frontend/src/app/components/liquid-master-page/liquid-master-page.component.scss @@ -91,6 +91,10 @@ li.nav-item { } } +.dropdown-container { + margin-top: 5px; +} + nav { box-shadow: 0px 0px 15px 0px #000; } diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html index a42975016..55f7047e6 100644 --- a/frontend/src/app/components/transaction/transaction.component.html +++ b/frontend/src/app/components/transaction/transaction.component.html @@ -80,7 +80,9 @@

Accelerate

- + @if (!(isMobile && paymentType === 'cashapp')) { + + }
@@ -456,13 +458,18 @@ - @if (!isLoadingTx && transactionTime !== -1) { + @if (isLoadingTx) { + + } @else if (transactionTime === -1) { +
First seen
First seen
- @if ((auditStatus && auditStatus.accelerated) || accelerationInfo || (tx && tx.acceleration)) { + @if (isAcceleration) { Accelerated } @@ -606,15 +613,15 @@ @if (!isLoadingTx) { @if ((cpfpInfo && hasEffectiveFeeRate) || accelerationInfo) {
Accelerated fee rateEffective fee rate
- @if (accelerationInfo) { - + @if (accelerationInfo?.acceleratedFeeRate && (!tx.effectiveFeePerVsize || accelerationInfo.acceleratedFeeRate >= tx.effectiveFeePerVsize)) { + } @else { } diff --git a/frontend/src/app/components/transaction/transaction.component.scss b/frontend/src/app/components/transaction/transaction.component.scss index 80caa6003..375b5c4e0 100644 --- a/frontend/src/app/components/transaction/transaction.component.scss +++ b/frontend/src/app/components/transaction/transaction.component.scss @@ -311,6 +311,13 @@ } } +.accelerateFullSize { + width: 100%; + height: 100%; + padding: 0.5rem 0.25rem; + background-color: #653b9c; +} + .goggles-icon { display: block; width: 2.2em; diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index ab55d706a..029ee487b 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -9,7 +9,8 @@ import { delay, mergeMap, tap, - map + map, + retry } from 'rxjs/operators'; import { Transaction } from '../../interfaces/electrs.interface'; import { of, merge, Subscription, Observable, Subject, from, throwError, combineLatest } from 'rxjs'; @@ -93,12 +94,13 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { adjustedVsize: number | null; pool: Pool | null; auditStatus: AuditStatus | null; + isAcceleration: boolean = false; filters: Filter[] = []; showCpfpDetails = false; fetchCpfp$ = new Subject(); fetchRbfHistory$ = new Subject(); fetchCachedTx$ = new Subject(); - fetchAcceleration$ = new Subject(); + fetchAcceleration$ = new Subject(); fetchMiningInfo$ = new Subject<{ hash: string, height: number, txid: string }>(); isCached: boolean = false; now = Date.now(); @@ -117,6 +119,8 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { flowEnabled: boolean; tooltipPosition: { x: number, y: number }; isMobile: boolean; + paymentType: 'bitcoin' | 'cashapp' = 'bitcoin'; + firstLoad = true; featuresEnabled: boolean; segwitEnabled: boolean; @@ -154,6 +158,11 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { ngOnInit() { this.acceleratorAvailable = this.stateService.env.OFFICIAL_MEMPOOL_SPACE && this.stateService.env.ACCELERATOR && this.stateService.network === ''; + if (this.acceleratorAvailable && this.stateService.ref === 'https://cash.app/') { + this.showAccelerationSummary = true; + this.paymentType = 'cashapp'; + } + this.enterpriseService.page(); this.websocketService.want(['blocks', 'mempool-blocks']); @@ -280,9 +289,10 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { filter(() => this.stateService.env.ACCELERATOR === true), tap(() => { this.accelerationInfo = null; + this.setIsAccelerated(); }), - switchMap((blockHash: string) => { - return this.servicesApiService.getAccelerationHistory$({ blockHash }); + switchMap((blockHeight: number) => { + return this.servicesApiService.getAccelerationHistory$({ blockHeight }); }), catchError(() => { return of(null); @@ -290,8 +300,12 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { ).subscribe((accelerationHistory) => { for (const acceleration of accelerationHistory) { if (acceleration.txid === this.txId && (acceleration.status === 'completed' || acceleration.status === 'completed_provisional')) { - acceleration.acceleratedFee = Math.max(acceleration.effectiveFee, acceleration.effectiveFee + acceleration.feePaid - acceleration.baseFee - acceleration.vsizeFee); + const boostCost = acceleration.boostCost || (acceleration.feePaid - acceleration.baseFee - acceleration.vsizeFee); + acceleration.acceleratedFeeRate = Math.max(acceleration.effectiveFee, acceleration.effectiveFee + boostCost) / acceleration.effectiveVsize; + acceleration.boost = boostCost; + this.accelerationInfo = acceleration; + this.setIsAccelerated(); } } }); @@ -312,6 +326,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { map(block => { return block.extras.pool; }), + retry({ count: 3, delay: 2000 }), catchError(() => { return of(null); }) @@ -332,18 +347,21 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { accelerated: isAccelerated, }; }), + retry({ count: 3, delay: 2000 }), catchError(() => { return of(null); }) ) : of(isCoinbase ? { coinbase: true } : null) ]); }), - catchError(() => { + catchError((e) => { return of(null); }) ).subscribe(([pool, auditStatus]) => { this.pool = pool; this.auditStatus = auditStatus; + + this.setIsAccelerated(); }); this.mempoolPositionSubscription = this.stateService.mempoolTxPosition$.subscribe(txPosition => { @@ -475,7 +493,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { this.getTransactionTime(); } } else { - this.fetchAcceleration$.next(tx.status.block_hash); + this.fetchAcceleration$.next(tx.status.block_height); this.fetchMiningInfo$.next({ hash: tx.status.block_hash, height: tx.status.block_height, txid: tx.txid }); this.transactionTime = 0; } @@ -537,7 +555,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { } else { this.audioService.playSound('magic'); } - this.fetchAcceleration$.next(block.id); + this.fetchAcceleration$.next(block.height); this.fetchMiningInfo$.next({ hash: block.id, height: block.height, txid: this.tx.txid }); } }); @@ -666,10 +684,11 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { relatives.reduce((prev, val) => prev + val.fee, 0); this.tx.effectiveFeePerVsize = totalFees / (totalWeight / 4); } else { - this.tx.effectiveFeePerVsize = cpfpInfo.effectiveFeePerVsize; + this.tx.effectiveFeePerVsize = cpfpInfo.effectiveFeePerVsize || this.tx.effectiveFeePerVsize || this.tx.feePerVsize || (this.tx.fee / (this.tx.weight / 4)); } if (cpfpInfo.acceleration) { this.tx.acceleration = cpfpInfo.acceleration; + this.setIsAccelerated(); } this.cpfpInfo = cpfpInfo; @@ -681,6 +700,11 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { this.hasEffectiveFeeRate = hasRelatives || (this.tx.effectiveFeePerVsize && (Math.abs(this.tx.effectiveFeePerVsize - this.tx.feePerVsize) > 0.01)); } + setIsAccelerated() { + console.log(this.tx.acceleration, this.accelerationInfo, this.pool, this.accelerationInfo?.pools); + this.isAcceleration = (this.tx.acceleration || (this.accelerationInfo && this.pool && this.accelerationInfo.pools.some(pool => (pool === this.pool.id || pool?.['pool_unique_id'] === this.pool.id)))); + } + setFeatures(): void { if (this.tx) { this.segwitEnabled = !this.tx.status.confirmed || isFeatureActive(this.stateService.network, this.tx.status.block_height, 'segwit'); @@ -720,6 +744,11 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { } resetTransaction() { + if (!this.firstLoad) { + this.stateService.ref = ''; + } else { + this.firstLoad = false; + } this.error = undefined; this.tx = null; this.setFeatures(); @@ -742,6 +771,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { this.pool = null; this.auditStatus = null; document.body.scrollTo(0, 0); + this.isAcceleration = false; this.leaveTransaction(); } @@ -814,6 +844,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { } ngOnDestroy() { + this.stateService.ref = ''; this.subscription.unsubscribe(); this.fetchCpfpSubscription.unsubscribe(); this.fetchRbfSubscription.unsubscribe(); diff --git a/frontend/src/app/docs/api-docs/api-docs.component.html b/frontend/src/app/docs/api-docs/api-docs.component.html index 6a561a4f0..eb3ab3323 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.html +++ b/frontend/src/app/docs/api-docs/api-docs.component.html @@ -190,11 +190,11 @@ -

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 ways to accelerate the process.

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.

This site only provides data about the Bitcoin network—it cannot help you get your transaction confirmed quicker.

+

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 ways to accelerate the process.

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.

This site only provides data about the Bitcoin network. To get help with a transaction, get in touch with the entity that helped make the transaction (wallet software, exchange company, etc).

-

To get your transaction confirmed quicker, you will need to increase its effective feerate.

If your transaction was created with RBF enabled, your stuck transaction can simply be replaced with a new one that has a higher fee.

Otherwise, if you control any of the stuck transaction's outputs, you can use CPFP to increase your stuck transaction's effective feerate.

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.

+

To get your transaction confirmed quicker, you will need to increase its effective feerate.

If your transaction was created with RBF enabled, your stuck transaction can simply be replaced with a new one that has a higher fee. Otherwise, if you control any of the stuck transaction's outputs, you can use CPFP to increase your stuck transaction's effective feerate.

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).

Another option to get your transaction confirmed more quickly is Mempool Accelerator™. This service is still in development, but you can sign up for the waitlist to be notified when it's ready.

diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index 6d28612f0..dfc594e49 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -394,8 +394,11 @@ export interface Acceleration { blockHash: string; blockHeight: number; - acceleratedFee?: number; + acceleratedFeeRate?: number; boost?: number; + + boostCost?: number; + boostRate?: number; } export interface AccelerationHistoryParams { diff --git a/frontend/src/app/services/services-api.service.ts b/frontend/src/app/services/services-api.service.ts index 4a8314e4b..4cc05294f 100644 --- a/frontend/src/app/services/services-api.service.ts +++ b/frontend/src/app/services/services-api.service.ts @@ -128,12 +128,12 @@ export class ServicesApiServices { return this.httpClient.post(`${SERVICES_API_PREFIX}/accelerator/estimate`, { txInput: txInput }, { observe: 'response' }); } - accelerate$(txInput: string, userBid: number) { - return this.httpClient.post(`${SERVICES_API_PREFIX}/accelerator/accelerate`, { txInput: txInput, userBid: userBid }); + accelerate$(txInput: string, userBid: number, accelerationUUID: string) { + return this.httpClient.post(`${SERVICES_API_PREFIX}/accelerator/accelerate`, { txInput: txInput, userBid: userBid, accelerationUUID: accelerationUUID }); } - accelerateWithCashApp$(txInput: string, userBid: number, token: string, cashtag: string, referenceId: string) { - return this.httpClient.post(`${SERVICES_API_PREFIX}/accelerator/accelerate/cashapp`, { txInput: txInput, userBid: userBid, token: token, cashtag: cashtag, referenceId: referenceId }); + accelerateWithCashApp$(txInput: string, userBid: number, token: string, cashtag: string, referenceId: string, accelerationUUID: string) { + return this.httpClient.post(`${SERVICES_API_PREFIX}/accelerator/accelerate/cashapp`, { txInput: txInput, userBid: userBid, token: token, cashtag: cashtag, referenceId: referenceId, accelerationUUID: accelerationUUID }); } getAccelerations$(): Observable { diff --git a/frontend/src/locale/messages.fa.xlf b/frontend/src/locale/messages.fa.xlf index f1902ce34..eed8aee8a 100644 --- a/frontend/src/locale/messages.fa.xlf +++ b/frontend/src/locale/messages.fa.xlf @@ -954,6 +954,7 @@ Accelerations + شتاب‌دهی‌ها src/app/components/acceleration/accelerations-list/accelerations-list.component.html 2 @@ -987,6 +988,7 @@ Fee Rate + نرخ کارمزد src/app/components/acceleration/accelerations-list/accelerations-list.component.html 12 @@ -996,6 +998,7 @@ Acceleration Bid + پیشنهاد شتاب‌دهی src/app/components/acceleration/accelerations-list/accelerations-list.component.html 13 @@ -1005,6 +1008,7 @@ Requested + درخواست‌شده src/app/components/acceleration/accelerations-list/accelerations-list.component.html 14 @@ -1017,6 +1021,7 @@ Bid Boost + افزایش ناشی از پیشنهاد src/app/components/acceleration/accelerations-list/accelerations-list.component.html 17 @@ -1065,6 +1070,7 @@ Pending + در حال انتظار src/app/components/acceleration/accelerations-list/accelerations-list.component.html 53 @@ -1073,6 +1079,7 @@ Completed 🔄 + کامل‌شده 🔄 src/app/components/acceleration/accelerations-list/accelerations-list.component.html 54,55 @@ -1080,6 +1087,7 @@ Failed 🔄 + ناموفق 🔄 src/app/components/acceleration/accelerations-list/accelerations-list.component.html 55,56 @@ -1088,6 +1096,7 @@ There are no active accelerations + هیچ شتاب‌دهی فعالی وجود ندارد src/app/components/acceleration/accelerations-list/accelerations-list.component.html 96 @@ -1096,6 +1105,7 @@ There are no recent accelerations + هیچ شتاب‌دهی اخیری وجود ندارد src/app/components/acceleration/accelerations-list/accelerations-list.component.html 97 @@ -1104,6 +1114,7 @@ Active Accelerations + شتاب‌دهی‌های فعال src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.html 10 @@ -1116,6 +1127,7 @@ Acceleration stats + وضعیت شتاب‌دهی src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.html 24 @@ -1124,6 +1136,7 @@ (3 months) + (3 ماه) src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.html 25 @@ -1157,6 +1170,7 @@ Recent Accelerations + شتاب‌دهی‌های اخیر src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.html 86 @@ -1165,6 +1179,7 @@ Accelerator Dashboard + داشبورد شتاب‌دهی src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.ts 47 @@ -1176,6 +1191,7 @@ pending + در حال انتظار src/app/components/acceleration/pending-stats/pending-stats.component.html 7 @@ -1184,6 +1200,7 @@ Avg Max Bid + متوسط بیشینه پیشنهاد src/app/components/acceleration/pending-stats/pending-stats.component.html 11 @@ -1196,6 +1213,7 @@ Total Vsize + مجموع اندازه مجازی src/app/components/acceleration/pending-stats/pending-stats.component.html 20 @@ -1208,6 +1226,7 @@ of next block + از بلاک بعدی src/app/components/acceleration/pending-stats/pending-stats.component.html 23 @@ -1216,6 +1235,7 @@ Balance History + تاریخچه موجودی src/app/components/address-graph/address-graph.component.html 6 @@ -1224,6 +1244,7 @@ Balance:Balance + Balance:Balance src/app/components/address-graph/address-graph.component.ts 162 @@ -1231,6 +1252,7 @@ Balances + موجودی‌ها src/app/components/address-group/address-group.component.html 4 @@ -1239,6 +1261,7 @@ Total + مجموع src/app/components/address-group/address-group.component.html 9 @@ -6565,6 +6588,7 @@ Consolidation + تجمیع src/app/dashboard/dashboard.component.ts 79 @@ -6576,6 +6600,7 @@ Coinjoin + هم‌بازضرب src/app/dashboard/dashboard.component.ts 80 @@ -6587,6 +6612,7 @@ Data + داده src/app/dashboard/dashboard.component.ts 81 diff --git a/frontend/src/locale/messages.xlf b/frontend/src/locale/messages.xlf index b4dd11f58..513f64397 100644 --- a/frontend/src/locale/messages.xlf +++ b/frontend/src/locale/messages.xlf @@ -397,7 +397,7 @@ src/app/components/transaction/transaction.component.html - 534 + 581 src/app/components/transactions-list/transactions-list.component.html @@ -464,11 +464,11 @@ src/app/components/transaction/transaction.component.html - 189 + 101 src/app/components/transaction/transaction.component.html - 317 + 229 Transaction Virtual Size transaction.vsize @@ -710,15 +710,15 @@ src/app/components/transaction/transaction.component.html - 106 + 81 src/app/components/transaction/transaction.component.html - 145 + 534 src/app/components/transaction/transaction.component.html - 155 + 543 Accelerate button label transaction.accelerate @@ -727,7 +727,7 @@ If your tx is accelerated to ~ sat/vB src/app/components/accelerate-preview/accelerate-preview.component.html - 249 + 258 accelerator.accelerated-to-description @@ -893,7 +893,7 @@ src/app/components/transaction/transaction.component.html - 188 + 100 src/app/dashboard/dashboard.component.html @@ -1084,7 +1084,7 @@ Accelerator Dashboard src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.ts - 47 + 51 src/app/components/master-page/master-page.component.html @@ -1139,11 +1139,23 @@ address.balance-history - - Balance:Balance + + Balance src/app/components/address-graph/address-graph.component.ts - 162 + 164 + + + src/app/components/address/address-preview.component.html + 31 + + + src/app/components/address/address.component.html + 39 + + + src/app/components/liquid-reserves-audit/federation-addresses-list/federation-addresses-list.component.html + 9 @@ -1225,22 +1237,6 @@ address.total-sent - - Balance - - src/app/components/address/address-preview.component.html - 31 - - - src/app/components/address/address.component.html - 39 - - - src/app/components/liquid-reserves-audit/federation-addresses-list/federation-addresses-list.component.html - 9 - - address.balance - Transactions @@ -1991,7 +1987,7 @@ src/app/components/transaction/transaction.component.html - 130 + 461 src/app/lightning/node/node.component.html @@ -2028,7 +2024,7 @@ src/app/components/transaction/transaction.component.html - 63 + 449 src/app/shared/components/confirmations/confirmations.component.html @@ -2073,7 +2069,7 @@ src/app/components/transaction/transaction.component.html - 533 + 580 src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html @@ -2094,11 +2090,11 @@ src/app/components/transaction/transaction.component.html - 191 + 103 src/app/components/transaction/transaction.component.html - 537 + 591 src/app/lightning/channel/channel-box/channel-box.component.html @@ -2123,7 +2119,7 @@ src/app/components/transaction/transaction.component.html - 548 + 612 Effective transaction fee rate transaction.effective-fee-rate @@ -2149,7 +2145,7 @@ src/app/components/transaction/transaction.component.html - 190 + 102 Transaction Weight transaction.weight @@ -2219,7 +2215,7 @@ src/app/components/transaction/transaction.component.html - 81 + 504 Added tx-features.tag.added @@ -2232,7 +2228,7 @@ src/app/components/transaction/transaction.component.html - 82 + 507 Prioritized tx-features.tag.prioritized @@ -2245,7 +2241,7 @@ src/app/components/transaction/transaction.component.html - 83 + 510 Conflict tx-features.tag.conflict @@ -2262,7 +2258,7 @@ src/app/components/transaction/transaction.component.html - 583 + 564 src/app/shared/filters.utils.ts @@ -2356,7 +2352,7 @@ src/app/components/transaction/transaction.component.html - 313 + 225 src/app/dashboard/dashboard.component.html @@ -2395,7 +2391,7 @@ src/app/components/transaction/transaction.component.html - 329 + 241 @@ -2493,7 +2489,7 @@ src/app/components/transaction/transaction.component.html - 53 + 432 block.timestamp @@ -2558,7 +2554,7 @@ src/app/components/transaction/transaction.component.html - 562 + 641 block.miner @@ -2745,7 +2741,7 @@ src/app/components/transaction/transaction.component.html - 339 + 251 transaction.version @@ -2846,7 +2842,7 @@ src/app/components/transaction/transaction.component.html - 74 + 491 Toggle Audit block.toggle-audit @@ -2859,15 +2855,15 @@ src/app/components/transaction/transaction.component.html - 297 + 209 src/app/components/transaction/transaction.component.html - 305 + 217 src/app/components/transaction/transaction.component.html - 465 + 342 src/app/lightning/channel/channel.component.html @@ -4812,7 +4808,7 @@ src/app/components/transaction/transaction.component.html - 355 + 267 transaction.hex @@ -4906,7 +4902,7 @@ src/app/components/transaction/transaction.component.html - 276 + 188 src/app/components/transactions-list/transactions-list.component.html @@ -5440,7 +5436,7 @@ src/app/components/transaction/transaction.component.html - 77 + 495 src/app/components/transactions-list/transactions-list.component.html @@ -5460,7 +5456,7 @@ src/app/components/transaction/transaction.component.ts - 398 + 399 @@ -5471,7 +5467,7 @@ src/app/components/transaction/transaction.component.ts - 402 + 403 @@ -5483,15 +5479,170 @@ RBF replacement transaction.rbf.replacement + + Hide accelerator + + src/app/components/transaction/transaction.component.html + 83 + + hide-accelerator + + + Type + + src/app/components/transaction/transaction.component.html + 99 + + + src/app/components/transactions-list/transactions-list.component.html + 276 + + transactions-list.vout.scriptpubkey-type + + + Descendant + + src/app/components/transaction/transaction.component.html + 110 + + + src/app/components/transaction/transaction.component.html + 122 + + Descendant + transaction.descendant + + + Ancestor + + src/app/components/transaction/transaction.component.html + 134 + + Transaction Ancestor + transaction.ancestor + + + RBF History + + src/app/components/transaction/transaction.component.html + 153 + + RBF History + transaction.rbf-history + + + Flow + + src/app/components/transaction/transaction.component.html + 162 + + + src/app/components/transaction/transaction.component.html + 282 + + Transaction flow + transaction.flow + + + Hide diagram + + src/app/components/transaction/transaction.component.html + 165 + + hide-diagram + + + Show more + + src/app/components/transaction/transaction.component.html + 186 + + + src/app/components/transactions-list/transactions-list.component.html + 171 + + + src/app/components/transactions-list/transactions-list.component.html + 289 + + show-more + + + Inputs & Outputs + + src/app/components/transaction/transaction.component.html + 204 + + + src/app/components/transaction/transaction.component.html + 313 + + Transaction inputs and outputs + transaction.inputs-and-outputs + + + Show diagram + + src/app/components/transaction/transaction.component.html + 208 + + show-diagram + + + Adjusted vsize + + src/app/components/transaction/transaction.component.html + 233 + + Transaction Adjusted VSize + transaction.adjusted-vsize + + + Locktime + + src/app/components/transaction/transaction.component.html + 255 + + transaction.locktime + + + Sigops + + src/app/components/transaction/transaction.component.html + 259 + + Transaction Sigops + transaction.sigops + + + Transaction not found. + + src/app/components/transaction/transaction.component.html + 391 + + transaction.error.transaction-not-found + + + Waiting for it to appear in the mempool... + + src/app/components/transaction/transaction.component.html + 392 + + transaction.error.waiting-for-it-to-appear + + + Error loading transaction data. + + src/app/components/transaction/transaction.component.html + 398 + + transaction.error.loading-transaction-data + Features src/app/components/transaction/transaction.component.html - 68 - - - src/app/components/transaction/transaction.component.html - 163 + 474 src/app/lightning/node/node.component.html @@ -5508,7 +5659,7 @@ This transaction was projected to be included in the block src/app/components/transaction/transaction.component.html - 78 + 497 Expected in block tooltip @@ -5516,7 +5667,7 @@ Expected in Block src/app/components/transaction/transaction.component.html - 78 + 497 Expected in Block tx-features.tag.expected @@ -5525,7 +5676,7 @@ This transaction was seen in the mempool prior to mining src/app/components/transaction/transaction.component.html - 79 + 499 Seen in mempool tooltip @@ -5533,7 +5684,7 @@ Seen in Mempool src/app/components/transaction/transaction.component.html - 79 + 499 Seen in Mempool tx-features.tag.seen @@ -5542,7 +5693,7 @@ This transaction was missing from our mempool prior to mining src/app/components/transaction/transaction.component.html - 80 + 501 Not seen in mempool tooltip @@ -5550,7 +5701,7 @@ Not seen in Mempool src/app/components/transaction/transaction.component.html - 80 + 501 Not seen in Mempool tx-features.tag.not-seen @@ -5559,7 +5710,7 @@ This transaction may have been added out-of-band src/app/components/transaction/transaction.component.html - 81 + 504 Added transaction tooltip @@ -5567,7 +5718,7 @@ This transaction may have been prioritized out-of-band src/app/components/transaction/transaction.component.html - 82 + 507 Prioritized transaction tooltip @@ -5575,23 +5726,15 @@ This transaction conflicted with another version in our mempool src/app/components/transaction/transaction.component.html - 83 + 510 Conflict in mempool tooltip - - Hide accelerator - - src/app/components/transaction/transaction.component.html - 108 - - hide-accelerator - ETA src/app/components/transaction/transaction.component.html - 136 + 526 Transaction ETA transaction.eta @@ -5600,167 +5743,16 @@ In several hours (or more) src/app/components/transaction/transaction.component.html - 144 + 532 Transaction ETA in several hours or more transaction.eta.in-several-hours - - Type - - src/app/components/transaction/transaction.component.html - 187 - - - src/app/components/transactions-list/transactions-list.component.html - 276 - - transactions-list.vout.scriptpubkey-type - - - Descendant - - src/app/components/transaction/transaction.component.html - 198 - - - src/app/components/transaction/transaction.component.html - 210 - - Descendant - transaction.descendant - - - Ancestor - - src/app/components/transaction/transaction.component.html - 222 - - Transaction Ancestor - transaction.ancestor - - - RBF History - - src/app/components/transaction/transaction.component.html - 241 - - RBF History - transaction.rbf-history - - - Flow - - src/app/components/transaction/transaction.component.html - 250 - - - src/app/components/transaction/transaction.component.html - 405 - - Transaction flow - transaction.flow - - - Hide diagram - - src/app/components/transaction/transaction.component.html - 253 - - hide-diagram - - - Show more - - src/app/components/transaction/transaction.component.html - 274 - - - src/app/components/transactions-list/transactions-list.component.html - 171 - - - src/app/components/transactions-list/transactions-list.component.html - 289 - - show-more - - - Inputs & Outputs - - src/app/components/transaction/transaction.component.html - 292 - - - src/app/components/transaction/transaction.component.html - 436 - - Transaction inputs and outputs - transaction.inputs-and-outputs - - - Show diagram - - src/app/components/transaction/transaction.component.html - 296 - - show-diagram - - - Adjusted vsize - - src/app/components/transaction/transaction.component.html - 321 - - Transaction Adjusted VSize - transaction.adjusted-vsize - - - Locktime - - src/app/components/transaction/transaction.component.html - 343 - - transaction.locktime - - - Sigops - - src/app/components/transaction/transaction.component.html - 347 - - Transaction Sigops - transaction.sigops - - - Transaction not found. - - src/app/components/transaction/transaction.component.html - 514 - - transaction.error.transaction-not-found - - - Waiting for it to appear in the mempool... - - src/app/components/transaction/transaction.component.html - 515 - - transaction.error.waiting-for-it-to-appear - - - Error loading transaction data. - - src/app/components/transaction/transaction.component.html - 521 - - transaction.error.loading-transaction-data - Accelerated fee rate src/app/components/transaction/transaction.component.html - 547 + 610 Accelerated transaction fee rate transaction.accelerated-fee-rate diff --git a/frontend/src/locale/messages.zh.xlf b/frontend/src/locale/messages.zh.xlf index 1965a4914..b8ed655bd 100644 --- a/frontend/src/locale/messages.zh.xlf +++ b/frontend/src/locale/messages.zh.xlf @@ -721,6 +721,7 @@ Sign In + 登录 src/app/components/accelerate-preview/accelerate-preview.component.html 214 @@ -1201,6 +1202,7 @@ Balances + 余额 src/app/components/address-group/address-group.component.html 4 @@ -3592,6 +3594,7 @@ halving + 减半 src/app/components/difficulty/difficulty.component.html 10 @@ -4241,6 +4244,7 @@ Dust + 粉尘 src/app/components/liquid-reserves-audit/federation-utxos-list/federation-utxos-list.component.html 15 @@ -4785,6 +4789,7 @@ Other () + 其他( src/app/components/pool-ranking/pool-ranking.component.ts 186 @@ -5057,6 +5062,7 @@ Broadcast Transaction + 广播交易 src/app/components/push-transaction/push-transaction.component.ts 33 @@ -5064,6 +5070,7 @@ Broadcast a transaction to the network using the transaction's hash. + 使用交易的哈希值将交易广播到 网络。 src/app/components/push-transaction/push-transaction.component.ts 34 @@ -5276,6 +5283,7 @@ Block Height + 区块高度 src/app/components/search-form/search-results/search-results.component.html 3 @@ -5284,6 +5292,7 @@ Transaction + 交易 src/app/components/search-form/search-results/search-results.component.html 21 @@ -5292,6 +5301,7 @@ Address + 地址 src/app/components/search-form/search-results/search-results.component.html 27 @@ -5300,6 +5310,7 @@ Block + src/app/components/search-form/search-results/search-results.component.html 33 @@ -5442,6 +5453,8 @@ Immediately + 立刻 + src/app/components/time/time.component.ts 90 @@ -5637,6 +5650,7 @@ before + 之前 src/app/components/time/time.component.ts 214 @@ -5729,6 +5743,7 @@ Get real-time status, addresses, fees, script info, and more for transaction with txid . + 获取 交易的实时状态、地址、费用、脚本信息等,交易 ID 为 src/app/components/transaction/transaction-preview.component.ts 93 @@ -5797,6 +5812,7 @@ Seen in Mempool + 在Mempool中查看 src/app/components/transaction/transaction.component.html 79 @@ -6534,6 +6550,7 @@ Coinjoin + 代币混合 src/app/dashboard/dashboard.component.ts 80 @@ -6556,6 +6573,7 @@ mempool.space merely provides data about the Bitcoin network. It cannot help you with retrieving funds, wallet issues, etc.For any such requests, you need to get in touch with the entity that helped make the transaction (wallet software, exchange company, etc). + mempool.space 仅提供有关比特币网络的数据。它无法帮助您检索资金、解决钱包问题等。对于任何此类请求,您都需要联系帮助进行交易的实体(钱包软件、交易所等)。 src/app/docs/api-docs/api-docs.component.html 15,16 @@ -6657,6 +6675,7 @@ Get answers to common questions like: What is a mempool? Why isn't my transaction confirming? How can I run my own instance of The Mempool Open Source Project? And more. + 获取常见问题的答案,例如:什么是Mempool?为什么我的交易未被确认?如何运行自己的Mempool开源项目实例?等等。 src/app/docs/docs/docs.component.ts 47 @@ -6685,6 +6704,7 @@ WebSocket API + Websocket API src/app/docs/docs/docs.component.ts 59 @@ -7764,6 +7784,7 @@ Overview for the Lightning network node named . See channels, capacity, location, fee stats, and more. + 闪电网络节点 的概览。查看通道、容量、位置、费用统计等。 src/app/lightning/node/node-preview.component.ts 52 @@ -7962,6 +7983,7 @@ 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. + 在世界地图上查看非Tor的闪电网络节点的通道。将鼠标悬停/点击地图上的点可查看节点名称和详细信息。 src/app/lightning/nodes-channels-map/nodes-channels-map.component.ts 74 @@ -7986,6 +8008,7 @@ See the locations of non-Tor Lightning network nodes visualized on a world map. Hover/tap on points on the map for node names and details. + 在世界地图上查看非 Tor的闪电网络节点的通道。将鼠标悬停/点击地图上的点可查看节点名称和详细信息。 src/app/lightning/nodes-map/nodes-map.component.ts 52 @@ -8061,6 +8084,7 @@ See a geographical breakdown of the Lightning network: how many Lightning nodes are hosted in countries around the world, aggregate BTC capacity for each country, and more. + 查看闪电网络的地理分布:世界各国托管着多少个闪电节点、每个国家的总 BTC 容量等等。 src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts 47 @@ -8131,6 +8155,7 @@ Explore all the Lightning nodes hosted in and see an overview of each node's capacity, number of open channels, and more. + 探索 中托管的所有 Lightning 节点,并查看每个节点的容量、开放通道数量等概览。 src/app/lightning/nodes-per-country/nodes-per-country.component.ts 44 @@ -8480,6 +8505,7 @@ What is a mempool? + 什么是mempool? src/app/shared/components/global-footer/global-footer.component.html 51 @@ -8488,6 +8514,7 @@ What is a block explorer? + 什么是区块浏览器? src/app/shared/components/global-footer/global-footer.component.html 52 @@ -8496,6 +8523,7 @@ What is a mempool explorer? + 什么是内存池浏览器? src/app/shared/components/global-footer/global-footer.component.html 53 @@ -8528,6 +8556,7 @@ Mainnet Explorer + 主网浏览器 src/app/shared/components/global-footer/global-footer.component.html 60 @@ -8536,6 +8565,7 @@ Testnet Explorer + 测试网浏览器 src/app/shared/components/global-footer/global-footer.component.html 61 @@ -8568,6 +8598,7 @@ Tools + 工具 src/app/shared/components/global-footer/global-footer.component.html 68 diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss index 93cdda7fe..9d246e8dd 100644 --- a/frontend/src/styles.scss +++ b/frontend/src/styles.scss @@ -108,7 +108,6 @@ html, body { body { background-color: var(--active-bg); - min-width: 375px; padding-bottom: 60px; }