Refactoring backend routes code
This commit is contained in:
		
							parent
							
								
									97046a7dc4
								
							
						
					
					
						commit
						2253dd570d
					
				
							
								
								
									
										381
									
								
								backend/src/api/bisq/bisq.routes.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										381
									
								
								backend/src/api/bisq/bisq.routes.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,381 @@
 | 
				
			|||||||
 | 
					import { Application, Request, Response } from 'express';
 | 
				
			||||||
 | 
					import config from '../../config';
 | 
				
			||||||
 | 
					import { RequiredSpec } from '../../mempool.interfaces';
 | 
				
			||||||
 | 
					import bisq from './bisq';
 | 
				
			||||||
 | 
					import { MarketsApiError } from './interfaces';
 | 
				
			||||||
 | 
					import marketsApi from './markets-api';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BisqRoutes {
 | 
				
			||||||
 | 
					  public initRoutes(app: Application) {
 | 
				
			||||||
 | 
					    app
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'bisq/stats', this.getBisqStats)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'bisq/tx/:txId', this.getBisqTransaction)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'bisq/block/:hash', this.getBisqBlock)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'bisq/blocks/tip/height', this.getBisqTip)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'bisq/blocks/:index/:length', this.getBisqBlocks)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'bisq/address/:address', this.getBisqAddress)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'bisq/txs/:index/:length', this.getBisqTransactions)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/currencies', this.getBisqMarketCurrencies.bind(this))
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/depth', this.getBisqMarketDepth.bind(this))
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/hloc', this.getBisqMarketHloc.bind(this))
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/markets', this.getBisqMarketMarkets.bind(this))
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/offers', this.getBisqMarketOffers.bind(this))
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/ticker', this.getBisqMarketTicker.bind(this))
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/trades', this.getBisqMarketTrades.bind(this))
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/volumes', this.getBisqMarketVolumes.bind(this))
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/volumes/7d', this.getBisqMarketVolumes7d.bind(this))
 | 
				
			||||||
 | 
					    ;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private getBisqStats(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    const result = bisq.getStats();
 | 
				
			||||||
 | 
					    res.json(result);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private getBisqTip(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    const result = bisq.getLatestBlockHeight();
 | 
				
			||||||
 | 
					    res.type('text/plain');
 | 
				
			||||||
 | 
					    res.send(result.toString());
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private getBisqTransaction(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    const result = bisq.getTransaction(req.params.txId);
 | 
				
			||||||
 | 
					    if (result) {
 | 
				
			||||||
 | 
					      res.json(result);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      res.status(404).send('Bisq transaction not found');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private getBisqTransactions(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    const types: string[] = [];
 | 
				
			||||||
 | 
					    req.query.types = req.query.types || [];
 | 
				
			||||||
 | 
					    if (!Array.isArray(req.query.types)) {
 | 
				
			||||||
 | 
					      res.status(500).send('Types is not an array');
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const _type in req.query.types) {
 | 
				
			||||||
 | 
					      if (typeof req.query.types[_type] === 'string') {
 | 
				
			||||||
 | 
					        types.push(req.query.types[_type].toString());
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const index = parseInt(req.params.index, 10) || 0;
 | 
				
			||||||
 | 
					    const length = parseInt(req.params.length, 10) > 100 ? 100 : parseInt(req.params.length, 10) || 25;
 | 
				
			||||||
 | 
					    const [transactions, count] = bisq.getTransactions(index, length, types);
 | 
				
			||||||
 | 
					    res.header('X-Total-Count', count.toString());
 | 
				
			||||||
 | 
					    res.json(transactions);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private getBisqBlock(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    const result = bisq.getBlock(req.params.hash);
 | 
				
			||||||
 | 
					    if (result) {
 | 
				
			||||||
 | 
					      res.json(result);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      res.status(404).send('Bisq block not found');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private getBisqBlocks(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    const index = parseInt(req.params.index, 10) || 0;
 | 
				
			||||||
 | 
					    const length = parseInt(req.params.length, 10) > 100 ? 100 : parseInt(req.params.length, 10) || 25;
 | 
				
			||||||
 | 
					    const [transactions, count] = bisq.getBlocks(index, length);
 | 
				
			||||||
 | 
					    res.header('X-Total-Count', count.toString());
 | 
				
			||||||
 | 
					    res.json(transactions);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private getBisqAddress(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    const result = bisq.getAddress(req.params.address.substr(1));
 | 
				
			||||||
 | 
					    if (result) {
 | 
				
			||||||
 | 
					      res.json(result);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      res.status(404).send('Bisq address not found');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private getBisqMarketCurrencies(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    const constraints: RequiredSpec = {
 | 
				
			||||||
 | 
					      'type': {
 | 
				
			||||||
 | 
					        required: false,
 | 
				
			||||||
 | 
					        types: ['crypto', 'fiat', 'all']
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const p = this.parseRequestParameters(req.query, constraints);
 | 
				
			||||||
 | 
					    if (p.error) {
 | 
				
			||||||
 | 
					      res.status(400).json(this.getBisqMarketErrorResponse(p.error));
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const result = marketsApi.getCurrencies(p.type);
 | 
				
			||||||
 | 
					    if (result) {
 | 
				
			||||||
 | 
					      res.json(result);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      res.status(500).json(this.getBisqMarketErrorResponse('getBisqMarketCurrencies error'));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private getBisqMarketDepth(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    const constraints: RequiredSpec = {
 | 
				
			||||||
 | 
					      'market': {
 | 
				
			||||||
 | 
					        required: true,
 | 
				
			||||||
 | 
					        types: ['@string']
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const p = this.parseRequestParameters(req.query, constraints);
 | 
				
			||||||
 | 
					    if (p.error) {
 | 
				
			||||||
 | 
					      res.status(400).json(this.getBisqMarketErrorResponse(p.error));
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const result = marketsApi.getDepth(p.market);
 | 
				
			||||||
 | 
					    if (result) {
 | 
				
			||||||
 | 
					      res.json(result);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      res.status(500).json(this.getBisqMarketErrorResponse('getBisqMarketDepth error'));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private getBisqMarketMarkets(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    const result = marketsApi.getMarkets();
 | 
				
			||||||
 | 
					    if (result) {
 | 
				
			||||||
 | 
					      res.json(result);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      res.status(500).json(this.getBisqMarketErrorResponse('getBisqMarketMarkets error'));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private getBisqMarketTrades(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    const constraints: RequiredSpec = {
 | 
				
			||||||
 | 
					      'market': {
 | 
				
			||||||
 | 
					        required: true,
 | 
				
			||||||
 | 
					        types: ['@string']
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      'timestamp_from': {
 | 
				
			||||||
 | 
					        required: false,
 | 
				
			||||||
 | 
					        types: ['@number']
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      'timestamp_to': {
 | 
				
			||||||
 | 
					        required: false,
 | 
				
			||||||
 | 
					        types: ['@number']
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      'trade_id_to': {
 | 
				
			||||||
 | 
					        required: false,
 | 
				
			||||||
 | 
					        types: ['@string']
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      'trade_id_from': {
 | 
				
			||||||
 | 
					        required: false,
 | 
				
			||||||
 | 
					        types: ['@string']
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      'direction': {
 | 
				
			||||||
 | 
					        required: false,
 | 
				
			||||||
 | 
					        types: ['buy', 'sell']
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      'limit': {
 | 
				
			||||||
 | 
					        required: false,
 | 
				
			||||||
 | 
					        types: ['@number']
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      'sort': {
 | 
				
			||||||
 | 
					        required: false,
 | 
				
			||||||
 | 
					        types: ['asc', 'desc']
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const p = this.parseRequestParameters(req.query, constraints);
 | 
				
			||||||
 | 
					    if (p.error) {
 | 
				
			||||||
 | 
					      res.status(400).json(this.getBisqMarketErrorResponse(p.error));
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const result = marketsApi.getTrades(p.market, p.timestamp_from,
 | 
				
			||||||
 | 
					      p.timestamp_to, p.trade_id_from, p.trade_id_to, p.direction, p.limit, p.sort);
 | 
				
			||||||
 | 
					    if (result) {
 | 
				
			||||||
 | 
					      res.json(result);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      res.status(500).json(this.getBisqMarketErrorResponse('getBisqMarketTrades error'));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private getBisqMarketOffers(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    const constraints: RequiredSpec = {
 | 
				
			||||||
 | 
					      'market': {
 | 
				
			||||||
 | 
					        required: true,
 | 
				
			||||||
 | 
					        types: ['@string']
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      'direction': {
 | 
				
			||||||
 | 
					        required: false,
 | 
				
			||||||
 | 
					        types: ['buy', 'sell']
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const p = this.parseRequestParameters(req.query, constraints);
 | 
				
			||||||
 | 
					    if (p.error) {
 | 
				
			||||||
 | 
					      res.status(400).json(this.getBisqMarketErrorResponse(p.error));
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const result = marketsApi.getOffers(p.market, p.direction);
 | 
				
			||||||
 | 
					    if (result) {
 | 
				
			||||||
 | 
					      res.json(result);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      res.status(500).json(this.getBisqMarketErrorResponse('getBisqMarketOffers error'));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private getBisqMarketVolumes(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    const constraints: RequiredSpec = {
 | 
				
			||||||
 | 
					      'market': {
 | 
				
			||||||
 | 
					        required: false,
 | 
				
			||||||
 | 
					        types: ['@string']
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      'interval': {
 | 
				
			||||||
 | 
					        required: false,
 | 
				
			||||||
 | 
					        types: ['minute', 'half_hour', 'hour', 'half_day', 'day', 'week', 'month', 'year', 'auto']
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      'timestamp_from': {
 | 
				
			||||||
 | 
					        required: false,
 | 
				
			||||||
 | 
					        types: ['@number']
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      'timestamp_to': {
 | 
				
			||||||
 | 
					        required: false,
 | 
				
			||||||
 | 
					        types: ['@number']
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      'milliseconds': {
 | 
				
			||||||
 | 
					        required: false,
 | 
				
			||||||
 | 
					        types: ['@boolean']
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      'timestamp': {
 | 
				
			||||||
 | 
					        required: false,
 | 
				
			||||||
 | 
					        types: ['no', 'yes']
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const p = this.parseRequestParameters(req.query, constraints);
 | 
				
			||||||
 | 
					    if (p.error) {
 | 
				
			||||||
 | 
					      res.status(400).json(this.getBisqMarketErrorResponse(p.error));
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const result = marketsApi.getVolumes(p.market, p.timestamp_from, p.timestamp_to, p.interval, p.milliseconds, p.timestamp);
 | 
				
			||||||
 | 
					    if (result) {
 | 
				
			||||||
 | 
					      res.json(result);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      res.status(500).json(this.getBisqMarketErrorResponse('getBisqMarketVolumes error'));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private getBisqMarketHloc(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    const constraints: RequiredSpec = {
 | 
				
			||||||
 | 
					      'market': {
 | 
				
			||||||
 | 
					        required: true,
 | 
				
			||||||
 | 
					        types: ['@string']
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      'interval': {
 | 
				
			||||||
 | 
					        required: false,
 | 
				
			||||||
 | 
					        types: ['minute', 'half_hour', 'hour', 'half_day', 'day', 'week', 'month', 'year', 'auto']
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      'timestamp_from': {
 | 
				
			||||||
 | 
					        required: false,
 | 
				
			||||||
 | 
					        types: ['@number']
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      'timestamp_to': {
 | 
				
			||||||
 | 
					        required: false,
 | 
				
			||||||
 | 
					        types: ['@number']
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      'milliseconds': {
 | 
				
			||||||
 | 
					        required: false,
 | 
				
			||||||
 | 
					        types: ['@boolean']
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      'timestamp': {
 | 
				
			||||||
 | 
					        required: false,
 | 
				
			||||||
 | 
					        types: ['no', 'yes']
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const p = this.parseRequestParameters(req.query, constraints);
 | 
				
			||||||
 | 
					    if (p.error) {
 | 
				
			||||||
 | 
					      res.status(400).json(this.getBisqMarketErrorResponse(p.error));
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const result = marketsApi.getHloc(p.market, p.interval, p.timestamp_from, p.timestamp_to, p.milliseconds, p.timestamp);
 | 
				
			||||||
 | 
					    if (result) {
 | 
				
			||||||
 | 
					      res.json(result);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      res.status(500).json(this.getBisqMarketErrorResponse('getBisqMarketHloc error'));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private getBisqMarketTicker(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    const constraints: RequiredSpec = {
 | 
				
			||||||
 | 
					      'market': {
 | 
				
			||||||
 | 
					        required: false,
 | 
				
			||||||
 | 
					        types: ['@string']
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const p = this.parseRequestParameters(req.query, constraints);
 | 
				
			||||||
 | 
					    if (p.error) {
 | 
				
			||||||
 | 
					      res.status(400).json(this.getBisqMarketErrorResponse(p.error));
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const result = marketsApi.getTicker(p.market);
 | 
				
			||||||
 | 
					    if (result) {
 | 
				
			||||||
 | 
					      res.json(result);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      res.status(500).json(this.getBisqMarketErrorResponse('getBisqMarketTicker error'));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private getBisqMarketVolumes7d(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    const result = marketsApi.getVolumesByTime(604800);
 | 
				
			||||||
 | 
					    if (result) {
 | 
				
			||||||
 | 
					      res.json(result);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      res.status(500).json(this.getBisqMarketErrorResponse('getBisqMarketVolumes7d error'));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private parseRequestParameters(requestParams: object, params: RequiredSpec): { [name: string]: any; } {
 | 
				
			||||||
 | 
					    const final = {};
 | 
				
			||||||
 | 
					    for (const i in params) {
 | 
				
			||||||
 | 
					      if (params.hasOwnProperty(i)) {
 | 
				
			||||||
 | 
					        if (params[i].required && requestParams[i] === undefined) {
 | 
				
			||||||
 | 
					          return { error: i + ' parameter missing'};
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (typeof requestParams[i] === 'string') {
 | 
				
			||||||
 | 
					          const str = (requestParams[i] || '').toString().toLowerCase();
 | 
				
			||||||
 | 
					          if (params[i].types.indexOf('@number') > -1) {
 | 
				
			||||||
 | 
					            const number = parseInt((str).toString(), 10);
 | 
				
			||||||
 | 
					            final[i] = number;
 | 
				
			||||||
 | 
					          } else if (params[i].types.indexOf('@string') > -1) {
 | 
				
			||||||
 | 
					            final[i] = str;
 | 
				
			||||||
 | 
					          } else if (params[i].types.indexOf('@boolean') > -1) {
 | 
				
			||||||
 | 
					            final[i] = str === 'true' || str === 'yes';
 | 
				
			||||||
 | 
					          } else if (params[i].types.indexOf(str) > -1) {
 | 
				
			||||||
 | 
					            final[i] = str;
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            return { error: i + ' parameter invalid'};
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        } else if (typeof requestParams[i] === 'number') {
 | 
				
			||||||
 | 
					          final[i] = requestParams[i];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return final;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private getBisqMarketErrorResponse(message: string): MarketsApiError {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      'success': 0,
 | 
				
			||||||
 | 
					      'error': message
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default new BisqRoutes;
 | 
				
			||||||
							
								
								
									
										543
									
								
								backend/src/api/bitcoin/bitcoin.routes.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										543
									
								
								backend/src/api/bitcoin/bitcoin.routes.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,543 @@
 | 
				
			|||||||
 | 
					import { Application, Request, Response } from 'express';
 | 
				
			||||||
 | 
					import axios from 'axios';
 | 
				
			||||||
 | 
					import config from '../../config';
 | 
				
			||||||
 | 
					import websocketHandler from '../websocket-handler';
 | 
				
			||||||
 | 
					import mempool from '../mempool';
 | 
				
			||||||
 | 
					import feeApi from '../fee-api';
 | 
				
			||||||
 | 
					import mempoolBlocks from '../mempool-blocks';
 | 
				
			||||||
 | 
					import bitcoinApi from './bitcoin-api-factory';
 | 
				
			||||||
 | 
					import { Common } from '../common';
 | 
				
			||||||
 | 
					import backendInfo from '../backend-info';
 | 
				
			||||||
 | 
					import transactionUtils from '../transaction-utils';
 | 
				
			||||||
 | 
					import { IEsploraApi } from './esplora-api.interface';
 | 
				
			||||||
 | 
					import loadingIndicators from '../loading-indicators';
 | 
				
			||||||
 | 
					import { TransactionExtended } from '../../mempool.interfaces';
 | 
				
			||||||
 | 
					import logger from '../../logger';
 | 
				
			||||||
 | 
					import blocks from '../blocks';
 | 
				
			||||||
 | 
					import bitcoinClient from './bitcoin-client';
 | 
				
			||||||
 | 
					import difficultyAdjustment from '../difficulty-adjustment';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BitcoinRoutes {
 | 
				
			||||||
 | 
					  public initRoutes(app: Application) {
 | 
				
			||||||
 | 
					    app
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'transaction-times', this.getTransactionTimes)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'outspends', this.$getBatchedOutspends)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'cpfp/:txId', this.getCpfpInfo)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'difficulty-adjustment', this.getDifficultyChange)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'fees/recommended', this.getRecommendedFees)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'fees/mempool-blocks', this.getMempoolBlocks)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'backend-info', this.getBackendInfo)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'init-data', this.getInitData)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'validate-address/:address', this.validateAddress)
 | 
				
			||||||
 | 
					      .post(config.MEMPOOL.API_URL_PREFIX + 'tx/push', this.$postTransactionForm)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'donations', async (req, res) => {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					          const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/donations`, { responseType: 'stream', timeout: 10000 });
 | 
				
			||||||
 | 
					          response.data.pipe(res);
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					          res.status(500).end();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'donations/images/:id', async (req, res) => {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					          const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/donations/images/${req.params.id}`, {
 | 
				
			||||||
 | 
					            responseType: 'stream', timeout: 10000
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					          response.data.pipe(res);
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					          res.status(500).end();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'contributors', async (req, res) => {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					          const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/contributors`, { responseType: 'stream', timeout: 10000 });
 | 
				
			||||||
 | 
					          response.data.pipe(res);
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					          res.status(500).end();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'contributors/images/:id', async (req, res) => {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					          const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/contributors/images/${req.params.id}`, {
 | 
				
			||||||
 | 
					            responseType: 'stream', timeout: 10000
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					          response.data.pipe(res);
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					          res.status(500).end();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'translators', async (req, res) => {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					          const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/translators`, { responseType: 'stream', timeout: 10000 });
 | 
				
			||||||
 | 
					          response.data.pipe(res);
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					          res.status(500).end();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'translators/images/:id', async (req, res) => {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					          const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/translators/images/${req.params.id}`, {
 | 
				
			||||||
 | 
					            responseType: 'stream', timeout: 10000
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					          response.data.pipe(res);
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					          res.status(500).end();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'blocks', this.getBlocks.bind(this))
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'blocks/:height', this.getBlocks.bind(this))
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash', this.getBlock)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/summary', this.getStrippedBlockTransactions);
 | 
				
			||||||
 | 
					      ;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (config.MEMPOOL.BACKEND !== 'esplora') {
 | 
				
			||||||
 | 
					        app
 | 
				
			||||||
 | 
					          .get(config.MEMPOOL.API_URL_PREFIX + 'mempool', this.getMempool)
 | 
				
			||||||
 | 
					          .get(config.MEMPOOL.API_URL_PREFIX + 'mempool/txids', this.getMempoolTxIds)
 | 
				
			||||||
 | 
					          .get(config.MEMPOOL.API_URL_PREFIX + 'mempool/recent', this.getRecentMempoolTransactions)
 | 
				
			||||||
 | 
					          .get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId', this.getTransaction)
 | 
				
			||||||
 | 
					          .post(config.MEMPOOL.API_URL_PREFIX + 'tx', this.$postTransaction)
 | 
				
			||||||
 | 
					          .get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/hex', this.getRawTransaction)
 | 
				
			||||||
 | 
					          .get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/status', this.getTransactionStatus)
 | 
				
			||||||
 | 
					          .get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/outspends', this.getTransactionOutspends)
 | 
				
			||||||
 | 
					          .get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/header', this.getBlockHeader)
 | 
				
			||||||
 | 
					          .get(config.MEMPOOL.API_URL_PREFIX + 'blocks/tip/height', this.getBlockTipHeight)
 | 
				
			||||||
 | 
					          .get(config.MEMPOOL.API_URL_PREFIX + 'blocks/tip/hash', this.getBlockTipHash)
 | 
				
			||||||
 | 
					          .get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/txs', this.getBlockTransactions)
 | 
				
			||||||
 | 
					          .get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/txs/:index', this.getBlockTransactions)
 | 
				
			||||||
 | 
					          .get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/txids', this.getTxIdsForBlock)
 | 
				
			||||||
 | 
					          .get(config.MEMPOOL.API_URL_PREFIX + 'block-height/:height', this.getBlockHeight)
 | 
				
			||||||
 | 
					          .get(config.MEMPOOL.API_URL_PREFIX + 'address/:address', this.getAddress)
 | 
				
			||||||
 | 
					          .get(config.MEMPOOL.API_URL_PREFIX + 'address/:address/txs', this.getAddressTransactions)
 | 
				
			||||||
 | 
					          .get(config.MEMPOOL.API_URL_PREFIX + 'address/:address/txs/chain/:txId', this.getAddressTransactions)
 | 
				
			||||||
 | 
					          .get(config.MEMPOOL.API_URL_PREFIX + 'address-prefix/:prefix', this.getAddressPrefix)
 | 
				
			||||||
 | 
					          ;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private getInitData(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const result = websocketHandler.getInitData();
 | 
				
			||||||
 | 
					      res.json(result);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private getRecommendedFees(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    if (!mempool.isInSync()) {
 | 
				
			||||||
 | 
					      res.statusCode = 503;
 | 
				
			||||||
 | 
					      res.send('Service Unavailable');
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const result = feeApi.getRecommendedFee();
 | 
				
			||||||
 | 
					    res.json(result);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private getMempoolBlocks(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const result = mempoolBlocks.getMempoolBlocks();
 | 
				
			||||||
 | 
					      res.json(result);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private getTransactionTimes(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    if (!Array.isArray(req.query.txId)) {
 | 
				
			||||||
 | 
					      res.status(500).send('Not an array');
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const txIds: string[] = [];
 | 
				
			||||||
 | 
					    for (const _txId in req.query.txId) {
 | 
				
			||||||
 | 
					      if (typeof req.query.txId[_txId] === 'string') {
 | 
				
			||||||
 | 
					        txIds.push(req.query.txId[_txId].toString());
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const times = mempool.getFirstSeenForTransactions(txIds);
 | 
				
			||||||
 | 
					    res.json(times);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async $getBatchedOutspends(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    if (!Array.isArray(req.query.txId)) {
 | 
				
			||||||
 | 
					      res.status(500).send('Not an array');
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (req.query.txId.length > 50) {
 | 
				
			||||||
 | 
					      res.status(400).send('Too many txids requested');
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const txIds: string[] = [];
 | 
				
			||||||
 | 
					    for (const _txId in req.query.txId) {
 | 
				
			||||||
 | 
					      if (typeof req.query.txId[_txId] === 'string') {
 | 
				
			||||||
 | 
					        txIds.push(req.query.txId[_txId].toString());
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const batchedOutspends = await bitcoinApi.$getBatchedOutspends(txIds);
 | 
				
			||||||
 | 
					      res.json(batchedOutspends);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private getCpfpInfo(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    if (!/^[a-fA-F0-9]{64}$/.test(req.params.txId)) {
 | 
				
			||||||
 | 
					      res.status(501).send(`Invalid transaction ID.`);
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const tx = mempool.getMempool()[req.params.txId];
 | 
				
			||||||
 | 
					    if (!tx) {
 | 
				
			||||||
 | 
					      res.status(404).send(`Transaction doesn't exist in the mempool.`);
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (tx.cpfpChecked) {
 | 
				
			||||||
 | 
					      res.json({
 | 
				
			||||||
 | 
					        ancestors: tx.ancestors,
 | 
				
			||||||
 | 
					        bestDescendant: tx.bestDescendant || null,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const cpfpInfo = Common.setRelativesAndGetCpfpInfo(tx, mempool.getMempool());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    res.json(cpfpInfo);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private getBackendInfo(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    res.json(backendInfo.getBackendInfo());
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async getTransaction(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const transaction = await transactionUtils.$getTransactionExtended(req.params.txId, true);
 | 
				
			||||||
 | 
					      res.json(transaction);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      let statusCode = 500;
 | 
				
			||||||
 | 
					      if (e instanceof Error && e instanceof Error && e.message && e.message.indexOf('No such mempool or blockchain transaction') > -1) {
 | 
				
			||||||
 | 
					        statusCode = 404;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      res.status(statusCode).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async getRawTransaction(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const transaction: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(req.params.txId, true);
 | 
				
			||||||
 | 
					      res.setHeader('content-type', 'text/plain');
 | 
				
			||||||
 | 
					      res.send(transaction.hex);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      let statusCode = 500;
 | 
				
			||||||
 | 
					      if (e instanceof Error && e.message && e.message.indexOf('No such mempool or blockchain transaction') > -1) {
 | 
				
			||||||
 | 
					        statusCode = 404;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      res.status(statusCode).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async getTransactionStatus(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const transaction = await transactionUtils.$getTransactionExtended(req.params.txId, true);
 | 
				
			||||||
 | 
					      res.json(transaction.status);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      let statusCode = 500;
 | 
				
			||||||
 | 
					      if (e instanceof Error && e.message && e.message.indexOf('No such mempool or blockchain transaction') > -1) {
 | 
				
			||||||
 | 
					        statusCode = 404;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      res.status(statusCode).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async getBlock(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const block = await blocks.$getBlock(req.params.hash);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const blockAge = new Date().getTime() / 1000 - block.timestamp;
 | 
				
			||||||
 | 
					      const day = 24 * 3600;
 | 
				
			||||||
 | 
					      let cacheDuration;
 | 
				
			||||||
 | 
					      if (blockAge > 365 * day) {
 | 
				
			||||||
 | 
					        cacheDuration = 30 * day;
 | 
				
			||||||
 | 
					      } else if (blockAge > 30 * day) {
 | 
				
			||||||
 | 
					        cacheDuration = 10 * day;
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        cacheDuration = 600
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      res.setHeader('Expires', new Date(Date.now() + 1000 * cacheDuration).toUTCString());
 | 
				
			||||||
 | 
					      res.json(block);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async getBlockHeader(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const blockHeader = await bitcoinApi.$getBlockHeader(req.params.hash);
 | 
				
			||||||
 | 
					      res.setHeader('content-type', 'text/plain');
 | 
				
			||||||
 | 
					      res.send(blockHeader);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async getStrippedBlockTransactions(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const transactions = await blocks.$getStrippedBlockTransactions(req.params.hash);
 | 
				
			||||||
 | 
					      res.setHeader('Expires', new Date(Date.now() + 1000 * 3600 * 24 * 30).toUTCString());
 | 
				
			||||||
 | 
					      res.json(transactions);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async getBlocks(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK)) { // Bitcoin
 | 
				
			||||||
 | 
					        const height = req.params.height === undefined ? undefined : parseInt(req.params.height, 10);
 | 
				
			||||||
 | 
					        res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
 | 
				
			||||||
 | 
					        res.json(await blocks.$getBlocks(height, 15));
 | 
				
			||||||
 | 
					      } else { // Liquid, Bisq
 | 
				
			||||||
 | 
					        return await this.getLegacyBlocks(req, res);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async getLegacyBlocks(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const returnBlocks: IEsploraApi.Block[] = [];
 | 
				
			||||||
 | 
					      const fromHeight = parseInt(req.params.height, 10) || blocks.getCurrentBlockHeight();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Check if block height exist in local cache to skip the hash lookup
 | 
				
			||||||
 | 
					      const blockByHeight = blocks.getBlocks().find((b) => b.height === fromHeight);
 | 
				
			||||||
 | 
					      let startFromHash: string | null = null;
 | 
				
			||||||
 | 
					      if (blockByHeight) {
 | 
				
			||||||
 | 
					        startFromHash = blockByHeight.id;
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        startFromHash = await bitcoinApi.$getBlockHash(fromHeight);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      let nextHash = startFromHash;
 | 
				
			||||||
 | 
					      for (let i = 0; i < 10 && nextHash; i++) {
 | 
				
			||||||
 | 
					        const localBlock = blocks.getBlocks().find((b) => b.id === nextHash);
 | 
				
			||||||
 | 
					        if (localBlock) {
 | 
				
			||||||
 | 
					          returnBlocks.push(localBlock);
 | 
				
			||||||
 | 
					          nextHash = localBlock.previousblockhash;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          const block = await bitcoinApi.$getBlock(nextHash);
 | 
				
			||||||
 | 
					          returnBlocks.push(block);
 | 
				
			||||||
 | 
					          nextHash = block.previousblockhash;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
 | 
				
			||||||
 | 
					      res.json(returnBlocks);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  private async getBlockTransactions(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      loadingIndicators.setProgress('blocktxs-' + req.params.hash, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const txIds = await bitcoinApi.$getTxIdsForBlock(req.params.hash);
 | 
				
			||||||
 | 
					      const transactions: TransactionExtended[] = [];
 | 
				
			||||||
 | 
					      const startingIndex = Math.max(0, parseInt(req.params.index || '0', 10));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const endIndex = Math.min(startingIndex + 10, txIds.length);
 | 
				
			||||||
 | 
					      for (let i = startingIndex; i < endIndex; i++) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					          const transaction = await transactionUtils.$getTransactionExtended(txIds[i], true, true);
 | 
				
			||||||
 | 
					          transactions.push(transaction);
 | 
				
			||||||
 | 
					          loadingIndicators.setProgress('blocktxs-' + req.params.hash, (i - startingIndex + 1) / (endIndex - startingIndex) * 100);
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					          logger.debug('getBlockTransactions error: ' + (e instanceof Error ? e.message : e));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      res.json(transactions);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      loadingIndicators.setProgress('blocktxs-' + req.params.hash, 100);
 | 
				
			||||||
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async getBlockHeight(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const blockHash = await bitcoinApi.$getBlockHash(parseInt(req.params.height, 10));
 | 
				
			||||||
 | 
					      res.send(blockHash);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async getAddress(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    if (config.MEMPOOL.BACKEND === 'none') {
 | 
				
			||||||
 | 
					      res.status(405).send('Address lookups cannot be used with bitcoind as backend.');
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const addressData = await bitcoinApi.$getAddress(req.params.address);
 | 
				
			||||||
 | 
					      res.json(addressData);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      if (e instanceof Error && e.message && (e.message.indexOf('too long') > 0 || e.message.indexOf('confirmed status') > 0)) {
 | 
				
			||||||
 | 
					        return res.status(413).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async getAddressTransactions(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    if (config.MEMPOOL.BACKEND === 'none') {
 | 
				
			||||||
 | 
					      res.status(405).send('Address lookups cannot be used with bitcoind as backend.');
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const transactions = await bitcoinApi.$getAddressTransactions(req.params.address, req.params.txId);
 | 
				
			||||||
 | 
					      res.json(transactions);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      if (e instanceof Error && e.message && (e.message.indexOf('too long') > 0 || e.message.indexOf('confirmed status') > 0)) {
 | 
				
			||||||
 | 
					        return res.status(413).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async getAdressTxChain(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    res.status(501).send('Not implemented');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async getAddressPrefix(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const blockHash = await bitcoinApi.$getAddressPrefix(req.params.prefix);
 | 
				
			||||||
 | 
					      res.send(blockHash);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async getRecentMempoolTransactions(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    const latestTransactions = Object.entries(mempool.getMempool())
 | 
				
			||||||
 | 
					      .sort((a, b) => (b[1].firstSeen || 0) - (a[1].firstSeen || 0))
 | 
				
			||||||
 | 
					      .slice(0, 10).map((tx) => Common.stripTransaction(tx[1]));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    res.json(latestTransactions);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async getMempool(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    const info = mempool.getMempoolInfo();
 | 
				
			||||||
 | 
					    res.json({
 | 
				
			||||||
 | 
					      count: info.size,
 | 
				
			||||||
 | 
					      vsize: info.bytes,
 | 
				
			||||||
 | 
					      total_fee: info.total_fee * 1e8,
 | 
				
			||||||
 | 
					      fee_histogram: []
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async getMempoolTxIds(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const rawMempool = await bitcoinApi.$getRawMempool();
 | 
				
			||||||
 | 
					      res.send(rawMempool);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async getBlockTipHeight(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const result = await bitcoinApi.$getBlockHeightTip();
 | 
				
			||||||
 | 
					      res.json(result);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async getBlockTipHash(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const result = await bitcoinApi.$getBlockHashTip();
 | 
				
			||||||
 | 
					      res.setHeader('content-type', 'text/plain');
 | 
				
			||||||
 | 
					      res.send(result);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async getTxIdsForBlock(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const result = await bitcoinApi.$getTxIdsForBlock(req.params.hash);
 | 
				
			||||||
 | 
					      res.json(result);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async validateAddress(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const result = await bitcoinClient.validateAddress(req.params.address);
 | 
				
			||||||
 | 
					      res.json(result);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async getTransactionOutspends(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const result = await bitcoinApi.$getOutspends(req.params.txId);
 | 
				
			||||||
 | 
					      res.json(result);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private getDifficultyChange(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      res.json(difficultyAdjustment.getDifficultyAdjustment());
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async $postTransaction(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    res.setHeader('content-type', 'text/plain');
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      let rawTx;
 | 
				
			||||||
 | 
					      if (typeof req.body === 'object') {
 | 
				
			||||||
 | 
					        rawTx = Object.keys(req.body)[0];
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        rawTx = req.body;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      const txIdResult = await bitcoinApi.$sendRawTransaction(rawTx);
 | 
				
			||||||
 | 
					      res.send(txIdResult);
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					      res.status(400).send(e.message && e.code ? 'sendrawtransaction RPC error: ' + JSON.stringify({ code: e.code, message: e.message })
 | 
				
			||||||
 | 
					        : (e.message || 'Error'));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async $postTransactionForm(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    res.setHeader('content-type', 'text/plain');
 | 
				
			||||||
 | 
					    const matches = /tx=([a-z0-9]+)/.exec(req.body);
 | 
				
			||||||
 | 
					    let txHex = '';
 | 
				
			||||||
 | 
					    if (matches && matches[1]) {
 | 
				
			||||||
 | 
					      txHex = matches[1];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const txIdResult = await bitcoinClient.sendRawTransaction(txHex);
 | 
				
			||||||
 | 
					      res.send(txIdResult);
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					      res.status(400).send(e.message && e.code ? 'sendrawtransaction RPC error: ' + JSON.stringify({ code: e.code, message: e.message })
 | 
				
			||||||
 | 
					        : (e.message || 'Error'));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default new BitcoinRoutes();
 | 
				
			||||||
@ -19,7 +19,7 @@ import HashratesRepository from '../repositories/HashratesRepository';
 | 
				
			|||||||
import indexer from '../indexer';
 | 
					import indexer from '../indexer';
 | 
				
			||||||
import poolsParser from './pools-parser';
 | 
					import poolsParser from './pools-parser';
 | 
				
			||||||
import BlocksSummariesRepository from '../repositories/BlocksSummariesRepository';
 | 
					import BlocksSummariesRepository from '../repositories/BlocksSummariesRepository';
 | 
				
			||||||
import mining from './mining';
 | 
					import mining from './mining/mining';
 | 
				
			||||||
import DifficultyAdjustmentsRepository from '../repositories/DifficultyAdjustmentsRepository';
 | 
					import DifficultyAdjustmentsRepository from '../repositories/DifficultyAdjustmentsRepository';
 | 
				
			||||||
import difficultyAdjustment from './difficulty-adjustment';
 | 
					import difficultyAdjustment from './difficulty-adjustment';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										73
									
								
								backend/src/api/liquid/liquid.routes.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								backend/src/api/liquid/liquid.routes.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,73 @@
 | 
				
			|||||||
 | 
					import axios from 'axios';
 | 
				
			||||||
 | 
					import { Application, Request, Response } from 'express';
 | 
				
			||||||
 | 
					import config from '../../config';
 | 
				
			||||||
 | 
					import elementsParser from './elements-parser';
 | 
				
			||||||
 | 
					import icons from './icons';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class LiquidRoutes {
 | 
				
			||||||
 | 
					  public initRoutes(app: Application) {
 | 
				
			||||||
 | 
					    app
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'assets/icons', this.getAllLiquidIcon)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'assets/featured', this.$getAllFeaturedLiquidAssets)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'asset/:assetId/icon', this.getLiquidIcon)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'assets/group/:id', this.$getAssetGroup)
 | 
				
			||||||
 | 
					      ;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (config.DATABASE.ENABLED) {
 | 
				
			||||||
 | 
					      app
 | 
				
			||||||
 | 
					        .get(config.MEMPOOL.API_URL_PREFIX + 'liquid/pegs/month', this.$getElementsPegsByMonth)
 | 
				
			||||||
 | 
					        ;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private 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');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private getAllLiquidIcon(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    const result = icons.getAllIconIds();
 | 
				
			||||||
 | 
					    if (result) {
 | 
				
			||||||
 | 
					      res.json(result);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      res.status(404).send('Asset icons not found');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async $getAllFeaturedLiquidAssets(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.LIQUID_API}/assets/featured`, { responseType: 'stream', timeout: 10000 });
 | 
				
			||||||
 | 
					      response.data.pipe(res);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      res.status(500).end();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async $getAssetGroup(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.LIQUID_API}/assets/group/${parseInt(req.params.id, 10)}`,
 | 
				
			||||||
 | 
					        { responseType: 'stream', timeout: 10000 });
 | 
				
			||||||
 | 
					      response.data.pipe(res);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      res.status(500).end();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async $getElementsPegsByMonth(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const pegs = await elementsParser.$getPegDataByMonth();
 | 
				
			||||||
 | 
					      res.json(pegs);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default new LiquidRoutes();
 | 
				
			||||||
							
								
								
									
										238
									
								
								backend/src/api/mining/mining-routes.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										238
									
								
								backend/src/api/mining/mining-routes.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,238 @@
 | 
				
			|||||||
 | 
					import { Application, Request, Response } from 'express';
 | 
				
			||||||
 | 
					import config from "../../config";
 | 
				
			||||||
 | 
					import logger from '../../logger';
 | 
				
			||||||
 | 
					import BlocksAuditsRepository from '../../repositories/BlocksAuditsRepository';
 | 
				
			||||||
 | 
					import BlocksRepository from '../../repositories/BlocksRepository';
 | 
				
			||||||
 | 
					import DifficultyAdjustmentsRepository from '../../repositories/DifficultyAdjustmentsRepository';
 | 
				
			||||||
 | 
					import HashratesRepository from '../../repositories/HashratesRepository';
 | 
				
			||||||
 | 
					import bitcoinClient from '../bitcoin/bitcoin-client';
 | 
				
			||||||
 | 
					import mining from "./mining";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MiningRoutes {
 | 
				
			||||||
 | 
					  public initRoutes(app: Application) {
 | 
				
			||||||
 | 
					    app
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/:interval', this.$getPools)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:slug/hashrate', this.$getPoolHistoricalHashrate)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:slug/blocks', this.$getPoolBlocks)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:slug/blocks/:height', this.$getPoolBlocks)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:slug', this.$getPool)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'mining/hashrate/pools/:interval', this.$getPoolsHistoricalHashrate)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'mining/hashrate/:interval', this.$getHistoricalHashrate)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty-adjustments', this.$getDifficultyAdjustments)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'mining/reward-stats/:blockCount', this.$getRewardStats)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/fees/:interval', this.$getHistoricalBlockFees)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/rewards/:interval', this.$getHistoricalBlockRewards)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/fee-rates/:interval', this.$getHistoricalBlockFeeRates)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/sizes-weights/:interval', this.$getHistoricalBlockSizeAndWeight)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty-adjustments/:interval', this.$getDifficultyAdjustments)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/predictions/:interval', this.$getHistoricalBlockPrediction)
 | 
				
			||||||
 | 
					      ;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async $getPool(req: Request, res: Response): Promise<void> {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const stats = await mining.$getPoolStat(req.params.slug);
 | 
				
			||||||
 | 
					      res.header('Pragma', 'public');
 | 
				
			||||||
 | 
					      res.header('Cache-control', 'public');
 | 
				
			||||||
 | 
					      res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
 | 
				
			||||||
 | 
					      res.json(stats);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      if (e instanceof Error && e.message.indexOf('This mining pool does not exist') > -1) {
 | 
				
			||||||
 | 
					        res.status(404).send(e.message);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async $getPoolBlocks(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const poolBlocks = await BlocksRepository.$getBlocksByPool(
 | 
				
			||||||
 | 
					        req.params.slug,
 | 
				
			||||||
 | 
					        req.params.height === undefined ? undefined : parseInt(req.params.height, 10),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      res.header('Pragma', 'public');
 | 
				
			||||||
 | 
					      res.header('Cache-control', 'public');
 | 
				
			||||||
 | 
					      res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
 | 
				
			||||||
 | 
					      res.json(poolBlocks);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      if (e instanceof Error && e.message.indexOf('This mining pool does not exist') > -1) {
 | 
				
			||||||
 | 
					        res.status(404).send(e.message);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async $getPools(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const stats = await mining.$getPoolsStats(req.params.interval);
 | 
				
			||||||
 | 
					      const blockCount = await BlocksRepository.$blockCount(null, null);
 | 
				
			||||||
 | 
					      res.header('Pragma', 'public');
 | 
				
			||||||
 | 
					      res.header('Cache-control', 'public');
 | 
				
			||||||
 | 
					      res.header('X-total-count', blockCount.toString());
 | 
				
			||||||
 | 
					      res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
 | 
				
			||||||
 | 
					      res.json(stats);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async $getPoolsHistoricalHashrate(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const hashrates = await HashratesRepository.$getPoolsWeeklyHashrate(req.params.interval);
 | 
				
			||||||
 | 
					      const blockCount = await BlocksRepository.$blockCount(null, null);
 | 
				
			||||||
 | 
					      res.header('Pragma', 'public');
 | 
				
			||||||
 | 
					      res.header('Cache-control', 'public');
 | 
				
			||||||
 | 
					      res.header('X-total-count', blockCount.toString());
 | 
				
			||||||
 | 
					      res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
 | 
				
			||||||
 | 
					      res.json(hashrates);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async $getPoolHistoricalHashrate(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const hashrates = await HashratesRepository.$getPoolWeeklyHashrate(req.params.slug);
 | 
				
			||||||
 | 
					      const blockCount = await BlocksRepository.$blockCount(null, null);
 | 
				
			||||||
 | 
					      res.header('Pragma', 'public');
 | 
				
			||||||
 | 
					      res.header('Cache-control', 'public');
 | 
				
			||||||
 | 
					      res.header('X-total-count', blockCount.toString());
 | 
				
			||||||
 | 
					      res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
 | 
				
			||||||
 | 
					      res.json(hashrates);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      if (e instanceof Error && e.message.indexOf('This mining pool does not exist') > -1) {
 | 
				
			||||||
 | 
					        res.status(404).send(e.message);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async $getHistoricalHashrate(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    let currentHashrate = 0, currentDifficulty = 0;
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      currentHashrate = await bitcoinClient.getNetworkHashPs();
 | 
				
			||||||
 | 
					      currentDifficulty = await bitcoinClient.getDifficulty();
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      logger.debug('Bitcoin Core is not available, using zeroed value for current hashrate and difficulty');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const hashrates = await HashratesRepository.$getNetworkDailyHashrate(req.params.interval);
 | 
				
			||||||
 | 
					      const difficulty = await DifficultyAdjustmentsRepository.$getAdjustments(req.params.interval, false);
 | 
				
			||||||
 | 
					      const blockCount = await BlocksRepository.$blockCount(null, null);
 | 
				
			||||||
 | 
					      res.header('Pragma', 'public');
 | 
				
			||||||
 | 
					      res.header('Cache-control', 'public');
 | 
				
			||||||
 | 
					      res.header('X-total-count', blockCount.toString());
 | 
				
			||||||
 | 
					      res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
 | 
				
			||||||
 | 
					      res.json({
 | 
				
			||||||
 | 
					        hashrates: hashrates,
 | 
				
			||||||
 | 
					        difficulty: difficulty,
 | 
				
			||||||
 | 
					        currentHashrate: currentHashrate,
 | 
				
			||||||
 | 
					        currentDifficulty: currentDifficulty,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async $getHistoricalBlockFees(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const blockFees = await mining.$getHistoricalBlockFees(req.params.interval);
 | 
				
			||||||
 | 
					      const blockCount = await BlocksRepository.$blockCount(null, null);
 | 
				
			||||||
 | 
					      res.header('Pragma', 'public');
 | 
				
			||||||
 | 
					      res.header('Cache-control', 'public');
 | 
				
			||||||
 | 
					      res.header('X-total-count', blockCount.toString());
 | 
				
			||||||
 | 
					      res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
 | 
				
			||||||
 | 
					      res.json(blockFees);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async $getHistoricalBlockRewards(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const blockRewards = await mining.$getHistoricalBlockRewards(req.params.interval);
 | 
				
			||||||
 | 
					      const blockCount = await BlocksRepository.$blockCount(null, null);
 | 
				
			||||||
 | 
					      res.header('Pragma', 'public');
 | 
				
			||||||
 | 
					      res.header('Cache-control', 'public');
 | 
				
			||||||
 | 
					      res.header('X-total-count', blockCount.toString());
 | 
				
			||||||
 | 
					      res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
 | 
				
			||||||
 | 
					      res.json(blockRewards);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async $getHistoricalBlockFeeRates(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const blockFeeRates = await mining.$getHistoricalBlockFeeRates(req.params.interval);
 | 
				
			||||||
 | 
					      const blockCount = await BlocksRepository.$blockCount(null, null);
 | 
				
			||||||
 | 
					      res.header('Pragma', 'public');
 | 
				
			||||||
 | 
					      res.header('Cache-control', 'public');
 | 
				
			||||||
 | 
					      res.header('X-total-count', blockCount.toString());
 | 
				
			||||||
 | 
					      res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
 | 
				
			||||||
 | 
					      res.json(blockFeeRates);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async $getHistoricalBlockSizeAndWeight(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const blockSizes = await mining.$getHistoricalBlockSizes(req.params.interval);
 | 
				
			||||||
 | 
					      const blockWeights = await mining.$getHistoricalBlockWeights(req.params.interval);
 | 
				
			||||||
 | 
					      const blockCount = await BlocksRepository.$blockCount(null, null);
 | 
				
			||||||
 | 
					      res.header('Pragma', 'public');
 | 
				
			||||||
 | 
					      res.header('Cache-control', 'public');
 | 
				
			||||||
 | 
					      res.header('X-total-count', blockCount.toString());
 | 
				
			||||||
 | 
					      res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
 | 
				
			||||||
 | 
					      res.json({
 | 
				
			||||||
 | 
					        sizes: blockSizes,
 | 
				
			||||||
 | 
					        weights: blockWeights
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async $getDifficultyAdjustments(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const difficulty = await DifficultyAdjustmentsRepository.$getRawAdjustments(req.params.interval, true);
 | 
				
			||||||
 | 
					      res.header('Pragma', 'public');
 | 
				
			||||||
 | 
					      res.header('Cache-control', 'public');
 | 
				
			||||||
 | 
					      res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
 | 
				
			||||||
 | 
					      res.json(difficulty.map(adj => [adj.time, adj.height, adj.difficulty, adj.adjustment]));
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async $getRewardStats(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const response = await mining.$getRewardStats(parseInt(req.params.blockCount, 10));
 | 
				
			||||||
 | 
					      res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
 | 
				
			||||||
 | 
					      res.json(response);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      res.status(500).end();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async $getHistoricalBlockPrediction(req: Request, res: Response) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const blockPredictions = await mining.$getBlockPredictionsHistory(req.params.interval);
 | 
				
			||||||
 | 
					      const blockCount = await BlocksAuditsRepository.$getPredictionsCount();
 | 
				
			||||||
 | 
					      res.header('Pragma', 'public');
 | 
				
			||||||
 | 
					      res.header('Cache-control', 'public');
 | 
				
			||||||
 | 
					      res.header('X-total-count', blockCount.toString());
 | 
				
			||||||
 | 
					      res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
 | 
				
			||||||
 | 
					      res.json(blockPredictions.map(prediction => [prediction.time, prediction.height, prediction.match_rate]));
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default new MiningRoutes();
 | 
				
			||||||
@ -1,16 +1,16 @@
 | 
				
			|||||||
import { IndexedDifficultyAdjustment, PoolInfo, PoolStats, RewardStats } from '../mempool.interfaces';
 | 
					import { IndexedDifficultyAdjustment, PoolInfo, PoolStats, RewardStats } from '../../mempool.interfaces';
 | 
				
			||||||
import BlocksRepository from '../repositories/BlocksRepository';
 | 
					import BlocksRepository from '../../repositories/BlocksRepository';
 | 
				
			||||||
import PoolsRepository from '../repositories/PoolsRepository';
 | 
					import PoolsRepository from '../../repositories/PoolsRepository';
 | 
				
			||||||
import HashratesRepository from '../repositories/HashratesRepository';
 | 
					import HashratesRepository from '../../repositories/HashratesRepository';
 | 
				
			||||||
import bitcoinClient from './bitcoin/bitcoin-client';
 | 
					import bitcoinClient from '../bitcoin/bitcoin-client';
 | 
				
			||||||
import logger from '../logger';
 | 
					import logger from '../../logger';
 | 
				
			||||||
import { Common } from './common';
 | 
					import { Common } from '../common';
 | 
				
			||||||
import loadingIndicators from './loading-indicators';
 | 
					import loadingIndicators from '../loading-indicators';
 | 
				
			||||||
import { escape } from 'mysql2';
 | 
					import { escape } from 'mysql2';
 | 
				
			||||||
import indexer from '../indexer';
 | 
					import indexer from '../../indexer';
 | 
				
			||||||
import DifficultyAdjustmentsRepository from '../repositories/DifficultyAdjustmentsRepository';
 | 
					import DifficultyAdjustmentsRepository from '../../repositories/DifficultyAdjustmentsRepository';
 | 
				
			||||||
import config from '../config';
 | 
					import config from '../../config';
 | 
				
			||||||
import BlocksAuditsRepository from '../repositories/BlocksAuditsRepository';
 | 
					import BlocksAuditsRepository from '../../repositories/BlocksAuditsRepository';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Mining {
 | 
					class Mining {
 | 
				
			||||||
  constructor() {
 | 
					  constructor() {
 | 
				
			||||||
@ -1,160 +1,11 @@
 | 
				
			|||||||
import memPool from './mempool';
 | 
					import DB from '../../database';
 | 
				
			||||||
import DB from '../database';
 | 
					import logger from '../../logger';
 | 
				
			||||||
import logger from '../logger';
 | 
					import { Statistic, OptimizedStatistic } from '../../mempool.interfaces';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Statistic, TransactionExtended, OptimizedStatistic } from '../mempool.interfaces';
 | 
					class StatisticsApi {
 | 
				
			||||||
import config from '../config';
 | 
					 | 
				
			||||||
import { Common } from './common';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Statistics {
 | 
					 | 
				
			||||||
  protected intervalTimer: NodeJS.Timer | undefined;
 | 
					 | 
				
			||||||
  protected newStatisticsEntryCallback: ((stats: OptimizedStatistic) => void) | undefined;
 | 
					 | 
				
			||||||
  protected queryTimeout = 120000;
 | 
					  protected queryTimeout = 120000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public setNewStatisticsEntryCallback(fn: (stats: OptimizedStatistic) => void) {
 | 
					  public async $createZeroedStatistic(): Promise<number | undefined> {
 | 
				
			||||||
    this.newStatisticsEntryCallback = fn;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  constructor() { }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  public startStatistics(): void {
 | 
					 | 
				
			||||||
    logger.info('Starting statistics service');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const now = new Date();
 | 
					 | 
				
			||||||
    const nextInterval = new Date(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours(),
 | 
					 | 
				
			||||||
      Math.floor(now.getMinutes() / 1) * 1 + 1, 0, 0);
 | 
					 | 
				
			||||||
    const difference = nextInterval.getTime() - now.getTime();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    setTimeout(() => {
 | 
					 | 
				
			||||||
      this.runStatistics();
 | 
					 | 
				
			||||||
      this.intervalTimer = setInterval(() => {
 | 
					 | 
				
			||||||
        this.runStatistics();
 | 
					 | 
				
			||||||
      }, 1 * 60 * 1000);
 | 
					 | 
				
			||||||
    }, difference);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private async runStatistics(): Promise<void> {
 | 
					 | 
				
			||||||
    if (!memPool.isInSync()) {
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    const currentMempool = memPool.getMempool();
 | 
					 | 
				
			||||||
    const txPerSecond = memPool.getTxPerSecond();
 | 
					 | 
				
			||||||
    const vBytesPerSecond = memPool.getVBytesPerSecond();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    logger.debug('Running statistics');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let memPoolArray: TransactionExtended[] = [];
 | 
					 | 
				
			||||||
    for (const i in currentMempool) {
 | 
					 | 
				
			||||||
      if (currentMempool.hasOwnProperty(i)) {
 | 
					 | 
				
			||||||
        memPoolArray.push(currentMempool[i]);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    // Remove 0 and undefined
 | 
					 | 
				
			||||||
    memPoolArray = memPoolArray.filter((tx) => tx.effectiveFeePerVsize);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!memPoolArray.length) {
 | 
					 | 
				
			||||||
      try {
 | 
					 | 
				
			||||||
        const insertIdZeroed = await this.$createZeroedStatistic();
 | 
					 | 
				
			||||||
        if (this.newStatisticsEntryCallback && insertIdZeroed) {
 | 
					 | 
				
			||||||
          const newStats = await this.$get(insertIdZeroed);
 | 
					 | 
				
			||||||
          if (newStats) {
 | 
					 | 
				
			||||||
            this.newStatisticsEntryCallback(newStats);
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      } catch (e) {
 | 
					 | 
				
			||||||
        logger.err('Unable to insert zeroed statistics. ' + e);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    memPoolArray.sort((a, b) => a.effectiveFeePerVsize - b.effectiveFeePerVsize);
 | 
					 | 
				
			||||||
    const totalWeight = memPoolArray.map((tx) => tx.vsize).reduce((acc, curr) => acc + curr) * 4;
 | 
					 | 
				
			||||||
    const totalFee = memPoolArray.map((tx) => tx.fee).reduce((acc, curr) => acc + curr);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const logFees = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200,
 | 
					 | 
				
			||||||
      250, 300, 350, 400, 500, 600, 700, 800, 900, 1000, 1200, 1400, 1600, 1800, 2000];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const weightVsizeFees: { [feePerWU: number]: number } = {};
 | 
					 | 
				
			||||||
    const lastItem = logFees.length - 1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    memPoolArray.forEach((transaction) => {
 | 
					 | 
				
			||||||
      for (let i = 0; i < logFees.length; i++) {
 | 
					 | 
				
			||||||
        if (
 | 
					 | 
				
			||||||
          (Common.isLiquid() && (i === lastItem || transaction.effectiveFeePerVsize * 10 < logFees[i + 1]))
 | 
					 | 
				
			||||||
          ||
 | 
					 | 
				
			||||||
          (!Common.isLiquid() && (i === lastItem || transaction.effectiveFeePerVsize < logFees[i + 1]))
 | 
					 | 
				
			||||||
        ) {
 | 
					 | 
				
			||||||
          if (weightVsizeFees[logFees[i]]) {
 | 
					 | 
				
			||||||
            weightVsizeFees[logFees[i]] += transaction.vsize;
 | 
					 | 
				
			||||||
          } else {
 | 
					 | 
				
			||||||
            weightVsizeFees[logFees[i]] = transaction.vsize;
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          break;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const insertId = await this.$create({
 | 
					 | 
				
			||||||
        added: 'NOW()',
 | 
					 | 
				
			||||||
        unconfirmed_transactions: memPoolArray.length,
 | 
					 | 
				
			||||||
        tx_per_second: txPerSecond,
 | 
					 | 
				
			||||||
        vbytes_per_second: Math.round(vBytesPerSecond),
 | 
					 | 
				
			||||||
        mempool_byte_weight: totalWeight,
 | 
					 | 
				
			||||||
        total_fee: totalFee,
 | 
					 | 
				
			||||||
        fee_data: '',
 | 
					 | 
				
			||||||
        vsize_1: weightVsizeFees['1'] || 0,
 | 
					 | 
				
			||||||
        vsize_2: weightVsizeFees['2'] || 0,
 | 
					 | 
				
			||||||
        vsize_3: weightVsizeFees['3'] || 0,
 | 
					 | 
				
			||||||
        vsize_4: weightVsizeFees['4'] || 0,
 | 
					 | 
				
			||||||
        vsize_5: weightVsizeFees['5'] || 0,
 | 
					 | 
				
			||||||
        vsize_6: weightVsizeFees['6'] || 0,
 | 
					 | 
				
			||||||
        vsize_8: weightVsizeFees['8'] || 0,
 | 
					 | 
				
			||||||
        vsize_10: weightVsizeFees['10'] || 0,
 | 
					 | 
				
			||||||
        vsize_12: weightVsizeFees['12'] || 0,
 | 
					 | 
				
			||||||
        vsize_15: weightVsizeFees['15'] || 0,
 | 
					 | 
				
			||||||
        vsize_20: weightVsizeFees['20'] || 0,
 | 
					 | 
				
			||||||
        vsize_30: weightVsizeFees['30'] || 0,
 | 
					 | 
				
			||||||
        vsize_40: weightVsizeFees['40'] || 0,
 | 
					 | 
				
			||||||
        vsize_50: weightVsizeFees['50'] || 0,
 | 
					 | 
				
			||||||
        vsize_60: weightVsizeFees['60'] || 0,
 | 
					 | 
				
			||||||
        vsize_70: weightVsizeFees['70'] || 0,
 | 
					 | 
				
			||||||
        vsize_80: weightVsizeFees['80'] || 0,
 | 
					 | 
				
			||||||
        vsize_90: weightVsizeFees['90'] || 0,
 | 
					 | 
				
			||||||
        vsize_100: weightVsizeFees['100'] || 0,
 | 
					 | 
				
			||||||
        vsize_125: weightVsizeFees['125'] || 0,
 | 
					 | 
				
			||||||
        vsize_150: weightVsizeFees['150'] || 0,
 | 
					 | 
				
			||||||
        vsize_175: weightVsizeFees['175'] || 0,
 | 
					 | 
				
			||||||
        vsize_200: weightVsizeFees['200'] || 0,
 | 
					 | 
				
			||||||
        vsize_250: weightVsizeFees['250'] || 0,
 | 
					 | 
				
			||||||
        vsize_300: weightVsizeFees['300'] || 0,
 | 
					 | 
				
			||||||
        vsize_350: weightVsizeFees['350'] || 0,
 | 
					 | 
				
			||||||
        vsize_400: weightVsizeFees['400'] || 0,
 | 
					 | 
				
			||||||
        vsize_500: weightVsizeFees['500'] || 0,
 | 
					 | 
				
			||||||
        vsize_600: weightVsizeFees['600'] || 0,
 | 
					 | 
				
			||||||
        vsize_700: weightVsizeFees['700'] || 0,
 | 
					 | 
				
			||||||
        vsize_800: weightVsizeFees['800'] || 0,
 | 
					 | 
				
			||||||
        vsize_900: weightVsizeFees['900'] || 0,
 | 
					 | 
				
			||||||
        vsize_1000: weightVsizeFees['1000'] || 0,
 | 
					 | 
				
			||||||
        vsize_1200: weightVsizeFees['1200'] || 0,
 | 
					 | 
				
			||||||
        vsize_1400: weightVsizeFees['1400'] || 0,
 | 
					 | 
				
			||||||
        vsize_1600: weightVsizeFees['1600'] || 0,
 | 
					 | 
				
			||||||
        vsize_1800: weightVsizeFees['1800'] || 0,
 | 
					 | 
				
			||||||
        vsize_2000: weightVsizeFees['2000'] || 0,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (this.newStatisticsEntryCallback && insertId) {
 | 
					 | 
				
			||||||
        const newStats = await this.$get(insertId);
 | 
					 | 
				
			||||||
        if (newStats) {
 | 
					 | 
				
			||||||
          this.newStatisticsEntryCallback(newStats);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    } catch (e) {
 | 
					 | 
				
			||||||
      logger.err('Unable to insert statistics. ' + e);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private async $createZeroedStatistic(): Promise<number | undefined> {
 | 
					 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const query = `INSERT INTO statistics(
 | 
					      const query = `INSERT INTO statistics(
 | 
				
			||||||
              added,
 | 
					              added,
 | 
				
			||||||
@ -212,7 +63,7 @@ class Statistics {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private async $create(statistics: Statistic): Promise<number | undefined> {
 | 
					  public async $create(statistics: Statistic): Promise<number | undefined> {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const query = `INSERT INTO statistics(
 | 
					      const query = `INSERT INTO statistics(
 | 
				
			||||||
              added,
 | 
					              added,
 | 
				
			||||||
@ -413,7 +264,7 @@ class Statistics {
 | 
				
			|||||||
      ORDER BY statistics.added DESC;`;
 | 
					      ORDER BY statistics.added DESC;`;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private async $get(id: number): Promise<OptimizedStatistic | undefined> {
 | 
					  public async $get(id: number): Promise<OptimizedStatistic | undefined> {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const query = `SELECT *, UNIX_TIMESTAMP(added) as added FROM statistics WHERE id = ?`;
 | 
					      const query = `SELECT *, UNIX_TIMESTAMP(added) as added FROM statistics WHERE id = ?`;
 | 
				
			||||||
      const [rows] = await DB.query(query, [id]);
 | 
					      const [rows] = await DB.query(query, [id]);
 | 
				
			||||||
@ -574,7 +425,6 @@ class Statistics {
 | 
				
			|||||||
      };
 | 
					      };
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default new Statistics();
 | 
					export default new StatisticsApi();
 | 
				
			||||||
							
								
								
									
										67
									
								
								backend/src/api/statistics/statistics.routes.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								backend/src/api/statistics/statistics.routes.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,67 @@
 | 
				
			|||||||
 | 
					import { Application, Request, Response } from 'express';
 | 
				
			||||||
 | 
					import config from '../../config';
 | 
				
			||||||
 | 
					import statisticsApi from './statistics-api';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class StatisticsRoutes {
 | 
				
			||||||
 | 
					  public initRoutes(app: Application) {
 | 
				
			||||||
 | 
					    app
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/2h', this.$getStatisticsByTime.bind(this, '2h'))
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/24h', this.$getStatisticsByTime.bind(this, '24h'))
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/1w', this.$getStatisticsByTime.bind(this, '1w'))
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/1m', this.$getStatisticsByTime.bind(this, '1m'))
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/3m', this.$getStatisticsByTime.bind(this, '3m'))
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/6m', this.$getStatisticsByTime.bind(this, '6m'))
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/1y', this.$getStatisticsByTime.bind(this, '1y'))
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/2y', this.$getStatisticsByTime.bind(this, '2y'))
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/3y', this.$getStatisticsByTime.bind(this, '3y'))
 | 
				
			||||||
 | 
					    ;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async $getStatisticsByTime(time: '2h' | '24h' | '1w' | '1m' | '3m' | '6m' | '1y' | '2y' | '3y', req: Request, res: Response) {
 | 
				
			||||||
 | 
					    res.header('Pragma', 'public');
 | 
				
			||||||
 | 
					    res.header('Cache-control', 'public');
 | 
				
			||||||
 | 
					    res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      let result;
 | 
				
			||||||
 | 
					      switch (time as string) {
 | 
				
			||||||
 | 
					        case '2h':
 | 
				
			||||||
 | 
					          result = await statisticsApi.$list2H();
 | 
				
			||||||
 | 
					          res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        case '24h':
 | 
				
			||||||
 | 
					          result = await statisticsApi.$list24H();
 | 
				
			||||||
 | 
					          res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        case '1w':
 | 
				
			||||||
 | 
					          result = await statisticsApi.$list1W();
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        case '1m':
 | 
				
			||||||
 | 
					          result = await statisticsApi.$list1M();
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        case '3m':
 | 
				
			||||||
 | 
					          result = await statisticsApi.$list3M();
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        case '6m':
 | 
				
			||||||
 | 
					          result = await statisticsApi.$list6M();
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        case '1y':
 | 
				
			||||||
 | 
					          result = await statisticsApi.$list1Y();
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        case '2y':
 | 
				
			||||||
 | 
					          result = await statisticsApi.$list2Y();
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        case '3y':
 | 
				
			||||||
 | 
					          result = await statisticsApi.$list3Y();
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					          result = await statisticsApi.$list2H();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      res.json(result);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default new StatisticsRoutes();
 | 
				
			||||||
							
								
								
									
										153
									
								
								backend/src/api/statistics/statistics.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								backend/src/api/statistics/statistics.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,153 @@
 | 
				
			|||||||
 | 
					import memPool from '../mempool';
 | 
				
			||||||
 | 
					import logger from '../../logger';
 | 
				
			||||||
 | 
					import { TransactionExtended, OptimizedStatistic } from '../../mempool.interfaces';
 | 
				
			||||||
 | 
					import { Common } from '../common';
 | 
				
			||||||
 | 
					import statisticsApi from './statistics-api';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Statistics {
 | 
				
			||||||
 | 
					  protected intervalTimer: NodeJS.Timer | undefined;
 | 
				
			||||||
 | 
					  protected newStatisticsEntryCallback: ((stats: OptimizedStatistic) => void) | undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public setNewStatisticsEntryCallback(fn: (stats: OptimizedStatistic) => void) {
 | 
				
			||||||
 | 
					    this.newStatisticsEntryCallback = fn;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public startStatistics(): void {
 | 
				
			||||||
 | 
					    logger.info('Starting statistics service');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const now = new Date();
 | 
				
			||||||
 | 
					    const nextInterval = new Date(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours(),
 | 
				
			||||||
 | 
					      Math.floor(now.getMinutes() / 1) * 1 + 1, 0, 0);
 | 
				
			||||||
 | 
					    const difference = nextInterval.getTime() - now.getTime();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setTimeout(() => {
 | 
				
			||||||
 | 
					      this.runStatistics();
 | 
				
			||||||
 | 
					      this.intervalTimer = setInterval(() => {
 | 
				
			||||||
 | 
					        this.runStatistics();
 | 
				
			||||||
 | 
					      }, 1 * 60 * 1000);
 | 
				
			||||||
 | 
					    }, difference);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async runStatistics(): Promise<void> {
 | 
				
			||||||
 | 
					    if (!memPool.isInSync()) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const currentMempool = memPool.getMempool();
 | 
				
			||||||
 | 
					    const txPerSecond = memPool.getTxPerSecond();
 | 
				
			||||||
 | 
					    const vBytesPerSecond = memPool.getVBytesPerSecond();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    logger.debug('Running statistics');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let memPoolArray: TransactionExtended[] = [];
 | 
				
			||||||
 | 
					    for (const i in currentMempool) {
 | 
				
			||||||
 | 
					      if (currentMempool.hasOwnProperty(i)) {
 | 
				
			||||||
 | 
					        memPoolArray.push(currentMempool[i]);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // Remove 0 and undefined
 | 
				
			||||||
 | 
					    memPoolArray = memPoolArray.filter((tx) => tx.effectiveFeePerVsize);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!memPoolArray.length) {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        const insertIdZeroed = await statisticsApi.$createZeroedStatistic();
 | 
				
			||||||
 | 
					        if (this.newStatisticsEntryCallback && insertIdZeroed) {
 | 
				
			||||||
 | 
					          const newStats = await statisticsApi.$get(insertIdZeroed);
 | 
				
			||||||
 | 
					          if (newStats) {
 | 
				
			||||||
 | 
					            this.newStatisticsEntryCallback(newStats);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } catch (e) {
 | 
				
			||||||
 | 
					        logger.err('Unable to insert zeroed statistics. ' + e);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    memPoolArray.sort((a, b) => a.effectiveFeePerVsize - b.effectiveFeePerVsize);
 | 
				
			||||||
 | 
					    const totalWeight = memPoolArray.map((tx) => tx.vsize).reduce((acc, curr) => acc + curr) * 4;
 | 
				
			||||||
 | 
					    const totalFee = memPoolArray.map((tx) => tx.fee).reduce((acc, curr) => acc + curr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const logFees = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200,
 | 
				
			||||||
 | 
					      250, 300, 350, 400, 500, 600, 700, 800, 900, 1000, 1200, 1400, 1600, 1800, 2000];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const weightVsizeFees: { [feePerWU: number]: number } = {};
 | 
				
			||||||
 | 
					    const lastItem = logFees.length - 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    memPoolArray.forEach((transaction) => {
 | 
				
			||||||
 | 
					      for (let i = 0; i < logFees.length; i++) {
 | 
				
			||||||
 | 
					        if (
 | 
				
			||||||
 | 
					          (Common.isLiquid() && (i === lastItem || transaction.effectiveFeePerVsize * 10 < logFees[i + 1]))
 | 
				
			||||||
 | 
					          ||
 | 
				
			||||||
 | 
					          (!Common.isLiquid() && (i === lastItem || transaction.effectiveFeePerVsize < logFees[i + 1]))
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					          if (weightVsizeFees[logFees[i]]) {
 | 
				
			||||||
 | 
					            weightVsizeFees[logFees[i]] += transaction.vsize;
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            weightVsizeFees[logFees[i]] = transaction.vsize;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const insertId = await statisticsApi.$create({
 | 
				
			||||||
 | 
					        added: 'NOW()',
 | 
				
			||||||
 | 
					        unconfirmed_transactions: memPoolArray.length,
 | 
				
			||||||
 | 
					        tx_per_second: txPerSecond,
 | 
				
			||||||
 | 
					        vbytes_per_second: Math.round(vBytesPerSecond),
 | 
				
			||||||
 | 
					        mempool_byte_weight: totalWeight,
 | 
				
			||||||
 | 
					        total_fee: totalFee,
 | 
				
			||||||
 | 
					        fee_data: '',
 | 
				
			||||||
 | 
					        vsize_1: weightVsizeFees['1'] || 0,
 | 
				
			||||||
 | 
					        vsize_2: weightVsizeFees['2'] || 0,
 | 
				
			||||||
 | 
					        vsize_3: weightVsizeFees['3'] || 0,
 | 
				
			||||||
 | 
					        vsize_4: weightVsizeFees['4'] || 0,
 | 
				
			||||||
 | 
					        vsize_5: weightVsizeFees['5'] || 0,
 | 
				
			||||||
 | 
					        vsize_6: weightVsizeFees['6'] || 0,
 | 
				
			||||||
 | 
					        vsize_8: weightVsizeFees['8'] || 0,
 | 
				
			||||||
 | 
					        vsize_10: weightVsizeFees['10'] || 0,
 | 
				
			||||||
 | 
					        vsize_12: weightVsizeFees['12'] || 0,
 | 
				
			||||||
 | 
					        vsize_15: weightVsizeFees['15'] || 0,
 | 
				
			||||||
 | 
					        vsize_20: weightVsizeFees['20'] || 0,
 | 
				
			||||||
 | 
					        vsize_30: weightVsizeFees['30'] || 0,
 | 
				
			||||||
 | 
					        vsize_40: weightVsizeFees['40'] || 0,
 | 
				
			||||||
 | 
					        vsize_50: weightVsizeFees['50'] || 0,
 | 
				
			||||||
 | 
					        vsize_60: weightVsizeFees['60'] || 0,
 | 
				
			||||||
 | 
					        vsize_70: weightVsizeFees['70'] || 0,
 | 
				
			||||||
 | 
					        vsize_80: weightVsizeFees['80'] || 0,
 | 
				
			||||||
 | 
					        vsize_90: weightVsizeFees['90'] || 0,
 | 
				
			||||||
 | 
					        vsize_100: weightVsizeFees['100'] || 0,
 | 
				
			||||||
 | 
					        vsize_125: weightVsizeFees['125'] || 0,
 | 
				
			||||||
 | 
					        vsize_150: weightVsizeFees['150'] || 0,
 | 
				
			||||||
 | 
					        vsize_175: weightVsizeFees['175'] || 0,
 | 
				
			||||||
 | 
					        vsize_200: weightVsizeFees['200'] || 0,
 | 
				
			||||||
 | 
					        vsize_250: weightVsizeFees['250'] || 0,
 | 
				
			||||||
 | 
					        vsize_300: weightVsizeFees['300'] || 0,
 | 
				
			||||||
 | 
					        vsize_350: weightVsizeFees['350'] || 0,
 | 
				
			||||||
 | 
					        vsize_400: weightVsizeFees['400'] || 0,
 | 
				
			||||||
 | 
					        vsize_500: weightVsizeFees['500'] || 0,
 | 
				
			||||||
 | 
					        vsize_600: weightVsizeFees['600'] || 0,
 | 
				
			||||||
 | 
					        vsize_700: weightVsizeFees['700'] || 0,
 | 
				
			||||||
 | 
					        vsize_800: weightVsizeFees['800'] || 0,
 | 
				
			||||||
 | 
					        vsize_900: weightVsizeFees['900'] || 0,
 | 
				
			||||||
 | 
					        vsize_1000: weightVsizeFees['1000'] || 0,
 | 
				
			||||||
 | 
					        vsize_1200: weightVsizeFees['1200'] || 0,
 | 
				
			||||||
 | 
					        vsize_1400: weightVsizeFees['1400'] || 0,
 | 
				
			||||||
 | 
					        vsize_1600: weightVsizeFees['1600'] || 0,
 | 
				
			||||||
 | 
					        vsize_1800: weightVsizeFees['1800'] || 0,
 | 
				
			||||||
 | 
					        vsize_2000: weightVsizeFees['2000'] || 0,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (this.newStatisticsEntryCallback && insertId) {
 | 
				
			||||||
 | 
					        const newStats = await statisticsApi.$get(insertId);
 | 
				
			||||||
 | 
					        if (newStats) {
 | 
				
			||||||
 | 
					          this.newStatisticsEntryCallback(newStats);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      logger.err('Unable to insert statistics. ' + e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default new Statistics();
 | 
				
			||||||
@ -3,15 +3,12 @@ import { Application, Request, Response, NextFunction } from 'express';
 | 
				
			|||||||
import * as http from 'http';
 | 
					import * as http from 'http';
 | 
				
			||||||
import * as WebSocket from 'ws';
 | 
					import * as WebSocket from 'ws';
 | 
				
			||||||
import cluster from 'cluster';
 | 
					import cluster from 'cluster';
 | 
				
			||||||
import axios from 'axios';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import DB from './database';
 | 
					import DB from './database';
 | 
				
			||||||
import config from './config';
 | 
					import config from './config';
 | 
				
			||||||
import routes from './routes';
 | 
					 | 
				
			||||||
import blocks from './api/blocks';
 | 
					import blocks from './api/blocks';
 | 
				
			||||||
import memPool from './api/mempool';
 | 
					import memPool from './api/mempool';
 | 
				
			||||||
import diskCache from './api/disk-cache';
 | 
					import diskCache from './api/disk-cache';
 | 
				
			||||||
import statistics from './api/statistics';
 | 
					import statistics from './api/statistics/statistics';
 | 
				
			||||||
import websocketHandler from './api/websocket-handler';
 | 
					import websocketHandler from './api/websocket-handler';
 | 
				
			||||||
import fiatConversion from './api/fiat-conversion';
 | 
					import fiatConversion from './api/fiat-conversion';
 | 
				
			||||||
import bisq from './api/bisq/bisq';
 | 
					import bisq from './api/bisq/bisq';
 | 
				
			||||||
@ -33,7 +30,11 @@ import channelsRoutes from './api/explorer/channels.routes';
 | 
				
			|||||||
import generalLightningRoutes from './api/explorer/general.routes';
 | 
					import generalLightningRoutes from './api/explorer/general.routes';
 | 
				
			||||||
import lightningStatsUpdater from './tasks/lightning/stats-updater.service';
 | 
					import lightningStatsUpdater from './tasks/lightning/stats-updater.service';
 | 
				
			||||||
import nodeSyncService from './tasks/lightning/node-sync.service';
 | 
					import nodeSyncService from './tasks/lightning/node-sync.service';
 | 
				
			||||||
import BlocksAuditsRepository from './repositories/BlocksAuditsRepository';
 | 
					import statisticsRoutes from "./api/statistics/statistics.routes";
 | 
				
			||||||
 | 
					import miningRoutes from "./api/mining/mining-routes";
 | 
				
			||||||
 | 
					import bisqRoutes from "./api/bisq/bisq.routes";
 | 
				
			||||||
 | 
					import liquidRoutes from "./api/liquid/liquid.routes";
 | 
				
			||||||
 | 
					import bitcoinRoutes from "./api/bitcoin/bitcoin.routes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Server {
 | 
					class Server {
 | 
				
			||||||
  private wss: WebSocket.Server | undefined;
 | 
					  private wss: WebSocket.Server | undefined;
 | 
				
			||||||
@ -206,173 +207,19 @@ class Server {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  setUpHttpApiRoutes() {
 | 
					  setUpHttpApiRoutes() {
 | 
				
			||||||
    this.app
 | 
					    bitcoinRoutes.initRoutes(this.app);
 | 
				
			||||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'transaction-times', routes.getTransactionTimes)
 | 
					 | 
				
			||||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'outspends', routes.$getBatchedOutspends)
 | 
					 | 
				
			||||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'cpfp/:txId', routes.getCpfpInfo)
 | 
					 | 
				
			||||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'difficulty-adjustment', routes.getDifficultyChange)
 | 
					 | 
				
			||||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'fees/recommended', routes.getRecommendedFees)
 | 
					 | 
				
			||||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'fees/mempool-blocks', routes.getMempoolBlocks)
 | 
					 | 
				
			||||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'backend-info', routes.getBackendInfo)
 | 
					 | 
				
			||||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'init-data', routes.getInitData)
 | 
					 | 
				
			||||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'validate-address/:address', routes.validateAddress)
 | 
					 | 
				
			||||||
      .post(config.MEMPOOL.API_URL_PREFIX + 'tx/push', routes.$postTransactionForm)
 | 
					 | 
				
			||||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'donations', async (req, res) => {
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
          const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/donations`, { responseType: 'stream', timeout: 10000 });
 | 
					 | 
				
			||||||
          response.data.pipe(res);
 | 
					 | 
				
			||||||
        } catch (e) {
 | 
					 | 
				
			||||||
          res.status(500).end();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'donations/images/:id', async (req, res) => {
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
          const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/donations/images/${req.params.id}`, {
 | 
					 | 
				
			||||||
            responseType: 'stream', timeout: 10000
 | 
					 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
          response.data.pipe(res);
 | 
					 | 
				
			||||||
        } catch (e) {
 | 
					 | 
				
			||||||
          res.status(500).end();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'contributors', async (req, res) => {
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
          const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/contributors`, { responseType: 'stream', timeout: 10000 });
 | 
					 | 
				
			||||||
          response.data.pipe(res);
 | 
					 | 
				
			||||||
        } catch (e) {
 | 
					 | 
				
			||||||
          res.status(500).end();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'contributors/images/:id', async (req, res) => {
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
          const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/contributors/images/${req.params.id}`, {
 | 
					 | 
				
			||||||
            responseType: 'stream', timeout: 10000
 | 
					 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
          response.data.pipe(res);
 | 
					 | 
				
			||||||
        } catch (e) {
 | 
					 | 
				
			||||||
          res.status(500).end();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'translators', async (req, res) => {
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
          const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/translators`, { responseType: 'stream', timeout: 10000 });
 | 
					 | 
				
			||||||
          response.data.pipe(res);
 | 
					 | 
				
			||||||
        } catch (e) {
 | 
					 | 
				
			||||||
          res.status(500).end();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'translators/images/:id', async (req, res) => {
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
          const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/translators/images/${req.params.id}`, {
 | 
					 | 
				
			||||||
            responseType: 'stream', timeout: 10000
 | 
					 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
          response.data.pipe(res);
 | 
					 | 
				
			||||||
        } catch (e) {
 | 
					 | 
				
			||||||
          res.status(500).end();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      ;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (config.STATISTICS.ENABLED && config.DATABASE.ENABLED) {
 | 
					    if (config.STATISTICS.ENABLED && config.DATABASE.ENABLED) {
 | 
				
			||||||
      this.app
 | 
					      statisticsRoutes.initRoutes(this.app);
 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/2h', routes.$getStatisticsByTime.bind(routes, '2h'))
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/24h', routes.$getStatisticsByTime.bind(routes, '24h'))
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/1w', routes.$getStatisticsByTime.bind(routes, '1w'))
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/1m', routes.$getStatisticsByTime.bind(routes, '1m'))
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/3m', routes.$getStatisticsByTime.bind(routes, '3m'))
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/6m', routes.$getStatisticsByTime.bind(routes, '6m'))
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/1y', routes.$getStatisticsByTime.bind(routes, '1y'))
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/2y', routes.$getStatisticsByTime.bind(routes, '2y'))
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/3y', routes.$getStatisticsByTime.bind(routes, '3y'))
 | 
					 | 
				
			||||||
        ;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (Common.indexingEnabled()) {
 | 
					    if (Common.indexingEnabled()) {
 | 
				
			||||||
      this.app
 | 
					      miningRoutes.initRoutes(this.app);
 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/:interval', routes.$getPools)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:slug/hashrate', routes.$getPoolHistoricalHashrate)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:slug/blocks', routes.$getPoolBlocks)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:slug/blocks/:height', routes.$getPoolBlocks)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:slug', routes.$getPool)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'mining/hashrate/pools/:interval', routes.$getPoolsHistoricalHashrate)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'mining/hashrate/:interval', routes.$getHistoricalHashrate)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty-adjustments', routes.$getDifficultyAdjustments)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'mining/reward-stats/:blockCount', routes.$getRewardStats)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/fees/:interval', routes.$getHistoricalBlockFees)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/rewards/:interval', routes.$getHistoricalBlockRewards)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/fee-rates/:interval', routes.$getHistoricalBlockFeeRates)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/sizes-weights/:interval', routes.$getHistoricalBlockSizeAndWeight)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty-adjustments/:interval', routes.$getDifficultyAdjustments)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/predictions/:interval', routes.$getHistoricalBlockPrediction)
 | 
					 | 
				
			||||||
        ;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (config.BISQ.ENABLED) {
 | 
					    if (config.BISQ.ENABLED) {
 | 
				
			||||||
      this.app
 | 
					      bisqRoutes.initRoutes(this.app);
 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'bisq/stats', routes.getBisqStats)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'bisq/tx/:txId', routes.getBisqTransaction)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'bisq/block/:hash', routes.getBisqBlock)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'bisq/blocks/tip/height', routes.getBisqTip)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'bisq/blocks/:index/:length', routes.getBisqBlocks)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'bisq/address/:address', routes.getBisqAddress)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'bisq/txs/:index/:length', routes.getBisqTransactions)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/currencies', routes.getBisqMarketCurrencies.bind(routes))
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/depth', routes.getBisqMarketDepth.bind(routes))
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/hloc', routes.getBisqMarketHloc.bind(routes))
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/markets', routes.getBisqMarketMarkets.bind(routes))
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/offers', routes.getBisqMarketOffers.bind(routes))
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/ticker', routes.getBisqMarketTicker.bind(routes))
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/trades', routes.getBisqMarketTrades.bind(routes))
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/volumes', routes.getBisqMarketVolumes.bind(routes))
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/volumes/7d', routes.getBisqMarketVolumes7d.bind(routes))
 | 
					 | 
				
			||||||
        ;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.app
 | 
					 | 
				
			||||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'blocks', routes.getBlocks.bind(routes))
 | 
					 | 
				
			||||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'blocks/:height', routes.getBlocks.bind(routes))
 | 
					 | 
				
			||||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash', routes.getBlock)
 | 
					 | 
				
			||||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/summary', routes.getStrippedBlockTransactions);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (config.MEMPOOL.BACKEND !== 'esplora') {
 | 
					 | 
				
			||||||
      this.app
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'mempool', routes.getMempool)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'mempool/txids', routes.getMempoolTxIds)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'mempool/recent', routes.getRecentMempoolTransactions)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId', routes.getTransaction)
 | 
					 | 
				
			||||||
        .post(config.MEMPOOL.API_URL_PREFIX + 'tx', routes.$postTransaction)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/hex', routes.getRawTransaction)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/status', routes.getTransactionStatus)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/outspends', routes.getTransactionOutspends)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/header', routes.getBlockHeader)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'blocks/tip/height', routes.getBlockTipHeight)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'blocks/tip/hash', routes.getBlockTipHash)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/txs', routes.getBlockTransactions)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/txs/:index', routes.getBlockTransactions)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/txids', routes.getTxIdsForBlock)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'block-height/:height', routes.getBlockHeight)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'address/:address', routes.getAddress)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'address/:address/txs', routes.getAddressTransactions)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'address/:address/txs/chain/:txId', routes.getAddressTransactions)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'address-prefix/:prefix', routes.getAddressPrefix)
 | 
					 | 
				
			||||||
        ;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (Common.isLiquid()) {
 | 
					    if (Common.isLiquid()) {
 | 
				
			||||||
      this.app
 | 
					      liquidRoutes.initRoutes(this.app);
 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'assets/icons', routes.getAllLiquidIcon)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'assets/featured', routes.$getAllFeaturedLiquidAssets)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'asset/:assetId/icon', routes.getLiquidIcon)
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'assets/group/:id', routes.$getAssetGroup)
 | 
					 | 
				
			||||||
        ;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (Common.isLiquid() && config.DATABASE.ENABLED) {
 | 
					 | 
				
			||||||
      this.app
 | 
					 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'liquid/pegs/month', routes.$getElementsPegsByMonth)
 | 
					 | 
				
			||||||
        ;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (config.LIGHTNING.ENABLED) {
 | 
					    if (config.LIGHTNING.ENABLED) {
 | 
				
			||||||
      generalLightningRoutes.initRoutes(this.app);
 | 
					      generalLightningRoutes.initRoutes(this.app);
 | 
				
			||||||
      nodesRoutes.initRoutes(this.app);
 | 
					      nodesRoutes.initRoutes(this.app);
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
import { Common } from './api/common';
 | 
					import { Common } from './api/common';
 | 
				
			||||||
import blocks from './api/blocks';
 | 
					import blocks from './api/blocks';
 | 
				
			||||||
import mempool from './api/mempool';
 | 
					import mempool from './api/mempool';
 | 
				
			||||||
import mining from './api/mining';
 | 
					import mining from './api/mining/mining';
 | 
				
			||||||
import logger from './logger';
 | 
					import logger from './logger';
 | 
				
			||||||
import HashratesRepository from './repositories/HashratesRepository';
 | 
					import HashratesRepository from './repositories/HashratesRepository';
 | 
				
			||||||
import bitcoinClient from './api/bitcoin/bitcoin-client';
 | 
					import bitcoinClient from './api/bitcoin/bitcoin-client';
 | 
				
			||||||
 | 
				
			|||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user