Merge branch 'master' into orangesurf/2024-07-19
This commit is contained in:
		
						commit
						811feec145
					
				| @ -396,10 +396,6 @@ class Mempool { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   public $updateAccelerations(newAccelerations: Acceleration[]): string[] { |   public $updateAccelerations(newAccelerations: Acceleration[]): string[] { | ||||||
|     if (!config.MEMPOOL_SERVICES.ACCELERATIONS) { |  | ||||||
|       return []; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     try { |     try { | ||||||
|       const changed: string[] = []; |       const changed: string[] = []; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -9,6 +9,7 @@ import bitcoinClient from '../bitcoin/bitcoin-client'; | |||||||
| import mining from "./mining"; | import mining from "./mining"; | ||||||
| import PricesRepository from '../../repositories/PricesRepository'; | import PricesRepository from '../../repositories/PricesRepository'; | ||||||
| import AccelerationRepository from '../../repositories/AccelerationRepository'; | import AccelerationRepository from '../../repositories/AccelerationRepository'; | ||||||
|  | import accelerationApi from '../services/acceleration'; | ||||||
| 
 | 
 | ||||||
| class MiningRoutes { | class MiningRoutes { | ||||||
|   public initRoutes(app: Application) { |   public initRoutes(app: Application) { | ||||||
| @ -41,6 +42,8 @@ class MiningRoutes { | |||||||
|       .get(config.MEMPOOL.API_URL_PREFIX + 'accelerations/block/:height', this.$getAccelerationsByHeight) |       .get(config.MEMPOOL.API_URL_PREFIX + 'accelerations/block/:height', this.$getAccelerationsByHeight) | ||||||
|       .get(config.MEMPOOL.API_URL_PREFIX + 'accelerations/recent/:interval', this.$getRecentAccelerations) |       .get(config.MEMPOOL.API_URL_PREFIX + 'accelerations/recent/:interval', this.$getRecentAccelerations) | ||||||
|       .get(config.MEMPOOL.API_URL_PREFIX + 'accelerations/total', this.$getAccelerationTotals) |       .get(config.MEMPOOL.API_URL_PREFIX + 'accelerations/total', this.$getAccelerationTotals) | ||||||
|  |       .get(config.MEMPOOL.API_URL_PREFIX + 'accelerations', this.$getActiveAccelerations) | ||||||
|  |       .post(config.MEMPOOL.API_URL_PREFIX + 'acceleration/request/:txid', this.$requestAcceleration) | ||||||
|     ; |     ; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -445,6 +448,37 @@ class MiningRoutes { | |||||||
|       res.status(500).send(e instanceof Error ? e.message : e); |       res.status(500).send(e instanceof Error ? e.message : e); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   private async $getActiveAccelerations(req: Request, res: Response): Promise<void> { | ||||||
|  |     try { | ||||||
|  |       res.header('Pragma', 'public'); | ||||||
|  |       res.header('Cache-control', 'public'); | ||||||
|  |       res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); | ||||||
|  |       if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid'].includes(config.MEMPOOL.NETWORK)) { | ||||||
|  |         res.status(400).send('Acceleration data is not available.'); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       res.status(200).send(accelerationApi.accelerations || []); | ||||||
|  |     } catch (e) { | ||||||
|  |       res.status(500).send(e instanceof Error ? e.message : e); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private async $requestAcceleration(req: Request, res: Response): Promise<void> { | ||||||
|  |     if (config.MEMPOOL_SERVICES.ACCELERATIONS || config.MEMPOOL.OFFICIAL) { | ||||||
|  |       res.status(405).send('not available.'); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     res.setHeader('Pragma', 'no-cache'); | ||||||
|  |     res.setHeader('Cache-control', 'private, no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0'); | ||||||
|  |     res.setHeader('expires', -1); | ||||||
|  |     try { | ||||||
|  |       accelerationApi.accelerationRequested(req.params.txid); | ||||||
|  |       res.status(200).send('ok'); | ||||||
|  |     } catch (e) { | ||||||
|  |       res.status(500).send(e instanceof Error ? e.message : e); | ||||||
|  |     } | ||||||
|  |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export default new MiningRoutes(); | export default new MiningRoutes(); | ||||||
|  | |||||||
| @ -1,8 +1,10 @@ | |||||||
| import config from '../../config'; | import config from '../../config'; | ||||||
| import logger from '../../logger'; | import logger from '../../logger'; | ||||||
| import { BlockExtended, PoolTag } from '../../mempool.interfaces'; | import { BlockExtended } from '../../mempool.interfaces'; | ||||||
| import axios from 'axios'; | import axios from 'axios'; | ||||||
| 
 | 
 | ||||||
|  | type MyAccelerationStatus = 'requested' | 'accelerating' | 'done'; | ||||||
|  | 
 | ||||||
| export interface Acceleration { | export interface Acceleration { | ||||||
|   txid: string, |   txid: string, | ||||||
|   added: number, |   added: number, | ||||||
| @ -35,18 +37,88 @@ export interface AccelerationHistory { | |||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class AccelerationApi { | class AccelerationApi { | ||||||
|   public async $fetchAccelerations(): Promise<Acceleration[] | null> { |   private apiPath = config.MEMPOOL.OFFICIAL ? (config.MEMPOOL_SERVICES.API + '/accelerator/accelerations') : (config.EXTERNAL_DATA_SERVER.MEMPOOL_API + '/accelerations'); | ||||||
|  |   private _accelerations: Acceleration[] | null = null; | ||||||
|  |   private lastPoll = 0; | ||||||
|  |   private forcePoll = false; | ||||||
|  |   private myAccelerations: Record<string, { status: MyAccelerationStatus, added: number, acceleration?: Acceleration }> = {}; | ||||||
|  | 
 | ||||||
|  |   public get accelerations(): Acceleration[] | null { | ||||||
|  |     return this._accelerations; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   public countMyAccelerationsWithStatus(filter: MyAccelerationStatus): number { | ||||||
|  |     return Object.values(this.myAccelerations).reduce((count, {status}) => { return count + (status === filter ? 1 : 0); }, 0); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   public accelerationRequested(txid: string): void { | ||||||
|  |     this.myAccelerations[txid] = { status: 'requested', added: Date.now() }; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   public accelerationConfirmed(): void { | ||||||
|  |     this.forcePoll = true; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private async $fetchAccelerations(): Promise<Acceleration[] | null> { | ||||||
|  |     try { | ||||||
|  |       const response = await axios.get(this.apiPath, { responseType: 'json', timeout: 10000 }); | ||||||
|  |       return response?.data || []; | ||||||
|  |     } catch (e) { | ||||||
|  |       logger.warn('Failed to fetch current accelerations from the mempool services backend: ' + (e instanceof Error ? e.message : e)); | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   public async $updateAccelerations(): Promise<Acceleration[] | null> { | ||||||
|     if (config.MEMPOOL_SERVICES.ACCELERATIONS) { |     if (config.MEMPOOL_SERVICES.ACCELERATIONS) { | ||||||
|       try { |       const accelerations = await this.$fetchAccelerations(); | ||||||
|         const response = await axios.get(`${config.MEMPOOL_SERVICES.API}/accelerator/accelerations`, { responseType: 'json', timeout: 10000 }); |       if (accelerations) { | ||||||
|         return response.data as Acceleration[]; |         this._accelerations = accelerations; | ||||||
|       } catch (e) { |         return this._accelerations; | ||||||
|         logger.warn('Failed to fetch current accelerations from the mempool services backend: ' + (e instanceof Error ? e.message : e)); |  | ||||||
|         return null; |  | ||||||
|       } |       } | ||||||
|     } else { |     } else { | ||||||
|       return []; |       return this.$updateAccelerationsOnDemand(); | ||||||
|     } |     } | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private async $updateAccelerationsOnDemand(): Promise<Acceleration[] | null> { | ||||||
|  |     const shouldUpdate = this.forcePoll | ||||||
|  |       || this.countMyAccelerationsWithStatus('requested') > 0 | ||||||
|  |       || (this.countMyAccelerationsWithStatus('accelerating') > 0 && this.lastPoll < (Date.now() - (10 * 60 * 1000))); | ||||||
|  | 
 | ||||||
|  |     // update accelerations if necessary
 | ||||||
|  |     if (shouldUpdate) { | ||||||
|  |       const accelerations = await this.$fetchAccelerations(); | ||||||
|  |       this.lastPoll = Date.now(); | ||||||
|  |       this.forcePoll = false; | ||||||
|  |       if (accelerations) { | ||||||
|  |         const latestAccelerations: Record<string, Acceleration> = {}; | ||||||
|  |         // set relevant accelerations to 'accelerating'
 | ||||||
|  |         for (const acc of accelerations) { | ||||||
|  |           if (this.myAccelerations[acc.txid]) { | ||||||
|  |             latestAccelerations[acc.txid] = acc; | ||||||
|  |             this.myAccelerations[acc.txid] = { status: 'accelerating', added: Date.now(), acceleration: acc }; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         // txs that are no longer accelerating are either confirmed or canceled, so mark for expiry
 | ||||||
|  |         for (const [txid, { status, acceleration }] of Object.entries(this.myAccelerations)) { | ||||||
|  |           if (status === 'accelerating' && !latestAccelerations[txid]) { | ||||||
|  |             this.myAccelerations[txid] = { status: 'done', added: Date.now(), acceleration }; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // clear expired accelerations (confirmed / failed / not accepted) after 10 minutes
 | ||||||
|  |     for (const [txid, { status, added }] of Object.entries(this.myAccelerations)) { | ||||||
|  |       if (['requested', 'done'].includes(status) && added < (Date.now() - (1000 * 60 * 10))) { | ||||||
|  |         delete this.myAccelerations[txid]; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     this._accelerations = Object.values(this.myAccelerations).map(({ acceleration }) => acceleration).filter(acc => acc) as Acceleration[]; | ||||||
|  |     return this._accelerations; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   public async $fetchAccelerationHistory(page?: number, status?: string): Promise<AccelerationHistory[] | null> { |   public async $fetchAccelerationHistory(page?: number, status?: string): Promise<AccelerationHistory[] | null> { | ||||||
|  | |||||||
| @ -538,9 +538,9 @@ class WebsocketHandler { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (config.MEMPOOL.RUST_GBT) { |     if (config.MEMPOOL.RUST_GBT) { | ||||||
|       await mempoolBlocks.$rustUpdateBlockTemplates(transactionIds, newMempool, added, removed, candidates, config.MEMPOOL_SERVICES.ACCELERATIONS); |       await mempoolBlocks.$rustUpdateBlockTemplates(transactionIds, newMempool, added, removed, candidates, true); | ||||||
|     } else { |     } else { | ||||||
|       await mempoolBlocks.$updateBlockTemplates(transactionIds, newMempool, added, removed, candidates, accelerationDelta, true, config.MEMPOOL_SERVICES.ACCELERATIONS); |       await mempoolBlocks.$updateBlockTemplates(transactionIds, newMempool, added, removed, candidates, accelerationDelta, true, true); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const mBlocks = mempoolBlocks.getMempoolBlocks(); |     const mBlocks = mempoolBlocks.getMempoolBlocks(); | ||||||
| @ -949,18 +949,14 @@ class WebsocketHandler { | |||||||
|     if (config.MEMPOOL.AUDIT && memPool.isInSync()) { |     if (config.MEMPOOL.AUDIT && memPool.isInSync()) { | ||||||
|       let projectedBlocks; |       let projectedBlocks; | ||||||
|       const auditMempool = _memPool; |       const auditMempool = _memPool; | ||||||
|       const isAccelerated = config.MEMPOOL_SERVICES.ACCELERATIONS && accelerationApi.isAcceleratedBlock(block, Object.values(mempool.getAccelerations())); |       const isAccelerated = accelerationApi.isAcceleratedBlock(block, Object.values(mempool.getAccelerations())); | ||||||
| 
 | 
 | ||||||
|       if ((config.MEMPOOL_SERVICES.ACCELERATIONS)) { |       if (config.MEMPOOL.RUST_GBT) { | ||||||
|         if (config.MEMPOOL.RUST_GBT) { |         const added = memPool.limitGBT ? (candidates?.added || []) : []; | ||||||
|           const added = memPool.limitGBT ? (candidates?.added || []) : []; |         const removed = memPool.limitGBT ? (candidates?.removed || []) : []; | ||||||
|           const removed = memPool.limitGBT ? (candidates?.removed || []) : []; |         projectedBlocks = await mempoolBlocks.$rustUpdateBlockTemplates(transactionIds, auditMempool, added, removed, candidates, isAccelerated, block.extras.pool.id); | ||||||
|           projectedBlocks = await mempoolBlocks.$rustUpdateBlockTemplates(transactionIds, auditMempool, added, removed, candidates, isAccelerated, block.extras.pool.id); |  | ||||||
|         } else { |  | ||||||
|           projectedBlocks = await mempoolBlocks.$makeBlockTemplates(transactionIds, auditMempool, candidates, false, isAccelerated, block.extras.pool.id); |  | ||||||
|         } |  | ||||||
|       } else { |       } else { | ||||||
|         projectedBlocks = mempoolBlocks.getMempoolBlocksWithTransactions(); |         projectedBlocks = await mempoolBlocks.$makeBlockTemplates(transactionIds, auditMempool, candidates, false, isAccelerated, block.extras.pool.id); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       if (Common.indexingEnabled()) { |       if (Common.indexingEnabled()) { | ||||||
| @ -1040,7 +1036,7 @@ class WebsocketHandler { | |||||||
|       const removed = memPool.limitGBT ? (candidates?.removed || []) : transactions; |       const removed = memPool.limitGBT ? (candidates?.removed || []) : transactions; | ||||||
|       await mempoolBlocks.$rustUpdateBlockTemplates(transactionIds, _memPool, added, removed, candidates, true); |       await mempoolBlocks.$rustUpdateBlockTemplates(transactionIds, _memPool, added, removed, candidates, true); | ||||||
|     } else { |     } else { | ||||||
|       await mempoolBlocks.$makeBlockTemplates(transactionIds, _memPool, candidates, true, config.MEMPOOL_SERVICES.ACCELERATIONS); |       await mempoolBlocks.$makeBlockTemplates(transactionIds, _memPool, candidates, true, true); | ||||||
|     } |     } | ||||||
|     const mBlocks = mempoolBlocks.getMempoolBlocks(); |     const mBlocks = mempoolBlocks.getMempoolBlocks(); | ||||||
|     const mBlockDeltas = mempoolBlocks.getMempoolBlockDeltas(); |     const mBlockDeltas = mempoolBlocks.getMempoolBlockDeltas(); | ||||||
|  | |||||||
| @ -229,7 +229,7 @@ class Server { | |||||||
|       const newMempool = await bitcoinApi.$getRawMempool(); |       const newMempool = await bitcoinApi.$getRawMempool(); | ||||||
|       const minFeeMempool = memPool.limitGBT ? await bitcoinSecondClient.getRawMemPool() : null; |       const minFeeMempool = memPool.limitGBT ? await bitcoinSecondClient.getRawMemPool() : null; | ||||||
|       const minFeeTip = memPool.limitGBT ? await bitcoinSecondClient.getBlockCount() : -1; |       const minFeeTip = memPool.limitGBT ? await bitcoinSecondClient.getBlockCount() : -1; | ||||||
|       const newAccelerations = await accelerationApi.$fetchAccelerations(); |       const newAccelerations = await accelerationApi.$updateAccelerations(); | ||||||
|       const numHandledBlocks = await blocks.$updateBlocks(); |       const numHandledBlocks = await blocks.$updateBlocks(); | ||||||
|       const pollRate = config.MEMPOOL.POLL_RATE_MS * (indexer.indexerIsRunning() ? 10 : 1); |       const pollRate = config.MEMPOOL.POLL_RATE_MS * (indexer.indexerIsRunning() ? 10 : 1); | ||||||
|       if (numHandledBlocks === 0) { |       if (numHandledBlocks === 0) { | ||||||
|  | |||||||
| @ -213,6 +213,15 @@ class AccelerationRepository { | |||||||
|         this.$saveAcceleration(accelerationInfo, block, block.extras.pool.id, successfulAccelerations); |         this.$saveAcceleration(accelerationInfo, block, block.extras.pool.id, successfulAccelerations); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |     let anyConfirmed = false; | ||||||
|  |     for (const acc of accelerations) { | ||||||
|  |       if (blockTxs[acc.txid]) { | ||||||
|  |         anyConfirmed = true; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     if (anyConfirmed) { | ||||||
|  |       accelerationApi.accelerationConfirmed(); | ||||||
|  |     } | ||||||
|     const lastSyncedHeight = await this.$getLastSyncedHeight(); |     const lastSyncedHeight = await this.$getLastSyncedHeight(); | ||||||
|     // if we've missed any blocks, let the indexer catch up from the last synced height on the next run
 |     // if we've missed any blocks, let the indexer catch up from the last synced height on the next run
 | ||||||
|     if (block.height === lastSyncedHeight + 1) { |     if (block.height === lastSyncedHeight + 1) { | ||||||
|  | |||||||
| @ -40,6 +40,7 @@ __MAINNET_BLOCK_AUDIT_START_HEIGHT__=${MAINNET_BLOCK_AUDIT_START_HEIGHT:=0} | |||||||
| __TESTNET_BLOCK_AUDIT_START_HEIGHT__=${TESTNET_BLOCK_AUDIT_START_HEIGHT:=0} | __TESTNET_BLOCK_AUDIT_START_HEIGHT__=${TESTNET_BLOCK_AUDIT_START_HEIGHT:=0} | ||||||
| __SIGNET_BLOCK_AUDIT_START_HEIGHT__=${SIGNET_BLOCK_AUDIT_START_HEIGHT:=0} | __SIGNET_BLOCK_AUDIT_START_HEIGHT__=${SIGNET_BLOCK_AUDIT_START_HEIGHT:=0} | ||||||
| __ACCELERATOR__=${ACCELERATOR:=false} | __ACCELERATOR__=${ACCELERATOR:=false} | ||||||
|  | __ACCELERATOR_BUTTON__=${ACCELERATOR_BUTTON:=true} | ||||||
| __SERVICES_API__=${SERVICES_API:=false} | __SERVICES_API__=${SERVICES_API:=false} | ||||||
| __PUBLIC_ACCELERATIONS__=${PUBLIC_ACCELERATIONS:=false} | __PUBLIC_ACCELERATIONS__=${PUBLIC_ACCELERATIONS:=false} | ||||||
| __HISTORICAL_PRICE__=${HISTORICAL_PRICE:=true} | __HISTORICAL_PRICE__=${HISTORICAL_PRICE:=true} | ||||||
| @ -70,6 +71,7 @@ export __MAINNET_BLOCK_AUDIT_START_HEIGHT__ | |||||||
| export __TESTNET_BLOCK_AUDIT_START_HEIGHT__ | export __TESTNET_BLOCK_AUDIT_START_HEIGHT__ | ||||||
| export __SIGNET_BLOCK_AUDIT_START_HEIGHT__ | export __SIGNET_BLOCK_AUDIT_START_HEIGHT__ | ||||||
| export __ACCELERATOR__ | export __ACCELERATOR__ | ||||||
|  | export __ACCELERATOR_BUTTON__ | ||||||
| export __SERVICES_API__ | export __SERVICES_API__ | ||||||
| export __PUBLIC_ACCELERATIONS__ | export __PUBLIC_ACCELERATIONS__ | ||||||
| export __HISTORICAL_PRICE__ | export __HISTORICAL_PRICE__ | ||||||
|  | |||||||
| @ -25,6 +25,7 @@ | |||||||
|   "HISTORICAL_PRICE": true, |   "HISTORICAL_PRICE": true, | ||||||
|   "ADDITIONAL_CURRENCIES": false, |   "ADDITIONAL_CURRENCIES": false, | ||||||
|   "ACCELERATOR": false, |   "ACCELERATOR": false, | ||||||
|  |   "ACCELERATOR_BUTTON": true, | ||||||
|   "PUBLIC_ACCELERATIONS": false, |   "PUBLIC_ACCELERATIONS": false, | ||||||
|   "SERVICES_API": "https://mempool.space/api/v1/services" |   "SERVICES_API": "https://mempool.space/api/v1/services" | ||||||
| } | } | ||||||
|  | |||||||
| @ -389,16 +389,22 @@ | |||||||
|                   </div> |                   </div> | ||||||
|                 } |                 } | ||||||
|               </div> |               </div> | ||||||
|               @if (canPayWithCashapp) { |               @if (canPayWithCashapp || canPayWithApplePay) { | ||||||
|                 <div class="col-sm text-center flex-grow-0  d-flex flex-column justify-content-center align-items-center"> |                 <div class="col-sm text-center flex-grow-0  d-flex flex-column justify-content-center align-items-center"> | ||||||
|                   <p class="text-nowrap">—<span i18n="or">OR</span>—</p> |                   <p class="text-nowrap">—<span i18n="or">OR</span>—</p> | ||||||
|                 </div> |                 </div> | ||||||
|               } |               } | ||||||
|             } |             } | ||||||
|             @if (canPayWithCashapp) { |             @if (canPayWithCashapp || canPayWithApplePay) { | ||||||
|               <div class="col-sm text-center d-flex flex-column justify-content-center align-items-center"> |               <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> <app-fiat [value]="cost"></app-fiat> with</p> |                 <p><ng-container i18n="transaction.pay|Pay button label">Pay</ng-container> <app-fiat [value]="cost"></app-fiat> with</p> | ||||||
|                 <img class="paymentMethod mx-2" src="/resources/cash-app.svg" height=55 (click)="moveToStep('cashapp')"> |                 @if (canPayWithCashapp) { | ||||||
|  |                   <img class="paymentMethod mx-2" style="width: 200px" src="/resources/cash-app.svg" height=55 (click)="moveToStep('cashapp')"> | ||||||
|  |                 } | ||||||
|  |                 @if (canPayWithApplePay) { | ||||||
|  |                   @if (canPayWithCashapp) { <hr class="w-25 mt-2 mb-2"> } | ||||||
|  |                   <img style="cursor: pointer;" src="/resources/apple-pay.svg" height=55 (click)="moveToStep('applepay')"> | ||||||
|  |                 } | ||||||
|               </div> |               </div> | ||||||
|             } |             } | ||||||
|           </div> |           </div> | ||||||
| @ -421,9 +427,9 @@ | |||||||
|         <button type="button" class="mt-1 btn btn-secondary btn-sm rounded-pill align-self-center" style="width: 200px" (click)="moveToStep('summary')" i18n="go-back">Go back</button> |         <button type="button" class="mt-1 btn btn-secondary btn-sm rounded-pill align-self-center" style="width: 200px" (click)="moveToStep('summary')" i18n="go-back">Go back</button> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|   } @else if (step === 'cashapp') { |   } @else if (step === 'cashapp' || step === 'applepay') { | ||||||
|     <!-- Show checkout page --> |     <!-- Show checkout page --> | ||||||
|     <div class="row mb-md-1 text-center"> |     <div class="row mb-md-1 text-center" id="confirm-title"> | ||||||
|       <div class="col-sm" id="confirm-payment-title"> |       <div class="col-sm" id="confirm-payment-title"> | ||||||
|         <h1 style="font-size: larger;"><ng-content select="[slot='checkout-title']"></ng-content><span class="default-slot" i18n="accelerator.confirm-your-payment">Confirm your payment</span></h1> |         <h1 style="font-size: larger;"><ng-content select="[slot='checkout-title']"></ng-content><span class="default-slot" i18n="accelerator.confirm-your-payment">Confirm your payment</span></h1> | ||||||
|       </div> |       </div> | ||||||
| @ -437,7 +443,7 @@ | |||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
| 
 | 
 | ||||||
|     @if (!loadingCashapp) { |     @if (step === 'cashapp' && !loadingCashapp || step === 'applepay' && !loadingApplePay) { | ||||||
|       <div class="row text-center mt-1"> |       <div class="row text-center mt-1"> | ||||||
|         <div class="col-sm"> |         <div class="col-sm"> | ||||||
|           <div class="form-group w-100"> |           <div class="form-group w-100"> | ||||||
| @ -456,8 +462,12 @@ | |||||||
|     <div class="row text-center mt-1"> |     <div class="row text-center mt-1"> | ||||||
|       <div class="col-sm"> |       <div class="col-sm"> | ||||||
|         <div class="form-group w-100"> |         <div class="form-group w-100"> | ||||||
|           <div id="cash-app-pay" class="d-inline-block" [style]="loadingCashapp ? 'opacity: 0; width: 0px; height: 0px; pointer-events: none;' : ''"></div> |           @if (step === 'applepay') { | ||||||
|           @if (loadingCashapp) { |             <div id="apple-pay-button" class="apple-pay-button apple-pay-button-white" [style]="loadingApplePay ? 'opacity: 0; width: 0px; height: 0px; pointer-events: none;' : ''"></div> | ||||||
|  |           } @else if (step === 'cashapp') {           | ||||||
|  |             <div id="cash-app-pay" class="d-inline-block" [style]="loadingCashapp ? 'opacity: 0; width: 0px; height: 0px; pointer-events: none;' : ''"></div> | ||||||
|  |           } | ||||||
|  |           @if (loadingCashapp || loadingApplePay) { | ||||||
|           <div display="d-flex flex-row justify-content-center"> |           <div display="d-flex flex-row justify-content-center"> | ||||||
|             <span i18n="accelerator.loading-payment-method">Loading payment method...</span> |             <span i18n="accelerator.loading-payment-method">Loading payment method...</span> | ||||||
|             <div class="ml-2 spinner-border text-light" style="width: 25px; height: 25px"></div> |             <div class="ml-2 spinner-border text-light" style="width: 25px; height: 25px"></div> | ||||||
| @ -549,7 +559,7 @@ | |||||||
|   <button type="button" *ngIf="advancedEnabled" class="btn btn-sm btn-outline-info btn-small-height ml-2" (click)="moveToStep('quote')" i18n="accelerator.customize">customize</button> |   <button type="button" *ngIf="advancedEnabled" class="btn btn-sm btn-outline-info btn-small-height ml-2" (click)="moveToStep('quote')" i18n="accelerator.customize">customize</button> | ||||||
| </ng-template> | </ng-template> | ||||||
| 
 | 
 | ||||||
| <ng-template #accelerateTo let-x i18n="accelerator.accelerate-to-x">Accelerate to ~{{ x | number : '1.0-0' }} sat/vB</ng-template> | <ng-template id="accelerate-to" #accelerateTo let-x i18n="accelerator.accelerate-to-x">Accelerate to ~{{ x | number : '1.0-0' }} sat/vB</ng-template> | ||||||
| 
 | 
 | ||||||
| <ng-template #accelerateButton> | <ng-template #accelerateButton> | ||||||
|   <div class="position-relative"> |   <div class="position-relative"> | ||||||
|  | |||||||
| @ -11,8 +11,7 @@ | |||||||
| .paymentMethod { | .paymentMethod { | ||||||
|   padding: 10px; |   padding: 10px; | ||||||
|   background-color: var(--secondary); |   background-color: var(--secondary); | ||||||
|   border-radius: 15px; |   border-radius: 10px; | ||||||
|   border: 2px solid var(--bg); |  | ||||||
|   cursor: pointer; |   cursor: pointer; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -203,3 +202,18 @@ | |||||||
| .btn-error-wrapper { | .btn-error-wrapper { | ||||||
|   height: 26px; |   height: 26px; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | .apple-pay-button { | ||||||
|  |     display: inline-block; | ||||||
|  |     -webkit-appearance: -apple-pay-button; | ||||||
|  |     -apple-pay-button-type: plain; /* Use any supported button type. */ | ||||||
|  | } | ||||||
|  | .apple-pay-button-black { | ||||||
|  |     -apple-pay-button-style: black; | ||||||
|  | } | ||||||
|  | .apple-pay-button-white { | ||||||
|  |     -apple-pay-button-style: white; | ||||||
|  | } | ||||||
|  | .apple-pay-button-white-with-line { | ||||||
|  |     -apple-pay-button-style: white-outline; | ||||||
|  | } | ||||||
| @ -1,7 +1,7 @@ | |||||||
| import { Component, OnInit, OnDestroy, Output, EventEmitter, Input, ChangeDetectorRef, SimpleChanges, HostListener } from '@angular/core'; | import { Component, OnInit, OnDestroy, Output, EventEmitter, Input, ChangeDetectorRef, SimpleChanges, HostListener } from '@angular/core'; | ||||||
| import { Subscription, tap, of, catchError, Observable, switchMap } from 'rxjs'; | import { Subscription, tap, of, catchError, Observable, switchMap } from 'rxjs'; | ||||||
| import { ServicesApiServices } from '../../services/services-api.service'; | import { ServicesApiServices } from '../../services/services-api.service'; | ||||||
| import { nextRoundNumber, insecureRandomUUID } from '../../shared/common.utils'; | import { md5, nextRoundNumber, insecureRandomUUID } from '../../shared/common.utils'; | ||||||
| import { StateService } from '../../services/state.service'; | import { StateService } from '../../services/state.service'; | ||||||
| import { AudioService } from '../../services/audio.service'; | import { AudioService } from '../../services/audio.service'; | ||||||
| import { ETA, EtaService } from '../../services/eta.service'; | import { ETA, EtaService } from '../../services/eta.service'; | ||||||
| @ -9,6 +9,7 @@ import { Transaction } from '../../interfaces/electrs.interface'; | |||||||
| import { MiningStats } from '../../services/mining.service'; | import { MiningStats } from '../../services/mining.service'; | ||||||
| import { IAuth, AuthServiceMempool } from '../../services/auth.service'; | import { IAuth, AuthServiceMempool } from '../../services/auth.service'; | ||||||
| import { EnterpriseService } from '../../services/enterprise.service'; | import { EnterpriseService } from '../../services/enterprise.service'; | ||||||
|  | import { ApiService } from '../../services/api.service'; | ||||||
| 
 | 
 | ||||||
| export type PaymentMethod = 'balance' | 'bitcoin' | 'cashapp'; | export type PaymentMethod = 'balance' | 'bitcoin' | 'cashapp'; | ||||||
| 
 | 
 | ||||||
| @ -46,7 +47,7 @@ export const MIN_BID_RATIO = 1; | |||||||
| export const DEFAULT_BID_RATIO = 2; | export const DEFAULT_BID_RATIO = 2; | ||||||
| export const MAX_BID_RATIO = 4; | export const MAX_BID_RATIO = 4; | ||||||
| 
 | 
 | ||||||
| type CheckoutStep = 'quote' | 'summary' | 'checkout' | 'cashapp' | 'processing' | 'paid' | 'success'; | type CheckoutStep = 'quote' | 'summary' | 'checkout' | 'cashapp' | 'applepay' | 'processing' | 'paid' | 'success'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-accelerate-checkout', |   selector: 'app-accelerate-checkout', | ||||||
| @ -60,6 +61,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | |||||||
|   @Input() eta: ETA; |   @Input() eta: ETA; | ||||||
|   @Input() scrollEvent: boolean; |   @Input() scrollEvent: boolean; | ||||||
|   @Input() cashappEnabled: boolean = true; |   @Input() cashappEnabled: boolean = true; | ||||||
|  |   @Input() applePayEnabled: boolean = false; | ||||||
|   @Input() advancedEnabled: boolean = false; |   @Input() advancedEnabled: boolean = false; | ||||||
|   @Input() forceMobile: boolean = false; |   @Input() forceMobile: boolean = false; | ||||||
|   @Input() showDetails: boolean = false; |   @Input() showDetails: boolean = false; | ||||||
| @ -109,11 +111,12 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | |||||||
| 
 | 
 | ||||||
|   // square
 |   // square
 | ||||||
|   loadingCashapp = false; |   loadingCashapp = false; | ||||||
|  |   loadingApplePay = false; | ||||||
|   cashappError = false; |   cashappError = false; | ||||||
|   cashappSubmit: any; |   cashappSubmit: any; | ||||||
|   payments: any; |   payments: any; | ||||||
|   cashAppPay: any; |   cashAppPay: any; | ||||||
|   cashAppSubscription: Subscription; |   applePay: any; | ||||||
|   conversionsSubscription: Subscription; |   conversionsSubscription: Subscription; | ||||||
|   conversions: any; |   conversions: any; | ||||||
|    |    | ||||||
| @ -123,6 +126,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | |||||||
| 
 | 
 | ||||||
|   constructor( |   constructor( | ||||||
|     public stateService: StateService, |     public stateService: StateService, | ||||||
|  |     private apiService: ApiService, | ||||||
|     private servicesApiService: ServicesApiServices, |     private servicesApiService: ServicesApiServices, | ||||||
|     private etaService: EtaService, |     private etaService: EtaService, | ||||||
|     private audioService: AudioService, |     private audioService: AudioService, | ||||||
| @ -131,6 +135,12 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | |||||||
|     private enterpriseService: EnterpriseService, |     private enterpriseService: EnterpriseService, | ||||||
|   ) { |   ) { | ||||||
|     this.accelerationUUID = insecureRandomUUID(); |     this.accelerationUUID = insecureRandomUUID(); | ||||||
|  | 
 | ||||||
|  |     // Check if Apple Pay available
 | ||||||
|  |     // @ts-ignore https://developer.apple.com/documentation/apple_pay_on_the_web/apple_pay_js_api/checking_for_apple_pay_availability#overview
 | ||||||
|  |     if (window.ApplePaySession) { | ||||||
|  |       this.applePayEnabled = true; | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   ngOnInit() { |   ngOnInit() { | ||||||
| @ -212,6 +222,12 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | |||||||
|       this.loadingCashapp = true; |       this.loadingCashapp = true; | ||||||
|       this.insertSquare(); |       this.insertSquare(); | ||||||
|       this.setupSquare(); |       this.setupSquare(); | ||||||
|  |       this.scrollToElementWithTimeout('confirm-title', 'center', 100); | ||||||
|  |     } else if (this._step === 'applepay' && this.applePayEnabled) { | ||||||
|  |       this.loadingApplePay = true; | ||||||
|  |       this.insertSquare(); | ||||||
|  |       this.setupSquare(); | ||||||
|  |       this.scrollToElementWithTimeout('confirm-title', 'center', 100); | ||||||
|     } else if (this._step === 'paid') { |     } else if (this._step === 'paid') { | ||||||
|       this.timePaid = Date.now(); |       this.timePaid = Date.now(); | ||||||
|       this.timeoutTimer = setTimeout(() => { |       this.timeoutTimer = setTimeout(() => { | ||||||
| @ -229,8 +245,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|   * Scroll to element id with or without setTimeout |    * Scroll to element id with or without setTimeout | ||||||
|   */ |    */ | ||||||
|   scrollToElementWithTimeout(id: string, position: ScrollLogicalPosition, timeout: number = 1000): void { |   scrollToElementWithTimeout(id: string, position: ScrollLogicalPosition, timeout: number = 1000): void { | ||||||
|     setTimeout(() => { |     setTimeout(() => { | ||||||
|       this.scrollToElement(id, position); |       this.scrollToElement(id, position); | ||||||
| @ -370,10 +386,11 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | |||||||
|       this.accelerationUUID |       this.accelerationUUID | ||||||
|     ).subscribe({ |     ).subscribe({ | ||||||
|       next: () => { |       next: () => { | ||||||
|  |         this.apiService.logAccelerationRequest$(this.tx.txid).subscribe(); | ||||||
|         this.audioService.playSound('ascend-chime-cartoon'); |         this.audioService.playSound('ascend-chime-cartoon'); | ||||||
|         this.showSuccess = true; |         this.showSuccess = true; | ||||||
|         this.estimateSubscription.unsubscribe(); |         this.estimateSubscription.unsubscribe(); | ||||||
|         this.moveToStep('paid') |         this.moveToStep('paid'); | ||||||
|       }, |       }, | ||||||
|       error: (response) => { |       error: (response) => { | ||||||
|         this.accelerateError = response.error; |         this.accelerateError = response.error; | ||||||
| @ -421,17 +438,113 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | |||||||
|     try { |     try { | ||||||
|       //@ts-ignore
 |       //@ts-ignore
 | ||||||
|       this.payments = window.Square.payments(this.square.appId, this.square.locationId) |       this.payments = window.Square.payments(this.square.appId, this.square.locationId) | ||||||
|       await this.requestCashAppPayment(); |       const urlParams = new URLSearchParams(window.location.search); | ||||||
|  |       if (this._step === 'cashapp' || urlParams.get('cash_request_id')) { | ||||||
|  |         await this.requestCashAppPayment(); | ||||||
|  |       } else if (this._step === 'applepay') { | ||||||
|  |         await this.requestApplePayPayment(); | ||||||
|  |       } | ||||||
|     } catch (e) { |     } catch (e) { | ||||||
|       console.debug('Error loading Square Payments', e); |       console.debug('Error loading Square Payments', e); | ||||||
|       this.cashappError = true; |       this.cashappError = true; | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   async requestCashAppPayment() { | 
 | ||||||
|     if (this.cashAppSubscription) { |   /** | ||||||
|       this.cashAppSubscription.unsubscribe(); |    * APPLE PAY | ||||||
|  |    */ | ||||||
|  |   async requestApplePayPayment() { | ||||||
|  |     if (this.conversionsSubscription) { | ||||||
|  |       this.conversionsSubscription.unsubscribe(); | ||||||
|     } |     } | ||||||
|  |      | ||||||
|  |     this.conversionsSubscription = this.stateService.conversions$.subscribe( | ||||||
|  |       async (conversions) => { | ||||||
|  |         this.conversions = conversions; | ||||||
|  |         if (this.applePay) { | ||||||
|  |           this.applePay.destroy(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const costUSD = this.cost / 100_000_000 * conversions.USD; | ||||||
|  |         const paymentRequest = this.payments.paymentRequest({ | ||||||
|  |           countryCode: 'US', | ||||||
|  |           currencyCode: 'USD', | ||||||
|  |           total: { | ||||||
|  |             amount: costUSD.toFixed(2), | ||||||
|  |             label: 'Total', | ||||||
|  |           }, | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         try { | ||||||
|  |           this.applePay = await this.payments.applePay(paymentRequest); | ||||||
|  |           const applePayButton = document.getElementById('apple-pay-button'); | ||||||
|  |           if (!applePayButton) { | ||||||
|  |             console.error(`Unable to find apple pay button id='apple-pay-button'`); | ||||||
|  |             // Try again
 | ||||||
|  |             setTimeout(this.requestApplePayPayment.bind(this), 500); | ||||||
|  |             return; | ||||||
|  |           } | ||||||
|  |           this.loadingApplePay = false; | ||||||
|  |           applePayButton.addEventListener('click', async event => { | ||||||
|  |             event.preventDefault(); | ||||||
|  |             const tokenResult = await this.applePay.tokenize(); | ||||||
|  |             if (tokenResult?.status === 'OK') { | ||||||
|  |               const card = tokenResult.details?.card; | ||||||
|  |               if (!card || !card.brand || !card.expMonth || !card.expYear || !card.last4) { | ||||||
|  |                 console.error(`Cannot retreive payment card details`); | ||||||
|  |                 this.accelerateError = 'apple_pay_no_card_details'; | ||||||
|  |                 return; | ||||||
|  |               } | ||||||
|  |               const cardTag = md5(`${card.brand}${card.expMonth}${card.expYear}${card.last4}`.toLowerCase()); | ||||||
|  |               this.servicesApiService.accelerateWithApplePay$( | ||||||
|  |                 this.tx.txid, | ||||||
|  |                 tokenResult.token, | ||||||
|  |                 cardTag, | ||||||
|  |                 `accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`, | ||||||
|  |                 this.accelerationUUID | ||||||
|  |               ).subscribe({ | ||||||
|  |                 next: () => { | ||||||
|  |                   this.audioService.playSound('ascend-chime-cartoon'); | ||||||
|  |                   if (this.applePay) { | ||||||
|  |                     this.applePay.destroy(); | ||||||
|  |                   } | ||||||
|  |                   setTimeout(() => { | ||||||
|  |                     this.moveToStep('paid'); | ||||||
|  |                   }, 1000); | ||||||
|  |                 }, | ||||||
|  |                 error: (response) => { | ||||||
|  |                   this.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); | ||||||
|  |                       window.location.assign(window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, ``)); | ||||||
|  |                     }, 3000); | ||||||
|  |                   } | ||||||
|  |                 } | ||||||
|  |               }); | ||||||
|  |             } else { | ||||||
|  |               let errorMessage = `Tokenization failed with status: ${tokenResult.status}`; | ||||||
|  |               if (tokenResult.errors) { | ||||||
|  |                 errorMessage += ` and errors: ${JSON.stringify( | ||||||
|  |                   tokenResult.errors, | ||||||
|  |                 )}`;
 | ||||||
|  |               } | ||||||
|  |               throw new Error(errorMessage); | ||||||
|  |             } | ||||||
|  |           }); | ||||||
|  |         } catch (e) { | ||||||
|  |           console.error(e); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * CASHAPP | ||||||
|  |    */ | ||||||
|  |   async requestCashAppPayment() { | ||||||
|     if (this.conversionsSubscription) { |     if (this.conversionsSubscription) { | ||||||
|       this.conversionsSubscription.unsubscribe(); |       this.conversionsSubscription.unsubscribe(); | ||||||
|     } |     } | ||||||
| @ -449,7 +562,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | |||||||
|           countryCode: 'US', |           countryCode: 'US', | ||||||
|           currencyCode: 'USD', |           currencyCode: 'USD', | ||||||
|           total: { |           total: { | ||||||
|             amount: costUSD.toString(), |             amount: costUSD.toFixed(2), | ||||||
|             label: 'Total', |             label: 'Total', | ||||||
|             pending: true, |             pending: true, | ||||||
|             productUrl: `${redirectHostname}/tracker/${this.tx.txid}`, |             productUrl: `${redirectHostname}/tracker/${this.tx.txid}`, | ||||||
| @ -467,23 +580,23 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | |||||||
|         } |         } | ||||||
|         this.loadingCashapp = false; |         this.loadingCashapp = false; | ||||||
| 
 | 
 | ||||||
|         const that = this; |         this.cashAppPay.addEventListener('ontokenization', event => { | ||||||
|         this.cashAppPay.addEventListener('ontokenization', function (event) { |  | ||||||
|           const { tokenResult, error } = event.detail; |           const { tokenResult, error } = event.detail; | ||||||
|           if (error) { |           if (error) { | ||||||
|             this.accelerateError = error; |             this.accelerateError = error; | ||||||
|           } else if (tokenResult.status === 'OK') { |           } else if (tokenResult.status === 'OK') { | ||||||
|             that.servicesApiService.accelerateWithCashApp$( |             this.servicesApiService.accelerateWithCashApp$( | ||||||
|               that.tx.txid, |               this.tx.txid, | ||||||
|               tokenResult.token, |               tokenResult.token, | ||||||
|               tokenResult.details.cashAppPay.cashtag, |               tokenResult.details.cashAppPay.cashtag, | ||||||
|               tokenResult.details.cashAppPay.referenceId, |               tokenResult.details.cashAppPay.referenceId, | ||||||
|               that.accelerationUUID |               this.accelerationUUID | ||||||
|             ).subscribe({ |             ).subscribe({ | ||||||
|               next: () => { |               next: () => { | ||||||
|                 that.audioService.playSound('ascend-chime-cartoon'); |                 this.apiService.logAccelerationRequest$(this.tx.txid).subscribe(); | ||||||
|                 if (that.cashAppPay) { |                 this.audioService.playSound('ascend-chime-cartoon'); | ||||||
|                   that.cashAppPay.destroy(); |                 if (this.cashAppPay) { | ||||||
|  |                   this.cashAppPay.destroy(); | ||||||
|                 } |                 } | ||||||
|                 setTimeout(() => { |                 setTimeout(() => { | ||||||
|                   this.moveToStep('paid'); |                   this.moveToStep('paid'); | ||||||
| @ -494,7 +607,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | |||||||
|                 }, 1000); |                 }, 1000); | ||||||
|               }, |               }, | ||||||
|               error: (response) => { |               error: (response) => { | ||||||
|                 that.accelerateError = response.error; |                 this.accelerateError = response.error; | ||||||
|                 if (!(response.status === 403 && response.error === 'not_available')) { |                 if (!(response.status === 403 && response.error === 'not_available')) { | ||||||
|                   setTimeout(() => { |                   setTimeout(() => { | ||||||
|                     // Reset everything by reloading the page :D, can be improved
 |                     // Reset everything by reloading the page :D, can be improved
 | ||||||
| @ -530,9 +643,10 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   bitcoinPaymentCompleted(): void { |   bitcoinPaymentCompleted(): void { | ||||||
|  |     this.apiService.logAccelerationRequest$(this.tx.txid).subscribe(); | ||||||
|     this.audioService.playSound('ascend-chime-cartoon'); |     this.audioService.playSound('ascend-chime-cartoon'); | ||||||
|     this.estimateSubscription.unsubscribe(); |     this.estimateSubscription.unsubscribe(); | ||||||
|     this.moveToStep('paid') |     this.moveToStep('paid'); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   isLoggedIn(): boolean { |   isLoggedIn(): boolean { | ||||||
| @ -565,6 +679,13 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | |||||||
|     return !!this.estimate?.availablePaymentMethods?.cashapp; |     return !!this.estimate?.availablePaymentMethods?.cashapp; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   get couldPayWithApplePay() { | ||||||
|  |     if (!this.applePayEnabled) { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |     return !!this.estimate?.availablePaymentMethods?.applePay; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   get couldPayWithBalance() { |   get couldPayWithBalance() { | ||||||
|     if (!this.hasAccessToBalanceMode) { |     if (!this.hasAccessToBalanceMode) { | ||||||
|       return false; |       return false; | ||||||
| @ -573,7 +694,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   get couldPay() { |   get couldPay() { | ||||||
|     return this.couldPayWithBalance || this.couldPayWithBitcoin || this.couldPayWithCashapp; |     return this.couldPayWithBalance || this.couldPayWithBitcoin || this.couldPayWithCashapp || this.couldPayWithApplePay; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   get canPayWithBitcoin() { |   get canPayWithBitcoin() { | ||||||
| @ -597,6 +718,22 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | |||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   get canPayWithApplePay() { | ||||||
|  |     if (!this.applePayEnabled || !this.conversions) { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const paymentMethod = this.estimate?.availablePaymentMethods?.applePay; | ||||||
|  |     if (paymentMethod) { | ||||||
|  |       const costUSD = (this.cost / 100_000_000 * this.conversions.USD); | ||||||
|  |       if (costUSD >= paymentMethod.min && costUSD <= paymentMethod.max) { | ||||||
|  |         return true; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   get canPayWithBalance() { |   get canPayWithBalance() { | ||||||
|     if (!this.hasAccessToBalanceMode) { |     if (!this.hasAccessToBalanceMode) { | ||||||
|       return false; |       return false; | ||||||
| @ -606,7 +743,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   get canPay() { |   get canPay() { | ||||||
|     return this.canPayWithBalance || this.canPayWithBitcoin || this.canPayWithCashapp; |     return this.canPayWithBalance || this.canPayWithBitcoin || this.canPayWithCashapp || this.canPayWithApplePay; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   get hasAccessToBalanceMode() { |   get hasAccessToBalanceMode() { | ||||||
|  | |||||||
| @ -16,6 +16,7 @@ | |||||||
|         <ng-container *ngIf="!pending"> |         <ng-container *ngIf="!pending"> | ||||||
|           <th class="fee text-right" i18n="transaction.bid-boost|Bid Boost">Bid Boost</th> |           <th class="fee text-right" i18n="transaction.bid-boost|Bid Boost">Bid Boost</th> | ||||||
|           <th class="block text-right" i18n="shared.block-title">Block</th> |           <th class="block text-right" i18n="shared.block-title">Block</th> | ||||||
|  |           <th class="pool text-right" i18n="mining.pool-name" *ngIf="!this.widget">Pool</th> | ||||||
|           <th class="status text-right" i18n="transaction.status|Transaction Status">Status</th> |           <th class="status text-right" i18n="transaction.status|Transaction Status">Status</th> | ||||||
|           <th class="date text-right" i18n="accelerator.requested" *ngIf="!this.widget">Requested</th> |           <th class="date text-right" i18n="accelerator.requested" *ngIf="!this.widget">Requested</th> | ||||||
|         </ng-container> |         </ng-container> | ||||||
| @ -49,6 +50,16 @@ | |||||||
|               <a *ngIf="acceleration.blockHeight" [routerLink]="['/block' | relativeUrl, acceleration.blockHeight]">{{ acceleration.blockHeight }}</a> |               <a *ngIf="acceleration.blockHeight" [routerLink]="['/block' | relativeUrl, acceleration.blockHeight]">{{ acceleration.blockHeight }}</a> | ||||||
|               <span *ngIf="!acceleration.blockHeight">~</span> |               <span *ngIf="!acceleration.blockHeight">~</span> | ||||||
|             </td> |             </td> | ||||||
|  |             <td class="pool text-right" *ngIf="!this.widget"> | ||||||
|  |               @if (acceleration.minedByPoolUniqueId && pools[acceleration.minedByPoolUniqueId]) { | ||||||
|  |                 <a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, pools[acceleration.minedByPoolUniqueId].slug]" class="badge" style="color: #FFF;padding:0;"> | ||||||
|  |                   <img class="pool-logo" [src]="'/resources/mining-pools/' + pools[acceleration.minedByPoolUniqueId].slug + '.svg'" onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + pools[acceleration.minedByPoolUniqueId].name + ' mining pool'"> | ||||||
|  |                   {{ pools[acceleration.minedByPoolUniqueId].name }} | ||||||
|  |                 </a> | ||||||
|  |               } @else { | ||||||
|  |                 ~ | ||||||
|  |               } | ||||||
|  |             </td> | ||||||
|             <td class="status text-right"> |             <td class="status text-right"> | ||||||
|               <span *ngIf="acceleration.status === 'accelerating'" class="badge badge-warning" i18n="accelerator.pending">Pending</span> |               <span *ngIf="acceleration.status === 'accelerating'" class="badge badge-warning" i18n="accelerator.pending">Pending</span> | ||||||
|               <span *ngIf="acceleration.status.includes('completed')" class="badge badge-success" i18n="">Completed <span *ngIf="acceleration.status === 'completed_provisional'">🔄</span></span> |               <span *ngIf="acceleration.status.includes('completed')" class="badge badge-success" i18n="">Completed <span *ngIf="acceleration.status === 'completed_provisional'">🔄</span></span> | ||||||
|  | |||||||
| @ -12,7 +12,7 @@ | |||||||
|   padding-bottom: 0px; |   padding-bottom: 0px; | ||||||
| } | } | ||||||
| .container-xl.legacy { | .container-xl.legacy { | ||||||
|   max-width: 1140px; |   max-width: 1200px; | ||||||
| } | } | ||||||
| .container-xl.widget-container { | .container-xl.widget-container { | ||||||
|   min-height: 335px; |   min-height: 335px; | ||||||
| @ -72,9 +72,25 @@ tr, td, th { | |||||||
| 
 | 
 | ||||||
| .block { | .block { | ||||||
|   width: 15%; |   width: 15%; | ||||||
|  |   @media (max-width: 900px) { | ||||||
|  |     display: none; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .pool { | ||||||
|  |   width: 15%; | ||||||
|  |    | ||||||
|   @media (max-width: 700px) { |   @media (max-width: 700px) { | ||||||
|     display: none; |     display: none; | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   .pool-logo { | ||||||
|  |     width: 22px; | ||||||
|  |     height: 22px; | ||||||
|  |     position: relative; | ||||||
|  |     top: -1px; | ||||||
|  |     margin-right: 2px; | ||||||
|  |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .status { | .status { | ||||||
|  | |||||||
| @ -1,11 +1,12 @@ | |||||||
| import { Component, OnInit, ChangeDetectionStrategy, Input, ChangeDetectorRef, OnDestroy, Inject, LOCALE_ID } from '@angular/core'; | import { Component, OnInit, ChangeDetectionStrategy, Input, ChangeDetectorRef, OnDestroy, Inject, LOCALE_ID } from '@angular/core'; | ||||||
| import { BehaviorSubject, Observable, Subscription, catchError, filter, of, switchMap, tap, throttleTime } from 'rxjs'; | import { BehaviorSubject, Observable, Subscription, catchError, filter, of, switchMap, tap, throttleTime } from 'rxjs'; | ||||||
| import { Acceleration, BlockExtended } from '../../../interfaces/node-api.interface'; | import { Acceleration, BlockExtended, SinglePoolStats } from '../../../interfaces/node-api.interface'; | ||||||
| import { StateService } from '../../../services/state.service'; | import { StateService } from '../../../services/state.service'; | ||||||
| import { WebsocketService } from '../../../services/websocket.service'; | import { WebsocketService } from '../../../services/websocket.service'; | ||||||
| import { ServicesApiServices } from '../../../services/services-api.service'; | import { ServicesApiServices } from '../../../services/services-api.service'; | ||||||
| import { SeoService } from '../../../services/seo.service'; | import { SeoService } from '../../../services/seo.service'; | ||||||
| import { ActivatedRoute, Router } from '@angular/router'; | import { ActivatedRoute, Router } from '@angular/router'; | ||||||
|  | import { MiningService } from '../../../services/mining.service'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-accelerations-list', |   selector: 'app-accelerations-list', | ||||||
| @ -30,11 +31,13 @@ export class AccelerationsListComponent implements OnInit, OnDestroy { | |||||||
|   keyNavigationSubscription: Subscription; |   keyNavigationSubscription: Subscription; | ||||||
|   dir: 'rtl' | 'ltr' = 'ltr'; |   dir: 'rtl' | 'ltr' = 'ltr'; | ||||||
|   paramSubscription: Subscription; |   paramSubscription: Subscription; | ||||||
|  |   pools: { [id: number]: SinglePoolStats } = {}; | ||||||
| 
 | 
 | ||||||
|   constructor( |   constructor( | ||||||
|     private servicesApiService: ServicesApiServices, |     private servicesApiService: ServicesApiServices, | ||||||
|     private websocketService: WebsocketService, |     private websocketService: WebsocketService, | ||||||
|     public stateService: StateService, |     public stateService: StateService, | ||||||
|  |     private miningService: MiningService, | ||||||
|     private cd: ChangeDetectorRef, |     private cd: ChangeDetectorRef, | ||||||
|     private seoService: SeoService, |     private seoService: SeoService, | ||||||
|     private route: ActivatedRoute, |     private route: ActivatedRoute, | ||||||
| @ -79,6 +82,12 @@ export class AccelerationsListComponent implements OnInit, OnDestroy { | |||||||
|       ).subscribe(() => { |       ).subscribe(() => { | ||||||
|         this.pageChange(this.page); |         this.pageChange(this.page); | ||||||
|       }); |       }); | ||||||
|  | 
 | ||||||
|  |       this.miningService.getMiningStats('1m').subscribe(stats => { | ||||||
|  |         for (const pool of stats.pools) { | ||||||
|  |           this.pools[pool.poolUniqueId] = pool; | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     this.skeletonLines = this.widget === true ? [...Array(6).keys()] : [...Array(15).keys()]; |     this.skeletonLines = this.widget === true ? [...Array(6).keys()] : [...Array(15).keys()]; | ||||||
|  | |||||||
| @ -249,7 +249,7 @@ | |||||||
| </ng-template> | </ng-template> | ||||||
| 
 | 
 | ||||||
| <ng-template #pendingBalanceRow> | <ng-template #pendingBalanceRow> | ||||||
|   <td i18n="address.unconfirmed-balance" class="font-italic">Unconfirmed balance</td> |   <td i18n="accelerator.pending-state" class="font-italic">Pending</td> | ||||||
|   <td *ngIf="mempoolStats.funded_txo_sum !== undefined; else confidentialTd" class="font-italic wrap-cell"><app-amount [satoshis]="mempoolStats.balance" [noFiat]="true" [addPlus]="true"></app-amount> <span class="fiat"><app-fiat [value]="mempoolStats.balance"></app-fiat></span></td> |   <td *ngIf="mempoolStats.funded_txo_sum !== undefined; else confidentialTd" class="font-italic wrap-cell"><app-amount [satoshis]="mempoolStats.balance" [noFiat]="true" [addPlus]="true"></app-amount> <span class="fiat"><app-fiat [value]="mempoolStats.balance"></app-fiat></span></td> | ||||||
| </ng-template> | </ng-template> | ||||||
| 
 | 
 | ||||||
| @ -259,7 +259,7 @@ | |||||||
| </ng-template> | </ng-template> | ||||||
| 
 | 
 | ||||||
| <ng-template #pendingUtxoRow> | <ng-template #pendingUtxoRow> | ||||||
|   <td i18n="address.unconfirmed-utxos" class="font-italic">Unconfirmed UTXOs</td> |   <td i18n="address.pending-utxos" class="font-italic">Pending UTXOs</td> | ||||||
|   <td class="font-italic wrap-cell">{{ mempoolStats.utxos > 0 ? '+' : ''}}{{ mempoolStats.utxos }}</td> |   <td class="font-italic wrap-cell">{{ mempoolStats.utxos > 0 ? '+' : ''}}{{ mempoolStats.utxos }}</td> | ||||||
| </ng-template> | </ng-template> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -303,7 +303,6 @@ export class SearchFormComponent implements OnInit { | |||||||
|           (error) => { console.log(error); this.isSearching = false; } |           (error) => { console.log(error); this.isSearching = false; } | ||||||
|         ); |         ); | ||||||
|       } else { |       } else { | ||||||
|         this.searchResults.searchButtonClick(); |  | ||||||
|         this.isSearching = false; |         this.isSearching = false; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -234,7 +234,7 @@ export class StartComponent implements OnInit, AfterViewChecked, OnDestroy { | |||||||
|     this.minScrollWidth = 40 + (8 * this.blockWidth) + (this.pageWidth * 2); |     this.minScrollWidth = 40 + (8 * this.blockWidth) + (this.pageWidth * 2); | ||||||
| 
 | 
 | ||||||
|     if (firstVisibleBlock != null) { |     if (firstVisibleBlock != null) { | ||||||
|       this.scrollToBlock(firstVisibleBlock, offset); |       this.scrollToBlock(firstVisibleBlock, offset + (this.isMobile ? this.blockWidth : 0)); | ||||||
|     } else { |     } else { | ||||||
|       this.updatePages(); |       this.updatePages(); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -11,7 +11,9 @@ import { | |||||||
|   tap, |   tap, | ||||||
|   map, |   map, | ||||||
|   retry, |   retry, | ||||||
|   startWith |   startWith, | ||||||
|  |   repeat, | ||||||
|  |   take | ||||||
| } from 'rxjs/operators'; | } from 'rxjs/operators'; | ||||||
| import { Transaction } from '../../interfaces/electrs.interface'; | import { Transaction } from '../../interfaces/electrs.interface'; | ||||||
| import { of, merge, Subscription, Observable, Subject, from, throwError, combineLatest, BehaviorSubject } from 'rxjs'; | import { of, merge, Subscription, Observable, Subject, from, throwError, combineLatest, BehaviorSubject } from 'rxjs'; | ||||||
| @ -76,6 +78,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | |||||||
|   transactionTime = -1; |   transactionTime = -1; | ||||||
|   subscription: Subscription; |   subscription: Subscription; | ||||||
|   fetchCpfpSubscription: Subscription; |   fetchCpfpSubscription: Subscription; | ||||||
|  |   transactionTimesSubscription: Subscription; | ||||||
|   fetchRbfSubscription: Subscription; |   fetchRbfSubscription: Subscription; | ||||||
|   fetchCachedTxSubscription: Subscription; |   fetchCachedTxSubscription: Subscription; | ||||||
|   fetchAccelerationSubscription: Subscription; |   fetchAccelerationSubscription: Subscription; | ||||||
| @ -88,6 +91,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | |||||||
|   blocksSubscription: Subscription; |   blocksSubscription: Subscription; | ||||||
|   miningSubscription: Subscription; |   miningSubscription: Subscription; | ||||||
|   auditSubscription: Subscription; |   auditSubscription: Subscription; | ||||||
|  |   txConfirmedSubscription: Subscription; | ||||||
|   currencyChangeSubscription: Subscription; |   currencyChangeSubscription: Subscription; | ||||||
|   fragmentParams: URLSearchParams; |   fragmentParams: URLSearchParams; | ||||||
|   rbfTransaction: undefined | Transaction; |   rbfTransaction: undefined | Transaction; | ||||||
| @ -106,6 +110,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | |||||||
|   showCpfpDetails = false; |   showCpfpDetails = false; | ||||||
|   miningStats: MiningStats; |   miningStats: MiningStats; | ||||||
|   fetchCpfp$ = new Subject<string>(); |   fetchCpfp$ = new Subject<string>(); | ||||||
|  |   transactionTimes$ = new Subject<string>(); | ||||||
|   fetchRbfHistory$ = new Subject<string>(); |   fetchRbfHistory$ = new Subject<string>(); | ||||||
|   fetchCachedTx$ = new Subject<string>(); |   fetchCachedTx$ = new Subject<string>(); | ||||||
|   fetchAcceleration$ = new Subject<number>(); |   fetchAcceleration$ = new Subject<number>(); | ||||||
| @ -141,7 +146,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | |||||||
|   taprootEnabled: boolean; |   taprootEnabled: boolean; | ||||||
|   hasEffectiveFeeRate: boolean; |   hasEffectiveFeeRate: boolean; | ||||||
|   accelerateCtaType: 'alert' | 'button' = 'button'; |   accelerateCtaType: 'alert' | 'button' = 'button'; | ||||||
|   acceleratorAvailable: boolean = this.stateService.env.ACCELERATOR && this.stateService.network === ''; |   acceleratorAvailable: boolean = this.stateService.env.ACCELERATOR_BUTTON && this.stateService.network === ''; | ||||||
|   eligibleForAcceleration: boolean = false; |   eligibleForAcceleration: boolean = false; | ||||||
|   forceAccelerationSummary = false; |   forceAccelerationSummary = false; | ||||||
|   hideAccelerationSummary = false; |   hideAccelerationSummary = false; | ||||||
| @ -195,7 +200,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | |||||||
|     this.stateService.networkChanged$.subscribe( |     this.stateService.networkChanged$.subscribe( | ||||||
|       (network) => { |       (network) => { | ||||||
|         this.network = network; |         this.network = network; | ||||||
|         this.acceleratorAvailable = this.stateService.env.ACCELERATOR && this.stateService.network === ''; |         this.acceleratorAvailable = this.stateService.env.ACCELERATOR_BUTTON && this.stateService.network === ''; | ||||||
|       } |       } | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
| @ -225,6 +230,25 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | |||||||
|       this.latestBlock = blocks[0]; |       this.latestBlock = blocks[0]; | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|  |     this.transactionTimesSubscription = this.transactionTimes$.pipe( | ||||||
|  |       tap(() => { | ||||||
|  |         this.isLoadingFirstSeen = true; | ||||||
|  |       }), | ||||||
|  |       switchMap((txid) => this.apiService.getTransactionTimes$([txid]).pipe( | ||||||
|  |         retry({ count: 2, delay: 2000 }), | ||||||
|  |         // Try again until we either get a valid response, or the transaction is confirmed
 | ||||||
|  |         repeat({ delay: 2000 }), | ||||||
|  |         filter((transactionTimes) => transactionTimes?.length && transactionTimes[0] > 0 && !this.tx.status?.confirmed), | ||||||
|  |         take(1), | ||||||
|  |       )), | ||||||
|  |     ) | ||||||
|  |     .subscribe((transactionTimes) => { | ||||||
|  |       this.isLoadingFirstSeen = false; | ||||||
|  |       if (transactionTimes?.length && transactionTimes[0]) { | ||||||
|  |         this.transactionTime = transactionTimes[0]; | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|     this.fetchCpfpSubscription = this.fetchCpfp$ |     this.fetchCpfpSubscription = this.fetchCpfp$ | ||||||
|       .pipe( |       .pipe( | ||||||
|         switchMap((txId) => |         switchMap((txId) => | ||||||
| @ -572,7 +596,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | |||||||
|             if (tx.firstSeen) { |             if (tx.firstSeen) { | ||||||
|               this.transactionTime = tx.firstSeen; |               this.transactionTime = tx.firstSeen; | ||||||
|             } else { |             } else { | ||||||
|               this.getTransactionTime(); |               this.transactionTimes$.next(tx.txid); | ||||||
|             } |             } | ||||||
|           } else { |           } else { | ||||||
|             this.fetchAcceleration$.next(tx.status.block_height); |             this.fetchAcceleration$.next(tx.status.block_height); | ||||||
| @ -625,7 +649,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | |||||||
|         } |         } | ||||||
|       ); |       ); | ||||||
| 
 | 
 | ||||||
|     this.stateService.txConfirmed$.subscribe(([txConfirmed, block]) => { |     this.txConfirmedSubscription = this.stateService.txConfirmed$.subscribe(([txConfirmed, block]) => { | ||||||
|       if (txConfirmed && this.tx && !this.tx.status.confirmed && txConfirmed === this.tx.txid) { |       if (txConfirmed && this.tx && !this.tx.status.confirmed && txConfirmed === this.tx.txid) { | ||||||
|         if (this.tx.acceleration) { |         if (this.tx.acceleration) { | ||||||
|           this.waitingForAccelerationInfo = true; |           this.waitingForAccelerationInfo = true; | ||||||
| @ -729,7 +753,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | |||||||
|           this.accelerationPositions, |           this.accelerationPositions, | ||||||
|         ); |         ); | ||||||
|       }) |       }) | ||||||
|     ) |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   ngAfterViewInit(): void { |   ngAfterViewInit(): void { | ||||||
| @ -763,28 +787,6 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | |||||||
|     return of(false); |     return of(false); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   getTransactionTime() { |  | ||||||
|     this.isLoadingFirstSeen = true; |  | ||||||
|     this.apiService |  | ||||||
|       .getTransactionTimes$([this.tx.txid]) |  | ||||||
|       .pipe( |  | ||||||
|         retry({ count: 2, delay: 2000 }), |  | ||||||
|         catchError(() => { |  | ||||||
|           this.isLoadingFirstSeen = false; |  | ||||||
|           return throwError(() => new Error('')); |  | ||||||
|         }) |  | ||||||
|       ) |  | ||||||
|       .subscribe((transactionTimes) => { |  | ||||||
|         if (transactionTimes?.length && transactionTimes[0]) { |  | ||||||
|           this.transactionTime = transactionTimes[0]; |  | ||||||
|         } else { |  | ||||||
|           setTimeout(() => { |  | ||||||
|             this.getTransactionTime(); |  | ||||||
|           }, 2000); |  | ||||||
|         } |  | ||||||
|       }); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   setCpfpInfo(cpfpInfo: CpfpInfo): void { |   setCpfpInfo(cpfpInfo: CpfpInfo): void { | ||||||
|     if (!cpfpInfo || !this.tx) { |     if (!cpfpInfo || !this.tx) { | ||||||
|       this.cpfpInfo = null; |       this.cpfpInfo = null; | ||||||
| @ -1057,6 +1059,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | |||||||
|   ngOnDestroy() { |   ngOnDestroy() { | ||||||
|     this.subscription.unsubscribe(); |     this.subscription.unsubscribe(); | ||||||
|     this.fetchCpfpSubscription.unsubscribe(); |     this.fetchCpfpSubscription.unsubscribe(); | ||||||
|  |     this.transactionTimesSubscription.unsubscribe(); | ||||||
|     this.fetchRbfSubscription.unsubscribe(); |     this.fetchRbfSubscription.unsubscribe(); | ||||||
|     this.fetchCachedTxSubscription.unsubscribe(); |     this.fetchCachedTxSubscription.unsubscribe(); | ||||||
|     this.fetchAccelerationSubscription.unsubscribe(); |     this.fetchAccelerationSubscription.unsubscribe(); | ||||||
| @ -1070,6 +1073,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | |||||||
|     this.blocksSubscription.unsubscribe(); |     this.blocksSubscription.unsubscribe(); | ||||||
|     this.miningSubscription?.unsubscribe(); |     this.miningSubscription?.unsubscribe(); | ||||||
|     this.auditSubscription?.unsubscribe(); |     this.auditSubscription?.unsubscribe(); | ||||||
|  |     this.txConfirmedSubscription?.unsubscribe(); | ||||||
|     this.currencyChangeSubscription?.unsubscribe(); |     this.currencyChangeSubscription?.unsubscribe(); | ||||||
|     this.leaveTransaction(); |     this.leaveTransaction(); | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -8993,7 +8993,7 @@ export const restApiDocsData = [ | |||||||
|     fragment: "accelerator-estimate", |     fragment: "accelerator-estimate", | ||||||
|     title: "POST Calculate Estimated Costs", |     title: "POST Calculate Estimated Costs", | ||||||
|     description: { |     description: { | ||||||
|       default: "<p>Returns estimated costs to accelerate a transaction. Optionally set the <code>api_key</code> header to get customized estimation.</p>" |       default: "<p>Returns estimated costs to accelerate a transaction. Optionally set the <code>X-Mempool-Auth</code> header to get customized estimation.</p>" | ||||||
|     }, |     }, | ||||||
|     urlString: "/v1/services/accelerator/estimate", |     urlString: "/v1/services/accelerator/estimate", | ||||||
|     showConditions: [""], |     showConditions: [""], | ||||||
| @ -9009,7 +9009,7 @@ export const restApiDocsData = [ | |||||||
|           esModule: [], |           esModule: [], | ||||||
|           commonJS: [], |           commonJS: [], | ||||||
|           curl: ["txInput=ee13ebb99632377c15c94980357f674d285ac413452050031ea6dcd3e9b2dc29"], |           curl: ["txInput=ee13ebb99632377c15c94980357f674d285ac413452050031ea6dcd3e9b2dc29"], | ||||||
|           headers: "api_key: stacksats", |           headers: "X-Mempool-Auth: stacksats", | ||||||
|           response: `{
 |           response: `{
 | ||||||
|   "txSummary": { |   "txSummary": { | ||||||
|     "txid": "ee13ebb99632377c15c94980357f674d285ac413452050031ea6dcd3e9b2dc29", |     "txid": "ee13ebb99632377c15c94980357f674d285ac413452050031ea6dcd3e9b2dc29", | ||||||
| @ -9240,7 +9240,7 @@ export const restApiDocsData = [ | |||||||
|           esModule: [], |           esModule: [], | ||||||
|           commonJS: [], |           commonJS: [], | ||||||
|           curl: [], |           curl: [], | ||||||
|           headers: "api_key: stacksats", |           headers: "X-Mempool-Auth: stacksats", | ||||||
|           response: `[
 |           response: `[
 | ||||||
|   { |   { | ||||||
|     "type": "Bitcoin", |     "type": "Bitcoin", | ||||||
| @ -9288,7 +9288,7 @@ export const restApiDocsData = [ | |||||||
|           esModule: [], |           esModule: [], | ||||||
|           commonJS: [], |           commonJS: [], | ||||||
|           curl: [], |           curl: [], | ||||||
|           headers: "api_key: stacksats", |           headers: "X-Mempool-Auth: stacksats", | ||||||
|           response: `{
 |           response: `{
 | ||||||
|   "balance": 99900000, |   "balance": 99900000, | ||||||
|   "hold": 101829, |   "hold": 101829, | ||||||
| @ -9322,7 +9322,7 @@ export const restApiDocsData = [ | |||||||
|           esModule: [], |           esModule: [], | ||||||
|           commonJS: [], |           commonJS: [], | ||||||
|           curl: ["txInput=ee13ebb99632377c15c94980357f674d285ac413452050031ea6dcd3e9b2dc29&userBid=21000000"], |           curl: ["txInput=ee13ebb99632377c15c94980357f674d285ac413452050031ea6dcd3e9b2dc29&userBid=21000000"], | ||||||
|           headers: "api_key: stacksats", |           headers: "X-Mempool-Auth: stacksats", | ||||||
|           response: `HTTP/1.1 200 OK`, |           response: `HTTP/1.1 200 OK`, | ||||||
|         }, |         }, | ||||||
|       } |       } | ||||||
| @ -9352,7 +9352,7 @@ export const restApiDocsData = [ | |||||||
|           esModule: [], |           esModule: [], | ||||||
|           commonJS: [], |           commonJS: [], | ||||||
|           curl: [], |           curl: [], | ||||||
|           headers: "api_key: stacksats", |           headers: "X-Mempool-Auth: stacksats", | ||||||
|           response: `[
 |           response: `[
 | ||||||
|   { |   { | ||||||
|     "id": 89, |     "id": 89, | ||||||
|  | |||||||
| @ -408,6 +408,7 @@ export interface Acceleration { | |||||||
|   bidBoost?: number; |   bidBoost?: number; | ||||||
|   boostCost?: number; |   boostCost?: number; | ||||||
|   boostRate?: number; |   boostRate?: number; | ||||||
|  |   minedByPoolUniqueId?: number; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export interface AccelerationHistoryParams { | export interface AccelerationHistoryParams { | ||||||
|  | |||||||
| @ -536,6 +536,10 @@ export class ApiService { | |||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   logAccelerationRequest$(txid: string): Observable<any> { | ||||||
|  |     return this.httpClient.post(this.apiBaseUrl + this.apiBasePath + '/api/v1/acceleration/request/' + txid, ''); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   // Cache methods
 |   // Cache methods
 | ||||||
|   async setBlockAuditLoaded(hash: string) { |   async setBlockAuditLoaded(hash: string) { | ||||||
|     this.blockAuditLoaded[hash] = true; |     this.blockAuditLoaded[hash] = true; | ||||||
|  | |||||||
| @ -30,6 +30,7 @@ export class EnterpriseService { | |||||||
|       this.fetchSubdomainInfo(); |       this.fetchSubdomainInfo(); | ||||||
|       this.disableSubnetworks(); |       this.disableSubnetworks(); | ||||||
|       this.stateService.env.ACCELERATOR = false; |       this.stateService.env.ACCELERATOR = false; | ||||||
|  |       this.stateService.env.ACCELERATOR_BUTTON = false; | ||||||
|     } else { |     } else { | ||||||
|       this.insertMatomo(); |       this.insertMatomo(); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -137,6 +137,10 @@ export class ServicesApiServices { | |||||||
|     return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/cashapp`, { txInput: txInput, token: token, cashtag: cashtag, referenceId: referenceId, accelerationUUID: accelerationUUID }); |     return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/cashapp`, { txInput: txInput, token: token, cashtag: cashtag, referenceId: referenceId, accelerationUUID: accelerationUUID }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   accelerateWithApplePay$(txInput: string, token: string, cardTag: string, referenceId: string, accelerationUUID: string) { | ||||||
|  |     return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/applePay`, { txInput: txInput, cardTag: cardTag, token: token, referenceId: referenceId, accelerationUUID: accelerationUUID }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   getAccelerations$(): Observable<Acceleration[]> { |   getAccelerations$(): Observable<Acceleration[]> { | ||||||
|     return this.httpClient.get<Acceleration[]>(`${this.stateService.env.SERVICES_API}/accelerator/accelerations`); |     return this.httpClient.get<Acceleration[]>(`${this.stateService.env.SERVICES_API}/accelerator/accelerations`); | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -71,6 +71,7 @@ export interface Env { | |||||||
|   SIGNET_BLOCK_AUDIT_START_HEIGHT: number; |   SIGNET_BLOCK_AUDIT_START_HEIGHT: number; | ||||||
|   HISTORICAL_PRICE: boolean; |   HISTORICAL_PRICE: boolean; | ||||||
|   ACCELERATOR: boolean; |   ACCELERATOR: boolean; | ||||||
|  |   ACCELERATOR_BUTTON: boolean; | ||||||
|   PUBLIC_ACCELERATIONS: boolean; |   PUBLIC_ACCELERATIONS: boolean; | ||||||
|   ADDITIONAL_CURRENCIES: boolean; |   ADDITIONAL_CURRENCIES: boolean; | ||||||
|   GIT_COMMIT_HASH_MEMPOOL_SPACE?: string; |   GIT_COMMIT_HASH_MEMPOOL_SPACE?: string; | ||||||
| @ -108,6 +109,7 @@ const defaultEnv: Env = { | |||||||
|   'SIGNET_BLOCK_AUDIT_START_HEIGHT': 0, |   'SIGNET_BLOCK_AUDIT_START_HEIGHT': 0, | ||||||
|   'HISTORICAL_PRICE': true, |   'HISTORICAL_PRICE': true, | ||||||
|   'ACCELERATOR': false, |   'ACCELERATOR': false, | ||||||
|  |   'ACCELERATOR_BUTTON': true, | ||||||
|   'PUBLIC_ACCELERATIONS': false, |   'PUBLIC_ACCELERATIONS': false, | ||||||
|   'ADDITIONAL_CURRENCIES': false, |   'ADDITIONAL_CURRENCIES': false, | ||||||
|   'SERVICES_API': 'https://mempool.space/api/v1/services', |   'SERVICES_API': 'https://mempool.space/api/v1/services', | ||||||
|  | |||||||
| @ -195,3 +195,47 @@ export function insecureRandomUUID(): string { | |||||||
|   } |   } | ||||||
|   return uuid.slice(0, -1); |   return uuid.slice(0, -1); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // https://stackoverflow.com/a/60467595
 | ||||||
|  | export function md5(inputString): string { | ||||||
|  |     var hc="0123456789abcdef"; | ||||||
|  |     function rh(n) {var j,s="";for(j=0;j<=3;j++) s+=hc.charAt((n>>(j*8+4))&0x0F)+hc.charAt((n>>(j*8))&0x0F);return s;} | ||||||
|  |     function ad(x,y) {var l=(x&0xFFFF)+(y&0xFFFF);var m=(x>>16)+(y>>16)+(l>>16);return (m<<16)|(l&0xFFFF);} | ||||||
|  |     function rl(n,c)            {return (n<<c)|(n>>>(32-c));} | ||||||
|  |     function cm(q,a,b,x,s,t)    {return ad(rl(ad(ad(a,q),ad(x,t)),s),b);} | ||||||
|  |     function ff(a,b,c,d,x,s,t)  {return cm((b&c)|((~b)&d),a,b,x,s,t);} | ||||||
|  |     function gg(a,b,c,d,x,s,t)  {return cm((b&d)|(c&(~d)),a,b,x,s,t);} | ||||||
|  |     function hh(a,b,c,d,x,s,t)  {return cm(b^c^d,a,b,x,s,t);} | ||||||
|  |     function ii(a,b,c,d,x,s,t)  {return cm(c^(b|(~d)),a,b,x,s,t);} | ||||||
|  |     function sb(x) { | ||||||
|  |         var i;var nblk=((x.length+8)>>6)+1;var blks=new Array(nblk*16);for(i=0;i<nblk*16;i++) blks[i]=0; | ||||||
|  |         for(i=0;i<x.length;i++) blks[i>>2]|=x.charCodeAt(i)<<((i%4)*8); | ||||||
|  |         blks[i>>2]|=0x80<<((i%4)*8);blks[nblk*16-2]=x.length*8;return blks; | ||||||
|  |     } | ||||||
|  |     var i,x=sb(""+inputString),a=1732584193,b=-271733879,c=-1732584194,d=271733878,olda,oldb,oldc,oldd; | ||||||
|  |     for(i=0;i<x.length;i+=16) {olda=a;oldb=b;oldc=c;oldd=d; | ||||||
|  |         a=ff(a,b,c,d,x[i+ 0], 7, -680876936);d=ff(d,a,b,c,x[i+ 1],12, -389564586);c=ff(c,d,a,b,x[i+ 2],17,  606105819); | ||||||
|  |         b=ff(b,c,d,a,x[i+ 3],22,-1044525330);a=ff(a,b,c,d,x[i+ 4], 7, -176418897);d=ff(d,a,b,c,x[i+ 5],12, 1200080426); | ||||||
|  |         c=ff(c,d,a,b,x[i+ 6],17,-1473231341);b=ff(b,c,d,a,x[i+ 7],22,  -45705983);a=ff(a,b,c,d,x[i+ 8], 7, 1770035416); | ||||||
|  |         d=ff(d,a,b,c,x[i+ 9],12,-1958414417);c=ff(c,d,a,b,x[i+10],17,     -42063);b=ff(b,c,d,a,x[i+11],22,-1990404162); | ||||||
|  |         a=ff(a,b,c,d,x[i+12], 7, 1804603682);d=ff(d,a,b,c,x[i+13],12,  -40341101);c=ff(c,d,a,b,x[i+14],17,-1502002290); | ||||||
|  |         b=ff(b,c,d,a,x[i+15],22, 1236535329);a=gg(a,b,c,d,x[i+ 1], 5, -165796510);d=gg(d,a,b,c,x[i+ 6], 9,-1069501632); | ||||||
|  |         c=gg(c,d,a,b,x[i+11],14,  643717713);b=gg(b,c,d,a,x[i+ 0],20, -373897302);a=gg(a,b,c,d,x[i+ 5], 5, -701558691); | ||||||
|  |         d=gg(d,a,b,c,x[i+10], 9,   38016083);c=gg(c,d,a,b,x[i+15],14, -660478335);b=gg(b,c,d,a,x[i+ 4],20, -405537848); | ||||||
|  |         a=gg(a,b,c,d,x[i+ 9], 5,  568446438);d=gg(d,a,b,c,x[i+14], 9,-1019803690);c=gg(c,d,a,b,x[i+ 3],14, -187363961); | ||||||
|  |         b=gg(b,c,d,a,x[i+ 8],20, 1163531501);a=gg(a,b,c,d,x[i+13], 5,-1444681467);d=gg(d,a,b,c,x[i+ 2], 9,  -51403784); | ||||||
|  |         c=gg(c,d,a,b,x[i+ 7],14, 1735328473);b=gg(b,c,d,a,x[i+12],20,-1926607734);a=hh(a,b,c,d,x[i+ 5], 4,    -378558); | ||||||
|  |         d=hh(d,a,b,c,x[i+ 8],11,-2022574463);c=hh(c,d,a,b,x[i+11],16, 1839030562);b=hh(b,c,d,a,x[i+14],23,  -35309556); | ||||||
|  |         a=hh(a,b,c,d,x[i+ 1], 4,-1530992060);d=hh(d,a,b,c,x[i+ 4],11, 1272893353);c=hh(c,d,a,b,x[i+ 7],16, -155497632); | ||||||
|  |         b=hh(b,c,d,a,x[i+10],23,-1094730640);a=hh(a,b,c,d,x[i+13], 4,  681279174);d=hh(d,a,b,c,x[i+ 0],11, -358537222); | ||||||
|  |         c=hh(c,d,a,b,x[i+ 3],16, -722521979);b=hh(b,c,d,a,x[i+ 6],23,   76029189);a=hh(a,b,c,d,x[i+ 9], 4, -640364487); | ||||||
|  |         d=hh(d,a,b,c,x[i+12],11, -421815835);c=hh(c,d,a,b,x[i+15],16,  530742520);b=hh(b,c,d,a,x[i+ 2],23, -995338651); | ||||||
|  |         a=ii(a,b,c,d,x[i+ 0], 6, -198630844);d=ii(d,a,b,c,x[i+ 7],10, 1126891415);c=ii(c,d,a,b,x[i+14],15,-1416354905); | ||||||
|  |         b=ii(b,c,d,a,x[i+ 5],21,  -57434055);a=ii(a,b,c,d,x[i+12], 6, 1700485571);d=ii(d,a,b,c,x[i+ 3],10,-1894986606); | ||||||
|  |         c=ii(c,d,a,b,x[i+10],15,   -1051523);b=ii(b,c,d,a,x[i+ 1],21,-2054922799);a=ii(a,b,c,d,x[i+ 8], 6, 1873313359); | ||||||
|  |         d=ii(d,a,b,c,x[i+15],10,  -30611744);c=ii(c,d,a,b,x[i+ 6],15,-1560198380);b=ii(b,c,d,a,x[i+13],21, 1309151649); | ||||||
|  |         a=ii(a,b,c,d,x[i+ 4], 6, -145523070);d=ii(d,a,b,c,x[i+11],10,-1120210379);c=ii(c,d,a,b,x[i+ 2],15,  718787259); | ||||||
|  |         b=ii(b,c,d,a,x[i+ 9],21, -343485551);a=ad(a,olda);b=ad(b,oldb);c=ad(c,oldc);d=ad(d,oldd); | ||||||
|  |     } | ||||||
|  |     return rh(a)+rh(b)+rh(c)+rh(d); | ||||||
|  | } | ||||||
|  | |||||||
| @ -1520,6 +1520,10 @@ | |||||||
|           <context context-type="sourcefile">src/app/components/acceleration/accelerations-list/accelerations-list.component.html</context> |           <context context-type="sourcefile">src/app/components/acceleration/accelerations-list/accelerations-list.component.html</context> | ||||||
|           <context context-type="linenumber">53</context> |           <context context-type="linenumber">53</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|  |         <context-group purpose="location"> | ||||||
|  |           <context context-type="sourcefile">src/app/components/address/address.component.html</context> | ||||||
|  |           <context context-type="linenumber">252</context> | ||||||
|  |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/tracker/tracker-bar.component.html</context> |           <context context-type="sourcefile">src/app/components/tracker/tracker-bar.component.html</context> | ||||||
|           <context context-type="linenumber">4</context> |           <context context-type="linenumber">4</context> | ||||||
| @ -2042,14 +2046,6 @@ | |||||||
|         </context-group> |         </context-group> | ||||||
|         <note priority="1" from="description">address.confirmed-balance</note> |         <note priority="1" from="description">address.confirmed-balance</note> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="8211b4be0291a035cd67aeb84782c9e87d4daca3" datatype="html"> |  | ||||||
|         <source>Unconfirmed balance</source> |  | ||||||
|         <context-group purpose="location"> |  | ||||||
|           <context context-type="sourcefile">src/app/components/address/address.component.html</context> |  | ||||||
|           <context context-type="linenumber">252</context> |  | ||||||
|         </context-group> |  | ||||||
|         <note priority="1" from="description">address.unconfirmed-balance</note> |  | ||||||
|       </trans-unit> |  | ||||||
|       <trans-unit id="c7daf1b7c126095c054f2d3728ae790b0ceef33a" datatype="html"> |       <trans-unit id="c7daf1b7c126095c054f2d3728ae790b0ceef33a" datatype="html"> | ||||||
|         <source>Confirmed UTXOs</source> |         <source>Confirmed UTXOs</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
| @ -2058,13 +2054,13 @@ | |||||||
|         </context-group> |         </context-group> | ||||||
|         <note priority="1" from="description">address.confirmed-utxos</note> |         <note priority="1" from="description">address.confirmed-utxos</note> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="928915f31e05788ae07a353761e10e5992b2b58c" datatype="html"> |       <trans-unit id="ba986bd9e2848d2ef7329394ec87b7f4eaf61118" datatype="html"> | ||||||
|         <source>Unconfirmed UTXOs</source> |         <source>Pending UTXOs</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/address/address.component.html</context> |           <context context-type="sourcefile">src/app/components/address/address.component.html</context> | ||||||
|           <context context-type="linenumber">262</context> |           <context context-type="linenumber">262</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <note priority="1" from="description">address.unconfirmed-utxos</note> |         <note priority="1" from="description">address.pending-utxos</note> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="f61c6867295f3b53d23557021f2f4e0aa1d0b8fc" datatype="html"> |       <trans-unit id="f61c6867295f3b53d23557021f2f4e0aa1d0b8fc" datatype="html"> | ||||||
|         <source>Type</source> |         <source>Type</source> | ||||||
| @ -6640,7 +6636,7 @@ | |||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/transaction/transaction.component.ts</context> |           <context context-type="sourcefile">src/app/components/transaction/transaction.component.ts</context> | ||||||
|           <context context-type="linenumber">498</context> |           <context context-type="linenumber">499</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="meta.description.bitcoin.transaction" datatype="html"> |       <trans-unit id="meta.description.bitcoin.transaction" datatype="html"> | ||||||
| @ -6655,7 +6651,7 @@ | |||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/transaction/transaction.component.ts</context> |           <context context-type="sourcefile">src/app/components/transaction/transaction.component.ts</context> | ||||||
|           <context context-type="linenumber">502</context> |           <context context-type="linenumber">503</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="7e06b8dd9f29261827018351cd71efe1c87839de" datatype="html"> |       <trans-unit id="7e06b8dd9f29261827018351cd71efe1c87839de" datatype="html"> | ||||||
|  | |||||||
							
								
								
									
										84
									
								
								frontend/src/resources/apple-pay.svg
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										84
									
								
								frontend/src/resources/apple-pay.svg
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,84 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  --> | ||||||
|  | <svg version="1.1" id="Artwork" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" | ||||||
|  | 	 width="165.52107px" height="105.9651px" viewBox="0 0 165.52107 105.9651" enable-background="new 0 0 165.52107 105.9651" | ||||||
|  | 	 xml:space="preserve"> | ||||||
|  | <g> | ||||||
|  | 	<path id="XMLID_4_" d="M150.69807,0H14.82318c-0.5659,0-1.1328,0-1.69769,0.0033c-0.47751,0.0034-0.95391,0.0087-1.43031,0.0217 | ||||||
|  | 		c-1.039,0.0281-2.0869,0.0894-3.1129,0.2738c-1.0424,0.1876-2.0124,0.4936-2.9587,0.9754 | ||||||
|  | 		c-0.9303,0.4731-1.782,1.0919-2.52009,1.8303c-0.73841,0.7384-1.35721,1.5887-1.83021,2.52 | ||||||
|  | 		c-0.4819,0.9463-0.7881,1.9166-0.9744,2.9598c-0.18539,1.0263-0.2471,2.074-0.2751,3.1119 | ||||||
|  | 		c-0.0128,0.4764-0.01829,0.9528-0.0214,1.4291c-0.0033,0.5661-0.0022,1.1318-0.0022,1.6989V91.142 | ||||||
|  | 		c0,0.5671-0.0011,1.13181,0.0022,1.69901c0.00311,0.4763,0.0086,0.9527,0.0214,1.4291 | ||||||
|  | 		c0.028,1.03699,0.08971,2.08469,0.2751,3.11069c0.1863,1.0436,0.4925,2.0135,0.9744,2.9599 | ||||||
|  | 		c0.473,0.9313,1.0918,1.7827,1.83021,2.52c0.73809,0.7396,1.58979,1.3583,2.52009,1.8302 | ||||||
|  | 		c0.9463,0.4831,1.9163,0.7892,2.9587,0.9767c1.026,0.1832,2.0739,0.2456,3.1129,0.2737c0.4764,0.0108,0.9528,0.0172,1.43031,0.0194 | ||||||
|  | 		c0.56489,0.0044,1.13179,0.0044,1.69769,0.0044h135.87489c0.5649,0,1.13181,0,1.69659-0.0044 | ||||||
|  | 		c0.47641-0.0022,0.95282-0.0086,1.4314-0.0194c1.0368-0.0281,2.0845-0.0905,3.11301-0.2737 | ||||||
|  | 		c1.041-0.1875,2.0112-0.4936,2.9576-0.9767c0.9313-0.4719,1.7805-1.0906,2.52011-1.8302c0.7372-0.7373,1.35599-1.5887,1.8302-2.52 | ||||||
|  | 		c0.48299-0.9464,0.78889-1.9163,0.97429-2.9599c0.1855-1.026,0.2457-2.0737,0.2738-3.11069 | ||||||
|  | 		c0.013-0.4764,0.01941-0.9528,0.02161-1.4291c0.00439-0.5672,0.00439-1.1319,0.00439-1.69901V14.8242 | ||||||
|  | 		c0-0.5671,0-1.1328-0.00439-1.6989c-0.0022-0.4763-0.00861-0.9527-0.02161-1.4291c-0.02811-1.0379-0.0883-2.0856-0.2738-3.1119 | ||||||
|  | 		c-0.18539-1.0432-0.4913-2.0135-0.97429-2.9598c-0.47421-0.9313-1.093-1.7816-1.8302-2.52 | ||||||
|  | 		c-0.73961-0.7384-1.58881-1.3572-2.52011-1.8303c-0.9464-0.4818-1.9166-0.7878-2.9576-0.9754 | ||||||
|  | 		c-1.0285-0.1844-2.0762-0.2457-3.11301-0.2738c-0.47858-0.013-0.95499-0.0183-1.4314-0.0217C151.82988,0,151.26297,0,150.69807,0 | ||||||
|  | 		L150.69807,0z"/> | ||||||
|  | 	<path id="XMLID_3_" fill="#FFFFFF" d="M150.69807,3.532l1.67149,0.0032c0.4528,0.0032,0.90561,0.0081,1.36092,0.0205 | ||||||
|  | 		c0.79201,0.0214,1.71849,0.0643,2.58209,0.2191c0.7507,0.1352,1.38029,0.3408,1.9845,0.6484 | ||||||
|  | 		c0.5965,0.3031,1.14301,0.7003,1.62019,1.1768c0.479,0.4797,0.87671,1.0271,1.18381,1.6302 | ||||||
|  | 		c0.30589,0.5995,0.51019,1.2261,0.64459,1.9823c0.1544,0.8542,0.1971,1.7832,0.21881,2.5801 | ||||||
|  | 		c0.01219,0.4498,0.01819,0.8996,0.0204,1.3601c0.00429,0.5569,0.0042,1.1135,0.0042,1.6715V91.142 | ||||||
|  | 		c0,0.558,0.00009,1.1136-0.0043,1.6824c-0.00211,0.4497-0.0081,0.8995-0.0204,1.3501c-0.02161,0.7957-0.0643,1.7242-0.2206,2.5885 | ||||||
|  | 		c-0.13251,0.7458-0.3367,1.3725-0.64429,1.975c-0.30621,0.6016-0.70331,1.1484-1.18022,1.6251 | ||||||
|  | 		c-0.47989,0.48-1.0246,0.876-1.62819,1.1819c-0.5997,0.3061-1.22821,0.51151-1.97151,0.6453 | ||||||
|  | 		c-0.88109,0.157-1.84639,0.2002-2.57339,0.2199c-0.4574,0.0103-0.9126,0.01649-1.37889,0.0187 | ||||||
|  | 		c-0.55571,0.0043-1.1134,0.0042-1.6692,0.0042H14.82318c-0.0074,0-0.0146,0-0.0221,0c-0.5494,0-1.0999,0-1.6593-0.0043 | ||||||
|  | 		c-0.4561-0.00211-0.9112-0.0082-1.3512-0.0182c-0.7436-0.0201-1.7095-0.0632-2.5834-0.2193 | ||||||
|  | 		c-0.74969-0.1348-1.3782-0.3402-1.9858-0.6503c-0.59789-0.3032-1.1422-0.6988-1.6223-1.1797 | ||||||
|  | 		c-0.4764-0.4756-0.8723-1.0207-1.1784-1.6232c-0.3064-0.6019-0.5114-1.2305-0.64619-1.9852 | ||||||
|  | 		c-0.15581-0.8626-0.19861-1.7874-0.22-2.5777c-0.01221-0.4525-0.01731-0.9049-0.02021-1.3547l-0.0022-1.3279l0.0001-0.3506V14.8242 | ||||||
|  | 		l-0.0001-0.3506l0.0021-1.3251c0.003-0.4525,0.0081-0.9049,0.02031-1.357c0.02139-0.7911,0.06419-1.7163,0.22129-2.5861 | ||||||
|  | 		c0.1336-0.7479,0.3385-1.3765,0.6465-1.9814c0.3037-0.5979,0.7003-1.1437,1.17921-1.6225 | ||||||
|  | 		c0.477-0.4772,1.02309-0.8739,1.62479-1.1799c0.6011-0.3061,1.2308-0.5116,1.9805-0.6465c0.8638-0.1552,1.7909-0.198,2.5849-0.2195 | ||||||
|  | 		c0.4526-0.0123,0.9052-0.0172,1.3544-0.0203l1.6771-0.0033H150.69807"/> | ||||||
|  | 	<g> | ||||||
|  | 		<g> | ||||||
|  | 			<path d="M45.1862,35.64053c1.41724-1.77266,2.37897-4.15282,2.12532-6.58506c-2.07464,0.10316-4.60634,1.36871-6.07207,3.14276 | ||||||
|  | 				c-1.31607,1.5192-2.4809,3.99902-2.17723,6.3293C41.39111,38.72954,43.71785,37.36345,45.1862,35.64053"/> | ||||||
|  | 			<path d="M47.28506,38.98252c-3.38211-0.20146-6.25773,1.91951-7.87286,1.91951c-1.61602,0-4.08931-1.81799-6.76438-1.76899 | ||||||
|  | 				c-3.48177,0.05114-6.71245,2.01976-8.4793,5.15079c-3.63411,6.2636-0.95904,15.55471,2.57494,20.65606 | ||||||
|  | 				c1.71618,2.5238,3.78447,5.30269,6.50976,5.20287c2.57494-0.10104,3.58421-1.66732,6.71416-1.66732 | ||||||
|  | 				c3.12765,0,4.03679,1.66732,6.76252,1.61681c2.82665-0.05054,4.59381-2.52506,6.30997-5.05132 | ||||||
|  | 				c1.96878-2.877,2.77473-5.65498,2.82542-5.80748c-0.0507-0.05051-5.45058-2.12204-5.50065-8.33358 | ||||||
|  | 				c-0.05098-5.20101,4.23951-7.6749,4.44144-7.82832C52.3832,39.4881,48.5975,39.08404,47.28506,38.98252"/> | ||||||
|  | 		</g> | ||||||
|  | 		<g> | ||||||
|  | 			<path d="M76.73385,31.94381c7.35096,0,12.4697,5.06708,12.4697,12.44437c0,7.40363-5.22407,12.49704-12.65403,12.49704h-8.13892 | ||||||
|  | 				v12.94318h-5.88037v-37.8846H76.73385z M68.41059,51.9493h6.74732c5.11975,0,8.0336-2.75636,8.0336-7.53479 | ||||||
|  | 				c0-4.77792-2.91385-7.50845-8.00727-7.50845h-6.77365V51.9493z"/> | ||||||
|  | 			<path d="M90.73997,61.97864c0-4.8311,3.70182-7.79761,10.26583-8.16526l7.56061-0.44614v-2.12639 | ||||||
|  | 				c0-3.07185-2.07423-4.90959-5.53905-4.90959c-3.28251,0-5.33041,1.57492-5.82871,4.04313h-5.35574 | ||||||
|  | 				c0.31499-4.98859,4.56777-8.66407,11.3941-8.66407c6.69466,0,10.97377,3.54432,10.97377,9.08388v19.03421h-5.43472v-4.54194 | ||||||
|  | 				h-0.13065c-1.60125,3.07185-5.09341,5.01441-8.71623,5.01441C94.52078,70.30088,90.73997,66.94038,90.73997,61.97864z | ||||||
|  | 				 M108.56641,59.4846v-2.17905l-6.8,0.41981c-3.38683,0.23649-5.30306,1.73291-5.30306,4.09579 | ||||||
|  | 				c0,2.41504,1.99523,3.99046,5.04075,3.99046C105.46823,65.81161,108.56641,63.08108,108.56641,59.4846z"/> | ||||||
|  | 			<path d="M119.34167,79.9889v-4.5946c0.4193,0.10483,1.36425,0.10483,1.83723,0.10483c2.6252,0,4.04313-1.10245,4.90908-3.9378 | ||||||
|  | 				c0-0.05267,0.49931-1.68025,0.49931-1.70658l-9.97616-27.64562h6.14268l6.98432,22.47371h0.10432l6.98433-22.47371h5.9857 | ||||||
|  | 				l-10.34483,29.06304c-2.36186,6.69517-5.0924,8.84789-10.81577,8.84789C121.17891,80.12006,119.76098,80.06739,119.34167,79.9889 | ||||||
|  | 				z"/> | ||||||
|  | 		</g> | ||||||
|  | 	</g> | ||||||
|  | </g> | ||||||
|  | <g> | ||||||
|  | </g> | ||||||
|  | <g> | ||||||
|  | </g> | ||||||
|  | <g> | ||||||
|  | </g> | ||||||
|  | <g> | ||||||
|  | </g> | ||||||
|  | <g> | ||||||
|  | </g> | ||||||
|  | <g> | ||||||
|  | </g> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 6.2 KiB | 
| @ -13,6 +13,7 @@ | |||||||
|   "ITEMS_PER_PAGE": 25, |   "ITEMS_PER_PAGE": 25, | ||||||
|   "LIGHTNING": true, |   "LIGHTNING": true, | ||||||
|   "ACCELERATOR": true, |   "ACCELERATOR": true, | ||||||
|  |   "ACCELERATOR_BUTTON": true, | ||||||
|   "PUBLIC_ACCELERATIONS": true, |   "PUBLIC_ACCELERATIONS": true, | ||||||
|   "AUDIT": true |   "AUDIT": true | ||||||
| } | } | ||||||
|  | |||||||
| @ -9,6 +9,7 @@ heat() | |||||||
| 
 | 
 | ||||||
| heatURLs=( | heatURLs=( | ||||||
|        '/api/v1/fees/recommended' |        '/api/v1/fees/recommended' | ||||||
|  |        '/api/v1/accelerations' | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| while true | while true | ||||||
|  | |||||||
| @ -2,6 +2,9 @@ | |||||||
| # routing # | # routing # | ||||||
| ########### | ########### | ||||||
| 
 | 
 | ||||||
|  | location /api/v1/accelerations { | ||||||
|  | 	try_files /dev/null @mempool-api-v1-services-cache-short; | ||||||
|  | } | ||||||
| location /api/v1/assets { | location /api/v1/assets { | ||||||
| 	try_files /dev/null @mempool-api-v1-services-cache-short; | 	try_files /dev/null @mempool-api-v1-services-cache-short; | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user