mirror of
https://github.com/mempool/mempool.git
synced 2025-02-24 22:58:30 +01:00
[tx] integrated accelerator
This commit is contained in:
parent
f9895a492c
commit
fcecbe4967
10 changed files with 519 additions and 54 deletions
|
@ -0,0 +1,231 @@
|
|||
<div class="row" *ngIf="showSuccess">
|
||||
<div class="col" id="successAlert">
|
||||
<div class="alert alert-success">
|
||||
Transaction has now been submitted to mining pools for acceleration. You can track the progress <a class="alert-link" routerLink="/services/accelerator/history">here</a>.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" *ngIf="error">
|
||||
<div class="col" id="mempoolError">
|
||||
<app-mempool-error [error]="error"></app-mempool-error>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" *ngIf="!error && estimate?.txSummary?.ancestorCount ?? 0 > 1">
|
||||
<div class="col">
|
||||
<div class="alert alert-mempool">
|
||||
<small>This transactions is part of a CPFP tree. Fee rates (in sats/vb) are provided for your information. Change in the CPFP tree will lead to different fee rates values.</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="estimate">
|
||||
<div [class]="{estimateDisabled: error}">
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
<table class="table table-borderless table-border table-dark">
|
||||
<tbody>
|
||||
<!-- NEXT BLOCK TX FEE -->
|
||||
<tr>
|
||||
<td style="width: 45%" class="pb-0">
|
||||
Next block market price
|
||||
</td>
|
||||
<td class="pb-0 text-right" style="font-size: 20px">
|
||||
{{ estimate.targetFeeRate | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width: 45%" class="pt-0 pb-0 text-muted">
|
||||
<i><small>Currently estimated fee to get into next block</small></i>
|
||||
</td>
|
||||
<td class="pt-0 pb-0 text-right">
|
||||
<span>
|
||||
{{ estimate.nextBlockFee| number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
||||
<span class="fiat"><app-fiat [value]="estimate.nextBlockFee"></app-fiat></span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- CURRENT TX FEE -->
|
||||
<tr>
|
||||
<td style="width: 45%" class="pb-0">
|
||||
Fees paid in-band
|
||||
</td>
|
||||
<td class="pb-0 text-right" style="font-size: 20px">
|
||||
<small>~</small>{{ (estimate.txSummary.effectiveFee / estimate.txSummary.effectiveVsize) | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width: 45%" class="pt-0 text-muted">
|
||||
<i><small>What you already paid when you made the transaction</small></i>
|
||||
</td>
|
||||
<td class="pt-0 text-right">
|
||||
<span>
|
||||
{{ estimate.txSummary.effectiveFee | number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
||||
<span class="fiat"><app-fiat [value]="estimate.txSummary.effectiveFee"></app-fiat></span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- MIN EXTRA FEE FOR NEXT BLOCK -->
|
||||
<tr style="border-top: 1px solid lightgrey">
|
||||
<td style="width: 45%" class="pb-0">
|
||||
Extra fee required
|
||||
</td>
|
||||
<td class="pb-1 text-right">
|
||||
{{ math.max(0, estimate.nextBlockFee - estimate.txSummary.effectiveFee) | number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
||||
<span class="fiat"><app-fiat [value]="math.max(0, estimate.nextBlockFee - estimate.txSummary.effectiveFee)"></app-fiat></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width: 45%" class="pt-0 text-muted">
|
||||
<i><small>Difference between the next block fee and your tx fee</small></i>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6>How much more are you willing to pay at most to get into the next block?</h6>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<small class="form-text text-muted mb-2">
|
||||
The maximum extra transaction fee you're willing to pay to get into the next block. If the next block market price becomes too expensive for you, we will automatically cancel your acceleration request. Final charged fee may be smaller based on the fee market.
|
||||
</small>
|
||||
<div class="form-group">
|
||||
<div class="fee-card">
|
||||
<div class="d-flex mb-2">
|
||||
<button type="button" class="btn btn-primary flex-grow-1 btn-border btn-sm feerate" [class]="{active: selectFeeRateIndex === 1}" (click)="setUserBid(2, 1)">{{ (estimate.txSummary.effectiveFee + minExtraCost * 2) / estimate.txSummary.effectiveVsize | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></button>
|
||||
<button type="button" class="btn btn-primary flex-grow-1 btn-border btn-sm feerate" [class]="{active: selectFeeRateIndex === 2}" (click)="setUserBid(5, 2)">{{ (estimate.txSummary.effectiveFee + minExtraCost * 5) / estimate.txSummary.effectiveVsize | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></button>
|
||||
<button type="button" class="btn btn-primary flex-grow-1 btn-border btn-sm feerate" [class]="{active: selectFeeRateIndex === 3}" (click)="setUserBid(10, 3)">{{ (estimate.txSummary.effectiveFee + minExtraCost * 10) / estimate.txSummary.effectiveVsize | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></button>
|
||||
<button type="button" class="btn btn-primary flex-grow-1 btn-border btn-sm feerate" [class]="{active: selectFeeRateIndex === 4}" (click)="setUserBid(20, 4)">{{ (estimate.txSummary.effectiveFee + minExtraCost * 20) / estimate.txSummary.effectiveVsize | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6>Acceleration summary</h6>
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
<table class="table table-borderless table-border table-dark">
|
||||
<tbody>
|
||||
<!-- USER MAX BID -->
|
||||
<tr>
|
||||
<td style="width: 45%" class="pb-0">
|
||||
Your maximum tx fees
|
||||
</td>
|
||||
<td class="pb-0 text-right" style="font-size: 20px">
|
||||
~{{ ((estimate.txSummary.effectiveFee + userBid) / estimate.txSummary.effectiveVsize) | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width: 45%" class="pt-0 pb-0 text-muted">
|
||||
<i><small>The maximum extra transaction fee you're willing to pay</small></i>
|
||||
</td>
|
||||
<td class="pt-0 pb-0 text-right">
|
||||
<span>
|
||||
{{ userBid | number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
||||
<span class="fiat"><app-fiat [value]="userBid"></app-fiat></span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- MEMPOOL BASE FEE -->
|
||||
<tr>
|
||||
<td style="width: 45%" class="pb-0">
|
||||
Mempool Accelerator™ fee
|
||||
</td>
|
||||
<td class="pb-0 text-right">
|
||||
+{{ estimate.mempoolBaseFee + estimate.vsizeFee | number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
||||
<span class="fiat"><app-fiat [value]="estimate.mempoolBaseFee + estimate.vsizeFee"></app-fiat></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width: 45%" class="pt-0 pb-0 text-muted">
|
||||
<i><small>mempool.space fee</small></i>
|
||||
</td>
|
||||
<td class="pt-0 pb-0 text-right text-muted">
|
||||
<small>
|
||||
{{ estimate.mempoolBaseFee | number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
||||
<span class="fiat"><app-fiat [value]="estimate.mempoolBaseFee" colorClass="text-success"></app-fiat></span>
|
||||
</small>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width: 45%" class="pt-0 text-muted">
|
||||
<i><small>Transaction vsize fee</small></i>
|
||||
</td>
|
||||
<td class="pt-0 pb-0 text-right text-muted">
|
||||
<small>
|
||||
{{ estimate.vsizeFee | number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
||||
<span class="fiat"><app-fiat [value]="estimate.vsizeFee" colorClass="text-success"></app-fiat></span>
|
||||
</small>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- NEXT BLOCK ESTIMATE -->
|
||||
<tr style="border-top: 1px solid lightgrey">
|
||||
<td style="width: 45%" class="pb-0 pt-3">
|
||||
<b style="background-color: #5E35B1" class="p-1 pl-0">Estimated acceleration cost</b>
|
||||
</td>
|
||||
<td class="pb-0 pt-3 text-right">
|
||||
<span style="background-color: #5E35B1" class="p-1 pl-0">
|
||||
{{ estimate.cost + estimate.mempoolBaseFee + estimate.vsizeFee | number }} <span class="symbol" i18n="shared.sats|sats">satss</span>
|
||||
<span class="fiat"><app-fiat [value]="estimate.cost + estimate.mempoolBaseFee + estimate.vsizeFee"></app-fiat></span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width: 45%" class="pt-0 pb-0">
|
||||
<i><small class="text-muted">Cost if your tx is accelerated using </small><small>{{ estimate.targetFeeRate | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></small></i>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- MAX COST -->
|
||||
<tr>
|
||||
<td style="width: 45%;" class="pt-2 pb-0">
|
||||
Maximum acceleration cost
|
||||
</td>
|
||||
<td class="pt-2 pb-0 text-right">
|
||||
{{ maxCost | number }} <span class="symbol" i18n="shared.sats|sats">satss</span>
|
||||
<span class="fiat">
|
||||
<app-fiat [value]="maxCost" [colorClass]="estimate.userBalance < maxCost ? 'red-color' : 'green-color'"></app-fiat>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width: 45%" class="pt-0 pb-3">
|
||||
<i><small class="text-muted">Cost if your tx is accelerated using </small><small>~{{ ((estimate.txSummary.effectiveFee + userBid) / estimate.txSummary.effectiveVsize) | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></small></i>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- USER BALANCE -->
|
||||
<tr style="border-top: 1px dashed grey">
|
||||
<td style="width: 45%" class="pt-2">
|
||||
Available balance
|
||||
</td>
|
||||
<td class="pt-2 text-right">
|
||||
{{ estimate.userBalance | number }} <span class="symbol" i18n="shared.sats|sats">satss</span>
|
||||
<span class="fiat">
|
||||
<app-fiat [value]="estimate.userBalance" [colorClass]="estimate.userBalance < maxCost ? 'red-color' : 'green-color'"></app-fiat>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
<div class="d-flex justify-content-end">
|
||||
<button class="btn btn-sm btn-primary btn-success" style="width: 150px" (click)="accelerate()">Accelerate</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</ng-container>
|
|
@ -0,0 +1,20 @@
|
|||
.fee-card {
|
||||
padding: 15px;
|
||||
background-color: #1d1f31;
|
||||
}
|
||||
|
||||
.btn-border {
|
||||
border: solid 1px black;
|
||||
background-color: #0c4a87;
|
||||
}
|
||||
|
||||
.feerate.active {
|
||||
background-color: #105fb0 !important;
|
||||
opacity: 1;
|
||||
border: 1px solid white !important;
|
||||
}
|
||||
|
||||
.estimateDisabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
|
@ -1,24 +1,169 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChanges } from '@angular/core';
|
||||
import { ApiService } from '../../services/api.service';
|
||||
import { Subscription, catchError, of, tap } from 'rxjs';
|
||||
|
||||
export type AccelerationEstimate = {
|
||||
txSummary: TxSummary;
|
||||
nextBlockFee: number;
|
||||
targetFeeRate: number;
|
||||
userBalance: number;
|
||||
enoughBalance: boolean;
|
||||
cost: number;
|
||||
mempoolBaseFee: number;
|
||||
vsizeFee: number;
|
||||
}
|
||||
export type TxSummary = {
|
||||
txid: string; // txid of the current transaction
|
||||
effectiveVsize: number; // Total vsize of the dependency tree
|
||||
effectiveFee: number; // Total fee of the dependency tree in sats
|
||||
ancestorCount: number; // Number of ancestors
|
||||
}
|
||||
|
||||
export const DEFAULT_BID_RATIO = 5;
|
||||
export const MIN_BID_RATIO = 2;
|
||||
export const MAX_BID_RATIO = 20;
|
||||
|
||||
@Component({
|
||||
selector: 'app-accelerator-preview',
|
||||
selector: 'app-accelerate-preview',
|
||||
templateUrl: 'accelerate-preview.component.html',
|
||||
styleUrls: ['accelerate-preview.component.scss']
|
||||
})
|
||||
export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges {
|
||||
@Input() txid: string | undefined;
|
||||
@Input() scrollEvent: boolean;
|
||||
|
||||
math = Math;
|
||||
error = '';
|
||||
showSuccess = false;
|
||||
estimateSubscription: Subscription;
|
||||
accelerationSubscription: Subscription;
|
||||
estimate: any;
|
||||
minExtraCost = 0;
|
||||
minBidAllowed = 0;
|
||||
maxBidAllowed = 0;
|
||||
defaultBid = 0;
|
||||
maxCost = 0;
|
||||
userBid = 0;
|
||||
selectFeeRateIndex = 2;
|
||||
|
||||
export class AcceleratePreviewComponent implements OnInit {
|
||||
constructor(
|
||||
private apiService: ApiService
|
||||
) { }
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.estimateSubscription) {
|
||||
this.estimateSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes.scrollEvent) {
|
||||
this.scrollToPreview('acceleratePreviewAnchor', 'center');
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.apiService.estimate$(this.txId).subscribe((estimate) => {
|
||||
console.log(estimate.body);
|
||||
document.getElementById('acceleratePreviewAnchor').scrollIntoView({
|
||||
this.estimateSubscription = this.apiService.estimate$(this.txid).pipe(
|
||||
tap((response) => {
|
||||
if (response.status === 204) {
|
||||
this.estimate = undefined;
|
||||
this.error = `cannot_accelerate_tx`;
|
||||
this.scrollToPreviewWithTimeout('mempoolError', 'center');
|
||||
this.estimateSubscription.unsubscribe();
|
||||
} else {
|
||||
this.estimate = response.body;
|
||||
if (!this.estimate) {
|
||||
this.error = `cannot_accelerate_tx`;
|
||||
this.scrollToPreviewWithTimeout('mempoolError', 'center');
|
||||
this.estimateSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
if (this.estimate.userBalance <= 0) {
|
||||
this.error = `not_enough_balance`;
|
||||
this.scrollToPreviewWithTimeout('mempoolError', 'center');
|
||||
}
|
||||
|
||||
// Make min extra fee at least 50% of the current tx fee
|
||||
this.minExtraCost = Math.max(this.estimate.cost, this.estimate.txSummary.effectiveFee / 2);
|
||||
this.minExtraCost = Math.round(this.minExtraCost);
|
||||
|
||||
this.minBidAllowed = this.minExtraCost * MIN_BID_RATIO;
|
||||
this.maxBidAllowed = this.minExtraCost * MAX_BID_RATIO;
|
||||
this.defaultBid = this.minExtraCost * DEFAULT_BID_RATIO;
|
||||
|
||||
this.userBid = this.defaultBid;
|
||||
if (this.userBid < this.minBidAllowed) {
|
||||
this.userBid = this.minBidAllowed;
|
||||
} else if (this.userBid > this.maxBidAllowed) {
|
||||
this.userBid = this.maxBidAllowed;
|
||||
}
|
||||
this.maxCost = this.userBid + this.estimate.mempoolBaseFee + this.estimate.vsizeFee;
|
||||
|
||||
if (!this.error) {
|
||||
this.scrollToPreview('acceleratePreviewAnchor', 'center');
|
||||
}
|
||||
}
|
||||
}),
|
||||
catchError((response) => {
|
||||
this.estimate = undefined;
|
||||
this.error = response.error;
|
||||
this.scrollToPreviewWithTimeout('mempoolError', 'center');
|
||||
this.estimateSubscription.unsubscribe();
|
||||
return of(null);
|
||||
})
|
||||
).subscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* User changed his bid
|
||||
*/
|
||||
setUserBid(multiplier: number, index: number) {
|
||||
if (this.estimate) {
|
||||
this.selectFeeRateIndex = index;
|
||||
this.userBid = Math.max(0, this.minExtraCost * multiplier);
|
||||
this.maxCost = this.userBid + this.estimate.mempoolBaseFee + this.estimate.vsizeFee;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll to element id with or without setTimeout
|
||||
*/
|
||||
scrollToPreviewWithTimeout(id: string, position: ScrollLogicalPosition) {
|
||||
setTimeout(() => {
|
||||
this.scrollToPreview(id, position);
|
||||
}, 100);
|
||||
}
|
||||
scrollToPreview(id: string, position: ScrollLogicalPosition) {
|
||||
const acceleratePreviewAnchor = document.getElementById(id);
|
||||
if (acceleratePreviewAnchor) {
|
||||
acceleratePreviewAnchor.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
inline: 'center',
|
||||
block: 'center',
|
||||
inline: position,
|
||||
block: position,
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send acceleration request
|
||||
*/
|
||||
accelerate() {
|
||||
if (this.accelerationSubscription) {
|
||||
this.accelerationSubscription.unsubscribe();
|
||||
}
|
||||
this.accelerationSubscription = this.apiService.accelerate$(
|
||||
this.txid,
|
||||
this.userBid
|
||||
).subscribe({
|
||||
next: () => {
|
||||
this.showSuccess = true;
|
||||
this.scrollToPreviewWithTimeout('successAlert', 'center');
|
||||
this.estimateSubscription.unsubscribe();
|
||||
},
|
||||
error: (response) => {
|
||||
this.error = response.error;
|
||||
this.scrollToPreviewWithTimeout('mempoolError', 'center');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
<app-truncate [text]="rbfTransaction.txid" [lastChars]="12" [link]="['/tx/' | relativeUrl, rbfTransaction.txid]"></app-truncate>
|
||||
</div>
|
||||
|
||||
<div *ngIf="acceleratorAvailable && accelerateCtaType === 'alert' && !tx?.status?.confirmed" class="alert alert-mempool alert-dismissible" role="alert">
|
||||
<div *ngIf="acceleratorAvailable && accelerateCtaType === 'alert' && !tx?.status?.confirmed && !tx.acceleration" class="alert alert-mempool alert-dismissible" role="alert">
|
||||
<span><a class="link accelerator" (click)="onAccelerateClicked()">Accelerate</a> this transaction using the Mempool Accelerator ™</span>
|
||||
<button type="button" class="close" aria-label="Close" (click)="dismissAccelAlert()">
|
||||
<span aria-hidden="true">×</span>
|
||||
|
@ -78,33 +78,17 @@
|
|||
|
||||
</ng-template>
|
||||
|
||||
<!-- Accelerator -->
|
||||
<ng-container *ngIf="!tx?.status?.confirmed && showAccelerationSummary">
|
||||
<div class="title mt-3" id="acceleratePreviewAnchor">
|
||||
<h2>Accelerate</h2>
|
||||
</div>
|
||||
|
||||
<div class="box">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
Test
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
Test
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
Test
|
||||
</div>
|
||||
</div>
|
||||
<app-accelerate-preview [txid]="txId" [scrollEvent]="scrollIntoAccelPreview"></app-accelerate-preview>
|
||||
</div>
|
||||
|
||||
</ng-container>
|
||||
|
||||
<span id="acceleratePreviewAnchor"></span>
|
||||
|
||||
<ng-template #unconfirmedTemplate>
|
||||
|
||||
<div class="box">
|
||||
|
@ -132,10 +116,9 @@
|
|||
</ng-template>
|
||||
<ng-template #estimationTmpl>
|
||||
<ng-template [ngIf]="this.mempoolPosition.block >= 7" [ngIfElse]="belowBlockLimit">
|
||||
<span class="eta" [class]="acceleratorAvailable ? 'd-flex justify-content-end' : ''">
|
||||
<span class="etaDeepMempool" [class]="acceleratorAvailable ? 'd-flex justify-content-end align-items-center' : ''">
|
||||
<span i18n="transaction.eta.in-several-hours|Transaction ETA in several hours or more">In several hours (or more)</span>
|
||||
<span *ngIf="acceleratorAvailable" class="ml-2"></span>
|
||||
<a *ngIf="acceleratorAvailable && accelerateCtaType === 'button'" [href]="'/services/accelerator/accelerate?txid=' + tx.txid" class="btn badge badge-primary accelerate ml-auto" i18n="transaction.accelerate|Accelerate button label">Accelerate</a>
|
||||
<a *ngIf="acceleratorAvailable && accelerateCtaType === 'button' && !tx.acceleration" [href]="'/services/accelerator/accelerate?txid=' + tx.txid" class="btn btn-sm accelerateDeepMempool" i18n="transaction.accelerate|Accelerate button label" (click)="onAccelerateClicked()">Accelerate</a>
|
||||
</span>
|
||||
</ng-template>
|
||||
<ng-template #belowBlockLimit>
|
||||
|
@ -143,10 +126,9 @@
|
|||
<app-time kind="until" [time]="(60 * 1000 * this.mempoolPosition.block) + now" [fastRender]="false" [fixedRender]="true"></app-time>
|
||||
</ng-template>
|
||||
<ng-template #timeEstimateDefault>
|
||||
<span [class]="acceleratorAvailable ? 'd-flex justify-content-end' : ''">
|
||||
<span class="eta" [class]="acceleratorAvailable ? 'd-flex justify-content-end align-items-center' : ''">
|
||||
<app-time kind="until" *ngIf="(da$ | async) as da;" [time]="da.timeAvg * (this.mempoolPosition.block + 1) + now + da.timeOffset" [fastRender]="false" [fixedRender]="true"></app-time>
|
||||
<span *ngIf="acceleratorAvailable" class="ml-2"></span>
|
||||
<a *ngIf="acceleratorAvailable && accelerateCtaType === 'button'" [href]="'/services/accelerator/accelerate?txid=' + tx.txid" class="btn badge badge-primary accelerate ml-auto" i18n="transaction.accelerate|Accelerate button label">Accelerate</a>
|
||||
<a *ngIf="acceleratorAvailable && accelerateCtaType === 'button' && !tx.acceleration" [href]="'/services/accelerator/accelerate?txid=' + tx.txid" class="btn btn-sm accelerate" i18n="transaction.accelerate|Accelerate button label" (click)="onAccelerateClicked()">Accelerate</a>
|
||||
</span>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
|
|
|
@ -130,7 +130,7 @@
|
|||
}
|
||||
|
||||
.table {
|
||||
tr td {
|
||||
tr td {
|
||||
padding: 0.75rem 0.5rem;
|
||||
@media (min-width: 576px) {
|
||||
padding: 0.75rem 0.75rem;
|
||||
|
@ -138,7 +138,7 @@
|
|||
&:last-child {
|
||||
text-align: right;
|
||||
@media (min-width: 850px) {
|
||||
text-align: left;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
.btn {
|
||||
|
@ -218,25 +218,52 @@
|
|||
}
|
||||
}
|
||||
|
||||
.eta {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
flex-wrap: wrap;
|
||||
align-content: center;
|
||||
@media (min-width: 850px) {
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
.link.accelerator {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.eta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-content: center;
|
||||
@media (min-width: 850px) {
|
||||
justify-content: left !important;
|
||||
}
|
||||
}
|
||||
|
||||
.accelerate {
|
||||
display: flex !important;
|
||||
align-self: auto;
|
||||
margin-top: 3px;
|
||||
margin-left: 10px;
|
||||
@media (min-width: 850px) {
|
||||
justify-self: start;
|
||||
margin-left: auto;
|
||||
background-color: #653b9c;
|
||||
@media (max-width: 849px) {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.etaDeepMempool {
|
||||
display: flex !important;
|
||||
justify-content: end;
|
||||
flex-wrap: wrap;
|
||||
align-content: center;
|
||||
@media (max-width: 995px) {
|
||||
justify-content: left !important;
|
||||
}
|
||||
@media (max-width: 849px) {
|
||||
justify-content: right !important;
|
||||
}
|
||||
}
|
||||
|
||||
.accelerateDeepMempool {
|
||||
align-self: auto;
|
||||
margin-top: 3px;
|
||||
margin-left: auto;
|
||||
background-color: #653b9c;
|
||||
@media (max-width: 995px) {
|
||||
margin-left: 0px;
|
||||
}
|
||||
@media (max-width: 849px) {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,7 +83,6 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
blockConversion: Price;
|
||||
tooltipPosition: { x: number, y: number };
|
||||
isMobile: boolean;
|
||||
acceleratorAvailable: boolean = false;
|
||||
|
||||
featuresEnabled: boolean;
|
||||
segwitEnabled: boolean;
|
||||
|
@ -93,6 +92,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
accelerateCtaType: 'alert' | 'button' = 'alert';
|
||||
acceleratorAvailable: boolean = this.stateService.env.OFFICIAL_MEMPOOL_SPACE && this.stateService.env.ACCELERATOR && this.stateService.network === '';
|
||||
showAccelerationSummary = false;
|
||||
scrollIntoAccelPreview = false;
|
||||
|
||||
@ViewChild('graphContainer')
|
||||
graphContainer: ElementRef;
|
||||
|
@ -504,12 +504,13 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.accelerateCtaType = 'button';
|
||||
}
|
||||
|
||||
onAccelerateClicked(): void {
|
||||
onAccelerateClicked() {
|
||||
if (!this.txId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.showAccelerationSummary = true && this.acceleratorAvailable;
|
||||
this.scrollIntoAccelPreview = !this.scrollIntoAccelPreview;
|
||||
return false;
|
||||
}
|
||||
|
||||
handleLoadElectrsTransactionError(error: any): Observable<any> {
|
||||
|
|
|
@ -392,4 +392,8 @@ export class ApiService {
|
|||
estimate$(txInput: string) {
|
||||
return this.httpClient.post<any>(`${SERVICES_API_PREFIX}/accelerator/estimate`, { txInput: txInput }, { observe: 'response' });
|
||||
}
|
||||
|
||||
accelerate$(txInput: string, userBid: number) {
|
||||
return this.httpClient.post<any>(`${SERVICES_API_PREFIX}/accelerator/accelerate`, { txInput: txInput, userBid: userBid });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
<div class="alert alert-danger" [innerHTML]="errorContent">
|
||||
</div>
|
|
@ -0,0 +1,47 @@
|
|||
import { Component, Input, OnInit } from "@angular/core";
|
||||
import { DomSanitizer, SafeHtml } from "@angular/platform-browser";
|
||||
|
||||
const MempoolErrors = {
|
||||
'acceleration_duplicated': `This transaction has already been accelerated.`,
|
||||
'acceleration_outbid': `Your fee delta is too low.`,
|
||||
'cannot_accelerate_tx': `Cannot accelerate this transaction.`,
|
||||
'cannot_decode_raw_tx': `Cannot decode this raw transaction.`,
|
||||
'cannot_fetch_raw_tx': `Cannot find this transaction.`,
|
||||
'database_error': `Something went wrong. Please try again later.`,
|
||||
'high_sigop_tx': `This transaction cannot be accelerated.`,
|
||||
'invalid_acceleration_request': `This acceleration request is not valid.`,
|
||||
'invalid_tx_dependencies': `This transaction dependencies are not valid.`,
|
||||
'mempool_rejected_raw_tx': `Our mempool rejected this transaction`,
|
||||
'no_mining_pool_available': `No mining pool available at the moment`,
|
||||
'not_available': `You current subscription does not allow you to access this feature. Consider <strong><a style="color: #105fb0;" href="/sponsor" target="_blank">upgrading.</a><strong>`,
|
||||
'not_enough_balance': `Your account balance is too low. Please make a <a style="color:#105fb0" href="/services/accelerator/overview">deposit.</a>`,
|
||||
'not_verified': `You must verify your account to use this feature.`,
|
||||
'recommended_fees_not_available': `Recommended fees are not available right now.`,
|
||||
'too_many_relatives': `This transaction has too many relatives.`,
|
||||
'txid_not_in_mempool': `This transaction is not in the mempool.`,
|
||||
'waitlisted': `You are currently on the wait list. You will get notified once you are granted access.`,
|
||||
'not_whitelisted_by_any_pool': `You are not whitelisted by any mining pool`,
|
||||
} as { [error: string]: string };
|
||||
|
||||
export function isMempoolError(error: string) {
|
||||
return Object.keys(MempoolErrors).includes(error);
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-mempool-error',
|
||||
templateUrl: './mempool-error.component.html'
|
||||
})
|
||||
export class MempoolErrorComponent implements OnInit {
|
||||
@Input() error: string;
|
||||
errorContent: SafeHtml;
|
||||
|
||||
constructor(private sanitizer: DomSanitizer) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
if (Object.keys(MempoolErrors).includes(this.error)) {
|
||||
this.errorContent = this.sanitizer.bypassSecurityTrustHtml(MempoolErrors[this.error]);
|
||||
} else {
|
||||
this.errorContent = this.error;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -93,6 +93,8 @@ import { ToggleComponent } from './components/toggle/toggle.component';
|
|||
import { GeolocationComponent } from '../shared/components/geolocation/geolocation.component';
|
||||
import { TestnetAlertComponent } from './components/testnet-alert/testnet-alert.component';
|
||||
import { GlobalFooterComponent } from './components/global-footer/global-footer.component';
|
||||
import { AcceleratePreviewComponent } from '../components/accelerate-preview/accelerate-preview.component';
|
||||
import { MempoolErrorComponent } from './components/mempool-error/mempool-error.component';
|
||||
|
||||
import { MempoolBlockOverviewComponent } from '../components/mempool-block-overview/mempool-block-overview.component';
|
||||
import { ClockchainComponent } from '../components/clockchain/clockchain.component';
|
||||
|
@ -189,6 +191,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
|
|||
GeolocationComponent,
|
||||
TestnetAlertComponent,
|
||||
GlobalFooterComponent,
|
||||
AcceleratePreviewComponent,
|
||||
CalculatorComponent,
|
||||
BitcoinsatoshisPipe,
|
||||
MempoolBlockOverviewComponent,
|
||||
|
@ -196,7 +199,8 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
|
|||
ClockComponent,
|
||||
ClockFaceComponent,
|
||||
OnlyVsizeDirective,
|
||||
OnlyWeightDirective
|
||||
OnlyWeightDirective,
|
||||
MempoolErrorComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
|
@ -310,6 +314,8 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
|
|||
GeolocationComponent,
|
||||
PreviewTitleComponent,
|
||||
GlobalFooterComponent,
|
||||
AcceleratePreviewComponent,
|
||||
MempoolErrorComponent,
|
||||
|
||||
MempoolBlockOverviewComponent,
|
||||
ClockchainComponent,
|
||||
|
|
Loading…
Add table
Reference in a new issue