Merge branch 'master' into simon/database-migration-feature
# Conflicts: # backend/src/index.ts
This commit is contained in:
		
						commit
						91e30fbc3c
					
				@ -1,7 +1,14 @@
 | 
			
		||||
---
 | 
			
		||||
name: 🐛 Bug Report
 | 
			
		||||
about: Report bugs or other issues to us on GitHub
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
<!--
 | 
			
		||||
   SUPPORT REQUESTS: This is for reporting bugs in Mempool.
 | 
			
		||||
   If you have a support request, please join our Keybase group:
 | 
			
		||||
   SUPPORT REQUESTS:
 | 
			
		||||
   This is for reporting bugs in Mempool, not for support requests. 
 | 
			
		||||
   If you have a support request, please join our Keybase or Matrix:
 | 
			
		||||
   https://keybase.io/team/mempool
 | 
			
		||||
   https://matrix.to/#/#mempool:bitcoin.kyoto
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
### Description
 | 
			
		||||
@ -14,11 +21,11 @@
 | 
			
		||||
 | 
			
		||||
### Steps to reproduce
 | 
			
		||||
 | 
			
		||||
<!--if you can reliably reproduce the bug, list the steps here  -->
 | 
			
		||||
<!-- if you can reliably reproduce the bug, list the steps here -->
 | 
			
		||||
 | 
			
		||||
### Expected behaviour
 | 
			
		||||
 | 
			
		||||
<!--description of the expected behavior -->
 | 
			
		||||
<!-- description of the expected behavior -->
 | 
			
		||||
 | 
			
		||||
### Actual behaviour
 | 
			
		||||
 | 
			
		||||
@ -26,7 +33,7 @@
 | 
			
		||||
 | 
			
		||||
### Screenshots
 | 
			
		||||
 | 
			
		||||
<!--Screenshots if gui related, drag and drop to add to the issue -->
 | 
			
		||||
<!-- Screenshots if gui related, drag and drop to add to the issue -->
 | 
			
		||||
 | 
			
		||||
#### Device or machine
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										28
									
								
								.github/ISSUE_TEMPLATE/30-feature-request.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								.github/ISSUE_TEMPLATE/30-feature-request.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,28 @@
 | 
			
		||||
---
 | 
			
		||||
name: ✨ Feature Request
 | 
			
		||||
about: Request a feature or suggest other enhancements 💡
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
<!--
 | 
			
		||||
   SUPPORT REQUESTS:
 | 
			
		||||
   This is for requesting features in Mempool, not for support requests. 
 | 
			
		||||
   If you have a support request, please join our Keybase or Matrix:
 | 
			
		||||
   https://keybase.io/team/mempool
 | 
			
		||||
   https://matrix.to/#/#mempool:bitcoin.kyoto
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
### Description
 | 
			
		||||
 | 
			
		||||
<!-- brief description of the feature request -->
 | 
			
		||||
 | 
			
		||||
### Problem to be solved
 | 
			
		||||
 | 
			
		||||
<!-- description of the the problem you're having -->
 | 
			
		||||
 | 
			
		||||
### Proposed solution
 | 
			
		||||
 | 
			
		||||
<!-- explain how you think we should solve the problem -->
 | 
			
		||||
 | 
			
		||||
#### Additional info
 | 
			
		||||
 | 
			
		||||
<!-- Additional information useful for implementing (e.g. docs, links, etc.) -->
 | 
			
		||||
							
								
								
									
										8
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
blank_issues_enabled: false
 | 
			
		||||
contact_links:
 | 
			
		||||
  - name: 💬 Need help? Chat with us on Matrix
 | 
			
		||||
    url: https://matrix.to/#/#mempool:bitcoin.kyoto
 | 
			
		||||
    about: For support requests or general questions
 | 
			
		||||
  - name: 💬 Need help? Chat with us on Keybase
 | 
			
		||||
    url: https://keybase.io/team/mempool
 | 
			
		||||
    about: For support requests or general questions
 | 
			
		||||
							
								
								
									
										7
									
								
								backend/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								backend/.gitignore
									
									
									
									
										vendored
									
									
								
							@ -1,7 +1,10 @@
 | 
			
		||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
 | 
			
		||||
 | 
			
		||||
# production config
 | 
			
		||||
mempool-config.json
 | 
			
		||||
# production config and external assets
 | 
			
		||||
*.json
 | 
			
		||||
!mempool-config.sample.json
 | 
			
		||||
 | 
			
		||||
icons.json
 | 
			
		||||
 | 
			
		||||
# compiled output
 | 
			
		||||
