Flow
diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts
index d2cc0789d..d1d3fe5d7 100644
--- a/frontend/src/app/components/transaction/transaction.component.ts
+++ b/frontend/src/app/components/transaction/transaction.component.ts
@@ -112,6 +112,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
txChanged$ = new BehaviorSubject(false); // triggered whenever this.tx changes (long term, we should refactor to make this.tx an observable itself)
isAccelerated$ = new BehaviorSubject(false); // refactor this to make isAccelerated an observable itself
ETA$: Observable;
+ standardETA$: Observable;
isCached: boolean = false;
now = Date.now();
da$: Observable;
@@ -809,6 +810,21 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
this.miningStats = stats;
this.isAccelerated$.next(this.isAcceleration); // hack to trigger recalculation of ETA without adding another source observable
});
+ if (!this.tx.status?.confirmed) {
+ this.standardETA$ = combineLatest([
+ this.stateService.mempoolBlocks$.pipe(startWith(null)),
+ this.stateService.difficultyAdjustment$.pipe(startWith(null)),
+ ]).pipe(
+ map(([mempoolBlocks, da]) => {
+ return this.etaService.calculateUnacceleratedETA(
+ this.tx,
+ mempoolBlocks,
+ da,
+ this.cpfpInfo,
+ );
+ })
+ )
+ }
}
this.isAccelerated$.next(this.isAcceleration);
}
diff --git a/frontend/src/app/services/eta.service.ts b/frontend/src/app/services/eta.service.ts
index cc1436e4c..f632c9adb 100644
--- a/frontend/src/app/services/eta.service.ts
+++ b/frontend/src/app/services/eta.service.ts
@@ -225,4 +225,58 @@ export class EtaService {
blocks: Math.ceil(eta / da.adjustedTimeAvg),
};
}
+
+ calculateUnacceleratedETA(
+ tx: Transaction,
+ mempoolBlocks: MempoolBlock[],
+ da: DifficultyAdjustment,
+ cpfpInfo: CpfpInfo | null,
+ ): ETA | null {
+ if (!tx || !mempoolBlocks) {
+ return null;
+ }
+ const now = Date.now();
+
+ // use known projected position, or fall back to feerate-based estimate
+ const mempoolPosition = this.mempoolPositionFromFees(this.getFeeRateFromCpfpInfo(tx, cpfpInfo), mempoolBlocks);
+ if (!mempoolPosition) {
+ return null;
+ }
+
+ // difficulty adjustment estimate is required to know avg block time on non-Liquid networks
+ if (!da) {
+ return null;
+ }
+
+ const blocks = mempoolPosition.block + 1;
+ const wait = da.adjustedTimeAvg * (mempoolPosition.block + 1);
+ return {
+ now,
+ time: wait + now + da.timeOffset,
+ wait,
+ blocks,
+ };
+ }
+
+
+ getFeeRateFromCpfpInfo(tx: Transaction, cpfpInfo: CpfpInfo | null): number {
+ if (!cpfpInfo) {
+ return tx.fee / (tx.weight / 4);
+ }
+
+ const relatives = [...(cpfpInfo.ancestors || []), ...(cpfpInfo.descendants || [])];
+ if (cpfpInfo.bestDescendant && !cpfpInfo.descendants?.length) {
+ relatives.push(cpfpInfo.bestDescendant);
+ }
+
+ if (!!relatives.length) {
+ const totalWeight = tx.weight + relatives.reduce((prev, val) => prev + val.weight, 0);
+ const totalFees = tx.fee + relatives.reduce((prev, val) => prev + val.fee, 0);
+
+ return totalFees / (totalWeight / 4);
+ }
+
+ return tx.fee / (tx.weight / 4);
+
+ }
}