diff --git a/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts b/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts index 951d9576c..697c68e43 100644 --- a/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts +++ b/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts @@ -3,7 +3,6 @@ import { IEsploraApi } from './esplora-api.interface'; export interface AbstractBitcoinApi { $getRawMempool(): Promise; $getRawTransaction(txId: string, skipConversion?: boolean, addPrevout?: boolean): Promise; - $getRawTransactionBitcoind(txId: string, skipConversion?: boolean, addPrevout?: boolean): Promise; $getBlockHeightTip(): Promise; $getTxIdsForBlock(hash: string): Promise; $getBlockHash(height: number): Promise; diff --git a/backend/src/api/bitcoin/bitcoin-api.ts b/backend/src/api/bitcoin/bitcoin-api.ts index 919b06092..2abebeb07 100644 --- a/backend/src/api/bitcoin/bitcoin-api.ts +++ b/backend/src/api/bitcoin/bitcoin-api.ts @@ -22,16 +22,6 @@ class BitcoinApi implements AbstractBitcoinApi { }); } - $getRawTransactionBitcoind(txId: string, skipConversion = false, addPrevout = false): Promise { - return this.bitcoindClient.getRawTransaction(txId, true) - .then((transaction: IBitcoinApi.Transaction) => { - if (skipConversion) { - return transaction; - } - return this.$convertTransaction(transaction, addPrevout); - }); - } - $getRawTransaction(txId: string, skipConversion = false, addPrevout = false): Promise { // If the transaction is in the mempool we already converted and fetched the fee. Only prevouts are missing const txInMempool = mempool.getMempool()[txId]; @@ -47,6 +37,9 @@ class BitcoinApi implements AbstractBitcoinApi { return this.bitcoindClient.getRawTransaction(txId, true) .then((transaction: IBitcoinApi.Transaction) => { if (skipConversion) { + transaction.vout.forEach((vout) => { + vout.value = vout.value * 100000000; + }); return transaction; } return this.$convertTransaction(transaction, addPrevout); diff --git a/backend/src/api/bitcoin/esplora-api.ts b/backend/src/api/bitcoin/esplora-api.ts index a98941899..5aa9f7fec 100644 --- a/backend/src/api/bitcoin/esplora-api.ts +++ b/backend/src/api/bitcoin/esplora-api.ts @@ -48,11 +48,6 @@ class ElectrsApi implements AbstractBitcoinApi { throw new Error('Method getAddressTransactions not implemented.'); } - $getRawTransactionBitcoind(txId: string): Promise { - return axios.get(config.ESPLORA.REST_API_URL + '/tx/' + txId, this.axiosConfig) - .then((response) => response.data); - } - $getAddressPrefix(prefix: string): string[] { throw new Error('Method not implemented.'); } diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index 7f9581ea9..25f92ff5c 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -84,14 +84,21 @@ class Blocks { } } + transactions.forEach((tx) => { + if (!tx.cpfpChecked) { + Common.setRelativesAndGetCpfpInfo(tx, mempool); + } + }); + logger.debug(`${transactionsFound} of ${txIds.length} found in mempool. ${txIds.length - transactionsFound} not found.`); const blockExtended: BlockExtended = Object.assign({}, block); blockExtended.reward = transactions[0].vout.reduce((acc, curr) => acc + curr.value, 0); blockExtended.coinbaseTx = transactionUtils.stripCoinbaseTransaction(transactions[0]); - transactions.sort((a, b) => b.feePerVsize - a.feePerVsize); - blockExtended.medianFee = transactions.length > 1 ? Common.median(transactions.map((tx) => tx.feePerVsize)) : 0; - blockExtended.feeRange = transactions.length > 1 ? Common.getFeesInRange(transactions.slice(0, transactions.length - 1), 8) : [0, 0]; + transactions.shift(); + transactions.sort((a, b) => b.effectiveFeePerVsize - a.effectiveFeePerVsize); + blockExtended.medianFee = transactions.length > 1 ? Common.median(transactions.map((tx) => tx.effectiveFeePerVsize)) : 0; + blockExtended.feeRange = transactions.length > 1 ? Common.getFeesInRange(transactions, 8) : [0, 0]; if (block.height % 2016 === 0) { this.lastDifficultyAdjustmentTime = block.timestamp; diff --git a/backend/src/api/common.ts b/backend/src/api/common.ts index 5ed2d4c4a..021990418 100644 --- a/backend/src/api/common.ts +++ b/backend/src/api/common.ts @@ -1,4 +1,4 @@ -import { TransactionExtended, TransactionStripped } from '../mempool.interfaces'; +import { CpfpInfo, TransactionExtended, TransactionStripped } from '../mempool.interfaces'; export class Common { static median(numbers: number[]) { @@ -20,16 +20,16 @@ export class Common { } static getFeesInRange(transactions: TransactionExtended[], rangeLength: number) { - const arr = [transactions[transactions.length - 1].feePerVsize]; + const arr = [transactions[transactions.length - 1].effectiveFeePerVsize]; const chunk = 1 / (rangeLength - 1); let itemsToAdd = rangeLength - 2; while (itemsToAdd > 0) { - arr.push(transactions[Math.floor(transactions.length * chunk * itemsToAdd)].feePerVsize); + arr.push(transactions[Math.floor(transactions.length * chunk * itemsToAdd)].effectiveFeePerVsize); itemsToAdd--; } - arr.push(transactions[0].feePerVsize); + arr.push(transactions[0].effectiveFeePerVsize); return arr; } @@ -71,4 +71,63 @@ export class Common { }, ms); }); } + + static setRelativesAndGetCpfpInfo(tx: TransactionExtended, memPool: { [txid: string]: TransactionExtended }): CpfpInfo { + const parents = this.findAllParents(tx, memPool); + + let totalWeight = tx.weight + parents.reduce((prev, val) => prev + val.weight, 0); + let totalFees = tx.fee + parents.reduce((prev, val) => prev + val.fee, 0); + + tx.ancestors = parents + .map((t) => { + return { + txid: t.txid, + weight: t.weight, + fee: t.fee, + }; + }); + + // Add high (high fee) decendant weight and fees + if (tx.bestDescendant) { + totalWeight += tx.bestDescendant.weight; + totalFees += tx.bestDescendant.fee; + } + + tx.effectiveFeePerVsize = totalFees / (totalWeight / 4); + tx.cpfpChecked = true; + + return { + ancestors: tx.ancestors, + bestDescendant: tx.bestDescendant || null, + }; + } + + + private static findAllParents(tx: TransactionExtended, memPool: { [txid: string]: TransactionExtended }): TransactionExtended[] { + let parents: TransactionExtended[] = []; + tx.vin.forEach((parent) => { + const parentTx = memPool[parent.txid]; + if (parentTx) { + if (tx.bestDescendant && tx.bestDescendant.fee / (tx.bestDescendant.weight / 4) > parentTx.feePerVsize) { + if (parentTx.bestDescendant && parentTx.bestDescendant.fee < tx.fee + tx.bestDescendant.fee) { + parentTx.bestDescendant = { + weight: tx.weight + tx.bestDescendant.weight, + fee: tx.fee + tx.bestDescendant.fee, + txid: tx.txid, + }; + } + } else if (tx.feePerVsize > parentTx.feePerVsize) { + parentTx.bestDescendant = { + weight: tx.weight, + fee: tx.fee, + txid: tx.txid + }; + } + parents.push(parentTx); + parents = parents.concat(this.findAllParents(parentTx, memPool)); + } + }); + return parents; + } + } diff --git a/backend/src/api/mempool-blocks.ts b/backend/src/api/mempool-blocks.ts index 823eeb6ab..c52c458e9 100644 --- a/backend/src/api/mempool-blocks.ts +++ b/backend/src/api/mempool-blocks.ts @@ -1,3 +1,4 @@ +import logger from '../logger'; import { MempoolBlock, TransactionExtended, MempoolBlockWithTransactions } from '../mempool.interfaces'; import { Common } from './common'; import config from '../config'; @@ -33,9 +34,40 @@ class MempoolBlocks { memPoolArray.push(latestMempool[i]); } } + const start = new Date().getTime(); + + // Clear bestDescendants & ancestors + memPoolArray.forEach((tx) => { + tx.bestDescendant = null; + tx.ancestors = []; + tx.cpfpChecked = false; + if (!tx.effectiveFeePerVsize) { + tx.effectiveFeePerVsize = tx.feePerVsize; + } + }); + + // First sort memPoolArray.sort((a, b) => b.feePerVsize - a.feePerVsize); - const transactionsSorted = memPoolArray.filter((tx) => tx.feePerVsize); - this.mempoolBlocks = this.calculateMempoolBlocks(transactionsSorted); + + // Loop through and traverse all ancestors and sum up all the sizes + fees + // Pass down size + fee to all unconfirmed children + let sizes = 0; + memPoolArray.forEach((tx, i) => { + sizes += tx.weight + if (sizes > 4000000 * 8) { + return; + } + Common.setRelativesAndGetCpfpInfo(tx, memPool); + }); + + // Final sort, by effective fee + memPoolArray.sort((a, b) => b.effectiveFeePerVsize - a.effectiveFeePerVsize); + + const end = new Date().getTime(); + const time = end - start; + logger.debug('Mempool blocks calculated in ' + time / 1000 + ' seconds'); + + this.mempoolBlocks = this.calculateMempoolBlocks(memPoolArray); } private calculateMempoolBlocks(transactionsSorted: TransactionExtended[]): MempoolBlockWithTransactions[] { @@ -77,7 +109,7 @@ class MempoolBlocks { blockVSize: blockVSize, nTx: transactions.length, totalFees: transactions.reduce((acc, cur) => acc + cur.fee, 0), - medianFee: Common.percentile(transactions.map((tx) => tx.feePerVsize), config.MEMPOOL.RECOMMENDED_FEE_PERCENTILE), + medianFee: Common.percentile(transactions.map((tx) => tx.effectiveFeePerVsize), config.MEMPOOL.RECOMMENDED_FEE_PERCENTILE), feeRange: Common.getFeesInRange(transactions, rangeLength), transactionIds: transactions.map((tx) => tx.txid), }; diff --git a/backend/src/api/statistics.ts b/backend/src/api/statistics.ts index dbac2936e..468f136b9 100644 --- a/backend/src/api/statistics.ts +++ b/backend/src/api/statistics.ts @@ -68,13 +68,13 @@ class Statistics { } } // Remove 0 and undefined - memPoolArray = memPoolArray.filter((tx) => tx.feePerVsize); + memPoolArray = memPoolArray.filter((tx) => tx.effectiveFeePerVsize); if (!memPoolArray.length) { return; } - memPoolArray.sort((a, b) => a.feePerVsize - b.feePerVsize); + memPoolArray.sort((a, b) => a.effectiveFeePerVsize - b.effectiveFeePerVsize); const totalWeight = memPoolArray.map((tx) => tx.vsize).reduce((acc, curr) => acc + curr) * 4; const totalFee = memPoolArray.map((tx) => tx.fee).reduce((acc, curr) => acc + curr); @@ -85,7 +85,7 @@ class Statistics { memPoolArray.forEach((transaction) => { for (let i = 0; i < logFees.length; i++) { - if ((logFees[i] === 2000 && transaction.feePerVsize >= 2000) || transaction.feePerVsize <= logFees[i]) { + if ((logFees[i] === 2000 && transaction.effectiveFeePerVsize >= 2000) || transaction.effectiveFeePerVsize <= logFees[i]) { if (weightVsizeFees[logFees[i]]) { weightVsizeFees[logFees[i]] += transaction.vsize; } else { diff --git a/backend/src/api/transaction-utils.ts b/backend/src/api/transaction-utils.ts index 8c6b89a6b..8c3867329 100644 --- a/backend/src/api/transaction-utils.ts +++ b/backend/src/api/transaction-utils.ts @@ -26,9 +26,11 @@ class TransactionUtils { } private extendTransaction(transaction: IEsploraApi.Transaction): TransactionExtended { + const feePerVbytes = Math.max(1, (transaction.fee || 0) / (transaction.weight / 4)); const transactionExtended: TransactionExtended = Object.assign({ vsize: Math.round(transaction.weight / 4), - feePerVsize: Math.max(1, (transaction.fee || 0) / (transaction.weight / 4)), + feePerVsize: feePerVbytes, + effectiveFeePerVsize: feePerVbytes, }, transaction); if (!transaction.status.confirmed) { transactionExtended.firstSeen = Math.round((new Date().getTime() / 1000)); diff --git a/backend/src/index.ts b/backend/src/index.ts index 2389d38b5..ff32c915b 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -73,6 +73,8 @@ class Server { this.server = http.createServer(this.app); this.wss = new WebSocket.Server({ server: this.server }); + this.setUpWebsocketHandling(); + diskCache.loadMempoolCache(); if (config.DATABASE.ENABLED) { @@ -86,7 +88,6 @@ class Server { fiatConversion.startService(); this.setUpHttpApiRoutes(); - this.setUpWebsocketHandling(); this.runMainUpdateLoop(); if (config.BISQ_BLOCKS.ENABLED) { @@ -145,6 +146,7 @@ class Server { setUpHttpApiRoutes() { this.app .get(config.MEMPOOL.API_URL_PREFIX + 'transaction-times', routes.getTransactionTimes) + .get(config.MEMPOOL.API_URL_PREFIX + 'cpfp/:txId', routes.getCpfpInfo) .get(config.MEMPOOL.API_URL_PREFIX + 'fees/recommended', routes.getRecommendedFees) .get(config.MEMPOOL.API_URL_PREFIX + 'fees/mempool-blocks', routes.getMempoolBlocks) .get(config.MEMPOOL.API_URL_PREFIX + 'backend-info', routes.getBackendInfo) diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index f0237cd10..2f080b110 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -26,6 +26,27 @@ export interface TransactionExtended extends IEsploraApi.Transaction { vsize: number; feePerVsize: number; firstSeen?: number; + effectiveFeePerVsize: number; + ancestors?: Ancestor[]; + bestDescendant?: BestDescendant | null; + cpfpChecked?: boolean; +} + +interface Ancestor { + txid: string; + weight: number; + fee: number; +} + +interface BestDescendant { + txid: string; + weight: number; + fee: number; +} + +export interface CpfpInfo { + ancestors: Ancestor[]; + bestDescendant: BestDescendant | null; } export interface TransactionStripped { diff --git a/backend/src/routes.ts b/backend/src/routes.ts index c03c08b75..0677663b3 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -94,6 +94,30 @@ class Routes { res.json(times); } + public getCpfpInfo(req: Request, res: Response) { + if (!/^[a-fA-F0-9]{64}$/.test(req.params.txId)) { + res.status(501).send(`Invalid transaction ID.`); + return; + } + + const tx = mempool.getMempool()[req.params.txId]; + if (!tx) { + res.status(404).send(`Transaction doesn't exist in the mempool.`); + return; + } + + if (tx.cpfpChecked) { + res.json({ + ancestors: tx.ancestors, + bestDescendant: tx.bestDescendant || null, + }); + } + + const cpfpInfo = Common.setRelativesAndGetCpfpInfo(tx, mempool.getMempool()); + + res.json(cpfpInfo); + } + public getBackendInfo(req: Request, res: Response) { res.json(backendInfo.getBackendInfo()); } diff --git a/frontend/src/app/components/api-docs/api-docs.component.html b/frontend/src/app/components/api-docs/api-docs.component.html index 14204e36d..583f89008 100644 --- a/frontend/src/app/components/api-docs/api-docs.component.html +++ b/frontend/src/app/components/api-docs/api-docs.component.html @@ -40,6 +40,10 @@ GET {{ network.val === '' ? '' : '/' + network.val }}/api/v1/fees/mempool-blocks Returns current mempool as projected blocks. + + GET {{ network.val === '' ? '' : '/' + network.val }}/api/v1/cpfp/:txid + Returns the ancestors and the best descendant fees for a transaction. + diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html index 48e6db4b8..7938429fa 100644 --- a/frontend/src/app/components/transaction/transaction.component.html +++ b/frontend/src/app/components/transaction/transaction.component.html @@ -73,22 +73,7 @@
- - - - - - - - - - - -
Fee{{ tx.fee | number }} sat ()
Fee per vByte - {{ tx.fee / (tx.weight / 4) | number : '1.1-1' }} sat/vB -   - -
+
@@ -146,18 +131,7 @@
- - - - - - - - - - - -
Fee{{ tx.fee | number }} sat ()
Fee per vByte{{ tx.fee / (tx.weight / 4) | number : '1.1-1' }} sat/vB
+
@@ -295,4 +269,36 @@ In ~{{ i }} minute {{ i }} block -{{ i }} blocks \ No newline at end of file +{{ i }} blocks + + + + + + + + + + + + + + + + + +
Fee{{ tx.fee | number }} sat ()
Fee per vByte + {{ tx.fee / (tx.weight / 4) | number : '1.1-1' }} sat/vB + +   + + +
Effective fee + {{ tx.effectiveFeePerVsize | number : '1.1-1' }} sat/vB + +   + + +
+ +
\ No newline at end of file diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index cbd3916fc..d0c396959 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -91,10 +91,28 @@ export class TransactionComponent implements OnInit, OnDestroy { this.getTransactionTime(); } } + if (this.tx.status.confirmed) { this.stateService.markBlock$.next({ blockHeight: tx.status.block_height }); } else { - this.stateService.markBlock$.next({ txFeePerVSize: tx.fee / (tx.weight / 4) }); + if (tx.effectiveFeePerVsize) { + this.stateService.markBlock$.next({ txFeePerVSize: tx.effectiveFeePerVsize }); + } else { + this.apiService.getCpfpinfo$(this.tx.txid) + .subscribe((cpfpInfo) => { + let totalWeight = tx.weight + cpfpInfo.ancestors.reduce((prev, val) => prev + val.weight, 0); + let totalFees = tx.fee + cpfpInfo.ancestors.reduce((prev, val) => prev + val.fee, 0); + + if (cpfpInfo.bestDescendant) { + totalWeight += cpfpInfo.bestDescendant.weight; + totalFees += cpfpInfo.bestDescendant.fee; + } + + const effectiveFeePerVsize = totalFees / (totalWeight / 4); + this.tx.effectiveFeePerVsize = effectiveFeePerVsize; + this.stateService.markBlock$.next({ txFeePerVSize: effectiveFeePerVsize }); + }); + } } }, (error) => { @@ -139,7 +157,7 @@ export class TransactionComponent implements OnInit, OnDestroy { return; } - const txFeePerVSize = this.tx.fee / (this.tx.weight / 4); + const txFeePerVSize = this.tx.effectiveFeePerVsize || this.tx.fee / (this.tx.weight / 4); for (const block of mempoolBlocks) { for (let i = 0; i < block.feeRange.length - 1; i++) { diff --git a/frontend/src/app/components/tx-fee-rating/tx-fee-rating.component.ts b/frontend/src/app/components/tx-fee-rating/tx-fee-rating.component.ts index a1a7b0b3c..2055e995f 100644 --- a/frontend/src/app/components/tx-fee-rating/tx-fee-rating.component.ts +++ b/frontend/src/app/components/tx-fee-rating/tx-fee-rating.component.ts @@ -52,7 +52,7 @@ export class TxFeeRatingComponent implements OnInit, OnChanges, OnDestroy { } calculateRatings(block: Block) { - const feePervByte = this.tx.fee / (this.tx.weight / 4); + const feePervByte = this.tx.effectiveFeePerVsize || this.tx.fee / (this.tx.weight / 4); this.medianFeeNeeded = block.medianFee; // Block not filled diff --git a/frontend/src/app/interfaces/electrs.interface.ts b/frontend/src/app/interfaces/electrs.interface.ts index b53c01ef9..1ea0184ae 100644 --- a/frontend/src/app/interfaces/electrs.interface.ts +++ b/frontend/src/app/interfaces/electrs.interface.ts @@ -11,6 +11,7 @@ export interface Transaction { // Custom properties firstSeen?: number; + effectiveFeePerVsize?: number; } export interface Recent { diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index 56b7c90c9..24e6bf35d 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -8,3 +8,20 @@ export interface OptimizedMempoolStats { mempool_byte_weight: number; vsizes: number[] | string[]; } + +interface Ancestor { + txid: string; + weight: number; + fee: number; +} + +interface BestDescendant { + txid: string; + weight: number; + fee: number; +} + +export interface CpfpInfo { + ancestors: Ancestor[]; + bestDescendant: BestDescendant | null; +} diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts index 21a1aa8b3..32ff8b092 100644 --- a/frontend/src/app/services/api.service.ts +++ b/frontend/src/app/services/api.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { HttpClient, HttpParams } from '@angular/common/http'; -import { OptimizedMempoolStats } from '../interfaces/node-api.interface'; +import { CpfpInfo, OptimizedMempoolStats } from '../interfaces/node-api.interface'; import { Observable } from 'rxjs'; import { StateService } from './state.service'; import { WebsocketResponse } from '../interfaces/websocket.interface'; @@ -88,4 +88,8 @@ export class ApiService { getInitData$(): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/init-data'); } + + getCpfpinfo$(txid: string): Observable { + return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/cpfp/' + txid); + } } diff --git a/frontend/src/locale/messages.xlf b/frontend/src/locale/messages.xlf index 6bdbd8d9d..cc70645cc 100644 --- a/frontend/src/locale/messages.xlf +++ b/frontend/src/locale/messages.xlf @@ -97,7 +97,7 @@ Inputs & Outputs src/app/components/transaction/transaction.component.html - 168 + 142 src/app/bisq/bisq-transaction/bisq-transaction.component.html @@ -114,7 +114,7 @@ Details src/app/components/transaction/transaction.component.html - 170 + 144 Transaction Details transaction.details @@ -123,11 +123,11 @@ Details src/app/components/transaction/transaction.component.html - 176 + 150 src/app/components/transaction/transaction.component.html - 252 + 226 src/app/bisq/bisq-transaction/bisq-transaction.component.html @@ -143,7 +143,7 @@ Size src/app/components/transaction/transaction.component.html - 181 + 155 Transaction Size transaction.size @@ -152,7 +152,7 @@ Virtual size src/app/components/transaction/transaction.component.html - 185 + 159 Transaction Virtual Size transaction.vsize @@ -161,7 +161,7 @@ Weight src/app/components/transaction/transaction.component.html - 189 + 163 Transaction Weight transaction.weight @@ -187,15 +187,156 @@ Transaction Timestamp transaction.timestamp + + Included in block + + src/app/components/transaction/transaction.component.html + 55 + + + src/app/bisq/bisq-transaction/bisq-transaction.component.html + 36 + + Transaction included in block + transaction.included-in-block + + + Confirmed + + src/app/components/transaction/transaction.component.html + 62 + + Transaction Confirmed state + transaction.confirmed + + + After + + src/app/components/transaction/transaction.component.html + 63 + + Transaction confirmed after + transaction.confirmed.after + + + Features + + src/app/components/transaction/transaction.component.html + 67 + + + src/app/components/transaction/transaction.component.html + 125 + + + src/app/bisq/bisq-transaction/bisq-transaction.component.html + 42 + + Transaction features + transaction.features + + + ETA + + src/app/components/transaction/transaction.component.html + 104 + + Transaction ETA + transaction.eta + + + First seen + + src/app/components/transaction/transaction.component.html + 98 + + Transaction first seen + transaction.first-seen + + + In several hours (or more) + + src/app/components/transaction/transaction.component.html + 111 + + Transaction ETA in several hours or more + transaction.eta.in-several-hours + + + Transaction not found. + + src/app/components/transaction/transaction.component.html + 251 + + transaction.error.transaction-not-found + + + Waiting for it to appear in the mempool... + + src/app/components/transaction/transaction.component.html + 252 + + transaction.error.waiting-for-it-to-appear + + + In ~ minutes + + src/app/components/transaction/transaction.component.html + 267 + + + src/app/components/mempool-blocks/mempool-blocks.component.html + 41 + + Block Frequency (plural) + mempool-blocks.eta-of-next-block-plural + + + In ~ minute + + src/app/components/transaction/transaction.component.html + 269 + + + src/app/components/mempool-blocks/mempool-blocks.component.html + 43 + + Block Frequency + mempool-blocks.eta-of-next-block + + + block + + src/app/components/transaction/transaction.component.html + 271 + + + src/app/components/footer/footer.component.html + 22 + + shared.block + + + blocks + + src/app/components/transaction/transaction.component.html + 272 + + + src/app/components/mempool-blocks/mempool-blocks.component.html + 30 + + + src/app/components/footer/footer.component.html + 23 + + shared.blocks + Fee src/app/components/transaction/transaction.component.html - 79 - - - src/app/components/transaction/transaction.component.html - 152 + 278 Transaction fee transaction.fee @@ -204,11 +345,7 @@ sat src/app/components/transaction/transaction.component.html - 80 - - - src/app/components/transaction/transaction.component.html - 153 + 279 Transaction Fee sat transaction.fee.sat @@ -217,11 +354,7 @@ Fee per vByte src/app/components/transaction/transaction.component.html - 83 - - - src/app/components/transaction/transaction.component.html - 156 + 282 src/app/bisq/bisq-transaction/bisq-transaction.component.html @@ -234,11 +367,11 @@ sat/vB src/app/components/transaction/transaction.component.html - 85 + 284 src/app/components/transaction/transaction.component.html - 157 + 294 src/app/components/transactions-list/transactions-list.component.html @@ -295,150 +428,14 @@ sat/vB shared.sat-vbyte - - Included in block + + Effective fee src/app/components/transaction/transaction.component.html - 55 + 292 - - src/app/bisq/bisq-transaction/bisq-transaction.component.html - 36 - - Transaction included in block - transaction.included-in-block - - - Confirmed - - src/app/components/transaction/transaction.component.html - 62 - - Transaction Confirmed state - transaction.confirmed - - - After - - src/app/components/transaction/transaction.component.html - 63 - - Transaction confirmed after - transaction.confirmed.after - - - Features - - src/app/components/transaction/transaction.component.html - 67 - - - src/app/components/transaction/transaction.component.html - 140 - - - src/app/bisq/bisq-transaction/bisq-transaction.component.html - 42 - - Transaction features - transaction.features - - - ETA - - src/app/components/transaction/transaction.component.html - 119 - - Transaction ETA - transaction.eta - - - First seen - - src/app/components/transaction/transaction.component.html - 113 - - Transaction first seen - transaction.first-seen - - - In several hours (or more) - - src/app/components/transaction/transaction.component.html - 126 - - Transaction ETA in several hours or more - transaction.eta.in-several-hours - - - Transaction not found. - - src/app/components/transaction/transaction.component.html - 277 - - transaction.error.transaction-not-found - - - Waiting for it to appear in the mempool... - - src/app/components/transaction/transaction.component.html - 278 - - transaction.error.waiting-for-it-to-appear - - - In ~ minutes - - src/app/components/transaction/transaction.component.html - 293 - - - src/app/components/mempool-blocks/mempool-blocks.component.html - 41 - - Block Frequency (plural) - mempool-blocks.eta-of-next-block-plural - - - In ~ minute - - src/app/components/transaction/transaction.component.html - 295 - - - src/app/components/mempool-blocks/mempool-blocks.component.html - 43 - - Block Frequency - mempool-blocks.eta-of-next-block - - - block - - src/app/components/transaction/transaction.component.html - 297 - - - src/app/components/footer/footer.component.html - 22 - - shared.block - - - blocks - - src/app/components/transaction/transaction.component.html - 298 - - - src/app/components/mempool-blocks/mempool-blocks.component.html - 30 - - - src/app/components/footer/footer.component.html - 23 - - shared.blocks + Effective transaction fee + transaction.effective-fee Coinbase @@ -929,7 +926,7 @@ src/app/components/api-docs/api-docs.component.html - 75 + 79 @@ -1060,7 +1057,7 @@ src/app/components/about/about.component.ts - 38 + 40 master-page.about @@ -1199,15 +1196,23 @@ Community Alliances src/app/components/about/about.component.html - 255 + 276 about.alliances + + Project Contributors + + src/app/components/about/about.component.html + 292 + + about.contributors + Project Maintainers src/app/components/about/about.component.html - 271 + 310 about.maintainers @@ -1215,7 +1220,7 @@ Terms of Service src/app/components/about/about.component.html - 295 + 357 src/app/dashboard/dashboard.component.html @@ -1223,7 +1228,7 @@ src/app/components/api-docs/api-docs.component.html - 284 + 288 Terms of Service shared.terms-of-service @@ -1847,27 +1852,27 @@ src/app/components/api-docs/api-docs.component.html - 54 + 58 src/app/components/api-docs/api-docs.component.html - 80 + 84 src/app/components/api-docs/api-docs.component.html - 134 + 138 src/app/components/api-docs/api-docs.component.html - 184 + 188 src/app/components/api-docs/api-docs.component.html - 218 + 222 src/app/components/api-docs/api-docs.component.html - 243 + 247 API Docs Endpoint api-docs.shared.endpoint @@ -1884,27 +1889,27 @@ src/app/components/api-docs/api-docs.component.html - 55 + 59 src/app/components/api-docs/api-docs.component.html - 81 + 85 src/app/components/api-docs/api-docs.component.html - 135 + 139 src/app/components/api-docs/api-docs.component.html - 185 + 189 src/app/components/api-docs/api-docs.component.html - 219 + 223 src/app/components/api-docs/api-docs.component.html - 244 + 248 API Docs Description api-docs.shared.description @@ -1944,11 +1949,20 @@ API Docs for /api/v1/fees/mempool-blocks api-docs.fees.mempool-blocks + + Returns the ancestors and the best descendant fees for a transaction. + + src/app/components/api-docs/api-docs.component.html + 45 + + API Docs for /api/v1/fees/cpfp + api-docs.fees.cpfp + Mempool src/app/components/api-docs/api-docs.component.html - 49 + 53 API Docs tab for Mempool api-docs.tab.mempool @@ -1957,7 +1971,7 @@ Returns current mempool backlog statistics. src/app/components/api-docs/api-docs.component.html - 59 + 63 API Docs for /api/mempool api-docs.mempool.mempool @@ -1966,7 +1980,7 @@ Get the full list of txids in the mempool as an array. The order of the txids is arbitrary and does not match bitcoind. src/app/components/api-docs/api-docs.component.html - 63 + 67 API Docs for /api/mempool/txids api-docs.mempool.txids @@ -1975,7 +1989,7 @@ Get a list of the last 10 transactions to enter the mempool. Each transaction object contains simplified overview data, with the following fields: txid, fee, vsize, and value. src/app/components/api-docs/api-docs.component.html - 67 + 71 API Docs for /api/mempool/recent api-docs.mempool.recent @@ -1984,77 +1998,77 @@ Returns the confirmation status of a block. Available fields: in_best_chain (boolean, false for orphaned blocks), next_best (the hash of the next block, only available for blocks in the best chain). src/app/components/api-docs/api-docs.component.html - 89 + 93 Returns a list of transactions in the block (up to 25 transactions beginning at start_index). Transactions returned here do not have the status field, since all the transactions share the same block and confirmation status. src/app/components/api-docs/api-docs.component.html - 93 + 97 Returns a list of all txids in the block. src/app/components/api-docs/api-docs.component.html - 97 + 101 Returns the transaction at index :index within the specified block. src/app/components/api-docs/api-docs.component.html - 101 + 105 Returns the raw block representation in binary. src/app/components/api-docs/api-docs.component.html - 105 + 109 Returns the hash of the block currently at :height. src/app/components/api-docs/api-docs.component.html - 109 + 113 Returns the 10 newest blocks starting at the tip or at :start_height if specified. src/app/components/api-docs/api-docs.component.html - 113 + 117 Returns the height of the last block. src/app/components/api-docs/api-docs.component.html - 117 + 121 Returns the hash of the last block. src/app/components/api-docs/api-docs.component.html - 121 + 125 Returns details about a block. Available fields: id, height, version, timestamp, bits, nonce, merkle_root, tx_count, size, weight,proof, and previousblockhash. src/app/components/api-docs/api-docs.component.html - 85 + 89 Transactions src/app/components/api-docs/api-docs.component.html - 129 + 133 API Docs tab for Transactions api-docs.tab.transactions @@ -2063,70 +2077,70 @@ Returns details about a transaction. Available fields: txid, version, locktime, size, weight, fee, vin, vout, and status. src/app/components/api-docs/api-docs.component.html - 139 + 143 Returns the confirmation status of a transaction. Available fields: confirmed (boolean), block_height (optional), and block_hash (optional). src/app/components/api-docs/api-docs.component.html - 143 + 147 Returns a transaction serialized as hex. src/app/components/api-docs/api-docs.component.html - 147 + 151 Returns a transaction as binary data. src/app/components/api-docs/api-docs.component.html - 151 + 155 Returns a merkle inclusion proof for the transaction using Electrum's blockchain.transaction.get_merkle format. src/app/components/api-docs/api-docs.component.html - 159 + 163 Returns the spending status of a transaction output. Available fields: spent (boolean), txid (optional), vin (optional), and status (optional, the status of the spending tx). src/app/components/api-docs/api-docs.component.html - 163 + 167 Returns the spending status of all transaction outputs. src/app/components/api-docs/api-docs.component.html - 167 + 171 Broadcast a raw transaction to the network. The transaction should be provided as hex in the request body. The txid will be returned on success. src/app/components/api-docs/api-docs.component.html - 171 + 175 Returns a merkle inclusion proof for the transaction using bitcoind's merkleblock format. src/app/components/api-docs/api-docs.component.html - 155 + 159 Addresses src/app/components/api-docs/api-docs.component.html - 179 + 183 API Docs tab for Addresses api-docs.tab.addresses @@ -2135,42 +2149,42 @@ Returns details about an address. Available fields: address, chain_stats, and mempool_stats. chain,mempool_stats each contain an object with tx_count, funded_txo_count, funded_txo_sum, spent_txo_count, and spent_txo_sum. src/app/components/api-docs/api-docs.component.html - 189 + 193 Get transaction history for the specified address/scripthash, sorted with newest first. Returns up to 50 mempool transactions plus the first 25 confirmed transactions. You can request more confirmed transactions using :last_seen_txid (see below). src/app/components/api-docs/api-docs.component.html - 193,194 + 197,198 Get confirmed transaction history for the specified address/scripthash, sorted with newest first. Returns 25 transactions per page. More can be requested by specifying the last txid seen by the previous query. src/app/components/api-docs/api-docs.component.html - 197 + 201 Get unconfirmed transaction history for the specified address/scripthash. Returns up to 50 transactions (no paging). src/app/components/api-docs/api-docs.component.html - 201 + 205 Get the list of unspent transaction outputs associated with the address/scripthash. Available fields: txid, vout, value, and status (with the status of the funding tx).There is also a valuecommitment field that may appear in place of value, plus the following additional fields: asset/assetcommitment, nonce/noncecommitment, surjection_proof, and range_proof. src/app/components/api-docs/api-docs.component.html - 205 + 209 Assets src/app/components/api-docs/api-docs.component.html - 213 + 217 API Docs tab for Assets api-docs.tab.assets @@ -2179,28 +2193,28 @@ Returns information about a Liquid asset. src/app/components/api-docs/api-docs.component.html - 223 + 227 Returns transactions associated with the specified Liquid asset. For the network's native asset, returns a list of peg in, peg out, and burn transactions. For user-issued assets, returns a list of issuance, reissuance, and burn transactions. Does not include regular transactions transferring this asset. src/app/components/api-docs/api-docs.component.html - 227 + 231 Get the current total supply of the specified asset. For the native asset (L-BTC), this is calculated as [chain,mempool]_stats.peg_in_amount - [chain,mempool]_stats.peg_out_amount - [chain,mempool]_stats.burned_amount. For issued assets, this is calculated as [chain,mempool]_stats.issued_amount - [chain,mempool]_stats.burned_amount. Not available for assets with blinded issuances. If /decimal is specified, returns the supply as a decimal according to the asset's divisibility. Otherwise, returned in base units. src/app/components/api-docs/api-docs.component.html - 231 + 235 BSQ src/app/components/api-docs/api-docs.component.html - 238 + 242 API Docs tab for BSQ api-docs.tab.bsq @@ -2209,49 +2223,49 @@ Returns statistics about all Bisq transactions. src/app/components/api-docs/api-docs.component.html - 248 + 252 Returns details about a Bisq transaction. src/app/components/api-docs/api-docs.component.html - 252 + 256 Returns :length of latest Bisq transactions, starting from :index. src/app/components/api-docs/api-docs.component.html - 256 + 260 Returns all Bisq transactions that exist in a Bitcoin block. src/app/components/api-docs/api-docs.component.html - 260 + 264 Returns :length Bitcoin blocks that contain Bisq transactions, starting from :index. src/app/components/api-docs/api-docs.component.html - 264 + 268 Returns the most recently processed Bitcoin block height processed by Bisq. src/app/components/api-docs/api-docs.component.html - 268 + 272 Returns all Bisq transactions belonging to a Bitcoin address, with 'B' prefixed in front of the address. src/app/components/api-docs/api-docs.component.html - 272 + 276