Your account will be debited no more than {{ cost | number }} sats
-
@@ -365,6 +377,11 @@
@if (invoice) {
Pay {{ ((invoice.btcDue * 100_000_000) || cost) | number }} sats
+ } @else if (btcpayInvoiceFailed) {
+
Failed to load invoice
+
+
+
} @else {
Loading invoice...
@@ -532,16 +549,21 @@
Accelerate to ~{{ x | number : '1.0-0' }} sat/vB
- @if (canPayWithBalance || canPayWithBitcoin || canPayWithCashapp) {
-
- } @else {
+ @if (!couldPay && !quoteError && !(estimate?.availablePaymentMethods.bitcoin || estimate?.availablePaymentMethods.balance)) {
+ } @else {
+
+
+ @if (quoteError || cantPayReason) {
+
+ }
+
}
diff --git a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.scss b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.scss
index 5b302b9ad..c7c0fdb6d 100644
--- a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.scss
+++ b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.scss
@@ -189,3 +189,10 @@
flex-direction: column;
}
}
+
+.btn-error {
+ position: absolute;
+ right: 0;
+ font-size: 12px;
+ color: var(--red);
+}
\ No newline at end of file
diff --git a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts
index 9805b5e96..68ed74ad6 100644
--- a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts
+++ b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts
@@ -59,13 +59,18 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
@Input() forceMobile: boolean = false;
@Input() showDetails: boolean = false;
@Input() noCTA: boolean = false;
+ @Output() unavailable = new EventEmitter
();
@Output() completed = new EventEmitter();
@Output() hasDetails = new EventEmitter();
@Output() changeMode = new EventEmitter();
calculating = true;
selectedOption: 'wait' | 'accel';
- error = '';
+ cantPayReason = '';
+ quoteError = ''; // error fetching estimate or initial data
+ accelerateError = ''; // error executing acceleration
+ btcpayInvoiceFailed = false;
+ timePaid: number = 0; // time acceleration requested
math = Math;
isMobile: boolean = window.innerWidth <= 767.98;
@@ -98,6 +103,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
// square
loadingCashapp = false;
+ cashappError = false;
cashappSubmit: any;
payments: any;
cashAppPay: any;
@@ -125,7 +131,10 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
if (this.auth?.user?.userId !== auth?.user?.userId) {
this.auth = auth;
this.estimate = null;
- this.error = null;
+ this.quoteError = null;
+ this.accelerateError = null;
+ this.timePaid = 0;
+ this.btcpayInvoiceFailed = false;
this.moveToStep('summary');
} else {
this.auth = auth;
@@ -182,6 +191,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
this.fetchEstimate();
}
if (this._step === 'checkout' && this.canPayWithBitcoin) {
+ this.btcpayInvoiceFailed = false;
this.loadingBtcpayInvoice = true;
this.invoice = null;
this.requestBTCPayInvoice();
@@ -226,19 +236,27 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
this.estimateSubscription.unsubscribe();
}
this.calculating = true;
+ this.quoteError = null;
+ this.accelerateError = null;
this.estimateSubscription = this.servicesApiService.estimate$(this.tx.txid).pipe(
tap((response) => {
if (response.status === 204) {
- this.error = `cannot_accelerate_tx`;
+ this.quoteError = `cannot_accelerate_tx`;
+ if (this.step === 'summary') {
+ this.unavailable.emit(true);
+ }
} else {
this.estimate = response.body;
if (!this.estimate) {
- this.error = `cannot_accelerate_tx`;
+ this.quoteError = `cannot_accelerate_tx`;
+ if (this.step === 'summary') {
+ this.unavailable.emit(true);
+ }
return;
}
if (this.estimate.hasAccess === true && this.estimate.userBalance <= 0) {
if (this.isLoggedIn()) {
- this.error = `not_enough_balance`;
+ this.quoteError = `not_enough_balance`;
}
}
this.hasAncestors = this.estimate.txSummary.ancestorCount > 1;
@@ -267,6 +285,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
}
this.cost = this.userBid + this.estimate.mempoolBaseFee + this.estimate.vsizeFee;
+ this.validateChoice();
+
if (this.step === 'checkout' && this.canPayWithBitcoin && !this.loadingBtcpayInvoice) {
this.loadingBtcpayInvoice = true;
this.requestBTCPayInvoice();
@@ -279,13 +299,32 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
catchError((response) => {
this.estimate = undefined;
- this.error = `cannot_accelerate_tx`;
+ this.quoteError = `cannot_accelerate_tx`;
this.estimateSubscription.unsubscribe();
+ if (this.step === 'summary') {
+ this.unavailable.emit(true);
+ } else {
+ this.accelerateError = 'cannot_accelerate_tx';
+ }
return of(null);
})
).subscribe();
}
+ validateChoice(): void {
+ if (!this.canPay) {
+ if (this.estimate?.availablePaymentMethods?.balance) {
+ if (this.cost >= this.estimate?.userBalance) {
+ this.cantPayReason = 'not_enough_balance';
+ }
+ } else {
+ this.cantPayReason = 'cannot_accelerate_tx';
+ }
+ } else {
+ this.cantPayReason = '';
+ }
+ }
+
/**
* User changed his bid
*/
@@ -319,11 +358,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
this.moveToStep('paid')
},
error: (response) => {
- if (response.status === 403 && response.error === 'not_available') {
- this.error = 'waitlisted';
- } else {
- this.error = response.error;
- }
+ this.accelerateError = response.error;
}
});
}
@@ -371,6 +406,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
await this.requestCashAppPayment();
} catch (e) {
console.debug('Error loading Square Payments', e);
+ this.cashappError = true;
return;
}
}
@@ -417,7 +453,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
this.cashAppPay.addEventListener('ontokenization', function (event) {
const { tokenResult, error } = event.detail;
if (error) {
- this.error = error;
+ this.accelerateError = error;
} else if (tokenResult.status === 'OK') {
that.servicesApiService.accelerateWithCashApp$(
that.tx.txid,
@@ -440,10 +476,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
}, 1000);
},
error: (response) => {
- if (response.status === 403 && response.error === 'not_available') {
- that.error = 'waitlisted';
- } else {
- that.error = response.error;
+ that.accelerateError = response.error;
+ if (!(response.status === 403 && response.error === 'not_available')) {
setTimeout(() => {
// Reset everything by reloading the page :D, can be improved
const urlParams = new URLSearchParams(window.location.search);
@@ -468,6 +502,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
}),
catchError(error => {
console.log(error);
+ this.btcpayInvoiceFailed = true;
return of(null);
})
).subscribe((invoice) => {
@@ -497,6 +532,32 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
return this._step;
}
+ get paymentMethods() {
+ return Object.keys(this.estimate?.availablePaymentMethods || {});
+ }
+
+ get couldPayWithBitcoin() {
+ return !!this.estimate?.availablePaymentMethods?.bitcoin;
+ }
+
+ get couldPayWithCashapp() {
+ if (!this.cashappEnabled || this.stateService.referrer !== 'https://cash.app/') {
+ return false;
+ }
+ return !!this.estimate?.availablePaymentMethods?.cashapp;
+ }
+
+ get couldPayWithBalance() {
+ if (!this.hasAccessToBalanceMode) {
+ return false;
+ }
+ return !!this.estimate?.availablePaymentMethods?.balance;
+ }
+
+ get couldPay() {
+ return this.couldPayWithBalance || this.couldPayWithBitcoin || this.couldPayWithCashapp;
+ }
+
get canPayWithBitcoin() {
const paymentMethod = this.estimate?.availablePaymentMethods?.bitcoin;
return paymentMethod && this.cost >= paymentMethod.min && this.cost <= paymentMethod.max;
@@ -523,7 +584,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
return false;
}
const paymentMethod = this.estimate?.availablePaymentMethods?.balance;
- return paymentMethod && this.cost >= paymentMethod.min && this.cost <= paymentMethod.max;
+ return paymentMethod && this.cost >= paymentMethod.min && this.cost <= paymentMethod.max && this.cost <= this.estimate?.userBalance;
}
get canPay() {
diff --git a/frontend/src/app/components/tracker/tracker.component.html b/frontend/src/app/components/tracker/tracker.component.html
index 6071a55e4..1efce182c 100644
--- a/frontend/src/app/components/tracker/tracker.component.html
+++ b/frontend/src/app/components/tracker/tracker.component.html
@@ -132,6 +132,7 @@
[miningStats]="miningStats"
[eta]="eta"
[scrollEvent]="scrollIntoAccelPreview"
+ (unavailable)="eligibleForAcceleration = false"
class="h-100 w-100"
>
diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html
index e898549cb..4bbb194fb 100644
--- a/frontend/src/app/components/transaction/transaction.component.html
+++ b/frontend/src/app/components/transaction/transaction.component.html
@@ -146,6 +146,7 @@
[noCTA]="true"
(hasDetails)="setHasAccelerationDetails($event)"
(completed)="onAccelerationCompleted()"
+ (unavailable)="eligibleForAcceleration = false"
class="h-100 w-100"
>
diff --git a/frontend/src/app/shared/components/mempool-error/mempool-error.component.html b/frontend/src/app/shared/components/mempool-error/mempool-error.component.html
index b8179e4c3..d461530b0 100644
--- a/frontend/src/app/shared/components/mempool-error/mempool-error.component.html
+++ b/frontend/src/app/shared/components/mempool-error/mempool-error.component.html
@@ -1,2 +1,7 @@
-
-
+@if (!textOnly) {
+
+
+} @else {
+
+
+}
diff --git a/frontend/src/app/shared/components/mempool-error/mempool-error.component.ts b/frontend/src/app/shared/components/mempool-error/mempool-error.component.ts
index d33b3c37c..889177dd7 100644
--- a/frontend/src/app/shared/components/mempool-error/mempool-error.component.ts
+++ b/frontend/src/app/shared/components/mempool-error/mempool-error.component.ts
@@ -1,7 +1,7 @@
import { Component, Input, OnInit } from "@angular/core";
import { DomSanitizer, SafeHtml } from "@angular/platform-browser";
-const MempoolErrors = {
+export const MempoolErrors = {
'bad_request': `Your request was not valid. Please try again.`,
'internal_server_error': `Something went wrong, please try again later`,
'acceleration_duplicated': `This transaction has already been accelerated.`,
@@ -44,6 +44,7 @@ export function isMempoolError(error: string) {
export class MempoolErrorComponent implements OnInit {
@Input() error: string;
@Input() alertClass = 'alert-danger';
+ @Input() textOnly = false;
errorContent: SafeHtml;
constructor(private sanitizer: DomSanitizer) { }
diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts
index 868cb1bd9..313a43e1f 100644
--- a/frontend/src/app/shared/shared.module.ts
+++ b/frontend/src/app/shared/shared.module.ts
@@ -4,7 +4,7 @@ import { NgbCollapseModule, NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstra
import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faHammer, faDatabase, faExchangeAlt, faInfoCircle,
faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faClock, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown,
- faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft, faArrowsRotate, faCircleLeft, faFastForward, faWallet, faUserClock, faWrench, faUserFriends, faQuestionCircle, faHistory, faSignOutAlt, faKey, faSuitcase, faIdCardAlt, faNetworkWired, faUserCheck, faCircleCheck, faUserCircle, faCheck, faRocket, faScaleBalanced, faHourglassStart, faHourglassHalf, faHourglassEnd, faWandMagicSparkles, faFaucetDrip, faTimeline } from '@fortawesome/free-solid-svg-icons';
+ faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft, faArrowsRotate, faCircleLeft, faFastForward, faWallet, faUserClock, faWrench, faUserFriends, faQuestionCircle, faHistory, faSignOutAlt, faKey, faSuitcase, faIdCardAlt, faNetworkWired, faUserCheck, faCircleCheck, faUserCircle, faCheck, faRocket, faScaleBalanced, faHourglassStart, faHourglassHalf, faHourglassEnd, faWandMagicSparkles, faFaucetDrip, faTimeline, faCircleXmark} from '@fortawesome/free-solid-svg-icons';
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
import { MenuComponent } from '../components/menu/menu.component';
import { PreviewTitleComponent } from '../components/master-page-preview/preview-title.component';
@@ -433,5 +433,6 @@ export class SharedModule {
library.addIcons(faWandMagicSparkles);
library.addIcons(faFaucetDrip);
library.addIcons(faTimeline);
+ library.addIcons(faCircleXmark);
}
}