Merge branch 'master' into nymkappa/internal-price-rest-api
| @ -7,7 +7,7 @@ import cpfpRepository from '../repositories/CpfpRepository'; | |||||||
| import { RowDataPacket } from 'mysql2'; | import { RowDataPacket } from 'mysql2'; | ||||||
| 
 | 
 | ||||||
| class DatabaseMigration { | class DatabaseMigration { | ||||||
|   private static currentVersion = 83; |   private static currentVersion = 93; | ||||||
|   private queryTimeout = 3600_000; |   private queryTimeout = 3600_000; | ||||||
|   private statisticsAddedIndexed = false; |   private statisticsAddedIndexed = false; | ||||||
|   private uniqueLogs: string[] = []; |   private uniqueLogs: string[] = []; | ||||||
| @ -710,6 +710,97 @@ class DatabaseMigration { | |||||||
|       await this.$executeQuery('ALTER TABLE `blocks` ADD first_seen datetime(6) DEFAULT NULL'); |       await this.$executeQuery('ALTER TABLE `blocks` ADD first_seen datetime(6) DEFAULT NULL'); | ||||||
|       await this.updateToSchemaVersion(83); |       await this.updateToSchemaVersion(83); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     // add new pools indexes
 | ||||||
|  |     if (databaseSchemaVersion < 84 && isBitcoin === true) { | ||||||
|  |       await this.$executeQuery(` | ||||||
|  |         ALTER TABLE \`pools\` | ||||||
|  |           ADD INDEX \`slug\` (\`slug\`),
 | ||||||
|  |           ADD INDEX \`unique_id\` (\`unique_id\`)
 | ||||||
|  |       `);
 | ||||||
|  |       await this.updateToSchemaVersion(84); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // lightning channels indexes
 | ||||||
|  |     if (databaseSchemaVersion < 85 && isBitcoin === true) { | ||||||
|  |       await this.$executeQuery(` | ||||||
|  |         ALTER TABLE \`channels\` | ||||||
|  |           ADD INDEX \`created\` (\`created\`),
 | ||||||
|  |           ADD INDEX \`capacity\` (\`capacity\`),
 | ||||||
|  |           ADD INDEX \`closing_reason\` (\`closing_reason\`),
 | ||||||
|  |           ADD INDEX \`closing_resolved\` (\`closing_resolved\`)
 | ||||||
|  |       `);
 | ||||||
|  |       await this.updateToSchemaVersion(85); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // lightning nodes indexes
 | ||||||
|  |     if (databaseSchemaVersion < 86 && isBitcoin === true) { | ||||||
|  |       await this.$executeQuery(` | ||||||
|  |         ALTER TABLE \`nodes\` | ||||||
|  |           ADD INDEX \`status\` (\`status\`),
 | ||||||
|  |           ADD INDEX \`channels\` (\`channels\`),
 | ||||||
|  |           ADD INDEX \`country_id\` (\`country_id\`),
 | ||||||
|  |           ADD INDEX \`as_number\` (\`as_number\`),
 | ||||||
|  |           ADD INDEX \`first_seen\` (\`first_seen\`)
 | ||||||
|  |       `);
 | ||||||
|  |       await this.updateToSchemaVersion(86); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // lightning node sockets indexes
 | ||||||
|  |     if (databaseSchemaVersion < 87 && isBitcoin === true) { | ||||||
|  |       await this.$executeQuery('ALTER TABLE `nodes_sockets` ADD INDEX `type` (`type`)'); | ||||||
|  |       await this.updateToSchemaVersion(87); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // lightning stats indexes
 | ||||||
|  |     if (databaseSchemaVersion < 88 && isBitcoin === true) { | ||||||
|  |       await this.$executeQuery('ALTER TABLE `lightning_stats` ADD INDEX `added` (`added`)'); | ||||||
|  |       await this.updateToSchemaVersion(88); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // geo names indexes
 | ||||||
|  |     if (databaseSchemaVersion < 89 && isBitcoin === true) { | ||||||
|  |       await this.$executeQuery('ALTER TABLE `geo_names` ADD INDEX `names` (`names`)'); | ||||||
|  |       await this.updateToSchemaVersion(89); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // hashrates indexes
 | ||||||
|  |     if (databaseSchemaVersion < 90 && isBitcoin === true) { | ||||||
|  |       await this.$executeQuery('ALTER TABLE `hashrates` ADD INDEX `type` (`type`)'); | ||||||
|  |       await this.updateToSchemaVersion(90); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // block audits indexes
 | ||||||
|  |     if (databaseSchemaVersion < 91 && isBitcoin === true) { | ||||||
|  |       await this.$executeQuery('ALTER TABLE `blocks_audits` ADD INDEX `time` (`time`)'); | ||||||
|  |       await this.updateToSchemaVersion(91); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // elements_pegs indexes
 | ||||||
|  |     if (databaseSchemaVersion < 92 && config.MEMPOOL.NETWORK === 'liquid') { | ||||||
|  |       await this.$executeQuery(` | ||||||
|  |         ALTER TABLE \`elements_pegs\` | ||||||
|  |           ADD INDEX \`block\` (\`block\`),
 | ||||||
|  |           ADD INDEX \`datetime\` (\`datetime\`),
 | ||||||
|  |           ADD INDEX \`amount\` (\`amount\`),
 | ||||||
|  |           ADD INDEX \`bitcoinaddress\` (\`bitcoinaddress\`),
 | ||||||
|  |           ADD INDEX \`bitcointxid\` (\`bitcointxid\`)
 | ||||||
|  |       `);
 | ||||||
|  |       await this.updateToSchemaVersion(92); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // federation_txos indexes
 | ||||||
|  |     if (databaseSchemaVersion < 93 && config.MEMPOOL.NETWORK === 'liquid') { | ||||||
|  |       await this.$executeQuery(` | ||||||
|  |         ALTER TABLE \`federation_txos\` | ||||||
|  |           ADD INDEX \`unspent\` (\`unspent\`),
 | ||||||
|  |           ADD INDEX \`lastblockupdate\` (\`lastblockupdate\`),
 | ||||||
|  |           ADD INDEX \`blocktime\` (\`blocktime\`),
 | ||||||
|  |           ADD INDEX \`emergencyKey\` (\`emergencyKey\`),
 | ||||||
|  |           ADD INDEX \`expiredAt\` (\`expiredAt\`)
 | ||||||
|  |       `);
 | ||||||
|  |       await this.updateToSchemaVersion(93); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|  | |||||||
| @ -382,7 +382,7 @@ class MempoolBlocks { | |||||||
| 
 | 
 | ||||||
|     const ancestors: Ancestor[] = []; |     const ancestors: Ancestor[] = []; | ||||||
|     const descendants: Ancestor[] = []; |     const descendants: Ancestor[] = []; | ||||||
|     let ancestor: MempoolTransactionExtended |     let ancestor: MempoolTransactionExtended; | ||||||
|     for (const cluster of clusters) { |     for (const cluster of clusters) { | ||||||
|       for (const memberTxid of cluster) { |       for (const memberTxid of cluster) { | ||||||
|         const mempoolTx = mempool[memberTxid]; |         const mempoolTx = mempool[memberTxid]; | ||||||
| @ -462,7 +462,7 @@ class MempoolBlocks { | |||||||
| 
 | 
 | ||||||
|       for (let i = 0; i < block.length; i++) { |       for (let i = 0; i < block.length; i++) { | ||||||
|         const txid = block[i]; |         const txid = block[i]; | ||||||
|         if (txid) { |         if (txid in mempool) { | ||||||
|           mempoolTx = mempool[txid]; |           mempoolTx = mempool[txid]; | ||||||
|           // save position in projected blocks
 |           // save position in projected blocks
 | ||||||
|           mempoolTx.position = { |           mempoolTx.position = { | ||||||
| @ -481,6 +481,9 @@ class MempoolBlocks { | |||||||
|               mempoolTx.acceleratedAt = acceleration?.added; |               mempoolTx.acceleratedAt = acceleration?.added; | ||||||
|               mempoolTx.feeDelta = acceleration?.feeDelta; |               mempoolTx.feeDelta = acceleration?.feeDelta; | ||||||
|               for (const ancestor of mempoolTx.ancestors || []) { |               for (const ancestor of mempoolTx.ancestors || []) { | ||||||
|  |                 if (!(ancestor.txid in mempool)) { | ||||||
|  |                   continue; | ||||||
|  |                 } | ||||||
|                 if (!mempool[ancestor.txid].acceleration) { |                 if (!mempool[ancestor.txid].acceleration) { | ||||||
|                   mempool[ancestor.txid].cpfpDirty = true; |                   mempool[ancestor.txid].cpfpDirty = true; | ||||||
|                 } |                 } | ||||||
| @ -688,7 +691,7 @@ class MempoolBlocks { | |||||||
|       [pool: string]: { name: string, block: number, vsize: number, accelerations: string[], complete: boolean }; |       [pool: string]: { name: string, block: number, vsize: number, accelerations: string[], complete: boolean }; | ||||||
|     } = {}; |     } = {}; | ||||||
|     // prepare a list of accelerations in ascending order (we'll pop items off the end of the list)
 |     // prepare a list of accelerations in ascending order (we'll pop items off the end of the list)
 | ||||||
|     const accQueue: { acceleration: Acceleration, rate: number, vsize: number }[] = Object.values(accelerations).map(acc => { |     const accQueue: { acceleration: Acceleration, rate: number, vsize: number }[] = Object.values(accelerations).filter(acc => acc.txid in mempoolCache).map(acc => { | ||||||
|       let vsize = mempoolCache[acc.txid].vsize; |       let vsize = mempoolCache[acc.txid].vsize; | ||||||
|       for (const ancestor of mempoolCache[acc.txid].ancestors || []) { |       for (const ancestor of mempoolCache[acc.txid].ancestors || []) { | ||||||
|         vsize += (ancestor.weight / 4); |         vsize += (ancestor.weight / 4); | ||||||
|  | |||||||
| @ -246,17 +246,22 @@ class AccelerationApi { | |||||||
|       this.startedWebsocketLoop = true; |       this.startedWebsocketLoop = true; | ||||||
|       if (!this.ws) { |       if (!this.ws) { | ||||||
|         this.ws = new WebSocket(this.websocketPath); |         this.ws = new WebSocket(this.websocketPath); | ||||||
|         this.websocketConnected = true; |         this.lastPing = 0; | ||||||
| 
 | 
 | ||||||
|         this.ws.on('open', () => { |         this.ws.on('open', () => { | ||||||
|           logger.info(`Acceleration websocket opened to ${this.websocketPath}`); |           logger.info(`Acceleration websocket opened to ${this.websocketPath}`); | ||||||
|  |           this.websocketConnected = true; | ||||||
|           this.ws?.send(JSON.stringify({ |           this.ws?.send(JSON.stringify({ | ||||||
|             'watch-accelerations': true |             'watch-accelerations': true | ||||||
|           })); |           })); | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         this.ws.on('error', (error) => { |         this.ws.on('error', (error) => { | ||||||
|           logger.err(`Acceleration websocket error on ${this.websocketPath}: ` + error); |           let errMsg = `Acceleration websocket error on ${this.websocketPath}: ${error['code']}`; | ||||||
|  |           if (error['errors']) { | ||||||
|  |             errMsg += ' - ' + error['errors'].join(' - '); | ||||||
|  |           } | ||||||
|  |           logger.err(errMsg); | ||||||
|           this.ws = null; |           this.ws = null; | ||||||
|           this.websocketConnected = false; |           this.websocketConnected = false; | ||||||
|         }); |         }); | ||||||
| @ -285,16 +290,28 @@ class AccelerationApi { | |||||||
|           logger.debug('received pong from acceleration websocket server'); |           logger.debug('received pong from acceleration websocket server'); | ||||||
|           this.lastPong = Date.now(); |           this.lastPong = Date.now(); | ||||||
|         }); |         }); | ||||||
|       } else { |       } else if (this.websocketConnected) { | ||||||
|         if (this.lastPing > this.lastPong && Date.now() - this.lastPing > 10000) { |         if (this.lastPing && this.lastPing > this.lastPong && (Date.now() - this.lastPing > 10000)) { | ||||||
|           logger.warn('No pong received within 10 seconds, terminating connection'); |           logger.warn('No pong received within 10 seconds, terminating connection'); | ||||||
|           this.ws.terminate(); |           try { | ||||||
|           this.ws = null; |             this.ws?.terminate(); | ||||||
|           this.websocketConnected = false; |           } catch (e) { | ||||||
|         } else if (Date.now() - this.lastPing > 30000) { |             logger.warn('failed to terminate acceleration websocket connection: ' + (e instanceof Error ? e.message : e)); | ||||||
|  |           } finally { | ||||||
|  |             this.ws = null; | ||||||
|  |             this.websocketConnected = false; | ||||||
|  |             this.lastPing = 0; | ||||||
|  |           } | ||||||
|  |         } else if (!this.lastPing || (Date.now() - this.lastPing > 30000)) { | ||||||
|           logger.debug('sending ping to acceleration websocket server'); |           logger.debug('sending ping to acceleration websocket server'); | ||||||
|           this.ws.ping(); |           if (this.ws?.readyState === WebSocket.OPEN) { | ||||||
|           this.lastPing = Date.now(); |             try { | ||||||
|  |               this.ws?.ping(); | ||||||
|  |               this.lastPing = Date.now(); | ||||||
|  |             } catch (e) { | ||||||
|  |               logger.warn('failed to send ping to acceleration websocket server: ' + (e instanceof Error ? e.message : e)); | ||||||
|  |             } | ||||||
|  |           } | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|       await new Promise(resolve => setTimeout(resolve, 5000)); |       await new Promise(resolve => setTimeout(resolve, 5000)); | ||||||
|  | |||||||
							
								
								
									
										51
									
								
								frontend/custom-meta-config.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,51 @@ | |||||||
|  | { | ||||||
|  |   "theme": "contrast", | ||||||
|  |   "enterprise": "meta", | ||||||
|  |   "branding": { | ||||||
|  |     "name": "metaplanet", | ||||||
|  |     "title": "Metaplanet", | ||||||
|  |     "site_id": 21, | ||||||
|  |     "header_img": "/resources/metalogo.svg", | ||||||
|  |     "footer_img": "/resources/metalogo.svg" | ||||||
|  |   }, | ||||||
|  |   "dashboard": { | ||||||
|  |     "widgets": [ | ||||||
|  |       { | ||||||
|  |         "component": "fees", | ||||||
|  |         "mobileOrder": 4 | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         "component": "walletBalance", | ||||||
|  |         "mobileOrder": 1, | ||||||
|  |         "props": { | ||||||
|  |           "wallet": "3350" | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         "component": "twitter", | ||||||
|  |         "mobileOrder": 5, | ||||||
|  |         "props": { | ||||||
|  |           "handle": "Metaplanet_JP" | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         "component": "wallet", | ||||||
|  |         "mobileOrder": 2, | ||||||
|  |         "props": { | ||||||
|  |           "wallet": "3350", | ||||||
|  |           "period": "all" | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         "component": "blocks" | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         "component": "walletTransactions", | ||||||
|  |         "mobileOrder": 3, | ||||||
|  |         "props": { | ||||||
|  |           "wallet": "3350" | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     ] | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -2,7 +2,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 '@app/services/services-api.service'; | import { ServicesApiServices } from '@app/services/services-api.service'; | ||||||
| import { md5, insecureRandomUUID } from '@app/shared/common.utils'; | import { md5 } from '@app/shared/common.utils'; | ||||||
| import { StateService } from '@app/services/state.service'; | import { StateService } from '@app/services/state.service'; | ||||||
| import { AudioService } from '@app/services/audio.service'; | import { AudioService } from '@app/services/audio.service'; | ||||||
| import { ETA, EtaService } from '@app/services/eta.service'; | import { ETA, EtaService } from '@app/services/eta.service'; | ||||||
| @ -94,7 +94,6 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | |||||||
|   auth: IAuth | null = null; |   auth: IAuth | null = null; | ||||||
| 
 | 
 | ||||||
|   // accelerator stuff
 |   // accelerator stuff
 | ||||||
|   accelerationUUID: string; |  | ||||||
|   accelerationSubscription: Subscription; |   accelerationSubscription: Subscription; | ||||||
|   difficultySubscription: Subscription; |   difficultySubscription: Subscription; | ||||||
|   estimateSubscription: Subscription; |   estimateSubscription: Subscription; | ||||||
| @ -138,7 +137,6 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | |||||||
|     private enterpriseService: EnterpriseService, |     private enterpriseService: EnterpriseService, | ||||||
|   ) { |   ) { | ||||||
|     this.isProdDomain = this.stateService.env.PROD_DOMAINS.indexOf(document.location.hostname) > -1; |     this.isProdDomain = this.stateService.env.PROD_DOMAINS.indexOf(document.location.hostname) > -1; | ||||||
|     this.accelerationUUID = insecureRandomUUID(); |  | ||||||
| 
 | 
 | ||||||
|     // Check if Apple Pay available
 |     // Check if Apple Pay available
 | ||||||
|     // https://developer.apple.com/documentation/apple_pay_on_the_web/apple_pay_js_api/checking_for_apple_pay_availability#overview
 |     // https://developer.apple.com/documentation/apple_pay_on_the_web/apple_pay_js_api/checking_for_apple_pay_availability#overview
 | ||||||
| @ -388,7 +386,6 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | |||||||
|     this.accelerationSubscription = this.servicesApiService.accelerate$( |     this.accelerationSubscription = this.servicesApiService.accelerate$( | ||||||
|       this.tx.txid, |       this.tx.txid, | ||||||
|       this.userBid, |       this.userBid, | ||||||
|       this.accelerationUUID |  | ||||||
|     ).subscribe({ |     ).subscribe({ | ||||||
|       next: () => { |       next: () => { | ||||||
|         this.processing = false; |         this.processing = false; | ||||||
| @ -522,7 +519,6 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | |||||||
|                 tokenResult.token, |                 tokenResult.token, | ||||||
|                 cardTag, |                 cardTag, | ||||||
|                 `accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`, |                 `accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`, | ||||||
|                 this.accelerationUUID, |  | ||||||
|                 costUSD |                 costUSD | ||||||
|               ).subscribe({ |               ).subscribe({ | ||||||
|                 next: () => { |                 next: () => { | ||||||
| @ -622,7 +618,6 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | |||||||
|               tokenResult.token, |               tokenResult.token, | ||||||
|               cardTag, |               cardTag, | ||||||
|               `accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`, |               `accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`, | ||||||
|               this.accelerationUUID, |  | ||||||
|               costUSD |               costUSD | ||||||
|             ).subscribe({ |             ).subscribe({ | ||||||
|               next: () => { |               next: () => { | ||||||
| @ -713,7 +708,6 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | |||||||
|               tokenResult.token, |               tokenResult.token, | ||||||
|               tokenResult.details.cashAppPay.cashtag, |               tokenResult.details.cashAppPay.cashtag, | ||||||
|               tokenResult.details.cashAppPay.referenceId, |               tokenResult.details.cashAppPay.referenceId, | ||||||
|               this.accelerationUUID, |  | ||||||
|               costUSD |               costUSD | ||||||
|             ).subscribe({ |             ).subscribe({ | ||||||
|               next: () => { |               next: () => { | ||||||
|  | |||||||
| @ -10,7 +10,6 @@ import { RelativeUrlPipe } from '@app/shared/pipes/relative-url/relative-url.pip | |||||||
| import { StateService } from '@app/services/state.service'; | import { StateService } from '@app/services/state.service'; | ||||||
| import { PriceService } from '@app/services/price.service'; | import { PriceService } from '@app/services/price.service'; | ||||||
| import { FiatCurrencyPipe } from '@app/shared/pipes/fiat-currency.pipe'; | import { FiatCurrencyPipe } from '@app/shared/pipes/fiat-currency.pipe'; | ||||||
| import { FiatShortenerPipe } from '@app/shared/pipes/fiat-shortener.pipe'; |  | ||||||
| 
 | 
 | ||||||
| const periodSeconds = { | const periodSeconds = { | ||||||
|   '1d': (60 * 60 * 24), |   '1d': (60 * 60 * 24), | ||||||
| @ -77,7 +76,6 @@ export class AddressGraphComponent implements OnChanges, OnDestroy { | |||||||
|     private relativeUrlPipe: RelativeUrlPipe, |     private relativeUrlPipe: RelativeUrlPipe, | ||||||
|     private priceService: PriceService, |     private priceService: PriceService, | ||||||
|     private fiatCurrencyPipe: FiatCurrencyPipe, |     private fiatCurrencyPipe: FiatCurrencyPipe, | ||||||
|     private fiatShortenerPipe: FiatShortenerPipe, |  | ||||||
|     private zone: NgZone, |     private zone: NgZone, | ||||||
|   ) {} |   ) {} | ||||||
| 
 | 
 | ||||||
| @ -245,18 +243,19 @@ export class AddressGraphComponent implements OnChanges, OnDestroy { | |||||||
|           let tooltip = '<div>'; |           let tooltip = '<div>'; | ||||||
| 
 | 
 | ||||||
|           const hasTx = data[0].data[2].txid; |           const hasTx = data[0].data[2].txid; | ||||||
|  |           const date = new Date(data[0].data[0]).toLocaleTimeString(this.locale, { year: 'numeric', month: 'short', day: 'numeric' }); | ||||||
|  |            | ||||||
|  |           tooltip += `<div>
 | ||||||
|  |             <div style="text-align: right;"> | ||||||
|  |             <div><b>${date}</b></div>`;
 | ||||||
|  | 
 | ||||||
|           if (hasTx) { |           if (hasTx) { | ||||||
|             const header = data.length === 1 |             const header = data.length === 1 | ||||||
|             ? `${data[0].data[2].txid.slice(0, 6)}...${data[0].data[2].txid.slice(-6)}` |             ? `${data[0].data[2].txid.slice(0, 6)}...${data[0].data[2].txid.slice(-6)}` | ||||||
|             : `${data.length} transactions`; |             : `${data.length} transactions`; | ||||||
|             tooltip += `<span><b>${header}</b></span>`; |             tooltip += `<div><b>${header}</b></div>`; | ||||||
|           } |           } | ||||||
|            |            | ||||||
|           const date = new Date(data[0].data[0]).toLocaleTimeString(this.locale, { year: 'numeric', month: 'short', day: 'numeric' }); |  | ||||||
|            |  | ||||||
|           tooltip += `<div>
 |  | ||||||
|             <div style="text-align: right;">`;
 |  | ||||||
|            |  | ||||||
|           const formatBTC = (val, decimal) => (val / 100_000_000).toFixed(decimal); |           const formatBTC = (val, decimal) => (val / 100_000_000).toFixed(decimal); | ||||||
|           const formatFiat = (val) => this.fiatCurrencyPipe.transform(val, null, 'USD'); |           const formatFiat = (val) => this.fiatCurrencyPipe.transform(val, null, 'USD'); | ||||||
|            |            | ||||||
| @ -291,7 +290,7 @@ export class AddressGraphComponent implements OnChanges, OnDestroy { | |||||||
|             } |             } | ||||||
|           } |           } | ||||||
| 
 | 
 | ||||||
|           tooltip += `</div><span>${date}</span></div>`; |           tooltip += `</div></div>`; | ||||||
|           return tooltip; |           return tooltip; | ||||||
|         }.bind(this) |         }.bind(this) | ||||||
|       }, |       }, | ||||||
| @ -311,18 +310,21 @@ export class AddressGraphComponent implements OnChanges, OnDestroy { | |||||||
|             formatter: (val): string => { |             formatter: (val): string => { | ||||||
|               let valSpan = maxValue - (this.period === 'all' ? 0 : minValue); |               let valSpan = maxValue - (this.period === 'all' ? 0 : minValue); | ||||||
|               if (valSpan > 100_000_000_000) { |               if (valSpan > 100_000_000_000) { | ||||||
|                 return `${this.amountShortenerPipe.transform(Math.round(val / 100_000_000), 0)} BTC`; |                 return `${this.amountShortenerPipe.transform(Math.round(val / 100_000_000), 0, undefined, true)} BTC`; | ||||||
|               } |               } | ||||||
|               else if (valSpan > 1_000_000_000) { |               else if (valSpan > 1_000_000_000) { | ||||||
|                 return `${this.amountShortenerPipe.transform(Math.round(val / 100_000_000), 2)} BTC`; |                 return `${this.amountShortenerPipe.transform(Math.round(val / 100_000_000), 2, undefined, true)} BTC`; | ||||||
|               } else if (valSpan > 100_000_000) { |               } else if (valSpan > 100_000_000) { | ||||||
|                 return `${(val / 100_000_000).toFixed(1)} BTC`; |                 return `${(val / 100_000_000).toFixed(1)} BTC`; | ||||||
|               } else if (valSpan > 10_000_000) { |               } else if (valSpan > 10_000_000) { | ||||||
|                 return `${(val / 100_000_000).toFixed(2)} BTC`; |                 return `${(val / 100_000_000).toFixed(2)} BTC`; | ||||||
|               } else if (valSpan > 1_000_000) { |               } else if (valSpan > 1_000_000) { | ||||||
|  |                 if (maxValue > 100_000_000_000) { | ||||||
|  |                   return `${this.amountShortenerPipe.transform(Math.round(val / 100_000_000), 3, undefined, true)} BTC`; | ||||||
|  |                 } | ||||||
|                 return `${(val / 100_000_000).toFixed(3)} BTC`; |                 return `${(val / 100_000_000).toFixed(3)} BTC`; | ||||||
|               } else { |               } else { | ||||||
|                 return `${this.amountShortenerPipe.transform(val, 0)} sats`; |                 return `${this.amountShortenerPipe.transform(val, 0, undefined, true)} sats`; | ||||||
|               } |               } | ||||||
|             } |             } | ||||||
|           }, |           }, | ||||||
| @ -336,7 +338,7 @@ export class AddressGraphComponent implements OnChanges, OnDestroy { | |||||||
|           axisLabel: { |           axisLabel: { | ||||||
|             color: 'rgb(110, 112, 121)', |             color: 'rgb(110, 112, 121)', | ||||||
|             formatter: function(val) { |             formatter: function(val) { | ||||||
|               return this.fiatShortenerPipe.transform(val, null, 'USD'); |               return `$${this.amountShortenerPipe.transform(val, 0, undefined, true)}`; | ||||||
|             }.bind(this) |             }.bind(this) | ||||||
|           }, |           }, | ||||||
|           splitLine: { |           splitLine: { | ||||||
|  | |||||||
| @ -1,15 +1,17 @@ | |||||||
| <ng-template [ngIf]="button" [ngIfElse]="btnLink"> | <ng-template [ngIf]="button" [ngIfElse]="btnLink"> | ||||||
|   <button #btn [attr.data-clipboard-text]="text" [class]="class" type="button" [disabled]="text === ''"> |   <button [class]="class" type="button" [disabled]="text === ''" style="box-shadow: none;" (click)="copyText()"> | ||||||
|     <span #buttonWrapper [attr.data-tlite]="copiedMessage" style="position: relative;top: -2px;left: 1px;"> |     <span style="position: relative;top: -2px;left: 1px;"> | ||||||
|       <app-svg-images name="clippy" [width]="widths[size]" viewBox="0 0 1000 1000"></app-svg-images> |       <app-svg-images name="clippy" [width]="widths[size]" viewBox="0 0 1000 1000"></app-svg-images> | ||||||
|  |       <span *ngIf="showMessage" class="copied-message" style="top: 29px; left: -23.5px;">{{ copiedMessage }}</span> | ||||||
|     </span> |     </span> | ||||||
|   </button> |   </button> | ||||||
| </ng-template> | </ng-template> | ||||||
| 
 | 
 | ||||||
| <ng-template #btnLink> | <ng-template #btnLink> | ||||||
|   <span #buttonWrapper [attr.data-tlite]="copiedMessage" style="position: relative;"> |   <span style="position: relative;"> | ||||||
|     <button #btn class="btn btn-sm btn-link pt-0 {{ leftPadding ? 'padding' : '' }}" [attr.data-clipboard-text]="text">  |     <button class="btn btn-sm btn-link pt-0 {{ leftPadding ? 'padding' : '' }}" style="box-shadow: none;" (click)="copyText()"> | ||||||
|       <app-svg-images name="clippy" [width]="widths[size]" viewBox="0 0 1000 1000"></app-svg-images> |       <app-svg-images name="clippy" [width]="widths[size]" viewBox="0 0 1000 1000"></app-svg-images> | ||||||
|     </button> |     </button> | ||||||
|  |     <span *ngIf="showMessage" class="copied-message" style="top: 29px; left: -23.5px;">{{ copiedMessage }}</span> | ||||||
|   </span> |   </span> | ||||||
| </ng-template> | </ng-template> | ||||||
|  | |||||||
| @ -7,7 +7,19 @@ | |||||||
|   padding-left: 0.4rem; |   padding-left: 0.4rem; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| img { | .copied-message { | ||||||
|   position: relative; |   background: color-mix(in srgb, var(--active-bg) 95%, transparent); | ||||||
|   left: -3px; |   color: var(--fg); | ||||||
| } |   font-family: sans-serif; | ||||||
|  |   font-size: .8rem; | ||||||
|  |   font-weight: 400; | ||||||
|  |   text-decoration: none; | ||||||
|  |   text-align: left; | ||||||
|  |   padding: .6em .75rem; | ||||||
|  |   border-radius: 4px; | ||||||
|  |   position: absolute; | ||||||
|  |   white-space: nowrap; | ||||||
|  |   box-shadow: 0 .5rem 1rem -.5rem #000; | ||||||
|  |   z-index: 1000; | ||||||
|  |   opacity: .9; | ||||||
|  | } | ||||||
|  | |||||||
| @ -1,6 +1,4 @@ | |||||||
| import { Component, ViewChild, ElementRef, AfterViewInit, Input, ChangeDetectionStrategy } from '@angular/core'; | import { Component, Input, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; | ||||||
| import * as ClipboardJS from 'clipboard'; |  | ||||||
| import * as tlite from 'tlite'; |  | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-clipboard', |   selector: 'app-clipboard', | ||||||
| @ -8,15 +6,14 @@ import * as tlite from 'tlite'; | |||||||
|   styleUrls: ['./clipboard.component.scss'], |   styleUrls: ['./clipboard.component.scss'], | ||||||
|   changeDetection: ChangeDetectionStrategy.OnPush, |   changeDetection: ChangeDetectionStrategy.OnPush, | ||||||
| }) | }) | ||||||
| export class ClipboardComponent implements AfterViewInit { | export class ClipboardComponent { | ||||||
|   @ViewChild('btn') btn: ElementRef; |  | ||||||
|   @ViewChild('buttonWrapper') buttonWrapper: ElementRef; |  | ||||||
|   @Input() button = false; |   @Input() button = false; | ||||||
|   @Input() class = 'btn btn-secondary ml-1'; |   @Input() class = 'btn btn-secondary ml-1'; | ||||||
|   @Input() size: 'small' | 'normal' | 'large' = 'normal'; |   @Input() size: 'small' | 'normal' | 'large' = 'normal'; | ||||||
|   @Input() text: string; |   @Input() text: string; | ||||||
|   @Input() leftPadding = true; |   @Input() leftPadding = true; | ||||||
|   copiedMessage: string = $localize`:@@clipboard.copied-message:Copied!`; |   copiedMessage: string = $localize`:@@clipboard.copied-message:Copied!`; | ||||||
|  |   showMessage = false; | ||||||
| 
 | 
 | ||||||
|   widths = { |   widths = { | ||||||
|     small: '10', |     small: '10', | ||||||
| @ -24,22 +21,40 @@ export class ClipboardComponent implements AfterViewInit { | |||||||
|     large: '18', |     large: '18', | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   clipboard: any; |   constructor( | ||||||
|  |     private cd: ChangeDetectorRef, | ||||||
|  |   ) { } | ||||||
| 
 | 
 | ||||||
|   constructor() { } |   async copyText() { | ||||||
| 
 |     if (this.text && !this.showMessage) { | ||||||
|   ngAfterViewInit() { |       try { | ||||||
|     this.clipboard = new ClipboardJS(this.btn.nativeElement); |         await this.copyToClipboard(this.text); | ||||||
|     this.clipboard.on('success', () => { |         this.showMessage = true; | ||||||
|       tlite.show(this.buttonWrapper.nativeElement); |         this.cd.markForCheck(); | ||||||
|       setTimeout(() => { |         setTimeout(() => { | ||||||
|         tlite.hide(this.buttonWrapper.nativeElement); |           this.showMessage = false; | ||||||
|       }, 1000); |           this.cd.markForCheck(); | ||||||
|     }); |         }, 1000); | ||||||
|  |       } catch (error) { | ||||||
|  |         console.error('Clipboard copy failed:', error); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   onDestroy() { |   async copyToClipboard(text: string) { | ||||||
|     this.clipboard.destroy(); |     if (navigator.clipboard) { | ||||||
|  |       await navigator.clipboard.writeText(text); | ||||||
|  |     } else { | ||||||
|  |       // Use the 'out of viewport hidden text area' trick on non-secure contexts
 | ||||||
|  |       const textarea = document.createElement('textarea'); | ||||||
|  |       textarea.value = this.text; | ||||||
|  |       textarea.style.opacity = '0'; | ||||||
|  |       textarea.setAttribute('readonly', 'true'); // Don't trigger keyboard on mobile
 | ||||||
|  |       document.body.appendChild(textarea); | ||||||
|  |       textarea.select(); | ||||||
|  |       document.execCommand('copy'); | ||||||
|  |       textarea.remove(); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -217,10 +217,10 @@ | |||||||
|     <tr> |     <tr> | ||||||
|       <td class="td-width" i18n="transaction.fee|Transaction fee">Fee</td> |       <td class="td-width" i18n="transaction.fee|Transaction fee">Fee</td> | ||||||
|       <td class="text-wrap">{{ tx.fee | number }} <span class="symbol" i18n="shared.sats">sats</span> |       <td class="text-wrap">{{ tx.fee | number }} <span class="symbol" i18n="shared.sats">sats</span> | ||||||
|         @if (accelerationInfo?.bidBoost ?? tx.feeDelta > 0) { |         @if (isAcceleration && accelerationInfo?.bidBoost ?? tx.feeDelta > 0) { | ||||||
|           <span class="oobFees" i18n-ngbTooltip="Acceleration Fees" ngbTooltip="Acceleration fees paid out-of-band"> +{{ accelerationInfo?.bidBoost ?? tx.feeDelta | number }} </span><span class="symbol" i18n="shared.sats">sats</span> |           <span class="oobFees" i18n-ngbTooltip="Acceleration Fees" ngbTooltip="Acceleration fees paid out-of-band"> +{{ accelerationInfo?.bidBoost ?? tx.feeDelta | number }} </span><span class="symbol" i18n="shared.sats">sats</span> | ||||||
|         } |         } | ||||||
|         <span class="fiat"><app-fiat [blockConversion]="tx.price" [value]="tx.fee + ((accelerationInfo?.bidBoost ?? tx.feeDelta) || 0)"></app-fiat></span> |         <span class="fiat"><app-fiat [blockConversion]="tx.price" [value]="tx.fee + (isAcceleration ? ((accelerationInfo?.bidBoost ?? tx.feeDelta) || 0) : 0)"></app-fiat></span> | ||||||
|       </td> |       </td> | ||||||
|     </tr> |     </tr> | ||||||
|   } @else { |   } @else { | ||||||
|  | |||||||
| @ -879,6 +879,9 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | |||||||
|       this.tx.acceleratedAt = cpfpInfo.acceleratedAt; |       this.tx.acceleratedAt = cpfpInfo.acceleratedAt; | ||||||
|       this.tx.feeDelta = cpfpInfo.feeDelta; |       this.tx.feeDelta = cpfpInfo.feeDelta; | ||||||
|       this.setIsAccelerated(firstCpfp); |       this.setIsAccelerated(firstCpfp); | ||||||
|  |     } else if (this.tx.acceleration) { // Acceleration was cancelled while on the tx page, reset acceleration state
 | ||||||
|  |       this.tx.acceleration = false; | ||||||
|  |       this.setIsAccelerated(firstCpfp); | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     if (this.notAcceleratedOnLoad === null) { |     if (this.notAcceleratedOnLoad === null) { | ||||||
|  | |||||||
| @ -9339,7 +9339,7 @@ export const restApiDocsData = [ | |||||||
|     fragment: "accelerator-history", |     fragment: "accelerator-history", | ||||||
|     title: "GET Acceleration History", |     title: "GET Acceleration History", | ||||||
|     description: { |     description: { | ||||||
|       default: "<p>Returns the user's past acceleration requests.</p><p>Pass one of the following for <code>:status</code>: <code>all</code>, <code>requested</code>, <code>accelerating</code>, <code>mined</code>, <code>completed</code>, <code>failed</code>. Pass <code>true</code> in <code>:details</code> to get a detailed <code>history</code> of the acceleration request.</p>" |       default: "<p>Returns the user's past acceleration requests.</p><p>Pass one of the following for <code>:status</code> (required): <code>all</code>, <code>requested</code>, <code>accelerating</code>, <code>mined</code>, <code>completed</code>, <code>failed</code>.<br>Pass <code>true</code> in <code>:details</code> to get a detailed <code>history</code> of the acceleration request.</p>" | ||||||
|     }, |     }, | ||||||
|     urlString: "/v1/services/accelerator/history?status=:status&details=:details", |     urlString: "/v1/services/accelerator/history?status=:status&details=:details", | ||||||
|     showConditions: [""], |     showConditions: [""], | ||||||
| @ -9449,6 +9449,36 @@ export const restApiDocsData = [ | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     options: { officialOnly: true }, | ||||||
|  |     type: "endpoint", | ||||||
|  |     category: "accelerator-private", | ||||||
|  |     httpRequestMethod: "POST", | ||||||
|  |     fragment: "accelerator-cancel", | ||||||
|  |     title: "POST Cancel Acceleration (Pro)", | ||||||
|  |     description: { | ||||||
|  |       default: "<p>Sends a request to cancel an acceleration in the <code>accelerating</code> status.<br>You can retreive eligible acceleration <code>id</code> using the history endpoint GET <code>/api/v1/services/accelerator/history?status=accelerating</code>." | ||||||
|  |     }, | ||||||
|  |     urlString: "/v1/services/accelerator/cancel", | ||||||
|  |     showConditions: [""], | ||||||
|  |     showJsExamples: showJsExamplesDefaultFalse, | ||||||
|  |     codeExample: { | ||||||
|  |       default: { | ||||||
|  |         codeTemplate: { | ||||||
|  |           curl: `%{1}" "[[hostname]][[baseNetworkUrl]]/api/v1/services/accelerator/cancel`, //custom interpolation technique handled in replaceCurlPlaceholder()
 | ||||||
|  |           commonJS: ``, | ||||||
|  |           esModule: `` | ||||||
|  |         }, | ||||||
|  |         codeSampleMainnet: { | ||||||
|  |           esModule: [], | ||||||
|  |           commonJS: [], | ||||||
|  |           curl: ["id=42"], | ||||||
|  |           headers: "X-Mempool-Auth: stacksats", | ||||||
|  |           response: `HTTP/1.1 200 OK`, | ||||||
|  |         }, | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }, | ||||||
| ]; | ]; | ||||||
| 
 | 
 | ||||||
| export const faqData = [ | export const faqData = [ | ||||||
|  | |||||||
| @ -131,20 +131,20 @@ export class ServicesApiServices { | |||||||
|     return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/estimate`, { txInput: txInput }, { observe: 'response' }); |     return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/estimate`, { txInput: txInput }, { observe: 'response' }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   accelerate$(txInput: string, userBid: number, accelerationUUID: string) { |   accelerate$(txInput: string, userBid: number) { | ||||||
|     return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate`, { txInput: txInput, userBid: userBid, accelerationUUID: accelerationUUID }); |     return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate`, { txInput: txInput, userBid: userBid}); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   accelerateWithCashApp$(txInput: string, token: string, cashtag: string, referenceId: string, accelerationUUID: string, userApprovedUSD: number) { |   accelerateWithCashApp$(txInput: string, token: string, cashtag: string, referenceId: string, userApprovedUSD: number) { | ||||||
|     return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/cashapp`, { txInput: txInput, token: token, cashtag: cashtag, referenceId: referenceId, accelerationUUID: accelerationUUID, userApprovedUSD: userApprovedUSD }); |     return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/cashapp`, { txInput: txInput, token: token, cashtag: cashtag, referenceId: referenceId, userApprovedUSD: userApprovedUSD }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   accelerateWithApplePay$(txInput: string, token: string, cardTag: string, referenceId: string, accelerationUUID: string, userApprovedUSD: number) { |   accelerateWithApplePay$(txInput: string, token: string, cardTag: string, referenceId: string, userApprovedUSD: number) { | ||||||
|     return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/applePay`, { txInput: txInput, cardTag: cardTag, token: token, referenceId: referenceId, accelerationUUID: accelerationUUID, userApprovedUSD: userApprovedUSD }); |     return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/applePay`, { txInput: txInput, cardTag: cardTag, token: token, referenceId: referenceId, userApprovedUSD: userApprovedUSD }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   accelerateWithGooglePay$(txInput: string, token: string, cardTag: string, referenceId: string, accelerationUUID: string, userApprovedUSD: number) { |   accelerateWithGooglePay$(txInput: string, token: string, cardTag: string, referenceId: string, userApprovedUSD: number) { | ||||||
|     return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/googlePay`, { txInput: txInput, cardTag: cardTag, token: token, referenceId: referenceId, accelerationUUID: accelerationUUID, userApprovedUSD: userApprovedUSD }); |     return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/googlePay`, { txInput: txInput, cardTag: cardTag, token: token, referenceId: referenceId, userApprovedUSD: userApprovedUSD }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   getAccelerations$(): Observable<Acceleration[]> { |   getAccelerations$(): Observable<Acceleration[]> { | ||||||
|  | |||||||
| @ -37,6 +37,7 @@ export class WebsocketService { | |||||||
|   private isTrackingWallet: boolean = false; |   private isTrackingWallet: boolean = false; | ||||||
|   private trackingWalletName: string; |   private trackingWalletName: string; | ||||||
|   private trackingMempoolBlock: number; |   private trackingMempoolBlock: number; | ||||||
|  |   private trackingMempoolBlockNetwork: string; | ||||||
|   private stoppingTrackMempoolBlock: any | null = null; |   private stoppingTrackMempoolBlock: any | null = null; | ||||||
|   private latestGitCommit = ''; |   private latestGitCommit = ''; | ||||||
|   private onlineCheckTimeout: number; |   private onlineCheckTimeout: number; | ||||||
| @ -226,10 +227,11 @@ export class WebsocketService { | |||||||
|       clearTimeout(this.stoppingTrackMempoolBlock); |       clearTimeout(this.stoppingTrackMempoolBlock); | ||||||
|     } |     } | ||||||
|     // skip duplicate tracking requests
 |     // skip duplicate tracking requests
 | ||||||
|     if (force || this.trackingMempoolBlock !== block) { |     if (force || this.trackingMempoolBlock !== block || this.network !== this.trackingMempoolBlockNetwork) { | ||||||
|       this.websocketSubject.next({ 'track-mempool-block': block }); |       this.websocketSubject.next({ 'track-mempool-block': block }); | ||||||
|       this.isTrackingMempoolBlock = true; |       this.isTrackingMempoolBlock = true; | ||||||
|       this.trackingMempoolBlock = block; |       this.trackingMempoolBlock = block; | ||||||
|  |       this.trackingMempoolBlockNetwork = this.network; | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|     return false; |     return false; | ||||||
|  | |||||||
| @ -214,19 +214,6 @@ export function renderSats(value: number, network: string, mode: 'sats' | 'btc' | |||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function insecureRandomUUID(): string { |  | ||||||
|   const hexDigits = '0123456789abcdef'; |  | ||||||
|   const uuidLengths = [8, 4, 4, 4, 12]; |  | ||||||
|   let uuid = ''; |  | ||||||
|   for (const length of uuidLengths) { |  | ||||||
|       for (let i = 0; i < length; i++) { |  | ||||||
|           uuid += hexDigits[Math.floor(Math.random() * 16)]; |  | ||||||
|       } |  | ||||||
|       uuid += '-'; |  | ||||||
|   } |  | ||||||
|   return uuid.slice(0, -1); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export function sleep$(ms: number): Promise<void> { | export function sleep$(ms: number): Promise<void> { | ||||||
|   return new Promise((resolve) => { |   return new Promise((resolve) => { | ||||||
|      setTimeout(() => { |      setTimeout(() => { | ||||||
|  | |||||||
							
								
								
									
										45
									
								
								frontend/src/index.mempool.meta.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,45 @@ | |||||||
|  | <!doctype html> | ||||||
|  | <html lang="en"> | ||||||
|  | 
 | ||||||
|  | <head> | ||||||
|  |   <meta charset="utf-8"> | ||||||
|  |   <title>Metaplanet Inc</title> | ||||||
|  |   <script src="/resources/config.js"></script> | ||||||
|  |   <script src="/resources/customize.js"></script> | ||||||
|  |   <base href="/"> | ||||||
|  | 
 | ||||||
|  |   <meta name="description" content="Secure the Future with Bitcoin." /> | ||||||
|  |   <meta property="og:image" content="https://mempool.space/resources/meta/meta-preview.jpg" /> | ||||||
|  |   <meta property="og:image:type" content="image/jpeg" /> | ||||||
|  |   <meta property="og:image:width" content="2000" /> | ||||||
|  |   <meta property="og:image:height" content="1000" /> | ||||||
|  |   <meta property="og:description" content="Secure the Future with Bitcoin." /> | ||||||
|  |   <meta name="twitter:card" content="summary_large_image"> | ||||||
|  |   <meta name="twitter:site" content="@mempool"> | ||||||
|  |   <meta name="twitter:creator" content="@mempool"> | ||||||
|  |   <meta name="twitter:title" content="Metaplanet Inc"> | ||||||
|  |   <meta name="twitter:description" content="Secure the Future with Bitcoin." /> | ||||||
|  |   <meta name="twitter:image" content="https://mempool.space/resources/meta/meta-preview.jpg" /> | ||||||
|  |   <meta name="twitter:domain" content="metaplanet.mempool.space"> | ||||||
|  | 
 | ||||||
|  |   <link rel="apple-touch-icon" sizes="180x180" href="/resources/meta/favicons/apple-touch-icon.png"> | ||||||
|  |   <link rel="icon" type="image/png" sizes="32x32" href="/resources/meta/favicons/favicon-32x32.png"> | ||||||
|  |   <link rel="icon" type="image/png" sizes="16x16" href="/resources/meta/favicons/favicon-16x16.png"> | ||||||
|  |   <link rel="manifest" href="/resources/meta/favicons/site.webmanifest"> | ||||||
|  |   <link rel="shortcut icon" href="/resources/meta/favicons/favicon.ico"> | ||||||
|  |   <link id="canonical" rel="canonical" href="https://metaplanet.mempool.space"> | ||||||
|  | 
 | ||||||
|  |   <meta name="apple-mobile-web-app-capable" content="yes"> | ||||||
|  |   <meta name="apple-mobile-web-app-status-bar-style" content="black"> | ||||||
|  |   <meta name="msapplication-TileColor" content="#000000"> | ||||||
|  |   <meta name="msapplication-config" content="/resources/favicons/browserconfig.xml"> | ||||||
|  |   <meta name="theme-color" content="#1d1f31"> | ||||||
|  | 
 | ||||||
|  |   <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||||
|  | </head> | ||||||
|  | 
 | ||||||
|  | <body> | ||||||
|  |   <app-root></app-root> | ||||||
|  | </body> | ||||||
|  | 
 | ||||||
|  | </html> | ||||||
							
								
								
									
										
											BIN
										
									
								
								frontend/src/resources/meta/favicons/android-chrome-192x192.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 20 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/src/resources/meta/favicons/android-chrome-512x512.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 92 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/src/resources/meta/favicons/apple-touch-icon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 18 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/src/resources/meta/favicons/favicon-16x16.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 698 B | 
							
								
								
									
										
											BIN
										
									
								
								frontend/src/resources/meta/favicons/favicon-32x32.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/src/resources/meta/favicons/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 15 KiB | 
							
								
								
									
										1
									
								
								frontend/src/resources/meta/favicons/site.webmanifest
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1 @@ | |||||||
|  | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} | ||||||
							
								
								
									
										
											BIN
										
									
								
								frontend/src/resources/meta/meta-preview.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.4 MiB | 
							
								
								
									
										1
									
								
								frontend/src/resources/metalogo.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 5.4 KiB | 
| @ -4,7 +4,6 @@ txindex=1 | |||||||
| coinstatsindex=1 | coinstatsindex=1 | ||||||
| listen=1 | listen=1 | ||||||
| discover=1 | discover=1 | ||||||
| par=16 |  | ||||||
| dbcache=8192 | dbcache=8192 | ||||||
| mempoolfullrbf=1 | mempoolfullrbf=1 | ||||||
| maxconnections=100 | maxconnections=100 | ||||||
|  | |||||||
| @ -153,6 +153,6 @@ | |||||||
|   }, |   }, | ||||||
|   "WALLETS": { |   "WALLETS": { | ||||||
|     "ENABLED": true, |     "ENABLED": true, | ||||||
|     "WALLETS": ["BITB"] |     "WALLETS": ["BITB", "3350"] | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||