/dist
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,8 @@
 | 
			
		||||
    "INITIAL_BLOCKS_AMOUNT": 8,
 | 
			
		||||
    "MEMPOOL_BLOCKS_AMOUNT": 8,
 | 
			
		||||
    "PRICE_FEED_UPDATE_INTERVAL": 3600,
 | 
			
		||||
    "USE_SECOND_NODE_FOR_MINFEE": false
 | 
			
		||||
    "USE_SECOND_NODE_FOR_MINFEE": false,
 | 
			
		||||
    "EXTERNAL_ASSETS": []
 | 
			
		||||
  },
 | 
			
		||||
  "CORE_RPC": {
 | 
			
		||||
    "HOST": "127.0.0.1",
 | 
			
		||||
 | 
			
		||||
@ -108,7 +108,7 @@ export namespace IBitcoinApi {
 | 
			
		||||
    scriptPubKey: string;            //  (string) The hex-encoded scriptPubKey generated by the address
 | 
			
		||||
    isscript: boolean;               //  (boolean) If the key is a script
 | 
			
		||||
    iswitness: boolean;              //  (boolean) If the address is a witness
 | 
			
		||||
    witness_version?: boolean;       //  (numeric, optional) The version number of the witness program
 | 
			
		||||
    witness_version?: number;        //  (numeric, optional) The version number of the witness program
 | 
			
		||||
    witness_program: string;         //  (string, optional) The hex value of the witness program
 | 
			
		||||
    confidential_key?: string;       //  (string) Elements only
 | 
			
		||||
    unconfidential?: string;         //  (string) Elements only
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										39
									
								
								backend/src/api/liquid/icons.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								backend/src/api/liquid/icons.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,39 @@
 | 
			
		||||
import * as fs from 'fs';
 | 
			
		||||
import config from '../../config';
 | 
			
		||||
import logger from '../../logger';
 | 
			
		||||
 | 
			
		||||
class Icons {
 | 
			
		||||
  private static FILE_NAME = './icons.json';
 | 
			
		||||
  private iconIds: string[] = [];
 | 
			
		||||
  private icons: { [assetId: string]: string; } = {};
 | 
			
		||||
 | 
			
		||||
  constructor() {}
 | 
			
		||||
 | 
			
		||||
  public loadIcons() {
 | 
			
		||||
    if (!fs.existsSync(Icons.FILE_NAME)) {
 | 
			
		||||
      logger.warn(`${Icons.FILE_NAME} does not exist. No Liquid icons loaded.`);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    const cacheData = fs.readFileSync(Icons.FILE_NAME, 'utf8');
 | 
			
		||||
    this.icons = JSON.parse(cacheData);
 | 
			
		||||
 | 
			
		||||
    for (const i in this.icons) {
 | 
			
		||||
      this.iconIds.push(i);
 | 
			
		||||
    }
 | 
			
		||||
    logger.debug(`Liquid icons has been loaded.`);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public getIconByAssetId(assetId: string): Buffer | undefined {
 | 
			
		||||
    const icon = this.icons[assetId];
 | 
			
		||||
    if (icon) {
 | 
			
		||||
      return Buffer.from(icon, 'base64');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public getAllIconIds() {
 | 
			
		||||
    return this.iconIds;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default new Icons();
 | 
			
		||||
@ -267,8 +267,57 @@ class Statistics {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private getQueryForDays(div: number) {
 | 
			
		||||
    return `SELECT id, added, unconfirmed_transactions,
 | 
			
		||||
  private getQueryForDaysAvg(div: number, interval: string) {
 | 
			
		||||
    return `SELECT id, UNIX_TIMESTAMP(added) as added,
 | 
			
		||||
      CAST(avg(unconfirmed_transactions) as FLOAT) as unconfirmed_transactions,
 | 
			
		||||
      CAST(avg(tx_per_second) as FLOAT) as tx_per_second,
 | 
			
		||||
      CAST(avg(vbytes_per_second) as FLOAT) as vbytes_per_second,
 | 
			
		||||
      CAST(avg(vsize_1) as FLOAT) as vsize_1,
 | 
			
		||||
      CAST(avg(vsize_2) as FLOAT) as vsize_2,
 | 
			
		||||
      CAST(avg(vsize_3) as FLOAT) as vsize_3,
 | 
			
		||||
      CAST(avg(vsize_4) as FLOAT) as vsize_4,
 | 
			
		||||
      CAST(avg(vsize_5) as FLOAT) as vsize_5,
 | 
			
		||||
      CAST(avg(vsize_6) as FLOAT) as vsize_6,
 | 
			
		||||
      CAST(avg(vsize_8) as FLOAT) as vsize_8,
 | 
			
		||||
      CAST(avg(vsize_10) as FLOAT) as vsize_10,
 | 
			
		||||
      CAST(avg(vsize_12) as FLOAT) as vsize_12,
 | 
			
		||||
      CAST(avg(vsize_15) as FLOAT) as vsize_15,
 | 
			
		||||
      CAST(avg(vsize_20) as FLOAT) as vsize_20,
 | 
			
		||||
      CAST(avg(vsize_30) as FLOAT) as vsize_30,
 | 
			
		||||
      CAST(avg(vsize_40) as FLOAT) as vsize_40,
 | 
			
		||||
      CAST(avg(vsize_50) as FLOAT) as vsize_50,
 | 
			
		||||
      CAST(avg(vsize_60) as FLOAT) as vsize_60,
 | 
			
		||||
      CAST(avg(vsize_70) as FLOAT) as vsize_70,
 | 
			
		||||
      CAST(avg(vsize_80) as FLOAT) as vsize_80,
 | 
			
		||||
      CAST(avg(vsize_90) as FLOAT) as vsize_90,
 | 
			
		||||
      CAST(avg(vsize_100) as FLOAT) as vsize_100,
 | 
			
		||||
      CAST(avg(vsize_125) as FLOAT) as vsize_125,
 | 
			
		||||
      CAST(avg(vsize_150) as FLOAT) as vsize_150,
 | 
			
		||||
      CAST(avg(vsize_175) as FLOAT) as vsize_175,
 | 
			
		||||
      CAST(avg(vsize_200) as FLOAT) as vsize_200,
 | 
			
		||||
      CAST(avg(vsize_250) as FLOAT) as vsize_250,
 | 
			
		||||
      CAST(avg(vsize_300) as FLOAT) as vsize_300,
 | 
			
		||||
      CAST(avg(vsize_350) as FLOAT) as vsize_350,
 | 
			
		||||
      CAST(avg(vsize_400) as FLOAT) as vsize_400,
 | 
			
		||||
      CAST(avg(vsize_500) as FLOAT) as vsize_500,
 | 
			
		||||
      CAST(avg(vsize_600) as FLOAT) as vsize_600,
 | 
			
		||||
      CAST(avg(vsize_700) as FLOAT) as vsize_700,
 | 
			
		||||
      CAST(avg(vsize_800) as FLOAT) as vsize_800,
 | 
			
		||||
      CAST(avg(vsize_900) as FLOAT) as vsize_900,
 | 
			
		||||
      CAST(avg(vsize_1000) as FLOAT) as vsize_1000,
 | 
			
		||||
      CAST(avg(vsize_1200) as FLOAT) as vsize_1200,
 | 
			
		||||
      CAST(avg(vsize_1400) as FLOAT) as vsize_1400,
 | 
			
		||||
      CAST(avg(vsize_1600) as FLOAT) as vsize_1600,
 | 
			
		||||
      CAST(avg(vsize_1800) as FLOAT) as vsize_1800,
 | 
			
		||||
      CAST(avg(vsize_2000) as FLOAT) as vsize_2000 \
 | 
			
		||||
      FROM statistics \
 | 
			
		||||
      WHERE added BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW() \
 | 
			
		||||
      GROUP BY UNIX_TIMESTAMP(added) DIV ${div} \
 | 
			
		||||
      ORDER BY id DESC;`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private getQueryForDays(div: number, interval: string) {
 | 
			
		||||
    return `SELECT id, UNIX_TIMESTAMP(added) as added, unconfirmed_transactions,
 | 
			
		||||
      tx_per_second,
 | 
			
		||||
      vbytes_per_second,
 | 
			
		||||
      vsize_1,
 | 
			
		||||
@ -308,13 +357,17 @@ class Statistics {
 | 
			
		||||
      vsize_1400,
 | 
			
		||||
      vsize_1600,
 | 
			
		||||
      vsize_1800,
 | 
			
		||||
      vsize_2000 FROM statistics GROUP BY UNIX_TIMESTAMP(added) DIV ${div} ORDER BY id DESC LIMIT 480`;
 | 
			
		||||
      vsize_2000 \
 | 
			
		||||
      FROM statistics \
 | 
			
		||||
      WHERE added BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW() \
 | 
			
		||||
      GROUP BY UNIX_TIMESTAMP(added) DIV ${div} \
 | 
			
		||||
      ORDER BY id DESC;`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async $get(id: number): Promise<OptimizedStatistic | undefined> {
 | 
			
		||||
    try {
 | 
			
		||||
      const connection = await DB.pool.getConnection();
 | 
			
		||||
      const query = `SELECT * FROM statistics WHERE id = ?`;
 | 
			
		||||
      const query = `SELECT *, UNIX_TIMESTAMP(added) as added FROM statistics WHERE id = ?`;
 | 
			
		||||
      const [rows] = await connection.query<any>(query, [id]);
 | 
			
		||||
      connection.release();
 | 
			
		||||
      if (rows[0]) {
 | 
			
		||||
@ -328,7 +381,7 @@ class Statistics {
 | 
			
		||||
  public async $list2H(): Promise<OptimizedStatistic[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      const connection = await DB.pool.getConnection();
 | 
			
		||||
      const query = `SELECT * FROM statistics ORDER BY id DESC LIMIT 120`;
 | 
			
		||||
      const query = `SELECT *, UNIX_TIMESTAMP(added) as added FROM statistics ORDER BY id DESC LIMIT 120`;
 | 
			
		||||
      const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
@ -341,7 +394,7 @@ class Statistics {
 | 
			
		||||
  public async $list24H(): Promise<OptimizedStatistic[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      const connection = await DB.pool.getConnection();
 | 
			
		||||
      const query = this.getQueryForDays(180);
 | 
			
		||||
      const query = `SELECT *, UNIX_TIMESTAMP(added) as added FROM statistics ORDER BY id DESC LIMIT 1440`;
 | 
			
		||||
      const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
@ -354,7 +407,7 @@ class Statistics {
 | 
			
		||||
  public async $list1W(): Promise<OptimizedStatistic[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      const connection = await DB.pool.getConnection();
 | 
			
		||||
      const query = this.getQueryForDays(1260);
 | 
			
		||||
      const query = this.getQueryForDaysAvg(600, '1 WEEK'); // 10m interval
 | 
			
		||||
      const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
@ -367,7 +420,7 @@ class Statistics {
 | 
			
		||||
  public async $list1M(): Promise<OptimizedStatistic[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      const connection = await DB.pool.getConnection();
 | 
			
		||||
      const query = this.getQueryForDays(5040);
 | 
			
		||||
      const query = this.getQueryForDaysAvg(3600, '1 MONTH'); // 1h interval
 | 
			
		||||
      const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
@ -380,7 +433,7 @@ class Statistics {
 | 
			
		||||
  public async $list3M(): Promise<OptimizedStatistic[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      const connection = await DB.pool.getConnection();
 | 
			
		||||
      const query = this.getQueryForDays(15120);
 | 
			
		||||
      const query = this.getQueryForDaysAvg(14400, '3 MONTH'); // 4h interval
 | 
			
		||||
      const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
@ -393,7 +446,7 @@ class Statistics {
 | 
			
		||||
  public async $list6M(): Promise<OptimizedStatistic[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      const connection = await DB.pool.getConnection();
 | 
			
		||||
      const query = this.getQueryForDays(30240);
 | 
			
		||||
      const query = this.getQueryForDaysAvg(21600, '6 MONTH'); // 6h interval 
 | 
			
		||||
      const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
@ -406,7 +459,7 @@ class Statistics {
 | 
			
		||||
  public async $list1Y(): Promise<OptimizedStatistic[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      const connection = await DB.pool.getConnection();
 | 
			
		||||
      const query = this.getQueryForDays(60480);
 | 
			
		||||
      const query = this.getQueryForDays(43200, '1 YEAR'); // 12h interval
 | 
			
		||||
      const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
@ -419,7 +472,7 @@ class Statistics {
 | 
			
		||||
  public async $list2Y(): Promise<OptimizedStatistic[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      const connection = await DB.pool.getConnection();
 | 
			
		||||
      const query = this.getQueryForDays(120960);
 | 
			
		||||
      const query = this.getQueryForDays(86400, "2 YEAR"); // 1d interval
 | 
			
		||||
      const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
@ -432,7 +485,7 @@ class Statistics {
 | 
			
		||||
  public async $list3Y(): Promise<OptimizedStatistic[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      const connection = await DB.pool.getConnection();
 | 
			
		||||
      const query = this.getQueryForDays(181440);
 | 
			
		||||
      const query = this.getQueryForDays(86400, "3 YEAR"); // 1d interval
 | 
			
		||||
      const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,7 @@ interface IConfig {
 | 
			
		||||
    MEMPOOL_BLOCKS_AMOUNT: number;
 | 
			
		||||
    PRICE_FEED_UPDATE_INTERVAL: number;
 | 
			
		||||
    USE_SECOND_NODE_FOR_MINFEE: boolean;
 | 
			
		||||
    EXTERNAL_ASSETS: string[];
 | 
			
		||||
  };
 | 
			
		||||
  ESPLORA: {
 | 
			
		||||
    REST_API_URL: string;
 | 
			
		||||
@ -78,6 +79,7 @@ const defaults: IConfig = {
 | 
			
		||||
    'MEMPOOL_BLOCKS_AMOUNT': 8,
 | 
			
		||||
    'PRICE_FEED_UPDATE_INTERVAL': 3600,
 | 
			
		||||
    'USE_SECOND_NODE_FOR_MINFEE': false,
 | 
			
		||||
    'EXTERNAL_ASSETS': [],
 | 
			
		||||
  },
 | 
			
		||||
  'ESPLORA': {
 | 
			
		||||
    'REST_API_URL': 'http://127.0.0.1:3000',
 | 
			
		||||
 | 
			
		||||
@ -22,6 +22,8 @@ import loadingIndicators from './api/loading-indicators';
 | 
			
		||||
import mempool from './api/mempool';
 | 
			
		||||
import elementsParser from './api/liquid/elements-parser';
 | 
			
		||||
import databaseMigration from './api/database-migration';
 | 
			
		||||
import syncAssets from './sync-assets';
 | 
			
		||||
import icons from './api/liquid/icons';
 | 
			
		||||
 | 
			
		||||
class Server {
 | 
			
		||||
  private wss: WebSocket.Server | undefined;
 | 
			
		||||
@ -78,6 +80,7 @@ class Server {
 | 
			
		||||
 | 
			
		||||
    this.setUpWebsocketHandling();
 | 
			
		||||
 | 
			
		||||
    await syncAssets.syncAssets();
 | 
			
		||||
    diskCache.loadMempoolCache();
 | 
			
		||||
 | 
			
		||||
    if (config.DATABASE.ENABLED) {
 | 
			
		||||
@ -93,6 +96,10 @@ class Server {
 | 
			
		||||
      statistics.startStatistics();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (config.MEMPOOL.NETWORK === 'liquid') {
 | 
			
		||||
      icons.loadIcons();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fiatConversion.startService();
 | 
			
		||||
 | 
			
		||||
    this.setUpHttpApiRoutes();
 | 
			
		||||
@ -276,6 +283,13 @@ class Server {
 | 
			
		||||
      ;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (config.MEMPOOL.NETWORK === 'liquid') {
 | 
			
		||||
      this.app
 | 
			
		||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'assets/icons', routes.getAllLiquidIcon)
 | 
			
		||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'asset/:assetId/icon', routes.getLiquidIcon)
 | 
			
		||||
      ;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (config.MEMPOOL.NETWORK === 'liquid' && config.DATABASE.ENABLED) {
 | 
			
		||||
      this.app
 | 
			
		||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'liquid/pegs/month', routes.$getElementsPegsByMonth)
 | 
			
		||||
 | 
			
		||||
@ -19,6 +19,7 @@ import loadingIndicators from './api/loading-indicators';
 | 
			
		||||
import { Common } from './api/common';
 | 
			
		||||
import bitcoinClient from './api/bitcoin/bitcoin-client';
 | 
			
		||||
import elementsParser from './api/liquid/elements-parser';
 | 
			
		||||
import icons from './api/liquid/icons';
 | 
			
		||||
 | 
			
		||||
class Routes {
 | 
			
		||||
  constructor() {}
 | 
			
		||||
@ -807,6 +808,26 @@ class Routes {
 | 
			
		||||
        : (e.message || 'Error'));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public getLiquidIcon(req: Request, res: Response) {
 | 
			
		||||
    const result = icons.getIconByAssetId(req.params.assetId);
 | 
			
		||||
    if (result) {
 | 
			
		||||
      res.setHeader('content-type', 'image/png');
 | 
			
		||||
      res.setHeader('content-length', result.length);
 | 
			
		||||
      res.send(result);
 | 
			
		||||
    } else {
 | 
			
		||||
      res.status(404).send('Asset icon not found');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public getAllLiquidIcon(req: Request, res: Response) {
 | 
			
		||||
    const result = icons.getAllIconIds();
 | 
			
		||||
    if (result) {
 | 
			
		||||
      res.json(result);
 | 
			
		||||
    } else {
 | 
			
		||||
      res.status(404).send('Asset icons not found');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default new Routes();
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										32
									
								
								backend/src/sync-assets.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								backend/src/sync-assets.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
			
		||||
import axios from 'axios';
 | 
			
		||||
import * as fs from 'fs';
 | 
			
		||||
const fsPromises = fs.promises;
 | 
			
		||||
import config from './config';
 | 
			
		||||
import logger from './logger';
 | 
			
		||||
 | 
			
		||||
const PATH = './';
 | 
			
		||||
 | 
			
		||||
class SyncAssets {
 | 
			
		||||
  constructor() { }
 | 
			
		||||
 | 
			
		||||
  public async syncAssets() {
 | 
			
		||||
    for (const url of config.MEMPOOL.EXTERNAL_ASSETS) {
 | 
			
		||||
      await this.downloadFile(url);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async downloadFile(url: string) {
 | 
			
		||||
    const fileName = url.split('/').slice(-1)[0];
 | 
			
		||||
    logger.info(`Downloading external asset: ${fileName}...`);
 | 
			
		||||
    try {
 | 
			
		||||
      const response = await axios.get(url, {
 | 
			
		||||
        responseType: 'stream', timeout: 30000
 | 
			
		||||
      });
 | 
			
		||||
      await fsPromises.writeFile(PATH + fileName, response.data);
 | 
			
		||||
    } catch (e: any) {
 | 
			
		||||
      throw new Error(`Failed to download external asset. ` + e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default new SyncAssets();
 | 
			
		||||
							
								
								
									
										52
									
								
								frontend/cypress/fixtures/mainnet_rbf.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								frontend/cypress/fixtures/mainnet_rbf.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,52 @@
 | 
			
		||||
{
 | 
			
		||||
  "rbfTransaction": {
 | 
			
		||||
    "txid": "8913ec7ba0ede285dbd120e46f6d61a28f2903c10814a6f6c4f97d0edf3e1f46",
 | 
			
		||||
    "version": 2,
 | 
			
		||||
    "locktime": 632699,
 | 
			
		||||
    "vin": [
 | 
			
		||||
      {
 | 
			
		||||
        "txid": "02238126a63ea2669c5f378012180ef8b54402a949316f9b2f1352c51730a086",
 | 
			
		||||
        "vout": 0,
 | 
			
		||||
        "prevout": {
 | 
			
		||||
          "scriptpubkey": "a914f8e495456956c833e5e8c69b9a9dc041aa14c72f87",
 | 
			
		||||
          "scriptpubkey_asm": "OP_HASH160 OP_PUSHBYTES_20 f8e495456956c833e5e8c69b9a9dc041aa14c72f OP_EQUAL",
 | 
			
		||||
          "scriptpubkey_type": "p2sh",
 | 
			
		||||
          "scriptpubkey_address": "3QP3LMD8veT5GtWV83Nosif2Bhr73857VB",
 | 
			
		||||
          "value": 25000000
 | 
			
		||||
        },
 | 
			
		||||
        "scriptsig": "22002043288fbbc0fc5efa86c229dbb7d88ab78d57957c65b5d5ceaece70838976ad1b",
 | 
			
		||||
        "scriptsig_asm": "OP_PUSHBYTES_34 002043288fbbc0fc5efa86c229dbb7d88ab78d57957c65b5d5ceaece70838976ad1b",
 | 
			
		||||
        "witness": [
 | 
			
		||||
          "",
 | 
			
		||||
          "3044022009e2d3a8e645f65bc89c8492cd9c08e6fb02609fd402214884a754a1970145340220575bb325429def59f3a3f1e22d9740a3feecbe97438ff3bb5796b2c46b3c477f01",
 | 
			
		||||
          "3044022039c34372882da8fc1c1243bd72b5e7e5e6870301ef56bdebb87bc647fb50f9b5022071a704ee77d742f78b10e45be675d4c45a5f31e884139e75c975144fde70e41701",
 | 
			
		||||
          "522102346eb7133f11e0dc279bc592d5ac948a91676372a6144c9ae2085625d7fbf70421021b9508a458f9d59be4eb8cc87ad582c3b494106fb1d4ec22801569be0700eb7b52ae"
 | 
			
		||||
        ],
 | 
			
		||||
        "is_coinbase": false,
 | 
			
		||||
        "sequence": 4294967293,
 | 
			
		||||
        "inner_redeemscript_asm": "OP_0 OP_PUSHBYTES_32 43288fbbc0fc5efa86c229dbb7d88ab78d57957c65b5d5ceaece70838976ad1b",
 | 
			
		||||
        "inner_witnessscript_asm": "OP_PUSHNUM_2 OP_PUSHBYTES_33 02346eb7133f11e0dc279bc592d5ac948a91676372a6144c9ae2085625d7fbf704 OP_PUSHBYTES_33 021b9508a458f9d59be4eb8cc87ad582c3b494106fb1d4ec22801569be0700eb7b OP_PUSHNUM_2 OP_CHECKMULTISIG"
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "vout": [
 | 
			
		||||
      {
 | 
			
		||||
        "scriptpubkey": "a914fd4e5e59dd5cf2dc48eaedf1a2a1650ca1ce9d7f87",
 | 
			
		||||
        "scriptpubkey_asm": "OP_HASH160 OP_PUSHBYTES_20 fd4e5e59dd5cf2dc48eaedf1a2a1650ca1ce9d7f OP_EQUAL",
 | 
			
		||||
        "scriptpubkey_type": "p2sh",
 | 
			
		||||
        "scriptpubkey_address": "3QnNmDhZS7toHA7bhhbTPBdtpLJoeecq5c",
 | 
			
		||||
        "value": 13986350
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "scriptpubkey": "76a914edc93d0446deec1c2d514f3a490f050096e74e0e88ac",
 | 
			
		||||
        "scriptpubkey_asm": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 edc93d0446deec1c2d514f3a490f050096e74e0e OP_EQUALVERIFY OP_CHECKSIG",
 | 
			
		||||
        "scriptpubkey_type": "p2pkh",
 | 
			
		||||
        "scriptpubkey_address": "1NgJDkTUqJxxCAAZrrsC87kWag5kphrRtM",
 | 
			
		||||
        "value": 11000000
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "size": 372,
 | 
			
		||||
    "weight": 828,
 | 
			
		||||
    "fee": 1.5,
 | 
			
		||||
    "status": { "confirmed": false }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -59,9 +59,8 @@ describe('Bisq', () => {
 | 
			
		||||
            cy.visit(`${basePath}`);
 | 
			
		||||
            cy.waitForSkeletonGone();
 | 
			
		||||
            cy.get('li:nth-of-type(5) > a').click().then(() => {
 | 
			
		||||
                cy.get('.card').should('have.length.at.least', 1);
 | 
			
		||||
                cy.get('.card').first().click();
 | 
			
		||||
                cy.get('.card-body');
 | 
			
		||||
                cy.get('.section-header').should('have.length.at.least', 1);
 | 
			
		||||
                cy.get('.endpoint-container').should('have.length.at.least', 1);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -62,6 +62,20 @@ describe('Liquid', () => {
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('renders unconfidential addresses correctly on mobile', () => {
 | 
			
		||||
            cy.viewport('iphone-6');
 | 
			
		||||
            cy.visit(`${basePath}/address/ex1qqmmjdwrlg59c8q4l75sj6wedjx57tj5grt8pat`);
 | 
			
		||||
            cy.waitForSkeletonGone();
 | 
			
		||||
            //TODO: Add proper IDs for these selectors
 | 
			
		||||
            const firstRowSelector = '.container-xl > :nth-child(3) > div > :nth-child(1) > .table > tbody';
 | 
			
		||||
            const thirdRowSelector = '.container-xl > :nth-child(3) > div > :nth-child(3)';
 | 
			
		||||
            cy.get(firstRowSelector).invoke('css', 'width').then(firstRowWidth => {
 | 
			
		||||
                cy.get(thirdRowSelector).invoke('css', 'width').then(thirdRowWidth => {
 | 
			
		||||
                    expect(parseInt(firstRowWidth)).to.be.lessThan(parseInt(thirdRowWidth));
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        describe('peg in/peg out', () => {
 | 
			
		||||
            it('loads peg in addresses', () => {
 | 
			
		||||
                cy.visit(`${basePath}/tx/fe764f7bedfc2a37b29d9c8aef67d64a57d253a6b11c5a55555cfd5826483a58`);
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,36 @@ import { emitMempoolInfo, dropWebSocket } from "../../support/websocket";
 | 
			
		||||
 | 
			
		||||
const baseModule = Cypress.env("BASE_MODULE");
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//Credit: https://github.com/bahmutov/cypress-examples/blob/6cedb17f83a3bb03ded13cf1d6a3f0656ca2cdf5/docs/recipes/overlapping-elements.md
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns true if two DOM rectangles are overlapping
 | 
			
		||||
 * @param {DOMRect} rect1 the bounding client rectangle of the first element
 | 
			
		||||
 * @param {DOMRect} rect2 the bounding client rectangle of the second element
 | 
			
		||||
 * @returns {boolean}
 | 
			
		||||
*/
 | 
			
		||||
const areOverlapping = (rect1, rect2) => {
 | 
			
		||||
    // if one rectangle is on the left side of the other
 | 
			
		||||
    if (rect1.right < rect2.left || rect2.right < rect1.left) {
 | 
			
		||||
        return false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // if one rectangle is above the other
 | 
			
		||||
    if (rect1.bottom < rect2.top || rect2.bottom < rect1.top) {
 | 
			
		||||
        return false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // the rectangles must overlap
 | 
			
		||||
    return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns the bounding rectangle of the first DOM
 | 
			
		||||
 * element in the given jQuery object.
 | 
			
		||||
 */
 | 
			
		||||
const getRectangle = ($el) => $el[0].getBoundingClientRect();
 | 
			
		||||
 | 
			
		||||
describe('Mainnet', () => {
 | 
			
		||||
    beforeEach(() => {
 | 
			
		||||
        //cy.intercept('/sockjs-node/info*').as('socket');
 | 
			
		||||
@ -56,7 +86,7 @@ describe('Mainnet', () => {
 | 
			
		||||
            cy.get('.badge', {timeout: 25000}).should('not.exist');
 | 
			
		||||
            emitMempoolInfo({
 | 
			
		||||
                'params': {
 | 
			
		||||
                loaded: true
 | 
			
		||||
                    command: 'init'
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist');
 | 
			
		||||
@ -283,7 +313,7 @@ describe('Mainnet', () => {
 | 
			
		||||
 | 
			
		||||
            emitMempoolInfo({
 | 
			
		||||
                'params': {
 | 
			
		||||
                loaded: true
 | 
			
		||||
                    command: 'init'
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
@ -428,6 +458,78 @@ describe('Mainnet', () => {
 | 
			
		||||
                cy.get('.pagination-container ul.pagination').first().children().should('have.length', 7);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        describe('RBF transactions', () => {
 | 
			
		||||
            it('shows RBF transactions properly (mobile)', () => {
 | 
			
		||||
                cy.viewport('iphone-xr');
 | 
			
		||||
                cy.mockMempoolSocket();
 | 
			
		||||
                cy.visit('/tx/f81a08699b62b2070ad8fe0f2a076f8bea0386a2fdcd8124caee42cbc564a0d5');
 | 
			
		||||
 | 
			
		||||
                cy.waitForSkeletonGone();
 | 
			
		||||
 | 
			
		||||
                emitMempoolInfo({
 | 
			
		||||
                    'params': {
 | 
			
		||||
                        command: 'init'
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                cy.get('#mempool-block-0');
 | 
			
		||||
 | 
			
		||||
                emitMempoolInfo({
 | 
			
		||||
                    'params': {
 | 
			
		||||
                        command: 'rbfTransaction'
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                cy.get('.alert-mempool').should('be.visible');
 | 
			
		||||
                cy.get('.alert-mempool').invoke('css', 'width').then((alertWidth) => {
 | 
			
		||||
                    cy.get('.container-xl > :nth-child(3)').invoke('css', 'width').should('equal', alertWidth);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                cy.get('.btn-success').then(getRectangle).then((rectA) => {
 | 
			
		||||
                    cy.get('.alert-mempool').then(getRectangle).then((rectB) => {
 | 
			
		||||
                        expect(areOverlapping(rectA, rectB), 'Confirmations box and RBF alert are overlapping').to.be.false;
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it('shows RBF transactions properly (desktop)', () => {
 | 
			
		||||
                cy.viewport('macbook-16');
 | 
			
		||||
                cy.mockMempoolSocket();
 | 
			
		||||
                cy.visit('/tx/f81a08699b62b2070ad8fe0f2a076f8bea0386a2fdcd8124caee42cbc564a0d5');
 | 
			
		||||
 | 
			
		||||
                cy.waitForSkeletonGone();
 | 
			
		||||
 | 
			
		||||
                emitMempoolInfo({
 | 
			
		||||
                    'params': {
 | 
			
		||||
                        command: 'init'
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                cy.get('#mempool-block-0');
 | 
			
		||||
 | 
			
		||||
                emitMempoolInfo({
 | 
			
		||||
                    'params': {
 | 
			
		||||
                        command: 'rbfTransaction'
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                cy.get('.alert-mempool').should('be.visible');
 | 
			
		||||
 | 
			
		||||
                const alertLocator = '.alert-mempool';
 | 
			
		||||
                const tableLocator = '.container-xl > :nth-child(3)';
 | 
			
		||||
 | 
			
		||||
                cy.get(tableLocator).invoke('css', 'width').then((firstWidth) => {
 | 
			
		||||
                    cy.get(alertLocator).invoke('css', 'width').should('equal', firstWidth);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                cy.get('.btn-success').then(getRectangle).then((rectA) => {
 | 
			
		||||
                    cy.get('.alert-mempool').then(getRectangle).then((rectB) => {
 | 
			
		||||
                        expect(areOverlapping(rectA, rectB), 'Confirmations box and RBF alert are overlapping').to.be.false;
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    } else {
 | 
			
		||||
        it.skip(`Tests cannot be run on the selected BASE_MODULE ${baseModule}`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -31,19 +31,19 @@ export const mockWebSocket = () => {
 | 
			
		||||
	cy.on('window:before:load', (win) => {
 | 
			
		||||
		const winWebSocket = win.WebSocket;
 | 
			
		||||
		cy.stub(win, 'WebSocket').callsFake((url) => {
 | 
			
		||||
            console.log(url);
 | 
			
		||||
			console.log(url);
 | 
			
		||||
			if ((new URL(url).pathname.indexOf('/sockjs-node/') !== 0)) {
 | 
			
		||||
				const { server, websocket } = createMock(url);
 | 
			
		||||
 | 
			
		||||
				win.mockServer = server;
 | 
			
		||||
				win.mockServer.on('connection', (socket) => {
 | 
			
		||||
					win.mockSocket = socket;
 | 
			
		||||
                    win.mockSocket.send('{"action":"init"}');
 | 
			
		||||
					win.mockSocket.send('{"action":"init"}');
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
                win.mockServer.on('message', (message) => {
 | 
			
		||||
                    console.log(message);
 | 
			
		||||
                });
 | 
			
		||||
				win.mockServer.on('message', (message) => {
 | 
			
		||||
					console.log(message);
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
				return websocket;
 | 
			
		||||
			} else {
 | 
			
		||||
@ -68,7 +68,13 @@ export const emitMempoolInfo = ({
 | 
			
		||||
			//TODO: Use network specific mocks
 | 
			
		||||
			case "signet":
 | 
			
		||||
			case "testnet":
 | 
			
		||||
			case "mainnet":
 | 
			
		||||
			default:
 | 
			
		||||
				break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		switch (params.command) {
 | 
			
		||||
			case "init": {
 | 
			
		||||
				win.mockSocket.send('{"action":"init"}');
 | 
			
		||||
				win.mockSocket.send('{"action":"want","data":["blocks","stats","mempool-blocks","live-2h-chart"]}');
 | 
			
		||||
				win.mockSocket.send('{"conversions":{"USD":32365.338815782445}}');
 | 
			
		||||
@ -78,6 +84,16 @@ export const emitMempoolInfo = ({
 | 
			
		||||
				cy.readFile('cypress/fixtures/mainnet_mempoolInfo.json', 'ascii').then((fixture) => {
 | 
			
		||||
					win.mockSocket.send(JSON.stringify(fixture));
 | 
			
		||||
				});
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
			case "rbfTransaction": {
 | 
			
		||||
				cy.readFile('cypress/fixtures/mainnet_rbf.json', 'ascii').then((fixture) => {
 | 
			
		||||
					win.mockSocket.send(JSON.stringify(fixture));
 | 
			
		||||
				});
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
			default:
 | 
			
		||||
				break;
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
    cy.waitForSkeletonGone();
 | 
			
		||||
@ -89,4 +105,4 @@ export const dropWebSocket = (() => {
 | 
			
		||||
        win.mockServer.simulate("error");
 | 
			
		||||
    });
 | 
			
		||||
    return cy.wait(500);
 | 
			
		||||
});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										96
									
								
								frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										96
									
								
								frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							@ -26,7 +26,7 @@
 | 
			
		||||
        "@fortawesome/fontawesome-svg-core": "^1.2.35",
 | 
			
		||||
        "@fortawesome/free-solid-svg-icons": "^5.15.3",
 | 
			
		||||
        "@juggle/resize-observer": "^3.3.1",
 | 
			
		||||
        "@mempool/mempool.js": "^2.2.4",
 | 
			
		||||
        "@mempool/mempool.js": "2.3.0-dev1",
 | 
			
		||||
        "@ng-bootstrap/ng-bootstrap": "^10.0.0",
 | 
			
		||||
        "@nguniversal/express-engine": "11.2.1",
 | 
			
		||||
        "@types/qrcode": "1.4.1",
 | 
			
		||||
@ -3230,12 +3230,40 @@
 | 
			
		||||
      "integrity": "sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@mempool/mempool.js": {
 | 
			
		||||
      "version": "2.2.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@mempool/mempool.js/-/mempool.js-2.2.4.tgz",
 | 
			
		||||
      "integrity": "sha512-G9Ga2jHLfAuU/qXikRBtTecYr7BhLJH1WbIahefnGpgP48DCQaj1jizvuRZHhoElUvUT5flRt/O9kLjlbToqhw==",
 | 
			
		||||
      "version": "2.3.0-dev1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@mempool/mempool.js/-/mempool.js-2.3.0-dev1.tgz",
 | 
			
		||||
      "integrity": "sha512-+UYGuG8qqdgrtC4J94hCs7+Dry8OprIixEarIC6rww1Nb5POz8n3NTDH8to1r0XLjPr+Du6/OX8fEN1QW94rNA==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "axios": "^0.21.1",
 | 
			
		||||
        "ws": "^7.4.3"
 | 
			
		||||
        "axios": "0.24.0",
 | 
			
		||||
        "ws": "8.3.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@mempool/mempool.js/node_modules/axios": {
 | 
			
		||||
      "version": "0.24.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz",
 | 
			
		||||
      "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "follow-redirects": "^1.14.4"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@mempool/mempool.js/node_modules/ws": {
 | 
			
		||||
      "version": "8.3.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ws/-/ws-8.3.0.tgz",
 | 
			
		||||
      "integrity": "sha512-Gs5EZtpqZzLvmIM59w4igITU57lrtYVFneaa434VROv4thzJyV6UjIL3D42lslWlI+D4KzLYnxSwtfuiO79sNw==",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=10.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "bufferutil": "^4.0.1",
 | 
			
		||||
        "utf-8-validate": "^5.0.2"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependenciesMeta": {
 | 
			
		||||
        "bufferutil": {
 | 
			
		||||
          "optional": true
 | 
			
		||||
        },
 | 
			
		||||
        "utf-8-validate": {
 | 
			
		||||
          "optional": true
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@ng-bootstrap/ng-bootstrap": {
 | 
			
		||||
@ -4541,6 +4569,7 @@
 | 
			
		||||
      "version": "0.21.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
 | 
			
		||||
      "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
 | 
			
		||||
      "devOptional": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "follow-redirects": "^1.14.0"
 | 
			
		||||
      }
 | 
			
		||||
@ -7264,7 +7293,7 @@
 | 
			
		||||
      "version": "4.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
 | 
			
		||||
      "devOptional": true,
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=0.3.1"
 | 
			
		||||
      }
 | 
			
		||||
@ -9122,7 +9151,7 @@
 | 
			
		||||
      "version": "2.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
 | 
			
		||||
      "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
 | 
			
		||||
      "devOptional": true,
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=4"
 | 
			
		||||
      }
 | 
			
		||||
@ -9131,7 +9160,7 @@
 | 
			
		||||
      "version": "5.1.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
 | 
			
		||||
      "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
 | 
			
		||||
      "devOptional": true,
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "ajv": "^6.12.3",
 | 
			
		||||
        "har-schema": "^2.0.0"
 | 
			
		||||
@ -9416,7 +9445,7 @@
 | 
			
		||||
      "version": "1.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
 | 
			
		||||
      "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
 | 
			
		||||
      "devOptional": true,
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "assert-plus": "^1.0.0",
 | 
			
		||||
        "jsprim": "^1.2.2",
 | 
			
		||||
@ -10385,7 +10414,7 @@
 | 
			
		||||
      "version": "0.4.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
 | 
			
		||||
      "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
 | 
			
		||||
      "devOptional": true
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/json-schema-traverse": {
 | 
			
		||||
      "version": "0.4.1",
 | 
			
		||||
@ -10453,7 +10482,7 @@
 | 
			
		||||
      "version": "1.4.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
 | 
			
		||||
      "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
 | 
			
		||||
      "devOptional": true,
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "assert-plus": "1.0.0",
 | 
			
		||||
        "extsprintf": "1.3.0",
 | 
			
		||||
@ -17962,6 +17991,7 @@
 | 
			
		||||
      "version": "7.4.6",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
 | 
			
		||||
      "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
 | 
			
		||||
      "devOptional": true,
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=8.3.0"
 | 
			
		||||
      }
 | 
			
		||||
@ -20409,12 +20439,28 @@
 | 
			
		||||
      "integrity": "sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw=="
 | 
			
		||||
    },
 | 
			
		||||
    "@mempool/mempool.js": {
 | 
			
		||||
      "version": "2.2.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@mempool/mempool.js/-/mempool.js-2.2.4.tgz",
 | 
			
		||||
      "integrity": "sha512-G9Ga2jHLfAuU/qXikRBtTecYr7BhLJH1WbIahefnGpgP48DCQaj1jizvuRZHhoElUvUT5flRt/O9kLjlbToqhw==",
 | 
			
		||||
      "version": "2.3.0-dev1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@mempool/mempool.js/-/mempool.js-2.3.0-dev1.tgz",
 | 
			
		||||
      "integrity": "sha512-+UYGuG8qqdgrtC4J94hCs7+Dry8OprIixEarIC6rww1Nb5POz8n3NTDH8to1r0XLjPr+Du6/OX8fEN1QW94rNA==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "axios": "^0.21.1",
 | 
			
		||||
        "ws": "^7.4.3"
 | 
			
		||||
        "axios": "0.24.0",
 | 
			
		||||
        "ws": "8.3.0"
 | 
			
		||||
      },
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "axios": {
 | 
			
		||||
          "version": "0.24.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz",
 | 
			
		||||
          "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "follow-redirects": "^1.14.4"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "ws": {
 | 
			
		||||
          "version": "8.3.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/ws/-/ws-8.3.0.tgz",
 | 
			
		||||
          "integrity": "sha512-Gs5EZtpqZzLvmIM59w4igITU57lrtYVFneaa434VROv4thzJyV6UjIL3D42lslWlI+D4KzLYnxSwtfuiO79sNw==",
 | 
			
		||||
          "requires": {}
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "@ng-bootstrap/ng-bootstrap": {
 | 
			
		||||
@ -21532,6 +21578,7 @@
 | 
			
		||||
      "version": "0.21.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
 | 
			
		||||
      "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
 | 
			
		||||
      "devOptional": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "follow-redirects": "^1.14.0"
 | 
			
		||||
      }
 | 
			
		||||
@ -23795,7 +23842,7 @@
 | 
			
		||||
      "version": "4.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
 | 
			
		||||
      "devOptional": true
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "diffie-hellman": {
 | 
			
		||||
      "version": "5.0.3",
 | 
			
		||||
@ -25290,13 +25337,13 @@
 | 
			
		||||
      "version": "2.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
 | 
			
		||||
      "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
 | 
			
		||||
      "devOptional": true
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "har-validator": {
 | 
			
		||||
      "version": "5.1.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
 | 
			
		||||
      "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
 | 
			
		||||
      "devOptional": true,
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "ajv": "^6.12.3",
 | 
			
		||||
        "har-schema": "^2.0.0"
 | 
			
		||||
@ -25544,7 +25591,7 @@
 | 
			
		||||
      "version": "1.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
 | 
			
		||||
      "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
 | 
			
		||||
      "devOptional": true,
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "assert-plus": "^1.0.0",
 | 
			
		||||
        "jsprim": "^1.2.2",
 | 
			
		||||
@ -26286,7 +26333,7 @@
 | 
			
		||||
      "version": "0.4.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
 | 
			
		||||
      "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
 | 
			
		||||
      "devOptional": true
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "json-schema-traverse": {
 | 
			
		||||
      "version": "0.4.1",
 | 
			
		||||
@ -26339,7 +26386,7 @@
 | 
			
		||||
      "version": "1.4.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
 | 
			
		||||
      "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
 | 
			
		||||
      "devOptional": true,
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "assert-plus": "1.0.0",
 | 
			
		||||
        "extsprintf": "1.3.0",
 | 
			
		||||
@ -32158,7 +32205,8 @@
 | 
			
		||||
    "ws": {
 | 
			
		||||
      "version": "7.4.6",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
 | 
			
		||||
      "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A=="
 | 
			
		||||
      "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
 | 
			
		||||
      "devOptional": true
 | 
			
		||||
    },
 | 
			
		||||
    "xhr2": {
 | 
			
		||||
      "version": "0.2.0",
 | 
			
		||||
 | 
			
		||||
@ -34,7 +34,10 @@
 | 
			
		||||
    "sync-assets": "node sync-assets.js && rsync -av ./dist/mempool/browser/en-US/resources ./dist/mempool/browser/resources",
 | 
			
		||||
    "sync-assets-dev": "node sync-assets.js dev",
 | 
			
		||||
    "generate-config": "node generate-config.js",
 | 
			
		||||
    "build-mempool.js": "tsc | browserify -p tinyify ./node_modules/@mempool/mempool.js/lib/index.js --standalone mempoolJS > ./dist/mempool/browser/en-US/mempool.js",
 | 
			
		||||
    "build-mempool.js": "npm run build-mempool-js && npm run build-mempool-liquid-js && npm run build-mempool-bisq-js",
 | 
			
		||||
    "build-mempool-js": "browserify -p tinyify ./node_modules/@mempool/mempool.js/lib/index.js --standalone mempoolJS > ./dist/mempool/browser/en-US/mempool.js",
 | 
			
		||||
    "build-mempool-bisq-js": "browserify -p tinyify ./node_modules/@mempool/mempool.js/lib/index-bisq.js --standalone bisqJS > ./dist/mempool/browser/en-US/bisq.js",
 | 
			
		||||
    "build-mempool-liquid-js": "browserify -p tinyify ./node_modules/@mempool/mempool.js/lib/index-liquid.js --standalone liquidJS > ./dist/mempool/browser/en-US/liquid.js",
 | 
			
		||||
    "test": "ng test",
 | 
			
		||||
    "lint": "ng lint",
 | 
			
		||||
    "e2e": "npm run generate-config && ng e2e",
 | 
			
		||||
@ -70,7 +73,7 @@
 | 
			
		||||
    "@fortawesome/fontawesome-svg-core": "^1.2.35",
 | 
			
		||||
    "@fortawesome/free-solid-svg-icons": "^5.15.3",
 | 
			
		||||
    "@juggle/resize-observer": "^3.3.1",
 | 
			
		||||
    "@mempool/mempool.js": "^2.2.4",
 | 
			
		||||
    "@mempool/mempool.js": "2.3.0-dev1",
 | 
			
		||||
    "@ng-bootstrap/ng-bootstrap": "^10.0.0",
 | 
			
		||||
    "@nguniversal/express-engine": "11.2.1",
 | 
			
		||||
    "@types/qrcode": "1.4.1",
 | 
			
		||||
 | 
			
		||||
@ -48,9 +48,10 @@ import { FeesBoxComponent } from './components/fees-box/fees-box.component';
 | 
			
		||||
import { DashboardComponent } from './dashboard/dashboard.component';
 | 
			
		||||
import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
 | 
			
		||||
import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faDatabase, faExchangeAlt, faInfoCircle,
 | 
			
		||||
  faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown, faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook } from '@fortawesome/free-solid-svg-icons';
 | 
			
		||||
  faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown, faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl } from '@fortawesome/free-solid-svg-icons';
 | 
			
		||||
import { ApiDocsComponent } from './components/docs/api-docs.component';
 | 
			
		||||
import { DocsComponent } from './components/docs/docs.component';
 | 
			
		||||
import { ApiDocsNavComponent } from './components/docs/api-docs-nav.component';
 | 
			
		||||
import { CodeTemplateComponent } from './components/docs/code-template.component';
 | 
			
		||||
import { TermsOfServiceComponent } from './components/terms-of-service/terms-of-service.component';
 | 
			
		||||
import { PrivacyPolicyComponent } from './components/privacy-policy/privacy-policy.component';
 | 
			
		||||
@ -59,6 +60,7 @@ import { StorageService } from './services/storage.service';
 | 
			
		||||
import { HttpCacheInterceptor } from './services/http-cache.interceptor';
 | 
			
		||||
import { SponsorComponent } from './components/sponsor/sponsor.component';
 | 
			
		||||
import { PushTransactionComponent } from './components/push-transaction/push-transaction.component';
 | 
			
		||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
  declarations: [
 | 
			
		||||
@ -102,6 +104,7 @@ import { PushTransactionComponent } from './components/push-transaction/push-tra
 | 
			
		||||
    SponsorComponent,
 | 
			
		||||
    PushTransactionComponent,
 | 
			
		||||
    DocsComponent,
 | 
			
		||||
    ApiDocsNavComponent,
 | 
			
		||||
  ],
 | 
			
		||||
  imports: [
 | 
			
		||||
    BrowserModule.withServerTransition({ appId: 'serverApp' }),
 | 
			
		||||
@ -111,6 +114,7 @@ import { PushTransactionComponent } from './components/push-transaction/push-tra
 | 
			
		||||
    BrowserAnimationsModule,
 | 
			
		||||
    InfiniteScrollModule,
 | 
			
		||||
    NgbTypeaheadModule,
 | 
			
		||||
    NgbModule,
 | 
			
		||||
    FontAwesomeModule,
 | 
			
		||||
    SharedModule,
 | 
			
		||||
    NgxEchartsModule.forRoot({
 | 
			
		||||
@ -161,5 +165,6 @@ export class AppModule {
 | 
			
		||||
    library.addIcons(faAngleRight);
 | 
			
		||||
    library.addIcons(faAngleLeft);
 | 
			
		||||
    library.addIcons(faBook);
 | 
			
		||||
    library.addIcons(faListUl);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -23,12 +23,12 @@
 | 
			
		||||
    <a target="_blank" href="https://twitter.com/mempool">
 | 
			
		||||
      <svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="twitter" class="svg-inline--fa fa-twitter fa-w-16 fa-4x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"></path></svg>
 | 
			
		||||
    </a>
 | 
			
		||||
    <a target="_blank" href="https://t.me/mempoolspace">
 | 
			
		||||
      <svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="telegram-plane" class="svg-inline--fa fa-telegram-plane fa-w-14 fa-4x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M446.7 98.6l-67.6 318.8c-5.1 22.5-18.4 28.1-37.3 17.5l-103-75.9-49.7 47.8c-5.5 5.5-10.1 10.1-20.7 10.1l7.4-104.9 190.9-172.5c8.3-7.4-1.8-11.5-12.9-4.1L117.8 284 16.2 252.2c-22.1-6.9-22.5-22.1 4.6-32.7L418.2 66.4c18.4-6.9 34.5 4.1 28.5 32.2z"></path></svg>
 | 
			
		||||
    </a>
 | 
			
		||||
    <a target="_blank" href="https://keybase.io/team/mempool">
 | 
			
		||||
      <svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="keybase" class="svg-inline--fa fa-keybase fa-w-14 fa-4x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M286.17 419a18 18 0 1 0 18 18 18 18 0 0 0-18-18zm111.92-147.6c-9.5-14.62-39.37-52.45-87.26-73.71q-9.1-4.06-18.38-7.27a78.43 78.43 0 0 0-47.88-104.13c-12.41-4.1-23.33-6-32.41-5.77-.6-2-1.89-11 9.4-35L198.66 32l-5.48 7.56c-8.69 12.06-16.92 23.55-24.34 34.89a51 51 0 0 0-8.29-1.25c-41.53-2.45-39-2.33-41.06-2.33-50.61 0-50.75 52.12-50.75 45.88l-2.36 36.68c-1.61 27 19.75 50.21 47.63 51.85l8.93.54a214 214 0 0 0-46.29 35.54C14 304.66 14 374 14 429.77v33.64l23.32-29.8a148.6 148.6 0 0 0 14.56 37.56c5.78 10.13 14.87 9.45 19.64 7.33 4.21-1.87 10-6.92 3.75-20.11a178.29 178.29 0 0 1-15.76-53.13l46.82-59.83-24.66 74.11c58.23-42.4 157.38-61.76 236.25-38.59 34.2 10.05 67.45.69 84.74-23.84.72-1 1.2-2.16 1.85-3.22a156.09 156.09 0 0 1 2.8 28.43c0 23.3-3.69 52.93-14.88 81.64-2.52 6.46 1.76 14.5 8.6 15.74 7.42 1.57 15.33-3.1 18.37-11.15C429 443 434 414 434 382.32c0-38.58-13-77.46-35.91-110.92zM142.37 128.58l-15.7-.93-1.39 21.79 13.13.78a93 93 0 0 0 .32 19.57l-22.38-1.34a12.28 12.28 0 0 1-11.76-12.79L107 119c1-12.17 13.87-11.27 13.26-11.32l29.11 1.73a144.35 144.35 0 0 0-7 19.17zm148.42 172.18a10.51 10.51 0 0 1-14.35-1.39l-9.68-11.49-34.42 27a8.09 8.09 0 0 1-11.13-1.08l-15.78-18.64a7.38 7.38 0 0 1 1.34-10.34l34.57-27.18-14.14-16.74-17.09 13.45a7.75 7.75 0 0 1-10.59-1s-3.72-4.42-3.8-4.53a7.38 7.38 0 0 1 1.37-10.34L214 225.19s-18.51-22-18.6-22.14a9.56 9.56 0 0 1 1.74-13.42 10.38 10.38 0 0 1 14.3 1.37l81.09 96.32a9.58 9.58 0 0 1-1.74 13.44zM187.44 419a18 18 0 1 0 18 18 18 18 0 0 0-18-18z"></path></svg>
 | 
			
		||||
    </a>
 | 
			
		||||
    <a target="_blank" href="https://matrix.to/#/#mempool:bitcoin.kyoto">
 | 
			
		||||
      <svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="matrix" class="svg-inline--fa fa-matrix fa-w-16 fa-4x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1536 1792"><path fill="currentColor" d="M40.467 163.152v1465.696H145.92V1664H0V128h145.92v35.152zm450.757 464.64v74.14h2.069c19.79-28.356 43.717-50.215 71.483-65.575 27.765-15.656 59.963-23.336 96-23.336 34.56 0 66.165 6.795 94.818 20.086 28.652 13.293 50.216 37.22 65.28 70.893 16.246-23.926 38.4-45.194 66.166-63.507 27.766-18.314 60.848-27.472 98.954-27.472 28.948 0 55.828 3.545 80.64 10.635 24.812 7.088 45.785 18.314 63.508 33.968 17.722 15.656 31.31 35.742 41.354 60.85 9.747 25.107 14.768 55.236 14.768 90.683v366.573h-150.35V865.28c0-18.314-.59-35.741-2.068-51.987-1.476-16.247-5.316-30.426-11.52-42.24-6.499-12.112-15.656-21.563-28.062-28.653-12.405-7.088-29.242-10.634-50.214-10.634-21.268 0-38.4 4.135-51.397 12.112-12.997 8.27-23.336 18.608-30.72 31.901-7.386 12.997-12.407 27.765-14.77 44.602-2.363 16.542-3.84 33.379-3.84 50.216v305.133H692.971v-307.2c0-16.247-.294-32.197-1.18-48.149-.591-15.95-3.84-30.424-9.157-44.011-5.317-13.293-14.178-24.223-26.585-32.197-12.406-7.976-30.425-12.112-54.646-12.112-7.088 0-16.542 1.478-28.062 4.726-11.52 3.25-23.04 9.157-33.968 18.02-10.93 8.86-20.383 21.563-28.063 38.103-7.68 16.543-11.52 38.4-11.52 65.28v317.834H349.44V627.792zm1004.309 1001.056V163.152H1390.08V128H1536v1536h-145.92v-35.152z"/></svg>
 | 
			
		||||
    </a>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <div class="enterprise-sponsor">
 | 
			
		||||
@ -54,6 +54,10 @@
 | 
			
		||||
        <img class="image" src="/resources/profile/unchained.svg" />
 | 
			
		||||
        <span>Unchained</span>
 | 
			
		||||
      </a>
 | 
			
		||||
      <a href="https://blockstream.com/" target="_blank" title="Blockstream">
 | 
			
		||||
        <img class="image" src="/resources/profile/blockstream.svg" />
 | 
			
		||||
        <span>Blockstream</span>
 | 
			
		||||
      </a>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
@ -174,7 +178,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="maintainers" *ngIf="contributors.core.length">
 | 
			
		||||
      <h3 i18n="about.project_staff">Project Staff</h3>
 | 
			
		||||
      <h3 i18n="about.project_members">Project Members</h3>
 | 
			
		||||
      <div class="wrapper">
 | 
			
		||||
        <ng-template ngFor let-contributor [ngForOf]="contributors.core">
 | 
			
		||||
          <a [href]="'https://github.com/' + contributor.name" target="_blank" [title]="contributor.name">
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,10 @@
 | 
			
		||||
            <tbody>
 | 
			
		||||
              <tr *ngIf="addressInfo && addressInfo.unconfidential">
 | 
			
		||||
                <td i18n="address.unconfidential">Unconfidential</td>
 | 
			
		||||
                <td><a [routerLink]="['/address/' | relativeUrl, addressInfo.unconfidential]">{{ addressInfo.unconfidential }}</a> <app-clipboard [text]="addressInfo.unconfidential"></app-clipboard></td>
 | 
			
		||||
                <td><a [routerLink]="['/address/' | relativeUrl, addressInfo.unconfidential]">
 | 
			
		||||
                  <span class="d-inline d-lg-none">{{ addressInfo.unconfidential | shortenString : 14 }}</span>
 | 
			
		||||
                  <span class="d-none d-lg-inline">{{ addressInfo.unconfidential }}</span>
 | 
			
		||||
                </a> <app-clipboard [text]="addressInfo.unconfidential"></app-clipboard></td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <ng-template [ngIf]="!address.electrum">
 | 
			
		||||
                <tr>
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,8 @@
 | 
			
		||||
<div class="blocks-container blockchain-blocks-container" *ngIf="(loadingBlocks$ | async) === false; else loadingBlocksTemplate">
 | 
			
		||||
  <div *ngFor="let block of blocks; let i = index; trackBy: trackByBlocksFn" >
 | 
			
		||||
    <div class="text-center bitcoin-block mined-block blockchain-blocks-{{ i }}" id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]" [class.blink-bg]="(specialBlocks[block.height] !== undefined)">
 | 
			
		||||
      <a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }" class="blockLink"> </a>
 | 
			
		||||
      <a draggable="false" [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }" 
 | 
			
		||||
        class="blockLink" [ngClass]="{'disabled': (this.stateService.blockScrolling$ | async)}"> </a>
 | 
			
		||||
      <div class="block-height">
 | 
			
		||||
        <a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
@ -15,6 +15,10 @@
 | 
			
		||||
  text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.blockLink.disabled {
 | 
			
		||||
  pointer-events: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mined-block {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 0px;
 | 
			
		||||
 | 
			
		||||
@ -41,7 +41,7 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private stateService: StateService,
 | 
			
		||||
    public stateService: StateService,
 | 
			
		||||
    private router: Router,
 | 
			
		||||
    private cd: ChangeDetectorRef,
 | 
			
		||||
  ) { }
 | 
			
		||||
 | 
			
		||||
@ -18,6 +18,11 @@
 | 
			
		||||
.blockchain-wrapper {
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  height: 250px;
 | 
			
		||||
 | 
			
		||||
  -webkit-user-select: none; /* Safari */        
 | 
			
		||||
  -moz-user-select: none; /* Firefox */
 | 
			
		||||
  -ms-user-select: none; /* IE10+/Edge */
 | 
			
		||||
  user-select: none; /* Standard */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.position-container {
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,3 @@
 | 
			
		||||
.btn-link {
 | 
			
		||||
  padding: 0.25rem 0 0.1rem 0.5rem;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										76
									
								
								frontend/src/app/components/docs/api-docs-nav.component.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								frontend/src/app/components/docs/api-docs-nav.component.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,76 @@
 | 
			
		||||
<ng-template [ngIf]="network.val !== 'bisq' && network.val !== 'liquid'">
 | 
			
		||||
  <p>General</p>
 | 
			
		||||
  <a [routerLink]="['./']" fragment="get-difficulty-adjustment" (click)="collapseItem.toggle()">GET Difficulty Adjustment</a>
 | 
			
		||||
</ng-template>
 | 
			
		||||
 | 
			
		||||
<ng-template [ngIf]="network.val === 'bisq'">
 | 
			
		||||
  <p>Markets</p>
 | 
			
		||||
  <a [routerLink]="['./']" fragment="get-market-currencies" (click)="collapseItem.toggle()">GET Market Currencies</a>
 | 
			
		||||
  <a [routerLink]="['./']" fragment="get-market-depth" (click)="collapseItem.toggle()">GET Market Depth</a>
 | 
			
		||||
  <a [routerLink]="['./']" fragment="get-market-hloc" (click)="collapseItem.toggle()">GET Market HLOC</a>
 | 
			
		||||
  <a [routerLink]="['./']" fragment="get-markets" (click)="collapseItem.toggle()">GET Markets</a>
 | 
			
		||||
  <a [routerLink]="['./']" fragment="get-market-offers" (click)="collapseItem.toggle()">GET Market Offers</a>
 | 
			
		||||
  <a [routerLink]="['./']" fragment="get-market-ticker" (click)="collapseItem.toggle()">GET Market Ticker</a>
 | 
			
		||||
  <a [routerLink]="['./']" fragment="get-market-trades" (click)="collapseItem.toggle()">GET Market Trades</a>
 | 
			
		||||
  <a [routerLink]="['./']" fragment="get-market-volumes" (click)="collapseItem.toggle()">GET Market Volumes</a>
 | 
			
		||||
</ng-template>
 | 
			
		||||
 | 
			
		||||
<ng-template [ngIf]="network.val === 'bisq'">
 | 
			
		||||
  <p>General</p>
 | 
			
		||||
  <a [routerLink]="['./']" fragment="get-stats" (click)="collapseItem.toggle()">GET Stats</a>
 | 
			
		||||
</ng-template>
 | 
			
		||||
 | 
			
		||||
<p>Addresses</p>
 | 
			
		||||
<a [routerLink]="['./']" fragment="get-address" (click)="collapseItem.toggle()">GET Address</a>
 | 
			
		||||
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-address-transactions" (click)="collapseItem.toggle()">GET Address Transactions</a>
 | 
			
		||||
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-address-transactions-chain" (click)="collapseItem.toggle()">GET Address Transactions Chain</a>
 | 
			
		||||
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-address-transactions-mempool" (click)="collapseItem.toggle()">GET Address Transactions Mempool</a>
 | 
			
		||||
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-address-utxo" (click)="collapseItem.toggle()">GET Address UTXO</a>
 | 
			
		||||
 | 
			
		||||
<ng-template [ngIf]="network.val === 'liquid'">
 | 
			
		||||
  <p>Assets</p>
 | 
			
		||||
  <a [routerLink]="['./']" fragment="get-assets" (click)="collapseItem.toggle()">GET Assets</a>
 | 
			
		||||
  <a [routerLink]="['./']" fragment="get-assets-icons" (click)="collapseItem.toggle()">GET Assets Icons</a>
 | 
			
		||||
  <a [routerLink]="['./']" fragment="get-asset-transactions" (click)="collapseItem.toggle()">GET Asset Transactions</a>
 | 
			
		||||
  <a [routerLink]="['./']" fragment="get-asset-supply" (click)="collapseItem.toggle()">GET Asset Supply</a>
 | 
			
		||||
  <a [routerLink]="['./']" fragment="get-asset-icon" (click)="collapseItem.toggle()">GET Asset Icon</a>
 | 
			
		||||
</ng-template>
 | 
			
		||||
 | 
			
		||||
<p>Blocks</p>
 | 
			
		||||
<a [routerLink]="['./']" fragment="get-block" (click)="collapseItem.toggle()">GET Block</a>
 | 
			
		||||
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-block-header" (click)="collapseItem.toggle()">GET Block Header</a>
 | 
			
		||||
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-block-height" (click)="collapseItem.toggle()">GET Block Height</a>
 | 
			
		||||
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-block-raw" (click)="collapseItem.toggle()">GET Block Raw</a>
 | 
			
		||||
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-block-status" (click)="collapseItem.toggle()">GET Block Status</a>
 | 
			
		||||
<a [routerLink]="['./']" fragment="get-block-tip-height" (click)="collapseItem.toggle()">GET Block Tip Height</a>
 | 
			
		||||
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-block-tip-hash" (click)="collapseItem.toggle()">GET Block Tip Hash</a>
 | 
			
		||||
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-block-transaction-id" (click)="collapseItem.toggle()">GET Block Transaction ID</a>
 | 
			
		||||
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-block-transaction-ids" (click)="collapseItem.toggle()">GET Block Transaction IDs</a>
 | 
			
		||||
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-block-transactions" (click)="collapseItem.toggle()">GET Block Transactions</a>
 | 
			
		||||
<a [routerLink]="['./']" fragment="get-blocks" (click)="collapseItem.toggle()">GET Blocks</a>
 | 
			
		||||
 | 
			
		||||
<ng-template [ngIf]="network.val !== 'bisq'">
 | 
			
		||||
  <p>Fees</p>
 | 
			
		||||
  <a [routerLink]="['./']" fragment="get-mempool-blocks-fees" (click)="collapseItem.toggle()">GET Mempool Blocks Fees</a>
 | 
			
		||||
  <a [routerLink]="['./']" fragment="get-recommended-fees" (click)="collapseItem.toggle()">GET Recommended Fees</a>
 | 
			
		||||
</ng-template>
 | 
			
		||||
 | 
			
		||||
<ng-template [ngIf]="network.val !== 'bisq'">
 | 
			
		||||
  <p>Mempool</p>
 | 
			
		||||
  <a [routerLink]="['./']" fragment="get-mempool" (click)="collapseItem.toggle()">GET Mempool</a>
 | 
			
		||||
  <a [routerLink]="['./']" fragment="get-mempool-transaction-ids" (click)="collapseItem.toggle()">GET Mempool Transaction IDs</a>
 | 
			
		||||
  <a [routerLink]="['./']" fragment="get-mempool-recent" (click)="collapseItem.toggle()">GET Mempool Recent</a>
 | 
			
		||||
</ng-template>
 | 
			
		||||
 | 
			
		||||
<p>Transactions</p>
 | 
			
		||||
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-cpfp" (click)="collapseItem.toggle()">GET Children Pay for Parent</a>
 | 
			
		||||
<a [routerLink]="['./']" fragment="get-transaction" (click)="collapseItem.toggle()">GET Transaction</a>
 | 
			
		||||
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-transaction-hex" (click)="collapseItem.toggle()">GET Transaction Hex</a>
 | 
			
		||||
<a *ngIf="network.val !== 'bisq' && network.val !== 'liquid'" [routerLink]="['./']" fragment="get-transaction-merkleblock-proof" (click)="collapseItem.toggle()">GET Transaction Merkleblock Proof</a>
 | 
			
		||||
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-transaction-merkle-proof" (click)="collapseItem.toggle()">GET Transaction Merkle Proof</a>
 | 
			
		||||
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-transaction-outspend" (click)="collapseItem.toggle()">GET Transaction Outspend</a>
 | 
			
		||||
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-transaction-outspends" (click)="collapseItem.toggle()">GET Transaction Outspends</a>
 | 
			
		||||
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-transaction-raw" (click)="collapseItem.toggle()">GET Transaction Raw</a>
 | 
			
		||||
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-transaction-status" (click)="collapseItem.toggle()">GET Transaction Status</a>
 | 
			
		||||
<a *ngIf="network.val === 'bisq'" [routerLink]="['./']" fragment="get-transactions" (click)="collapseItem.toggle()">GET Transactions</a>
 | 
			
		||||
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="post-transaction" (click)="collapseItem.toggle()">POST Transaction</a>
 | 
			
		||||
							
								
								
									
										17
									
								
								frontend/src/app/components/docs/api-docs-nav.component.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								frontend/src/app/components/docs/api-docs-nav.component.scss
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
			
		||||
p {
 | 
			
		||||
  color: #4a68b9;
 | 
			
		||||
  font-weight: 700;
 | 
			
		||||
  margin: 10px 0;
 | 
			
		||||
  margin: 15px 0 10px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
p:first-child {
 | 
			
		||||
  margin-top: 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a {
 | 
			
		||||
  color: #fff;
 | 
			
		||||
  text-decoration: none;
 | 
			
		||||
  display: block;
 | 
			
		||||
  margin: 5px 0;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								frontend/src/app/components/docs/api-docs-nav.component.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								frontend/src/app/components/docs/api-docs-nav.component.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
import { Component, OnInit, Input } from '@angular/core';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-api-docs-nav',
 | 
			
		||||
  templateUrl: './api-docs-nav.component.html',
 | 
			
		||||
  styleUrls: ['./api-docs-nav.component.scss']
 | 
			
		||||
})
 | 
			
		||||
export class ApiDocsNavComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
  @Input() network: any;
 | 
			
		||||
  @Input() collapseItem: any = { toggle: () => {} };
 | 
			
		||||
 | 
			
		||||
  constructor() { }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -22,6 +22,10 @@ li.nav-item {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.no-bottom-space {
 | 
			
		||||
  margin-bottom: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.nav-tabs .nav-link.active {
 | 
			
		||||
  border-bottom: 1px solid #fff;
 | 
			
		||||
  @media (min-width: 676px){
 | 
			
		||||
@ -72,10 +76,131 @@ li.nav-item {
 | 
			
		||||
  padding: 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#restAPI .api-category {
 | 
			
		||||
  margin: 30px 0;
 | 
			
		||||
#doc-nav-desktop {
 | 
			
		||||
  width: 300px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.api-category h4 {
 | 
			
		||||
  margin-bottom: 15px;
 | 
			
		||||
#doc-nav-desktop.relative {
 | 
			
		||||
  float: left;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#doc-nav-desktop.fixed {
 | 
			
		||||
  float: unset;
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  top: 20px;
 | 
			
		||||
  overflow-y: auto;
 | 
			
		||||
  height: calc(100vh - 50px);
 | 
			
		||||
  scrollbar-color: #2d3348 #11131f;
 | 
			
		||||
  scrollbar-width: thin;
 | 
			
		||||
}
 | 
			
		||||
::-webkit-scrollbar {
 | 
			
		||||
  width: 3px;
 | 
			
		||||
}
 | 
			
		||||
::-webkit-scrollbar-track {
 | 
			
		||||
  background: #11131f;
 | 
			
		||||
}
 | 
			
		||||
::-webkit-scrollbar-thumb {
 | 
			
		||||
  background-color: #2d3348;
 | 
			
		||||
  border-radius: 5px;
 | 
			
		||||
  border: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.doc-content {
 | 
			
		||||
  width: calc(100% - 330px);
 | 
			
		||||
  float: right;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.endpoint-container:before {
 | 
			
		||||
	display: block;
 | 
			
		||||
 	content: " ";
 | 
			
		||||
  height: 1px;
 | 
			
		||||
  margin-top: -1px;
 | 
			
		||||
  visibility: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.endpoint-container .section-header {
 | 
			
		||||
  display: block;
 | 
			
		||||
  background-color: #2d3348;
 | 
			
		||||
  color: #1bd8f4;
 | 
			
		||||
  padding: 1rem 1.3rem 1rem 1.3rem;
 | 
			
		||||
  font-weight: bold;
 | 
			
		||||
  border-radius: 0.25rem;
 | 
			
		||||
  margin: 20px 0 20px 0;
 | 
			
		||||
  font-size: 24px;
 | 
			
		||||
  position: relative;
 | 
			
		||||
}
 | 
			
		||||
.endpoint-container .section-header:hover {
 | 
			
		||||
  text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.endpoint-container .section-header span {
 | 
			
		||||
  color: #fff;
 | 
			
		||||
  background-color: #653b9c;
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
  text-transform: uppercase;
 | 
			
		||||
  font-weight: 400;
 | 
			
		||||
  padding: 8px 10px;
 | 
			
		||||
  letter-spacing: 1px;
 | 
			
		||||
  border-radius: 0.25rem;
 | 
			
		||||
  font-family: monospace;
 | 
			
		||||
  float: right;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#doc-nav-mobile {
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  top: 20px;
 | 
			
		||||
  width: calc(100% - 60px);
 | 
			
		||||
  z-index: 100;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#doc-nav-mobile > div {
 | 
			
		||||
  background-color: #2d3348;
 | 
			
		||||
  z-index: 100;
 | 
			
		||||
  border-radius: 0 0 0.5rem 0.5rem;
 | 
			
		||||
  height: 55vh;
 | 
			
		||||
  overflow-y: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#doc-nav-mobile button {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  background-color: #105fb0;
 | 
			
		||||
  color: #fff;
 | 
			
		||||
  border-color: #105fb0;
 | 
			
		||||
  border-radius: 0.5rem 0.5rem 0 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (max-width: 992px) {
 | 
			
		||||
 | 
			
		||||
  .hide-on-mobile {
 | 
			
		||||
    display: none;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .doc-content {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .endpoint-container .section-header {
 | 
			
		||||
    margin: 40px 0 70px 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .endpoint-container .section-header span {
 | 
			
		||||
    float: none;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: unset;
 | 
			
		||||
    left: 0;
 | 
			
		||||
    bottom: -50px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .endpoint-container:before {
 | 
			
		||||
    height: 30px;
 | 
			
		||||
    margin-top: -12px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (min-width: 992px) {
 | 
			
		||||
  .hide-on-desktop {
 | 
			
		||||
    display: none;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import { Component, OnInit, Input } from '@angular/core';
 | 
			
		||||
import { Component, OnInit, Input, ViewChild, ElementRef } from '@angular/core';
 | 
			
		||||
import { Env, StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { Observable, merge, of } from 'rxjs';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
@ -17,12 +17,26 @@ export class ApiDocsComponent implements OnInit {
 | 
			
		||||
  code: any;
 | 
			
		||||
  baseNetworkUrl = '';
 | 
			
		||||
  @Input() restTabActivated: Boolean;
 | 
			
		||||
  @ViewChild( "mobileFixedApiNav", { static: false } ) mobileFixedApiNav: ElementRef;
 | 
			
		||||
  desktopDocsNavPosition = "relative";
 | 
			
		||||
  showFloatingDocsNav = false;
 | 
			
		||||
  mobileMenuOpen = true;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private stateService: StateService,
 | 
			
		||||
    private seoService: SeoService,
 | 
			
		||||
  ) { }
 | 
			
		||||
 | 
			
		||||
  ngAfterViewInit() {
 | 
			
		||||
    const that = this;
 | 
			
		||||
    setTimeout( () => {
 | 
			
		||||
      window.addEventListener('scroll', function() {
 | 
			
		||||
        that.desktopDocsNavPosition = ( window.pageYOffset > 182 ) ? "fixed" : "relative";
 | 
			
		||||
        that.showFloatingDocsNav = ( window.pageYOffset > ( that.mobileFixedApiNav.nativeElement.offsetHeight + 188 ) ) ? true : false;
 | 
			
		||||
      });
 | 
			
		||||
    }, 1 );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.env = this.stateService.env;
 | 
			
		||||
    this.seoService.setTitle($localize`:@@e351b40b3869a5c7d19c3d4918cb1ac7aaab95c4:API`);
 | 
			
		||||
@ -628,24 +642,6 @@ export class ApiDocsComponent implements OnInit {
 | 
			
		||||
  console.log(asset);
 | 
			
		||||
          `,
 | 
			
		||||
        },
 | 
			
		||||
        codeSampleMainnet: {
 | 
			
		||||
          esModule: [],
 | 
			
		||||
          commonJS: [],
 | 
			
		||||
          curl: [],
 | 
			
		||||
          response: ''
 | 
			
		||||
        },
 | 
			
		||||
        codeSampleTestnet: {
 | 
			
		||||
          esModule: [],
 | 
			
		||||
          commonJS: [],
 | 
			
		||||
          curl: [],
 | 
			
		||||
          response: ''
 | 
			
		||||
        },
 | 
			
		||||
        codeSampleSignet: {
 | 
			
		||||
          esModule: [],
 | 
			
		||||
          commonJS: [],
 | 
			
		||||
          curl: [],
 | 
			
		||||
          response: ''
 | 
			
		||||
        },
 | 
			
		||||
        codeSampleLiquid: {
 | 
			
		||||
          esModule: [`6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`],
 | 
			
		||||
          commonJS: [`6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`],
 | 
			
		||||
@ -679,6 +675,47 @@ export class ApiDocsComponent implements OnInit {
 | 
			
		||||
          response: ''
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      assetIcons: {
 | 
			
		||||
        codeTemplate: {
 | 
			
		||||
          curl: `/api/v1/assets/icons`,
 | 
			
		||||
          commonJS: `
 | 
			
		||||
        const { %{0}: { assets } } = mempoolJS();
 | 
			
		||||
 | 
			
		||||
        const assetsIcons = await assets.getAssetsIcons();
 | 
			
		||||
 | 
			
		||||
        document.getElementById("result").textContent = JSON.stringify(assetsIcons, undefined, 2);
 | 
			
		||||
        `,
 | 
			
		||||
          esModule: `
 | 
			
		||||
  const { %{0}: { assets } } = mempoolJS();
 | 
			
		||||
 | 
			
		||||
  const assetsIcons = await assets.getAssetsIcons();
 | 
			
		||||
  console.log(assetsIcons);
 | 
			
		||||
          `,
 | 
			
		||||
        },
 | 
			
		||||
        codeSampleLiquid: {
 | 
			
		||||
          esModule: [`6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`],
 | 
			
		||||
          commonJS: [`6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`],
 | 
			
		||||
          curl: [`6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`],
 | 
			
		||||
          response: `[
 | 
			
		||||
  "6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d",
 | 
			
		||||
  "ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2"
 | 
			
		||||
  ...
 | 
			
		||||
]`,
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      assetIcon: {
 | 
			
		||||
        noWrap: true,
 | 
			
		||||
        codeTemplate: {
 | 
			
		||||
          curl: `/api/v1/asset/%{1}/icon`,
 | 
			
		||||
          commonJS: `<img src="https://liquid.place/api/v1/asset/%{1}/icon">`,
 | 
			
		||||
        },
 | 
			
		||||
        codeSampleLiquid: {
 | 
			
		||||
          esModule: [`6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`],
 | 
			
		||||
          commonJS: [`6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`],
 | 
			
		||||
          curl: [`6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`],
 | 
			
		||||
          response: `PNG`,
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      assetTransactions: {
 | 
			
		||||
        codeTemplate: {
 | 
			
		||||
          curl: `/api/asset/%{1}/txs`,
 | 
			
		||||
 | 
			
		||||
@ -164,6 +164,10 @@ init();`;
 | 
			
		||||
        codeText = this.replaceJSPlaceholder(codeText, code.codeSampleBisq.esModule);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (code.noWrap) {
 | 
			
		||||
        return codeText;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      let importText = `<script src="https://mempool.space/mempool.js"></script>`;
 | 
			
		||||
      if (this.env.BASE_MODULE === 'bisq') {
 | 
			
		||||
        importText = `<script src="https://bisq.markets/bisq.js"></script>`;
 | 
			
		||||
 | 
			
		||||
@ -27,5 +27,13 @@
 | 
			
		||||
 | 
			
		||||
    <div id="main-tab-content" [ngbNavOutlet]="nav" class="mt-2"></div>
 | 
			
		||||
 | 
			
		||||
    <br>
 | 
			
		||||
 | 
			
		||||
    <div id="footer" class="text-center">
 | 
			
		||||
      <a [routerLink]="['/terms-of-service']" i18n="shared.terms-of-service|Terms of Service">Terms of Service</a>
 | 
			
		||||
      |
 | 
			
		||||
      <a [routerLink]="['/privacy-policy']" i18n="shared.privacy-policy|Privacy Policy">Privacy Policy</a>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,9 @@
 | 
			
		||||
#main-tab-content {
 | 
			
		||||
  text-align: left;
 | 
			
		||||
  padding-top: 10px;
 | 
			
		||||
  scroll-behavior: smooth;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#footer {
 | 
			
		||||
  clear: both;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,8 @@
 | 
			
		||||
import { Component, Input, Inject, LOCALE_ID, ChangeDetectionStrategy, OnInit } from '@angular/core';
 | 
			
		||||
import { formatDate } from '@angular/common';
 | 
			
		||||
import { EChartsOption } from 'echarts';
 | 
			
		||||
import { OnChanges } from '@angular/core';
 | 
			
		||||
import { StorageService } from 'src/app/services/storage.service';
 | 
			
		||||
import { formatterXAxis, formatterXAxisLabel } from 'src/app/shared/graphs.utils';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-incoming-transactions-graph',
 | 
			
		||||
@ -25,6 +25,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
  @Input() top: number | string = '20';
 | 
			
		||||
  @Input() left: number | string = '0';
 | 
			
		||||
  @Input() template: ('widget' | 'advanced') = 'widget';
 | 
			
		||||
  @Input() windowPreferenceOverride: string;
 | 
			
		||||
 | 
			
		||||
  isLoading = true;
 | 
			
		||||
  mempoolStatsChartOption: EChartsOption = {};
 | 
			
		||||
@ -46,7 +47,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
    if (!this.data) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    this.windowPreference = this.storageService.getValue('graphWindowPreference');
 | 
			
		||||
    this.windowPreference = this.windowPreferenceOverride ? this.windowPreferenceOverride : this.storageService.getValue('graphWindowPreference');
 | 
			
		||||
    this.mountChart();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -73,10 +74,12 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
        maxSpan: 100,
 | 
			
		||||
        minSpan: 10,
 | 
			
		||||
      }, {
 | 
			
		||||
        showDetail: false,
 | 
			
		||||
        show: (this.template === 'advanced') ? true : false,
 | 
			
		||||
        type: 'slider',
 | 
			
		||||
        brushSelect: false,
 | 
			
		||||
        realtime: true,
 | 
			
		||||
        bottom: 0,
 | 
			
		||||
        selectedDataBackground: {
 | 
			
		||||
          lineStyle: {
 | 
			
		||||
            color: '#fff',
 | 
			
		||||
@ -85,7 +88,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
          areaStyle: {
 | 
			
		||||
            opacity: 0,
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        },
 | 
			
		||||
      }],
 | 
			
		||||
      tooltip: {
 | 
			
		||||
        trigger: 'axis',
 | 
			
		||||
@ -102,29 +105,39 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
          type: 'line',
 | 
			
		||||
        },
 | 
			
		||||
        formatter: (params: any) => {
 | 
			
		||||
          const axisValueLabel: string = formatterXAxis(this.locale, this.windowPreference, params[0].axisValue);         
 | 
			
		||||
          const colorSpan = (color: string) => `<span class="indicator" style="background-color: ` + color + `"></span>`;
 | 
			
		||||
          let itemFormatted = '<div class="title">' + params[0].axisValue + '</div>';
 | 
			
		||||
          let itemFormatted = '<div class="title">' + axisValueLabel + '</div>';
 | 
			
		||||
          params.map((item: any, index: number) => {
 | 
			
		||||
            if (index < 26) {
 | 
			
		||||
              itemFormatted += `<div class="item">
 | 
			
		||||
                <div class="indicator-container">${colorSpan(item.color)}</div>
 | 
			
		||||
                <div class="grow"></div>
 | 
			
		||||
                <div class="value">${item.value} <span class="symbol">vB/s</span></div>
 | 
			
		||||
                <div class="value">${item.value[1]} <span class="symbol">vB/s</span></div>
 | 
			
		||||
              </div>`;
 | 
			
		||||
            }
 | 
			
		||||
          });
 | 
			
		||||
          return `<div class="tx-wrapper-tooltip-chart ${(this.template === 'advanced') ? 'tx-wrapper-tooltip-chart-advanced' : ''}">${itemFormatted}</div>`;
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      xAxis: {
 | 
			
		||||
        type: 'category',
 | 
			
		||||
        axisLabel: {
 | 
			
		||||
          align: 'center',
 | 
			
		||||
          fontSize: 11,
 | 
			
		||||
          lineHeight: 12
 | 
			
		||||
        },
 | 
			
		||||
        data: this.data.labels.map((value: any) => `${formatDate(value, 'M/d', this.locale)}\n${formatDate(value, 'H:mm', this.locale)}`),
 | 
			
		||||
      },
 | 
			
		||||
      xAxis: [
 | 
			
		||||
        {
 | 
			
		||||
          name: formatterXAxisLabel(this.locale, this.windowPreference),
 | 
			
		||||
          nameLocation: 'middle',
 | 
			
		||||
          nameTextStyle: {
 | 
			
		||||
            padding: [20, 0, 0, 0],
 | 
			
		||||
          },
 | 
			
		||||
          type: 'time',
 | 
			
		||||
          axisLabel: {
 | 
			
		||||
            margin: 20,
 | 
			
		||||
            align: 'center',
 | 
			
		||||
            fontSize: 11,
 | 
			
		||||
            lineHeight: 12,
 | 
			
		||||
            hideOverlap: true,
 | 
			
		||||
            padding: [0, 5],
 | 
			
		||||
          },
 | 
			
		||||
        }
 | 
			
		||||
      ],
 | 
			
		||||
      yAxis: {
 | 
			
		||||
        type: 'value',
 | 
			
		||||
        axisLabel: {
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,8 @@
 | 
			
		||||
    <div class="flashing">
 | 
			
		||||
      <ng-template ngFor let-projectedBlock [ngForOf]="mempoolBlocks$ | async" let-i="index" [ngForTrackBy]="trackByFn">
 | 
			
		||||
        <div class="bitcoin-block text-center mempool-block" id="mempool-block-{{ i }}" [ngStyle]="mempoolBlockStyles[i]" [class.blink-bg]="projectedBlock.blink">
 | 
			
		||||
          <a [routerLink]="['/mempool-block/' | relativeUrl, i]" class="blockLink"> </a>
 | 
			
		||||
          <a draggable="false" [routerLink]="['/mempool-block/' | relativeUrl, i]"
 | 
			
		||||
            class="blockLink" [ngClass]="{'disabled': (this.stateService.blockScrolling$ | async)}"> </a>
 | 
			
		||||
          <div class="block-body">
 | 
			
		||||
            <div class="fees">
 | 
			
		||||
              ~{{ projectedBlock.medianFee | number:feeRounding }} <span i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
 | 
			
		||||
 | 
			
		||||
@ -117,6 +117,10 @@
 | 
			
		||||
  z-index: 10;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.blockLink.disabled {
 | 
			
		||||
  pointer-events: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.blockLink:hover {
 | 
			
		||||
  text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,13 +1,12 @@
 | 
			
		||||
import { Component, OnInit, Input, Inject, LOCALE_ID, ChangeDetectionStrategy, OnChanges } from '@angular/core';
 | 
			
		||||
import { formatDate } from '@angular/common';
 | 
			
		||||
import { VbytesPipe } from 'src/app/shared/pipes/bytes-pipe/vbytes.pipe';
 | 
			
		||||
import { formatNumber } from "@angular/common";
 | 
			
		||||
 | 
			
		||||
import { OptimizedMempoolStats } from 'src/app/interfaces/node-api.interface';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { StorageService } from 'src/app/services/storage.service';
 | 
			
		||||
import { EChartsOption } from 'echarts';
 | 
			
		||||
import { feeLevels, chartColors } from 'src/app/app.constants';
 | 
			
		||||
import { formatterXAxis, formatterXAxisLabel } from 'src/app/shared/graphs.utils';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-mempool-graph',
 | 
			
		||||
@ -32,6 +31,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
  @Input() left: number | string = 75;
 | 
			
		||||
  @Input() template: ('widget' | 'advanced') = 'widget';
 | 
			
		||||
  @Input() showZoom = true;
 | 
			
		||||
  @Input() windowPreferenceOverride: string;
 | 
			
		||||
 | 
			
		||||
  isLoading = true;
 | 
			
		||||
  mempoolVsizeFeesData: any;
 | 
			
		||||
@ -62,7 +62,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
    if (!this.data) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    this.windowPreference = this.storageService.getValue('graphWindowPreference');
 | 
			
		||||
    this.windowPreference = this.windowPreferenceOverride ? this.windowPreferenceOverride : this.storageService.getValue('graphWindowPreference');
 | 
			
		||||
    this.mempoolVsizeFeesData = this.handleNewMempoolData(this.data.concat([]));
 | 
			
		||||
    this.mountFeeChart();
 | 
			
		||||
  }
 | 
			
		||||
@ -97,13 +97,13 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  generateArray(mempoolStats: OptimizedMempoolStats[]) {
 | 
			
		||||
    const finalArray: number[][] = [];
 | 
			
		||||
    let feesArray: number[] = [];
 | 
			
		||||
    const finalArray: number[][][] = [];
 | 
			
		||||
    let feesArray: number[][] = [];
 | 
			
		||||
    let limitFeesTemplate = this.template === 'advanced' ? 26 : 20;
 | 
			
		||||
    for (let index = limitFeesTemplate; index > -1; index--) {
 | 
			
		||||
      feesArray = [];
 | 
			
		||||
      mempoolStats.forEach((stats) => {
 | 
			
		||||
        feesArray.push(stats.vsizes[index] ? stats.vsizes[index] : 0);
 | 
			
		||||
        feesArray.push([stats.added * 1000, stats.vsizes[index] ? stats.vsizes[index] : 0]);
 | 
			
		||||
      });
 | 
			
		||||
      finalArray.push(feesArray);
 | 
			
		||||
    }
 | 
			
		||||
@ -113,7 +113,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
 | 
			
		||||
  mountFeeChart() {
 | 
			
		||||
    this.orderLevels();
 | 
			
		||||
    const { labels, series } = this.mempoolVsizeFeesData;
 | 
			
		||||
    const { series } = this.mempoolVsizeFeesData;
 | 
			
		||||
 | 
			
		||||
    const seriesGraph = [];
 | 
			
		||||
    const newColors = [];
 | 
			
		||||
@ -186,14 +186,15 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
          type: 'line',
 | 
			
		||||
        },
 | 
			
		||||
        formatter: (params: any) => {
 | 
			
		||||
          const axisValueLabel: string = formatterXAxis(this.locale, this.windowPreference, params[0].axisValue);         
 | 
			
		||||
          const { totalValue, totalValueArray } = this.getTotalValues(params);
 | 
			
		||||
          const itemFormatted = [];
 | 
			
		||||
          let totalParcial = 0;
 | 
			
		||||
          let progressPercentageText = '';
 | 
			
		||||
          const items = this.inverted ? [...params].reverse() : params;
 | 
			
		||||
          items.map((item: any, index: number) => {
 | 
			
		||||
            totalParcial += item.value;
 | 
			
		||||
            const progressPercentage = (item.value / totalValue) * 100;
 | 
			
		||||
            totalParcial += item.value[1];
 | 
			
		||||
            const progressPercentage = (item.value[1] / totalValue) * 100;
 | 
			
		||||
            const progressPercentageSum = (totalValueArray[index] / totalValue) * 100;
 | 
			
		||||
            let activeItemClass = '';
 | 
			
		||||
            let hoverActive = 0;
 | 
			
		||||
@ -233,7 +234,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
              </td>
 | 
			
		||||
              <td class="total-progress-sum">
 | 
			
		||||
                <span>
 | 
			
		||||
                  ${this.vbytesPipe.transform(item.value, 2, 'vB', 'MvB', false)}
 | 
			
		||||
                  ${this.vbytesPipe.transform(item.value[1], 2, 'vB', 'MvB', false)}
 | 
			
		||||
                </span>
 | 
			
		||||
              </td>
 | 
			
		||||
              <td class="total-progress-sum">
 | 
			
		||||
@ -257,7 +258,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
          const titleSum = $localize`Sum`;
 | 
			
		||||
          return `<div class="fees-wrapper-tooltip-chart ${classActive}">
 | 
			
		||||
            <div class="title">
 | 
			
		||||
              ${params[0].axisValue}
 | 
			
		||||
              ${axisValueLabel}
 | 
			
		||||
              <span class="total-value">
 | 
			
		||||
                ${this.vbytesPipe.transform(totalValue, 2, 'vB', 'MvB', false)}
 | 
			
		||||
              </span>
 | 
			
		||||
@ -288,6 +289,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
        maxSpan: 100,
 | 
			
		||||
        minSpan: 10,
 | 
			
		||||
      }, {
 | 
			
		||||
        showDetail: false,
 | 
			
		||||
        show: (this.template === 'advanced' && this.showZoom) ? true : false,
 | 
			
		||||
        type: 'slider',
 | 
			
		||||
        brushSelect: false,
 | 
			
		||||
@ -312,15 +314,22 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
      },
 | 
			
		||||
      xAxis: [
 | 
			
		||||
        {
 | 
			
		||||
          type: 'category',
 | 
			
		||||
          name: formatterXAxisLabel(this.locale, this.windowPreference),
 | 
			
		||||
          nameLocation: 'middle',
 | 
			
		||||
          nameTextStyle: {
 | 
			
		||||
            padding: [20, 0, 0, 0],
 | 
			
		||||
          },
 | 
			
		||||
          type: 'time',
 | 
			
		||||
          boundaryGap: false,
 | 
			
		||||
          axisLine: { onZero: true },
 | 
			
		||||
          axisLabel: {
 | 
			
		||||
            margin: 20,
 | 
			
		||||
            align: 'center',
 | 
			
		||||
            fontSize: 11,
 | 
			
		||||
            lineHeight: 12,
 | 
			
		||||
            hideOverlap: true,
 | 
			
		||||
            padding: [0, 5],
 | 
			
		||||
          },
 | 
			
		||||
          data: labels.map((value: any) => `${formatDate(value, 'M/d', this.locale)}\n${formatDate(value, 'H:mm', this.locale)}`),
 | 
			
		||||
        }
 | 
			
		||||
      ],
 | 
			
		||||
      yAxis: {
 | 
			
		||||
@ -346,7 +355,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
    const totalValueArray = [];
 | 
			
		||||
    const valuesInverted = this.inverted ? values : [...values].reverse();
 | 
			
		||||
    for (const item of valuesInverted) {
 | 
			
		||||
      totalValueTemp += item.value;
 | 
			
		||||
      totalValueTemp += item.value[1];
 | 
			
		||||
      totalValueArray.push(totalValueTemp);
 | 
			
		||||
    }
 | 
			
		||||
    return {
 | 
			
		||||
 | 
			
		||||
@ -8,8 +8,11 @@
 | 
			
		||||
 | 
			
		||||
<div *ngIf="countdown > 0" class="warning-label">{{ eventName }} in {{ countdown | number }} block{{ countdown === 1 ? '' : 's' }}!</div>
 | 
			
		||||
 | 
			
		||||
<div id="blockchain-container" dir="ltr">
 | 
			
		||||
  <app-blockchain></app-blockchain>
 | 
			
		||||
<div id="blockchain-container" dir="ltr" #blockchainContainer
 | 
			
		||||
  (mousedown)="onMouseDown($event)"
 | 
			
		||||
  (dragstart)="onDragStart($event)"
 | 
			
		||||
>
 | 
			
		||||
<app-blockchain></app-blockchain>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<router-outlet></router-outlet>
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,7 @@
 | 
			
		||||
import { Component, OnInit } from '@angular/core';
 | 
			
		||||
import { Component, ElementRef, HostListener, OnInit, ViewChild } from '@angular/core';
 | 
			
		||||
import { WebsocketService } from 'src/app/services/websocket.service';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { specialBlocks } from 'src/app/app.constants';
 | 
			
		||||
import { takeLast } from 'rxjs/operators';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-start',
 | 
			
		||||
@ -16,6 +15,9 @@ export class StartComponent implements OnInit {
 | 
			
		||||
  countdown = 0;
 | 
			
		||||
  specialEvent = false;
 | 
			
		||||
  eventName = '';
 | 
			
		||||
  mouseDragStartX: number;
 | 
			
		||||
  blockchainScrollLeftInit: number;
 | 
			
		||||
  @ViewChild('blockchainContainer') blockchainContainer: ElementRef;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private websocketService: WebsocketService,
 | 
			
		||||
@ -50,4 +52,27 @@ export class StartComponent implements OnInit {
 | 
			
		||||
      });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onMouseDown(event: MouseEvent) {
 | 
			
		||||
    this.mouseDragStartX = event.clientX;
 | 
			
		||||
    this.blockchainScrollLeftInit = this.blockchainContainer.nativeElement.scrollLeft;
 | 
			
		||||
  }
 | 
			
		||||
  onDragStart(event: MouseEvent) { // Ignore Firefox annoying default drag behavior
 | 
			
		||||
    event.preventDefault();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // We're catching the whole page event here because we still want to scroll blocks
 | 
			
		||||
  // even if the mouse leave the blockchain blocks container. Same idea for mouseup below.
 | 
			
		||||
  @HostListener('document:mousemove', ['$event'])
 | 
			
		||||
  onMouseMove(event: MouseEvent): void {
 | 
			
		||||
    if (this.mouseDragStartX != null) {
 | 
			
		||||
      this.stateService.setBlockScrollingInProgress(true);
 | 
			
		||||
      this.blockchainContainer.nativeElement.scrollLeft =
 | 
			
		||||
        this.blockchainScrollLeftInit + this.mouseDragStartX - event.clientX
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  @HostListener('document:mouseup', [])
 | 
			
		||||
  onMouseUp() {
 | 
			
		||||
    this.mouseDragStartX = null;
 | 
			
		||||
    this.stateService.setBlockScrollingInProgress(false);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -134,7 +134,7 @@ export class StatisticsComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
    this.mempoolTransactionsWeightPerSecondData = {
 | 
			
		||||
      labels: labels,
 | 
			
		||||
      series: [mempoolStats.map((stats) => stats.vbytes_per_second)],
 | 
			
		||||
      series: [mempoolStats.map((stats) => [stats.added * 1000, stats.vbytes_per_second])],
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -20,8 +20,6 @@
 | 
			
		||||
        <app-clipboard [text]="txId"></app-clipboard>
 | 
			
		||||
      </span>
 | 
			
		||||
 | 
			
		||||
      <span class="grow"></span>
 | 
			
		||||
 | 
			
		||||
      <div class="container-buttons">
 | 
			
		||||
        <ng-template [ngIf]="tx?.status?.confirmed">
 | 
			
		||||
          <button *ngIf="latestBlock" type="button" class="btn btn-sm btn-success">
 | 
			
		||||
@ -196,7 +194,7 @@
 | 
			
		||||
      <h2 i18n="transaction.inputs-and-outputs|Transaction inputs and outputs">Inputs & Outputs</h2>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <button type="button" class="btn btn-outline-info btn-sm float-right" (click)="txList.toggleDetails()" i18n="transaction.details|Transaction Details">Details</button>
 | 
			
		||||
    <button type="button" class="btn btn-outline-info details-button btn-sm float-right" (click)="txList.toggleDetails()" i18n="transaction.details|Transaction Details">Details</button>
 | 
			
		||||
 | 
			
		||||
    <div class="clearfix"></div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -3,25 +3,11 @@
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.container-buttons {
 | 
			
		||||
	text-align: right;
 | 
			
		||||
  align-self: start;
 | 
			
		||||
  width: auto;
 | 
			
		||||
  margin-right: 15px;
 | 
			
		||||
  right: 0;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
	@media (min-width: 650px) {
 | 
			
		||||
    right: auto;
 | 
			
		||||
    margin-right: auto;
 | 
			
		||||
    position: relative;
 | 
			
		||||
  }
 | 
			
		||||
	@media (min-width: 768px) {
 | 
			
		||||
    align-self: center;
 | 
			
		||||
		float: right;
 | 
			
		||||
	}
 | 
			
		||||
  align-self: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.title-block {
 | 
			
		||||
  flex-direction: column;
 | 
			
		||||
	flex-wrap: wrap;
 | 
			
		||||
  @media (min-width: 650px) {
 | 
			
		||||
    flex-direction: row;
 | 
			
		||||
  }
 | 
			
		||||
@ -32,6 +18,7 @@
 | 
			
		||||
}
 | 
			
		||||
.tx-link {
 | 
			
		||||
  display: flex;
 | 
			
		||||
	flex-grow: 1;
 | 
			
		||||
  margin-bottom: 0px;
 | 
			
		||||
  margin-top: 8px;
 | 
			
		||||
	@media (min-width: 650px) {
 | 
			
		||||
@ -45,6 +32,9 @@
 | 
			
		||||
    top: 1px;
 | 
			
		||||
    position: relative;
 | 
			
		||||
	}
 | 
			
		||||
	@media (max-width: 768px) {
 | 
			
		||||
	  order: 3;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.td-width {
 | 
			
		||||
@ -127,7 +117,6 @@
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.title {
 | 
			
		||||
  h2 {
 | 
			
		||||
    line-height: 1;
 | 
			
		||||
@ -137,7 +126,14 @@
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.btn-outline-info {
 | 
			
		||||
  margin-top: -10px;
 | 
			
		||||
	margin-top: 5px;
 | 
			
		||||
	@media (min-width: 768px){
 | 
			
		||||
		margin-top: 0px;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.details-button {
 | 
			
		||||
  margin-top: -5px;
 | 
			
		||||
	@media (min-width: 768px){
 | 
			
		||||
		display: inline-block;
 | 
			
		||||
    margin-top: 0px;
 | 
			
		||||
 | 
			
		||||
@ -54,6 +54,7 @@
 | 
			
		||||
                [limitFee]="150"
 | 
			
		||||
                [limitFilterFee]="1"
 | 
			
		||||
                [data]="mempoolStats.value?.mempool"
 | 
			
		||||
                [windowPreferenceOverride]="'2h'"
 | 
			
		||||
                ></app-mempool-graph>
 | 
			
		||||
              </div>
 | 
			
		||||
            </ng-container>
 | 
			
		||||
@ -73,6 +74,7 @@
 | 
			
		||||
                <app-incoming-transactions-graph
 | 
			
		||||
                  [left]="50"
 | 
			
		||||
                  [data]="mempoolStats.value?.weightPerSecond"
 | 
			
		||||
                  [windowPreferenceOverride]="'2h'"
 | 
			
		||||
                  ></app-incoming-transactions-graph>
 | 
			
		||||
              </div>
 | 
			
		||||
            </ng-template>
 | 
			
		||||
 | 
			
		||||
@ -254,7 +254,6 @@ export class DashboardComponent implements OnInit {
 | 
			
		||||
          );
 | 
			
		||||
        }),
 | 
			
		||||
        map((mempoolStats) => {
 | 
			
		||||
          const data = this.handleNewMempoolData(mempoolStats.concat([]));
 | 
			
		||||
          return {
 | 
			
		||||
            mempool: mempoolStats,
 | 
			
		||||
            weightPerSecond: this.handleNewMempoolData(mempoolStats.concat([])),
 | 
			
		||||
@ -286,7 +285,7 @@ export class DashboardComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      labels: labels,
 | 
			
		||||
      series: [mempoolStats.map((stats) => stats.vbytes_per_second)],
 | 
			
		||||
      series: [mempoolStats.map((stats) => [stats.added * 1000, stats.vbytes_per_second])],
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
export interface OptimizedMempoolStats {
 | 
			
		||||
  id: number;
 | 
			
		||||
  added: string;
 | 
			
		||||
  added: number;
 | 
			
		||||
  unconfirmed_transactions: number;
 | 
			
		||||
  tx_per_second: number;
 | 
			
		||||
  vbytes_per_second: number;
 | 
			
		||||
@ -42,7 +42,7 @@ export interface AddressInformation {
 | 
			
		||||
  scriptPubKey: string;            //  (string) The hex-encoded scriptPubKey generated by the address
 | 
			
		||||
  isscript: boolean;               //  (boolean) If the key is a script
 | 
			
		||||
  iswitness: boolean;              //  (boolean) If the address is a witness
 | 
			
		||||
  witness_version?: boolean;       //  (numeric, optional) The version number of the witness program
 | 
			
		||||
  witness_version?: number;        //  (numeric, optional) The version number of the witness program
 | 
			
		||||
  witness_program: string;         //  (string, optional) The hex value of the witness program
 | 
			
		||||
  confidential_key?: string;       //  (string) Elements only
 | 
			
		||||
  unconfidential?: string;         //  (string) Elements only
 | 
			
		||||
 | 
			
		||||
@ -89,6 +89,8 @@ export class StateService {
 | 
			
		||||
  markBlock$ = new ReplaySubject<MarkBlockState>();
 | 
			
		||||
  keyNavigation$ = new Subject<KeyboardEvent>();
 | 
			
		||||
 | 
			
		||||
  blockScrolling$: Subject<boolean> = new Subject<boolean>();
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    @Inject(PLATFORM_ID) private platformId: any,
 | 
			
		||||
    private router: Router,
 | 
			
		||||
@ -176,4 +178,8 @@ export class StateService {
 | 
			
		||||
    if (!prop) { return false; }
 | 
			
		||||
    return document[prop];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setBlockScrollingInProgress(value: boolean) {
 | 
			
		||||
    this.blockScrolling$.next(value);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,25 @@
 | 
			
		||||
import { Injectable } from '@angular/core';
 | 
			
		||||
import { Router, ActivatedRoute } from '@angular/router';
 | 
			
		||||
 | 
			
		||||
@Injectable({
 | 
			
		||||
  providedIn: 'root'
 | 
			
		||||
})
 | 
			
		||||
export class StorageService {
 | 
			
		||||
  constructor(private router: Router, private route: ActivatedRoute) {
 | 
			
		||||
    let graphWindowPreference: string = this.getValue('graphWindowPreference');
 | 
			
		||||
    if (graphWindowPreference === null) { // First visit to mempool.space
 | 
			
		||||
      if (this.router.url.includes("graphs")) {
 | 
			
		||||
        this.setValue('graphWindowPreference', this.route.snapshot.fragment ? this.route.snapshot.fragment : "2h");
 | 
			
		||||
      } else {
 | 
			
		||||
        this.setValue('graphWindowPreference', "2h");
 | 
			
		||||
      }
 | 
			
		||||
    } else if (this.router.url.includes("graphs")) { // Visit a different graphs#fragment from last visit
 | 
			
		||||
        if (this.route.snapshot.fragment !== null && graphWindowPreference !== this.route.snapshot.fragment) {
 | 
			
		||||
          this.setValue('graphWindowPreference', this.route.snapshot.fragment);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getValue(key: string): string {
 | 
			
		||||
    try {
 | 
			
		||||
      return localStorage.getItem(key);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										49
									
								
								frontend/src/app/shared/graphs.utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								frontend/src/app/shared/graphs.utils.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,49 @@
 | 
			
		||||
export const formatterXAxis = (
 | 
			
		||||
  locale: string,
 | 
			
		||||
  windowPreference: string,
 | 
			
		||||
  value: string
 | 
			
		||||
) => {
 | 
			
		||||
 | 
			
		||||
  if(value.length === 0){
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const date = new Date(value);
 | 
			
		||||
  switch (windowPreference) {
 | 
			
		||||
    case '2h':
 | 
			
		||||
      return date.toLocaleTimeString(locale, { hour: 'numeric', minute: 'numeric' });
 | 
			
		||||
    case '24h':
 | 
			
		||||
      return date.toLocaleTimeString(locale, { weekday: 'short', hour: 'numeric', minute: 'numeric' });
 | 
			
		||||
    case '1w':
 | 
			
		||||
    case '1m':
 | 
			
		||||
    case '3m':
 | 
			
		||||
    case '6m':
 | 
			
		||||
    case '1y':
 | 
			
		||||
      return date.toLocaleTimeString(locale, { month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric' });
 | 
			
		||||
    case '2y':
 | 
			
		||||
    case '3y':
 | 
			
		||||
      return date.toLocaleDateString(locale, { year: 'numeric', month: 'short', day: 'numeric' });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const formatterXAxisLabel = (
 | 
			
		||||
  locale: string,
 | 
			
		||||
  windowPreference: string,
 | 
			
		||||
) => {
 | 
			
		||||
  const date = new Date();
 | 
			
		||||
  switch (windowPreference) {
 | 
			
		||||
    case '2h':
 | 
			
		||||
    case '24h':
 | 
			
		||||
      return date.toLocaleDateString(locale, { year: 'numeric', month: 'short', day: 'numeric' });
 | 
			
		||||
    case '1w':
 | 
			
		||||
      return date.toLocaleDateString(locale, { year: 'numeric', month: 'long' });
 | 
			
		||||
    case '1m':
 | 
			
		||||
    case '3m':
 | 
			
		||||
    case '6m':
 | 
			
		||||
      return date.toLocaleDateString(locale, { year: 'numeric' });
 | 
			
		||||
    case '1y':
 | 
			
		||||
    case '2y':
 | 
			
		||||
    case '3y':
 | 
			
		||||
      return null;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										43
									
								
								frontend/src/resources/profile/blockstream.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								frontend/src/resources/profile/blockstream.svg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,43 @@
 | 
			
		||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.0" id="Layer_1" x="0px" y="0px" viewBox="200 200 600 600" style="enable-background:new 0 0 1000 1000;background-color: #111316 !important" xml:space="preserve">
 | 
			
		||||
<style type="text/css">
 | 
			
		||||
	.st0{fill:#111316;}
 | 
			
		||||
	.st1{fill:#00C3FF;}
 | 
			
		||||
	.st2{fill:#7EE0FF;}
 | 
			
		||||
</style>
 | 
			
		||||
<path class="st1" d="M659.7,392.3c10.2,14.3,18.4,29.9,24.5,46.4l21.8-7.1c-6.9-18.9-16.4-36.8-28.1-53.1L659.7,392.3z"/>
 | 
			
		||||
<path class="st1" d="M510.6,289.2c-5.8-0.2-11.7-0.2-17.5,0l1.6,22.8c8.8-0.3,17.6-0.1,26.3,0.7c8.7,0.8,17.4,2.2,26,4.2l5.8-22.1  c-9.8-2.3-19.7-3.9-29.7-4.8C519,289.6,514.7,289.3,510.6,289.2z"/>
 | 
			
		||||
<path class="st1" d="M297.1,605.5c-9.1-18.6-15.7-38.3-19.5-58.6l-23.9,3.8c4.2,23,11.6,45.3,22,66.2L297.1,605.5z"/>
 | 
			
		||||
<path class="st1" d="M284.8,375.6l21.2,11.8c10.6-17.8,23.5-34,38.5-48.3l-16.2-18C311.3,337.2,296.7,355.5,284.8,375.6z"/>
 | 
			
		||||
<path class="st1" d="M254.8,453.5l23.8,4.2c4.2-20.3,11.2-39.9,20.7-58.3l-21.2-11.7C267.3,408.5,259.5,430.6,254.8,453.5z"/>
 | 
			
		||||
<path class="st1" d="M409.9,268.8l9.5,22.2c19.3-7.6,39.5-12.5,60.1-14.5l-1.7-24.1C454.5,254.6,431.7,260.1,409.9,268.8z"/>
 | 
			
		||||
<path class="st1" d="M338.5,311.8l16.2,18c15.8-13.4,33.3-24.6,52.1-33.4l-9.5-22.2C376,283.9,356.2,296.6,338.5,311.8z"/>
 | 
			
		||||
<path class="st1" d="M697.1,667.6l-18.9-15.1c-13.4,15.8-28.9,29.7-46,41.4l13,20.5C664.6,701.3,682.1,685.6,697.1,667.6z"/>
 | 
			
		||||
<path class="st1" d="M402.5,710.7c-18.6-9.1-35.9-20.7-51.4-34.5l-16.5,17.7c17.4,15.6,37,28.6,58,38.8L402.5,710.7z"/>
 | 
			
		||||
<path class="st1" d="M755.4,528.2c3.1-32.6-0.2-65.5-9.7-96.8l-23,7.6c13.2,44.4,12.7,91.7-1.3,135.8l22.8,8.1  C749.9,565.2,753.7,546.8,755.4,528.2z"/>
 | 
			
		||||
<path class="st1" d="M614.2,689.2L602,670c-15.1,9-31.3,16-48.3,20.7l5.4,22.2C578.5,707.5,597,699.6,614.2,689.2z"/>
 | 
			
		||||
<path class="st1" d="M314.5,528.8c-1.7-14.2-1.9-28.6-0.5-42.9c0.3-3.5,0.7-6.5,1.2-9.6l-22.5-4c-0.5,3.8-1,7.6-1.4,11.5  c-1.5,16.1-1.3,32.4,0.7,48.5L314.5,528.8z"/>
 | 
			
		||||
<path class="st1" d="M568.2,284.7c19.9,5.8,38.9,14.4,56.4,25.4l13.5-20.2c-19.8-12.5-41.2-22.1-63.7-28.7L568.2,284.7z"/>
 | 
			
		||||
<path class="st1" d="M469.8,755.8l2.3-24.1c-19.5-2.6-38.6-7.8-56.8-15.3l-10.1,22.2C425.8,747.1,447.6,752.9,469.8,755.8z"/>
 | 
			
		||||
<path class="st1" d="M351.3,657.7l15.7-16.6c-12.4-12.5-23.1-26.5-31.8-41.8l-20.3,10.7C324.8,627.4,337.1,643.5,351.3,657.7z"/>
 | 
			
		||||
<path class="st1" d="M649.5,297.7l-13.6,20.2c16.9,12,32,26.3,45.1,42.4l19.4-14.8C685.7,327.2,668.6,311.2,649.5,297.7z"/>
 | 
			
		||||
<path class="st1" d="M672.7,633.2c12-16.1,21.8-33.7,29.1-52.5l-21.5-7.7c-6.4,16.4-15,31.9-25.5,46L672.7,633.2z"/>
 | 
			
		||||
<path class="st2" d="M690.6,449.6l-21.6,7.2c6,20.7,8,42.4,6,63.8c-1.1,11.9-3.4,23.7-6.9,35.2l21.5,7.6c4.1-13.2,6.9-26.9,8.2-40.7  C700.1,498.1,697.6,473.3,690.6,449.6z"/>
 | 
			
		||||
<path class="st2" d="M475.2,698l2.1-22.7c-13.3-2-26.4-5.5-38.9-10.5l-9.4,20.7C443.8,691.5,459.3,695.7,475.2,698z"/>
 | 
			
		||||
<path class="st2" d="M631.8,456.2l20.4-6.9c-4.9-12.9-11.4-25.2-19.4-36.6l-17.1,13C622.3,435.2,627.7,445.4,631.8,456.2z"/>
 | 
			
		||||
<path class="st2" d="M508.4,345.7h-11.2l1.5,21.4c11.5-0.3,22.9,0.7,34.2,3.2l5.5-20.7c-6.8-1.5-13.6-2.6-20.5-3.2  C514.8,346.1,511.6,345.9,508.4,345.7z"/>
 | 
			
		||||
<path class="st2" d="M335.5,403.8l20,11.1c7.5-12.4,16.5-23.7,26.9-33.8L367,364.1C354.8,375.9,344.2,389.2,335.5,403.8z"/>
 | 
			
		||||
<path class="st2" d="M553.8,339.5c13.8,4.2,27.1,10.2,39.4,17.7l12.7-19c-14.4-8.9-30-15.8-46.2-20.7L553.8,339.5z"/>
 | 
			
		||||
<path class="st2" d="M635.9,394.5l18.1-13.8c-10.7-13.2-23.2-24.9-36.9-34.8l-12.7,19C616.2,373.4,626.7,383.3,635.9,394.5z"/>
 | 
			
		||||
<path class="st2" d="M611.5,584.6l16.8,13.4c8.2-11.2,14.9-23.3,20.1-36.2l-20.2-7.2C623.8,565.2,618.2,575.3,611.5,584.6z"/>
 | 
			
		||||
<path class="st2" d="M389.9,635.1l-15.6,16.6c12.8,11.2,26.9,20.7,42.2,28.2l9.4-20.7C412.9,652.8,400.8,644.6,389.9,635.1z"/>
 | 
			
		||||
<path class="st2" d="M369.2,520.2c-1-9.7-1.1-19.5-0.2-29.2c0.2-1.7,0.4-3.5,0.6-5.1l-21.1-3.8c-0.3,2.3-0.6,4.6-0.8,6.9  c-1.1,11.5-0.9,23,0.3,34.5L369.2,520.2z"/>
 | 
			
		||||
<path class="st2" d="M333.6,538l-22.6,3.5c3.2,16.7,8.6,33,16,48.3l20.2-10.7C340.9,566,336.4,552.2,333.6,538z"/>
 | 
			
		||||
<path class="st2" d="M601.7,646.3l12.3,19.2c14-9.6,26.7-21,37.7-33.8l-17.9-14.2C624.4,628.4,613.6,638.1,601.7,646.3z"/>
 | 
			
		||||
<path class="st2" d="M348.8,426.9l-19.9-11c-7.8,15.1-13.5,31.2-17,47.8l22.5,4C337.4,453.5,342.2,439.8,348.8,426.9z"/>
 | 
			
		||||
<path class="st2" d="M540.6,636.9l5,20.7c13.3-3.8,26.1-9.2,38.1-16.2l-11.6-18.1C562.2,629,551.6,633.6,540.6,636.9z"/>
 | 
			
		||||
<path class="st2" d="M384,573.5l-19,9.9c6.9,12,15.4,23,25.1,32.9l14.8-15.7C396.9,592.4,389.9,583.3,384,573.5z"/>
 | 
			
		||||
<path class="st2" d="M496.7,677.1c-1.9,0-3.8-0.2-5.7-0.4l-2.1,22.7c17.9,1.3,35.9,0.1,53.4-3.5l-5.3-22.2  C523.8,676.5,510.2,677.6,496.7,677.1z"/>
 | 
			
		||||
<path class="st2" d="M377.3,354.9l15.3,16.9c11.1-9.3,23.3-17.1,36.4-23.3l-9-21C404.6,334.7,390.3,343.9,377.3,354.9z"/>
 | 
			
		||||
<path class="st2" d="M432.7,322.1l9,21c13.5-5.2,27.6-8.7,42-10.3L482,310C465.1,311.9,448.5,315.9,432.7,322.1z"/>
 | 
			
		||||
<path class="st1" d="M490.3,757.5c21.5,0.7,43-1.1,64.2-5.2l-5-23.3c-18.3,3.8-37,5.3-55.8,4.6c-3,0-5.2-0.4-8.2-0.6l-2.1,24.4  c2.3,0.1,4.6,0.1,6.9,0L490.3,757.5z"/>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 4.9 KiB  | 
@ -555,7 +555,7 @@ html:lang(ru) .card-title {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tx-wrapper-tooltip-chart-advanced {
 | 
			
		||||
  width: 115px;
 | 
			
		||||
  width: 140px;
 | 
			
		||||
  .indicator-container {
 | 
			
		||||
    .indicator {
 | 
			
		||||
      margin-right: 5px;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user