Merge pull request #6589 from mempool/nymkappa/accelerator-refactoring

[accelerator] update checkout component and documentation
This commit is contained in:
wiz 2026-07-01 14:13:08 +09:00 committed by GitHub
commit 9c96263395
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 92 additions and 95 deletions

View file

@ -32,7 +32,6 @@
<app-accelerate-fee-graph
[tx]="tx"
[estimate]="estimate"
[showEstimate]="hasAccessToBalanceMode"
[maxRateOptions]="maxRateOptions"
[maxRateIndex]="selectFeeRateIndex"
(setUserBid)="setUserBid($event)"
@ -112,7 +111,7 @@
<tbody>
<!-- ESTIMATED FEE -->
<ng-container *ngIf="showDetails">
@if (hasAccessToBalanceMode) {
@if (estimate.isProUser) {
<tr class="group-first">
<td class="item" i18n="accelerator.next-block-rate">Next block market rate</td>
<td class="amt" style="font-size: 16px">
@ -187,7 +186,7 @@
</ng-container>
<!-- NEXT BLOCK ESTIMATE -->
<ng-container *ngIf="hasAccessToBalanceMode">
<ng-container *ngIf="estimate.isProUser">
<tr class="group-first">
<td class="item">
<b style="background-color: #5E35B1" class="p-1" i18n="accelerator.estimated-cost">Estimated acceleration cost</b> ~{{ estimate.targetFeeRate | number : '1.0-0' }} sat/vB
@ -208,7 +207,7 @@
<ng-container>
<tr class="group-first group-last">
<td class="item">
@if (hasAccessToBalanceMode) {
@if (estimate.isProUser) {
<b style="background-color: var(--primary);" class="p-1" i18n="accelerator.maximum-cost">Maximum acceleration cost</b>
} @else {
<b style="background-color: var(--primary);" class="p-1" i18n="accelerator.cost">Acceleration cost</b>
@ -222,14 +221,14 @@
<td class="units">
<span class="symbol" i18n="shared.sats">sats</span>
<span class="fiat ms-1">
<app-fiat [value]="cost" [colorClass]="hasAccessToBalanceMode && estimate.userBalance < cost ? 'red-color' : 'green-color'"></app-fiat>
<app-fiat [value]="cost" [colorClass]="canUseBalance && estimate.userBalance < cost ? 'red-color' : 'green-color'"></app-fiat>
</span>
</td>
</tr>
</ng-container>
<!-- USER BALANCE -->
<ng-container *ngIf="hasAccessToBalanceMode && estimate.userBalance < cost">
<ng-container *ngIf="canUseBalance && estimate.userBalance < cost">
<tr class="group-first group-last dashed-top">
<td class="item" i18n="accelerator.available-balance">Available balance</td>
<td class="amt">
@ -371,63 +370,69 @@
<div class="row text-center justify-content-center mx-2 mb-3">
<span i18n="accelerator.payment-to-mempool-space">Payment to mempool.space for acceleration of txid <a [routerLink]="'/tx/' + tx.txid" target="_blank">{{ tx.txid.substr(0, 10) }}..{{ tx.txid.substr(-10) }}</a></span>
</div>
@if (canPayWithBalance || !(canPayWithBitcoin || canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay)) {
<div class="row">
<div class="row payment-options" [class.flex-column]="forceMobile">
@if (canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay || canPayWithCardOnFile) {
<div class="col-sm text-center d-flex flex-column justify-content-center align-items-center">
<p><ng-container i18n="accelerator.your-account-will-be-debited">Your account will be debited no more than</ng-container>&nbsp;<small style="font-family: monospace;">{{ cost | number }}</small>&nbsp;<span class="symbol" i18n="shared.sats">sats</span></p>
<div class="d-flex justify-content-center" [class.grayOut]="!canPayWithBalance || quoteError || accelerateError || showSuccess">
<ng-container *ngTemplateOutlet="accountPayButton"></ng-container>
</div>
<p><ng-container i18n="transaction.pay|Pay button label">Pay</ng-container>&nbsp;<app-fiat [value]="cost"></app-fiat> with</p>
@if (canPayWithCashapp) {
<img class="paymentMethod mx-2" style="width: 200px" [src]="isLightMode ? '/resources/cash-app-light.svg' : '/resources/cash-app.svg'" height=55 (click)="moveToStep('cashapp')">
}
@if (canPayWithApplePay) {
@if (canPayWithCashapp) { <span class="mt-1 mb-1"></span> }
<div class="paymentMethod mx-2" style="width: 200px; height: 55px" (click)="moveToStep('applepay')">
<img [src]="isLightMode ? '/resources/apple-pay-light.png' : '/resources/apple-pay.png'" height=37>
</div>
}
@if (canPayWithGooglePay) {
@if (canPayWithCashapp || canPayWithApplePay) { <span class="mt-1 mb-1"></span> }
<div class="paymentMethod mx-2" style="width: 200px; height: 55px" (click)="moveToStep('googlepay')">
<img [src]="isLightMode ? '/resources/google-pay-light.png' : '/resources/google-pay.png'" height=37>
</div>
}
@if (canPayWithCardOnFile) {
@if (canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay) { <span class="mt-1 mb-1"></span> }
<div class="paymentMethod mx-2 d-flex justify-content-center align-items-center" style="width: 200px; height: 55px" (click)="moveToStep('cardonfile')">
@if (isValidCardBrand(estimate?.availablePaymentMethods?.cardOnFile?.card?.brand)) {
<app-svg-images [name]="estimate?.availablePaymentMethods?.cardOnFile?.card?.brand" height="33" class="me-2"></app-svg-images>
} @else {
<app-svg-images name="OTHER_BRAND" height="33" class="me-2"></app-svg-images>
}
<span style="font-size: 22px; padding-bottom: 3px">{{ estimate?.availablePaymentMethods?.cardOnFile?.card?.last_4 }}</span>
</div>
}
</div>
</div>
} @else {
<div class="row payment-options" [class.flex-column]="forceMobile">
@if (canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay || canPayWithCardOnFile) {
<div class="col-sm text-center d-flex flex-column justify-content-center align-items-center">
<p><ng-container i18n="transaction.pay|Pay button label">Pay</ng-container>&nbsp;<app-fiat [value]="cost"></app-fiat> with</p>
@if (canPayWithCashapp) {
<img class="paymentMethod mx-2" style="width: 200px" [src]="isLightMode ? '/resources/cash-app-light.svg' : '/resources/cash-app.svg'" height=55 (click)="moveToStep('cashapp')">
}
@if (canPayWithApplePay) {
@if (canPayWithCashapp) { <span class="mt-1 mb-1"></span> }
<div class="paymentMethod mx-2" style="width: 200px; height: 55px" (click)="moveToStep('applepay')">
<img [src]="isLightMode ? '/resources/apple-pay-light.png' : '/resources/apple-pay.png'" height=37>
}
@if (((canPayWithBalance || canPayWithBitcoin) && (canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay || canPayWithCardOnFile))) {
<div class="col-sm text-center flex-grow-0 d-flex flex-column justify-content-center align-items-center">
<span class="text-nowrap mb-3 mt-3" [class.mt-md-0]="!forceMobile">&mdash;&mdash;<span i18n="or"> OR </span>&mdash;&mdash;</span>
</div>
}
@if (canPayWithBalance || canPayWithBitcoin) {
<div class="col-sm text-center d-flex flex-column justify-content-center align-items-center">
@if (canPayWithBalance) {
<p>
@if (estimate.isProUser) {
<ng-container i18n="accelerator.your-account-will-be-debited">Your account will be debited no more than </ng-container>
} @else {
<ng-container i18n="accelerator.use-accelerator-credits">Use accelerator credits for </ng-container>
}
&nbsp;<span style="white-space: nowrap"><small style="font-family: monospace;">{{ cost | number }}</small>&nbsp;<span class="symbol" i18n="shared.sats">sats</span></span>
</p>
<div class="d-flex justify-content-center" [class.disabled]="quoteError || accelerateError || showSuccess || calculating || processing">
<div class="paymentMethod mx-2 d-flex justify-content-center align-items-center" style="width: 200px; height: 55px; font-size: 30px" (click)="accelerateWithMempoolAccount()">
<img class="me-2 purple-filter" src="/resources/mempool-accelerator-sparkles-light.svg" height="30">
<span i18n="transaction.pay|Pay button label">Pay</span>
</div>
}
@if (canPayWithGooglePay) {
@if (canPayWithCashapp || canPayWithApplePay) { <span class="mt-1 mb-1"></span> }
<div class="paymentMethod mx-2" style="width: 200px; height: 55px" (click)="moveToStep('googlepay')">
<img [src]="isLightMode ? '/resources/google-pay-light.png' : '/resources/google-pay.png'" height=37>
</div>
}
@if (canPayWithCardOnFile) {
@if (canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay) { <span class="mt-1 mb-1"></span> }
<div class="paymentMethod mx-2 d-flex justify-content-center align-items-center" style="width: 200px; height: 55px" (click)="moveToStep('cardonfile')">
@if (['VISA', 'MASTERCARD', 'JCB', 'DISCOVER', 'DISCOVER_DINERS', 'AMERICAN_EXPRESS'].includes(estimate?.availablePaymentMethods?.cardOnFile?.card?.brand)) {
<app-svg-images [name]="estimate?.availablePaymentMethods?.cardOnFile?.card?.brand" height="33" class="me-2"></app-svg-images>
} @else {
<app-svg-images name="OTHER_BRAND" height="33" class="me-2"></app-svg-images>
}
<span style="font-size: 22px; padding-bottom: 3px">{{ estimate?.availablePaymentMethods?.cardOnFile?.card?.last_4 }}</span>
</div>
}
</div>
}
@if ((canPayWithBitcoin && (canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay || canPayWithCardOnFile))) {
<div class="col-sm text-center flex-grow-0 d-flex flex-column justify-content-center align-items-center">
<span class="text-nowrap mb-3 mt-3" [class.mt-md-0]="!forceMobile">&mdash;&mdash;<span i18n="or"> OR </span>&mdash;&mdash;</span>
</div>
}
@if (canPayWithBitcoin) {
<div class="col-sm text-center d-flex flex-column justify-content-center align-items-center">
</div>
} @else {
<p><ng-container i18n="transaction.pay|Pay button label">Pay</ng-container>&nbsp;<span><small style="font-family: monospace;">{{ ((invoice?.btcDue * 100_000_000) || cost) | number }}</small>&nbsp;<span class="symbol" i18n="shared.sats">sats</span></span> with</p>
<div class="paymentMethod mx-2 d-flex justify-content-center align-items-center" style="width: 200px; height: 55px" (click)="moveToStep('bitcoin')">
<img src="/resources/bitcoin-btc-logo-full.svg" height=30>
</div>
</div>
}
</div>
}
}
</div>
}
</div>
</div>
</ng-container>
<ng-template #loadingCheckout>
@ -510,7 +515,7 @@
<div id="google-pay-button" class="d-inline-block" style="height: 50px" [style]="loadingGooglePay ? 'opacity: 0; width: 0px; height: 0px; pointer-events: none;' : ''"></div>
} @else if (step === 'cardonfile') {
<div class="ms-auto me-auto paymentMethod mx-2 d-flex justify-content-center align-items-center" style="width: 200px; height: 55px" (click)="requestCardOnFilePayment()" [style]="loadingCardOnFile ? 'opacity: 0; width: 0px; height: 0px; pointer-events: none;' : ''">
@if (['VISA', 'MASTERCARD', 'JCB', 'DISCOVER', 'DISCOVER_DINERS', 'AMERICAN_EXPRESS'].includes(estimate?.availablePaymentMethods?.cardOnFile?.card?.brand)) {
@if (isValidCardBrand(estimate?.availablePaymentMethods?.cardOnFile?.card?.brand)) {
<app-svg-images [name]="estimate?.availablePaymentMethods?.cardOnFile?.card?.brand" height="33" class="me-2"></app-svg-images>
} @else {
<app-svg-images name="OTHER_BRAND" height="33" class="me-2"></app-svg-images>
@ -637,20 +642,6 @@
</div>
</ng-template>
<ng-template #accountPayButton>
@if (hasAccessToBalanceMode) {
<button type="button" class="mt-1 btn btn-purple rounded-pill align-self-center d-flex flex-row justify-content-center align-items-center" [class.disabled]="!canPay || calculating" style="width: 200px" (click)="accelerateWithMempoolAccount()">
<img src="/resources/mempool-accelerator-sparkles-light.svg" height="20" class="me-2" style="margin-left: -10px">
<span i18n="transaction.pay|Pay button label">Pay</span>
</button>
} @else {
<button type="button" class="mt-1 btn btn-purple rounded-pill align-self-center d-flex flex-row justify-content-center align-items-center disabled" style="width: 200px">
<img src="/resources/mempool-accelerator-sparkles-light.svg" height="20" class="me-2" style="margin-left: -10px">
<span>Coming soon</span>
</button>
}
</ng-template>
<ng-template #prioritizedBy let-i i18n="accelerator.hashrate-percentage-description">Your transaction will be prioritized by up to <strong>{{ i | number : '1.1-1' }}%</strong> of miners.</ng-template>
<ng-template #partnerCodeDisplay let-partnerCode="partnerCode">

View file

@ -26,6 +26,10 @@ hr {
cursor: pointer;
}
.purple-filter {
filter: brightness(0) saturate(100%) invert(30%) sepia(90%) saturate(5000%) hue-rotate(260deg);
}
.default-slot:not(:only-child) {
display: none;
}

View file

@ -18,7 +18,7 @@ import { ThemeService } from '../../services/theme.service';
export type PaymentMethod = 'balance' | 'bitcoin' | 'cashapp' | 'applePay' | 'googlePay' | 'cardOnFile';
export type AccelerationEstimate = {
hasAccess: boolean;
isProUser: boolean;
txSummary: TxSummary;
nextBlockFee: number;
targetFeeRate: number;
@ -343,11 +343,6 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
this.estimate$.next(null);
return;
}
if (this.estimate.hasAccess === true && this.estimate.userBalance <= 0) {
if (this.isLoggedIn()) {
this.quoteError = `not_enough_balance`;
}
}
if (this.estimate.unavailable) {
this.quoteError = `temporarily_unavailable`;
}
@ -1021,6 +1016,10 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
return this.auth !== null;
}
isValidCardBrand(brand: string | undefined): boolean {
return !!brand && ['VISA', 'MASTERCARD', 'JCB', 'DISCOVER', 'DISCOVER_DINERS', 'AMERICAN_EXPRESS'].includes(brand);
}
/**
* UI events
*/
@ -1058,15 +1057,19 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
return !!this.estimate?.availablePaymentMethods?.googlePay;
}
get couldPayWithBalance(): boolean {
if (!this.hasAccessToBalanceMode) {
get couldPayWithCardOnFile(): boolean {
if (!this.cardOnFileEnabled || (!this.isProdDomain && !isDevMode())) {
return false;
}
return !!this.estimate?.availablePaymentMethods?.balance;
return this.isLoggedIn() && !!this.estimate?.availablePaymentMethods?.cardOnFile;
}
get couldPayWithBalance(): boolean {
return this.isLoggedIn() && !!this.estimate?.availablePaymentMethods?.balance;
}
get couldPay(): boolean {
return this.couldPayWithBalance || this.couldPayWithBitcoin || this.couldPayWithCashapp || this.couldPayWithApplePay || this.couldPayWithGooglePay;
return this.couldPayWithBalance || this.couldPayWithBitcoin || this.couldPayWithCashapp || this.couldPayWithApplePay || this.couldPayWithGooglePay || this.couldPayWithCardOnFile;
}
get canOnlyPayWithBitcoin(): boolean {
@ -1143,7 +1146,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
}
get canPayWithBalance(): boolean {
if (!this.hasAccessToBalanceMode) {
if (!this.canUseBalance) {
return false;
}
const paymentMethod = this.estimate?.availablePaymentMethods?.balance;
@ -1151,11 +1154,11 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
}
get canPay(): boolean {
return this.canPayWithBalance || this.canPayWithBitcoin || this.canPayWithCashapp || this.canPayWithApplePay || this.canPayWithGooglePay;
return this.canPayWithBalance || this.canPayWithBitcoin || this.canPayWithCashapp || this.canPayWithApplePay || this.canPayWithGooglePay || this.canPayWithCardOnFile;
}
get hasAccessToBalanceMode(): boolean {
return this.isLoggedIn() && this.estimate?.hasAccess;
get canUseBalance(): boolean {
return this.isLoggedIn() && this.estimate?.userBalance > 0;
}
get timeSincePaid(): number {

View file

@ -22,7 +22,6 @@ interface GraphBar {
export class AccelerateFeeGraphComponent implements OnInit, AfterViewInit, OnChanges, OnDestroy {
@Input() tx: Transaction;
@Input() estimate: AccelerationEstimate;
@Input() showEstimate = false;
@Input() maxRateOptions: RateOption[] = [];
@Input() maxRateIndex: number = 0;
@Output() setUserBid = new EventEmitter<{ fee: number, index: number }>();
@ -89,7 +88,7 @@ export class AccelerateFeeGraphComponent implements OnInit, AfterViewInit, OnCha
rate: option.rate,
height: lastHeight,
class: 'max',
label: this.showEstimate ? $localize`maximum` : $localize`:@@b484583f0ce10f3341ab36750d05271d9d22c9a1:Accelerated`.toLowerCase(),
label: this.estimate.isProUser ? $localize`maximum` : $localize`:@@b484583f0ce10f3341ab36750d05271d9d22c9a1:Accelerated`.toLowerCase(),
active: option.index === this.maxRateIndex,
rateIndex: option.index,
fee: option.fee,

View file

@ -12015,7 +12015,7 @@ export const restApiDocsData = [
"fee": 12500
}
],
"hasAccess": false,
"isProUser": false,
"availablePaymentMethods": {
"bitcoin": {
"enabled": true,
@ -12259,7 +12259,7 @@ export const restApiDocsData = [
category: 'accelerator-private',
httpRequestMethod: 'POST',
fragment: 'accelerator-top-up',
title: 'POST Top-up Accelerator (Pro)',
title: 'POST Top-up Accelerator Credits',
description: {
default: '<p>Generate a bitcoin invoice to top-up your accelerator credits. Minimum is <code>1,000,000</code> sats.</p>'
},
@ -12299,7 +12299,7 @@ export const restApiDocsData = [
category: 'accelerator-private',
httpRequestMethod: 'GET',
fragment: 'accelerator-top-up-history',
title: 'GET Top-up History (Pro)',
title: 'GET Credits Top-up History',
description: {
default: '<p>Returns a list of top ups the user has made as prepayment for the accelerator service.</p>'
},
@ -12347,9 +12347,9 @@ export const restApiDocsData = [
category: 'accelerator-private',
httpRequestMethod: 'GET',
fragment: 'accelerator-balance',
title: 'GET Available Balance (Pro)',
title: 'GET Available Credit Balance',
description: {
default: '<p>Returns the user\'s currently available balance, currently locked funds, and total fees paid so far.</p>'
default: '<p>Returns the user\'s currently available accelerator credit balance, locked funds, and total fees paid so far.</p>'
},
urlString: '/v1/services/accelerator/balance',
showConditions: [''],
@ -12381,9 +12381,9 @@ export const restApiDocsData = [
category: 'accelerator-private',
httpRequestMethod: 'POST',
fragment: 'accelerator-accelerate',
title: 'POST Accelerate A Transaction (Pro)',
title: 'POST Accelerate A Transaction',
description: {
default: '<p>Sends a request to accelerate a transaction.</p>'
default: '<p>Sends a request to accelerate a transaction <u><strong>using accelerator credits.</strong></u></p>'
},
urlString: '/v1/services/accelerator/accelerate',
showConditions: [''],