Flow
diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts
index d2cc0789d..ee0980e7c 100644
--- a/frontend/src/app/components/transaction/transaction.component.ts
+++ b/frontend/src/app/components/transaction/transaction.component.ts
@@ -65,6 +65,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
txId: string;
txInBlockIndex: number;
mempoolPosition: MempoolPosition;
+ gotInitialPosition = false;
accelerationPositions: AccelerationPosition[];
isLoadingTx = true;
error: any = undefined;
@@ -112,6 +113,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;
@@ -431,9 +433,13 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
if (txPosition.position?.block > 0 && this.tx.weight < 4000) {
this.cashappEligible = true;
}
+ if (!this.gotInitialPosition && txPosition.position?.block === 0 && txPosition.position?.vsize < 750_000) {
+ this.accelerationFlowCompleted = true;
+ }
}
}
}
+ this.gotInitialPosition = true;
} else {
this.mempoolPosition = null;
this.accelerationPositions = null;
@@ -809,6 +815,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);
}
@@ -864,6 +885,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
resetTransaction() {
this.firstLoad = false;
+ this.gotInitialPosition = false;
this.error = undefined;
this.tx = null;
this.txChanged$.next(true);
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);
+
+ }
}