commit
						c1c0521ab4
					
				@ -15,7 +15,9 @@
 | 
			
		||||
  "TX_PER_SECOND_SPAN_SECONDS": 150,
 | 
			
		||||
  "ELECTRS_API_URL": "http://localhost:50001",
 | 
			
		||||
  "BISQ_ENABLED": false,
 | 
			
		||||
  "BSQ_BLOCKS_DATA_PATH": "/bisq/data",
 | 
			
		||||
  "BISQ_BLOCKS_DATA_PATH": "/bisq/seednode-data/btc_mainnet/db/json",
 | 
			
		||||
  "BISQ_MARKET_ENABLED": false,
 | 
			
		||||
  "BISQ_MARKETS_DATA_PATH": "/bisq/seednode-data/btc_mainnet/db",
 | 
			
		||||
  "SSL": false,
 | 
			
		||||
  "SSL_CERT_FILE_PATH": "/etc/letsencrypt/live/mysite/fullchain.pem",
 | 
			
		||||
  "SSL_KEY_FILE_PATH": "/etc/letsencrypt/live/mysite/privkey.pem"
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										18
									
								
								backend/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										18
									
								
								backend/package-lock.json
									
									
									
										generated
									
									
									
								
							@ -86,6 +86,11 @@
 | 
			
		||||
        "@types/range-parser": "*"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "@types/locutus": {
 | 
			
		||||
      "version": "0.0.6",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/locutus/-/locutus-0.0.6.tgz",
 | 
			
		||||
      "integrity": "sha512-P+BQds4wrJhqKiIOBWAYpbsE9UOztnnqW9zHk4Bci7kCXjEQAA7FJrD9HX5JU2Z36fhE2WDctuuIpLvqDsciWQ=="
 | 
			
		||||
    },
 | 
			
		||||
    "@types/mime": {
 | 
			
		||||
      "version": "2.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz",
 | 
			
		||||
@ -446,6 +451,11 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
 | 
			
		||||
      "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
 | 
			
		||||
    },
 | 
			
		||||
    "es6-promise": {
 | 
			
		||||
      "version": "4.2.8",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
 | 
			
		||||
      "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
 | 
			
		||||
    },
 | 
			
		||||
    "escape-html": {
 | 
			
		||||
      "version": "1.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
 | 
			
		||||
@ -733,6 +743,14 @@
 | 
			
		||||
        "verror": "1.10.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "locutus": {
 | 
			
		||||
      "version": "2.0.12",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/locutus/-/locutus-2.0.12.tgz",
 | 
			
		||||
      "integrity": "sha512-wnzhY9xOdDb2djr17kQhTh9oZgEfp78zI27KRRiiV1GnPXWA2xfVODbpH3QgpIuUMLupM02+6X/rJXvktTpnoA==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "es6-promise": "^4.2.5"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "long": {
 | 
			
		||||
      "version": "4.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
 | 
			
		||||
 | 
			
		||||
@ -29,6 +29,7 @@
 | 
			
		||||
    "express": "^4.17.1",
 | 
			
		||||
    "mysql2": "^1.6.1",
 | 
			
		||||
    "request": "^2.88.2",
 | 
			
		||||
    "locutus": "^2.0.12",
 | 
			
		||||
    "ws": "^7.3.1"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
@ -36,6 +37,7 @@
 | 
			
		||||
    "@types/express": "^4.17.2",
 | 
			
		||||
    "@types/request": "^2.48.2",
 | 
			
		||||
    "@types/ws": "^6.0.4",
 | 
			
		||||
    "@types/locutus": "^0.0.6",
 | 
			
		||||
    "tslint": "~6.1.0",
 | 
			
		||||
    "typescript": "~3.9.7"
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,8 @@
 | 
			
		||||
const config = require('../../mempool-config.json');
 | 
			
		||||
const config = require('../../../mempool-config.json');
 | 
			
		||||
import * as fs from 'fs';
 | 
			
		||||
import * as request from 'request';
 | 
			
		||||
import { BisqBlocks, BisqBlock, BisqTransaction, BisqStats, BisqTrade } from '../interfaces';
 | 
			
		||||
import { Common } from './common';
 | 
			
		||||
import { BisqBlocks, BisqBlock, BisqTransaction, BisqStats, BisqTrade } from './interfaces';
 | 
			
		||||
import { Common } from '../common';
 | 
			
		||||
 | 
			
		||||
class Bisq {
 | 
			
		||||
  private static BLOCKS_JSON_FILE_PATH = '/all/blocks.json';
 | 
			
		||||
@ -72,8 +72,8 @@ class Bisq {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private checkForBisqDataFolder() {
 | 
			
		||||
    if (!fs.existsSync(config.BSQ_BLOCKS_DATA_PATH + Bisq.BLOCKS_JSON_FILE_PATH)) {
 | 
			
		||||
      console.log(config.BSQ_BLOCKS_DATA_PATH + Bisq.BLOCKS_JSON_FILE_PATH + ` doesn't exist. Make sure Bisq is running and the config is correct before starting the server.`);
 | 
			
		||||
    if (!fs.existsSync(config.BISQ_BLOCKS_DATA_PATH + Bisq.BLOCKS_JSON_FILE_PATH)) {
 | 
			
		||||
      console.log(config.BISQ_BLOCKS_DATA_PATH + Bisq.BLOCKS_JSON_FILE_PATH + ` doesn't exist. Make sure Bisq is running and the config is correct before starting the server.`);
 | 
			
		||||
      return process.exit(1);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@ -83,7 +83,7 @@ class Bisq {
 | 
			
		||||
      this.topDirectoryWatcher.close();
 | 
			
		||||
    }
 | 
			
		||||
    let fsWait: NodeJS.Timeout | null = null;
 | 
			
		||||
    this.topDirectoryWatcher = fs.watch(config.BSQ_BLOCKS_DATA_PATH, () => {
 | 
			
		||||
    this.topDirectoryWatcher = fs.watch(config.BISQ_BLOCKS_DATA_PATH, () => {
 | 
			
		||||
      if (fsWait) {
 | 
			
		||||
        clearTimeout(fsWait);
 | 
			
		||||
      }
 | 
			
		||||
@ -105,13 +105,13 @@ class Bisq {
 | 
			
		||||
    if (this.subdirectoryWatcher) {
 | 
			
		||||
      this.subdirectoryWatcher.close();
 | 
			
		||||
    }
 | 
			
		||||
    if (!fs.existsSync(config.BSQ_BLOCKS_DATA_PATH + Bisq.BLOCKS_JSON_FILE_PATH)) {
 | 
			
		||||
      console.log(config.BSQ_BLOCKS_DATA_PATH + Bisq.BLOCKS_JSON_FILE_PATH + ` doesn't exist. Trying to restart sub directory watcher again in 3 minutes.`);
 | 
			
		||||
    if (!fs.existsSync(config.BISQ_BLOCKS_DATA_PATH + Bisq.BLOCKS_JSON_FILE_PATH)) {
 | 
			
		||||
      console.log(config.BISQ_BLOCKS_DATA_PATH + Bisq.BLOCKS_JSON_FILE_PATH + ` doesn't exist. Trying to restart sub directory watcher again in 3 minutes.`);
 | 
			
		||||
      setTimeout(() => this.startSubDirectoryWatcher(), 180000);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    let fsWait: NodeJS.Timeout | null = null;
 | 
			
		||||
    this.subdirectoryWatcher = fs.watch(config.BSQ_BLOCKS_DATA_PATH + '/all', () => {
 | 
			
		||||
    this.subdirectoryWatcher = fs.watch(config.BISQ_BLOCKS_DATA_PATH + '/all', () => {
 | 
			
		||||
      if (fsWait) {
 | 
			
		||||
        clearTimeout(fsWait);
 | 
			
		||||
      }
 | 
			
		||||
@ -243,10 +243,10 @@ class Bisq {
 | 
			
		||||
 | 
			
		||||
  private loadData(): Promise<string> {
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
      if (!fs.existsSync(config.BSQ_BLOCKS_DATA_PATH + Bisq.BLOCKS_JSON_FILE_PATH)) {
 | 
			
		||||
      if (!fs.existsSync(config.BISQ_BLOCKS_DATA_PATH + Bisq.BLOCKS_JSON_FILE_PATH)) {
 | 
			
		||||
        return reject(Bisq.BLOCKS_JSON_FILE_PATH + ` doesn't exist`);
 | 
			
		||||
      }
 | 
			
		||||
      fs.readFile(config.BSQ_BLOCKS_DATA_PATH + Bisq.BLOCKS_JSON_FILE_PATH, 'utf8', (err, data) => {
 | 
			
		||||
      fs.readFile(config.BISQ_BLOCKS_DATA_PATH + Bisq.BLOCKS_JSON_FILE_PATH, 'utf8', (err, data) => {
 | 
			
		||||
        if (err) {
 | 
			
		||||
          reject(err);
 | 
			
		||||
        }
 | 
			
		||||
							
								
								
									
										258
									
								
								backend/src/api/bisq/interfaces.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										258
									
								
								backend/src/api/bisq/interfaces.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,258 @@
 | 
			
		||||
 | 
			
		||||
export interface BisqBlocks {
 | 
			
		||||
  chainHeight: number;
 | 
			
		||||
  blocks: BisqBlock[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface BisqBlock {
 | 
			
		||||
  height: number;
 | 
			
		||||
  time: number;
 | 
			
		||||
  hash: string;
 | 
			
		||||
  previousBlockHash: string;
 | 
			
		||||
  txs: BisqTransaction[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface BisqTransaction {
 | 
			
		||||
  txVersion: string;
 | 
			
		||||
  id: string;
 | 
			
		||||
  blockHeight: number;
 | 
			
		||||
  blockHash: string;
 | 
			
		||||
  time: number;
 | 
			
		||||
  inputs: BisqInput[];
 | 
			
		||||
  outputs: BisqOutput[];
 | 
			
		||||
  txType: string;
 | 
			
		||||
  txTypeDisplayString: string;
 | 
			
		||||
  burntFee: number;
 | 
			
		||||
  invalidatedBsq: number;
 | 
			
		||||
  unlockBlockHeight: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface BisqStats {
 | 
			
		||||
  minted: number;
 | 
			
		||||
  burnt: number;
 | 
			
		||||
  addresses: number;
 | 
			
		||||
  unspent_txos: number;
 | 
			
		||||
  spent_txos: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface BisqInput {
 | 
			
		||||
  spendingTxOutputIndex: number;
 | 
			
		||||
  spendingTxId: string;
 | 
			
		||||
  bsqAmount: number;
 | 
			
		||||
  isVerified: boolean;
 | 
			
		||||
  address: string;
 | 
			
		||||
  time: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface BisqOutput {
 | 
			
		||||
  txVersion: string;
 | 
			
		||||
  txId: string;
 | 
			
		||||
  index: number;
 | 
			
		||||
  bsqAmount: number;
 | 
			
		||||
  btcAmount: number;
 | 
			
		||||
  height: number;
 | 
			
		||||
  isVerified: boolean;
 | 
			
		||||
  burntFee: number;
 | 
			
		||||
  invalidatedBsq: number;
 | 
			
		||||
  address: string;
 | 
			
		||||
  scriptPubKey: BisqScriptPubKey;
 | 
			
		||||
  time: any;
 | 
			
		||||
  txType: string;
 | 
			
		||||
  txTypeDisplayString: string;
 | 
			
		||||
  txOutputType: string;
 | 
			
		||||
  txOutputTypeDisplayString: string;
 | 
			
		||||
  lockTime: number;
 | 
			
		||||
  isUnspent: boolean;
 | 
			
		||||
  spentInfo: SpentInfo;
 | 
			
		||||
  opReturn?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface BisqScriptPubKey {
 | 
			
		||||
  addresses: string[];
 | 
			
		||||
  asm: string;
 | 
			
		||||
  hex: string;
 | 
			
		||||
  reqSigs: number;
 | 
			
		||||
  type: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface SpentInfo {
 | 
			
		||||
  height: number;
 | 
			
		||||
  inputIndex: number;
 | 
			
		||||
  txId: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface BisqTrade {
 | 
			
		||||
  direction: string;
 | 
			
		||||
  price: string;
 | 
			
		||||
  amount: string;
 | 
			
		||||
  volume: string;
 | 
			
		||||
  payment_method: string;
 | 
			
		||||
  trade_id: string;
 | 
			
		||||
  trade_date: number;
 | 
			
		||||
  market?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface Currencies { [txid: string]: Currency; }
 | 
			
		||||
 | 
			
		||||
export interface Currency {
 | 
			
		||||
  code: string;
 | 
			
		||||
  name: string;
 | 
			
		||||
  precision: number;
 | 
			
		||||
 | 
			
		||||
  _type: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface Depth { [market: string]: Market; }
 | 
			
		||||
 | 
			
		||||
interface Market {
 | 
			
		||||
  'buys': string[];
 | 
			
		||||
  'sells': string[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface HighLowOpenClose {
 | 
			
		||||
  period_start: number;
 | 
			
		||||
  open: string;
 | 
			
		||||
  high: string;
 | 
			
		||||
  low: string;
 | 
			
		||||
  close: string;
 | 
			
		||||
  volume_left: string;
 | 
			
		||||
  volume_right: string;
 | 
			
		||||
  avg: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface Markets { [txid: string]: Pair; }
 | 
			
		||||
 | 
			
		||||
interface Pair {
 | 
			
		||||
  pair: string;
 | 
			
		||||
  lname: string;
 | 
			
		||||
  rname: string;
 | 
			
		||||
  lsymbol: string;
 | 
			
		||||
  rsymbol: string;
 | 
			
		||||
  lprecision: number;
 | 
			
		||||
  rprecision: number;
 | 
			
		||||
  ltype: string;
 | 
			
		||||
  rtype: string;
 | 
			
		||||
  name: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface Offers { [market: string]: OffersMarket; }
 | 
			
		||||
 | 
			
		||||
interface OffersMarket {
 | 
			
		||||
  buys: Offer[] | null;
 | 
			
		||||
  sells: Offer[] | null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface OffsersData {
 | 
			
		||||
  direction: string;
 | 
			
		||||
  currencyCode: string;
 | 
			
		||||
  minAmount: number;
 | 
			
		||||
  amount: number;
 | 
			
		||||
  price: number;
 | 
			
		||||
  date: number;
 | 
			
		||||
  useMarketBasedPrice: boolean;
 | 
			
		||||
  marketPriceMargin: number;
 | 
			
		||||
  paymentMethod: string;
 | 
			
		||||
  id: string;
 | 
			
		||||
  currencyPair: string;
 | 
			
		||||
  primaryMarketDirection: string;
 | 
			
		||||
  priceDisplayString: string;
 | 
			
		||||
  primaryMarketAmountDisplayString: string;
 | 
			
		||||
  primaryMarketMinAmountDisplayString: string;
 | 
			
		||||
  primaryMarketVolumeDisplayString: string;
 | 
			
		||||
  primaryMarketMinVolumeDisplayString: string;
 | 
			
		||||
  primaryMarketPrice: number;
 | 
			
		||||
  primaryMarketAmount: number;
 | 
			
		||||
  primaryMarketMinAmount: number;
 | 
			
		||||
  primaryMarketVolume: number;
 | 
			
		||||
  primaryMarketMinVolume: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface Offer {
 | 
			
		||||
  offer_id: string;
 | 
			
		||||
  offer_date: number;
 | 
			
		||||
  direction: string;
 | 
			
		||||
  min_amount: string;
 | 
			
		||||
  amount: string;
 | 
			
		||||
  price: string;
 | 
			
		||||
  volume: string;
 | 
			
		||||
  payment_method: string;
 | 
			
		||||
  offer_fee_txid: any;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface Tickers { [market: string]: Ticker | null; }
 | 
			
		||||
 | 
			
		||||
export interface Ticker {
 | 
			
		||||
  last: string;
 | 
			
		||||
  high: string;
 | 
			
		||||
  low: string;
 | 
			
		||||
  volume_left: string;
 | 
			
		||||
  volume_right: string;
 | 
			
		||||
  buy: string | null;
 | 
			
		||||
  sell: string | null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface Trade {
 | 
			
		||||
  direction: string;
 | 
			
		||||
  price: string;
 | 
			
		||||
  amount: string;
 | 
			
		||||
  volume: string;
 | 
			
		||||
  payment_method: string;
 | 
			
		||||
  trade_id: string;
 | 
			
		||||
  trade_date: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface TradesData {
 | 
			
		||||
  currency: string;
 | 
			
		||||
  direction: string;
 | 
			
		||||
  tradePrice: number;
 | 
			
		||||
  tradeAmount: number;
 | 
			
		||||
  tradeDate: number;
 | 
			
		||||
  paymentMethod: string;
 | 
			
		||||
  offerDate: number;
 | 
			
		||||
  useMarketBasedPrice: boolean;
 | 
			
		||||
  marketPriceMargin: number;
 | 
			
		||||
  offerAmount: number;
 | 
			
		||||
  offerMinAmount: number;
 | 
			
		||||
  offerId: string;
 | 
			
		||||
  depositTxId?: string;
 | 
			
		||||
  currencyPair: string;
 | 
			
		||||
  primaryMarketDirection: string;
 | 
			
		||||
  primaryMarketTradePrice: number;
 | 
			
		||||
  primaryMarketTradeAmount: number;
 | 
			
		||||
  primaryMarketTradeVolume: number;
 | 
			
		||||
 | 
			
		||||
  _market: string;
 | 
			
		||||
  _tradePriceStr: string;
 | 
			
		||||
  _tradeAmountStr: string;
 | 
			
		||||
  _tradeVolumeStr: string;
 | 
			
		||||
  _offerAmountStr: string;
 | 
			
		||||
  _tradePrice: number;
 | 
			
		||||
  _tradeAmount: number;
 | 
			
		||||
  _tradeVolume: number;
 | 
			
		||||
  _offerAmount: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface MarketVolume {
 | 
			
		||||
  period_start: number;
 | 
			
		||||
  num_trades: number;
 | 
			
		||||
  volume: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface MarketsApiError {
 | 
			
		||||
  success: number;
 | 
			
		||||
  error: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type Interval = 'minute' | 'half_hour' | 'hour' | 'half_day' | 'day' | 'week' | 'month' | 'year' | 'auto';
 | 
			
		||||
 | 
			
		||||
export interface SummarizedIntervals { [market: string]: SummarizedInterval; }
 | 
			
		||||
export interface SummarizedInterval {
 | 
			
		||||
  'period_start': number;
 | 
			
		||||
  'open': number;
 | 
			
		||||
  'close': number;
 | 
			
		||||
  'high': number;
 | 
			
		||||
  'low': number;
 | 
			
		||||
  'avg': number;
 | 
			
		||||
  'volume_right': number;
 | 
			
		||||
  'volume_left': number;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										611
									
								
								backend/src/api/bisq/markets-api.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										611
									
								
								backend/src/api/bisq/markets-api.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,611 @@
 | 
			
		||||
import { Currencies, OffsersData, TradesData, Depth, Currency, Interval, HighLowOpenClose,
 | 
			
		||||
  Markets, Offers, Offer, BisqTrade, MarketVolume, Tickers, Ticker, SummarizedIntervals, SummarizedInterval } from './interfaces';
 | 
			
		||||
 | 
			
		||||
import * as datetime from 'locutus/php/datetime';
 | 
			
		||||
 | 
			
		||||
class BisqMarketsApi {
 | 
			
		||||
  private cryptoCurrencyData: Currency[] = [];
 | 
			
		||||
  private fiatCurrencyData: Currency[] = [];
 | 
			
		||||
  private offersData: OffsersData[] = [];
 | 
			
		||||
  private tradesData: TradesData[] = [];
 | 
			
		||||
  private fiatCurrenciesIndexed: { [code: string]: true } = {};
 | 
			
		||||
  private tradeDataByMarket: { [market: string]: TradesData[] } = {};
 | 
			
		||||
 | 
			
		||||
  constructor() { }
 | 
			
		||||
 | 
			
		||||
  public setData(cryptoCurrency: Currency[], fiatCurrency: Currency[], offers: OffsersData[], trades: TradesData[]) {
 | 
			
		||||
    this.cryptoCurrencyData = cryptoCurrency,
 | 
			
		||||
    this.fiatCurrencyData = fiatCurrency;
 | 
			
		||||
    this.offersData = offers;
 | 
			
		||||
    this.tradesData = trades;
 | 
			
		||||
 | 
			
		||||
    // Handle data for smarter memory caching
 | 
			
		||||
    this.fiatCurrencyData.forEach((currency) => {
 | 
			
		||||
      currency._type = 'fiat';
 | 
			
		||||
      this.fiatCurrenciesIndexed[currency.code] = true;
 | 
			
		||||
    });
 | 
			
		||||
    this.cryptoCurrencyData.forEach((currency) => currency._type = 'crypto');
 | 
			
		||||
    this.tradesData.forEach((trade) => {
 | 
			
		||||
      trade._market = trade.currencyPair.toLowerCase().replace('/', '_');
 | 
			
		||||
      if (!this.tradeDataByMarket[trade._market]) {
 | 
			
		||||
        this.tradeDataByMarket[trade._market] = [];
 | 
			
		||||
      }
 | 
			
		||||
      this.tradeDataByMarket[trade._market].push(trade);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getCurrencies(
 | 
			
		||||
    type: 'crypto' | 'fiat' | 'all' = 'all',
 | 
			
		||||
  ): Currencies {
 | 
			
		||||
    let currencies: Currency[];
 | 
			
		||||
 | 
			
		||||
    switch (type) {
 | 
			
		||||
      case 'fiat':
 | 
			
		||||
        currencies = this.fiatCurrencyData;
 | 
			
		||||
        break;
 | 
			
		||||
      case 'crypto':
 | 
			
		||||
        currencies = this.cryptoCurrencyData;
 | 
			
		||||
        break;
 | 
			
		||||
      case 'all':
 | 
			
		||||
      default:
 | 
			
		||||
        currencies = this.cryptoCurrencyData.concat(this.fiatCurrencyData);
 | 
			
		||||
    }
 | 
			
		||||
    const result = {};
 | 
			
		||||
    currencies.forEach((currency) => {
 | 
			
		||||
      result[currency.code] = currency;
 | 
			
		||||
    });
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getDepth(
 | 
			
		||||
    market: string,
 | 
			
		||||
  ): Depth {
 | 
			
		||||
    const currencyPair = market.replace('_', '/').toUpperCase();
 | 
			
		||||
 | 
			
		||||
    const buys = this.offersData
 | 
			
		||||
      .filter((offer) => offer.currencyPair === currencyPair && offer.primaryMarketDirection === 'BUY')
 | 
			
		||||
      .map((offer) => offer.price)
 | 
			
		||||
      .sort((a, b) => b - a)
 | 
			
		||||
      .map((price) => this.intToBtc(price));
 | 
			
		||||
 | 
			
		||||
    const sells = this.offersData
 | 
			
		||||
      .filter((offer) => offer.currencyPair === currencyPair && offer.primaryMarketDirection === 'SELL')
 | 
			
		||||
      .map((offer) => offer.price)
 | 
			
		||||
      .sort((a, b) => a - b)
 | 
			
		||||
      .map((price) => this.intToBtc(price));
 | 
			
		||||
 | 
			
		||||
    const result = {};
 | 
			
		||||
    result[market] = {
 | 
			
		||||
      'buys': buys,
 | 
			
		||||
      'sells': sells,
 | 
			
		||||
    };
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getOffers(
 | 
			
		||||
    market: string,
 | 
			
		||||
    direction?: 'buy' | 'sell',
 | 
			
		||||
  ): Offers {
 | 
			
		||||
    const currencyPair = market.replace('_', '/').toUpperCase();
 | 
			
		||||
 | 
			
		||||
    let buys: Offer[] | null = null;
 | 
			
		||||
    let sells: Offer[] | null = null;
 | 
			
		||||
 | 
			
		||||
    if (!direction || direction === 'buy') {
 | 
			
		||||
      buys = this.offersData
 | 
			
		||||
        .filter((offer) => offer.currencyPair === currencyPair && offer.primaryMarketDirection === 'BUY')
 | 
			
		||||
        .sort((a, b) => b.price - a.price)
 | 
			
		||||
        .map((offer) => this.offerDataToOffer(offer));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!direction || direction === 'sell') {
 | 
			
		||||
      sells = this.offersData
 | 
			
		||||
        .filter((offer) => offer.currencyPair === currencyPair && offer.primaryMarketDirection === 'SELL')
 | 
			
		||||
        .sort((a, b) => a.price - b.price)
 | 
			
		||||
        .map((offer) => this.offerDataToOffer(offer));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const result: Offers = {};
 | 
			
		||||
    result[market] = {
 | 
			
		||||
      'buys': buys,
 | 
			
		||||
      'sells': sells,
 | 
			
		||||
    };
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getMarkets(): Markets {
 | 
			
		||||
    const allCurrencies = this.getCurrencies();
 | 
			
		||||
    const markets = {};
 | 
			
		||||
 | 
			
		||||
    for (const currency of Object.keys(allCurrencies)) {
 | 
			
		||||
      if (allCurrencies[currency].code === 'BTC') {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const isFiat = allCurrencies[currency]._type === 'fiat';
 | 
			
		||||
      const pmarketname = allCurrencies['BTC']['name'];
 | 
			
		||||
 | 
			
		||||
      const lsymbol = isFiat ? 'BTC' : currency;
 | 
			
		||||
      const rsymbol = isFiat ? currency : 'BTC';
 | 
			
		||||
      const lname = isFiat ? pmarketname : allCurrencies[currency].name;
 | 
			
		||||
      const rname = isFiat ? allCurrencies[currency].name : pmarketname;
 | 
			
		||||
      const ltype = isFiat ? 'crypto' : allCurrencies[currency]._type;
 | 
			
		||||
      const rtype = isFiat ? 'fiat' : 'crypto';
 | 
			
		||||
      const lprecision = 8;
 | 
			
		||||
      const rprecision = isFiat ? 2 : 8;
 | 
			
		||||
      const pair = lsymbol.toLowerCase() + '_' + rsymbol.toLowerCase();
 | 
			
		||||
 | 
			
		||||
      markets[pair] = {
 | 
			
		||||
        'pair': pair,
 | 
			
		||||
        'lname': lname,
 | 
			
		||||
        'rname': rname,
 | 
			
		||||
        'lsymbol': lsymbol,
 | 
			
		||||
        'rsymbol': rsymbol,
 | 
			
		||||
        'lprecision': lprecision,
 | 
			
		||||
        'rprecision': rprecision,
 | 
			
		||||
        'ltype': ltype,
 | 
			
		||||
        'rtype': rtype,
 | 
			
		||||
        'name': lname + '/' + rname,
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return markets;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getTrades(
 | 
			
		||||
    market: string,
 | 
			
		||||
    timestamp_from?: number,
 | 
			
		||||
    timestamp_to?: number,
 | 
			
		||||
    trade_id_from?: string,
 | 
			
		||||
    trade_id_to?: string,
 | 
			
		||||
    direction?: 'buy' | 'sell',
 | 
			
		||||
    limit: number = 100,
 | 
			
		||||
    sort: 'asc' | 'desc' = 'desc',
 | 
			
		||||
  ): BisqTrade[] {
 | 
			
		||||
      limit = Math.min(limit, 2000);
 | 
			
		||||
      const _market = market === 'all' ? undefined : market;
 | 
			
		||||
 | 
			
		||||
      if (!timestamp_from) {
 | 
			
		||||
        timestamp_from = new Date('2016-01-01').getTime() / 1000;
 | 
			
		||||
      }
 | 
			
		||||
      if (!timestamp_to) {
 | 
			
		||||
        timestamp_to = new Date().getTime() / 1000;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const matches = this.getTradesByCriteria(_market, timestamp_to, timestamp_from, trade_id_to, trade_id_from, direction, sort, limit);
 | 
			
		||||
 | 
			
		||||
      if (sort === 'asc') {
 | 
			
		||||
        matches.sort((a, b) => a.tradeDate - b.tradeDate);
 | 
			
		||||
      } else {
 | 
			
		||||
        matches.sort((a, b) => b.tradeDate - a.tradeDate);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return matches.map((trade) => {
 | 
			
		||||
        const bsqTrade: BisqTrade = {
 | 
			
		||||
          direction: trade.primaryMarketDirection,
 | 
			
		||||
          price: trade._tradePriceStr,
 | 
			
		||||
          amount: trade._tradeAmountStr,
 | 
			
		||||
          volume: trade._tradeVolumeStr,
 | 
			
		||||
          payment_method: trade.paymentMethod,
 | 
			
		||||
          trade_id: trade.offerId,
 | 
			
		||||
          trade_date: trade.tradeDate,
 | 
			
		||||
        };
 | 
			
		||||
        if (market === 'all') {
 | 
			
		||||
          bsqTrade.market = trade._market;
 | 
			
		||||
        }
 | 
			
		||||
        return bsqTrade;
 | 
			
		||||
      });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getVolumes(
 | 
			
		||||
    market?: string,
 | 
			
		||||
    timestamp_from?: number,
 | 
			
		||||
    timestamp_to?: number,
 | 
			
		||||
    interval: Interval = 'auto',
 | 
			
		||||
    milliseconds?: boolean,
 | 
			
		||||
  ): MarketVolume[] {
 | 
			
		||||
    if (milliseconds) {
 | 
			
		||||
      timestamp_from = timestamp_from ? timestamp_from / 1000 : timestamp_from;
 | 
			
		||||
      timestamp_to = timestamp_to ? timestamp_to / 1000 : timestamp_to;
 | 
			
		||||
    }
 | 
			
		||||
    if (!timestamp_from) {
 | 
			
		||||
      timestamp_from = new Date('2016-01-01').getTime() / 1000;
 | 
			
		||||
    }
 | 
			
		||||
    if (!timestamp_to) {
 | 
			
		||||
      timestamp_to = new Date().getTime() / 1000;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const trades = this.getTradesByCriteria(market, timestamp_to, timestamp_from,
 | 
			
		||||
      undefined, undefined, undefined, 'asc', Number.MAX_SAFE_INTEGER);
 | 
			
		||||
 | 
			
		||||
    if (interval === 'auto') {
 | 
			
		||||
      const range = timestamp_to - timestamp_from;
 | 
			
		||||
      interval = this.getIntervalFromRange(range);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const intervals: any = {};
 | 
			
		||||
    const marketVolumes: MarketVolume[] = [];
 | 
			
		||||
 | 
			
		||||
    for (const trade of trades) {
 | 
			
		||||
      const traded_at = trade['tradeDate'] / 1000;
 | 
			
		||||
      const interval_start = this.intervalStart(traded_at, interval);
 | 
			
		||||
 | 
			
		||||
      if (!intervals[interval_start]) {
 | 
			
		||||
        intervals[interval_start] = {
 | 
			
		||||
          'volume': 0,
 | 
			
		||||
          'num_trades': 0,
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const period = intervals[interval_start];
 | 
			
		||||
      period['period_start'] = interval_start;
 | 
			
		||||
      period['volume'] += this.fiatCurrenciesIndexed[trade.currency] ? trade._tradeAmount : trade._tradeVolume;
 | 
			
		||||
      period['num_trades']++;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (const p in intervals) {
 | 
			
		||||
      if (intervals.hasOwnProperty(p)) {
 | 
			
		||||
        const period = intervals[p];
 | 
			
		||||
        marketVolumes.push({
 | 
			
		||||
          period_start: period['period_start'],
 | 
			
		||||
          num_trades: period['num_trades'],
 | 
			
		||||
          volume: this.intToBtc(period['volume']),
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return intervals;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getTicker(
 | 
			
		||||
    market?: string,
 | 
			
		||||
  ): Tickers | Ticker | null {
 | 
			
		||||
    if (market) {
 | 
			
		||||
      return this.getTickerFromMarket(market);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const allMarkets = this.getMarkets();
 | 
			
		||||
    const tickers = {};
 | 
			
		||||
    for (const m in allMarkets) {
 | 
			
		||||
      if (allMarkets.hasOwnProperty(m)) {
 | 
			
		||||
        tickers[allMarkets[m].pair] = this.getTickerFromMarket(allMarkets[m].pair);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return tickers;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getTickerFromMarket(market: string): Ticker | null {
 | 
			
		||||
    let ticker: Ticker;
 | 
			
		||||
    const timestamp_from = datetime.strtotime('-24 hour');
 | 
			
		||||
    const timestamp_to = new Date().getTime() / 1000;
 | 
			
		||||
    const trades = this.getTradesByCriteria(market, timestamp_to, timestamp_from,
 | 
			
		||||
      undefined, undefined, undefined, 'asc', Number.MAX_SAFE_INTEGER);
 | 
			
		||||
 | 
			
		||||
    const periods: SummarizedInterval[] = Object.values(this.getTradesSummarized(trades, timestamp_from));
 | 
			
		||||
 | 
			
		||||
    const allCurrencies = this.getCurrencies();
 | 
			
		||||
    const currencyRight = allCurrencies[market.split('_')[1].toUpperCase()];
 | 
			
		||||
 | 
			
		||||
    if (periods[0]) {
 | 
			
		||||
      ticker = {
 | 
			
		||||
        'last': this.intToBtc(periods[0].close),
 | 
			
		||||
        'high': this.intToBtc(periods[0].high),
 | 
			
		||||
        'low': this.intToBtc(periods[0].low),
 | 
			
		||||
        'volume_left': this.intToBtc(periods[0].volume_left),
 | 
			
		||||
        'volume_right': this.intToBtc(periods[0].volume_right),
 | 
			
		||||
        'buy': null,
 | 
			
		||||
        'sell': null,
 | 
			
		||||
      };
 | 
			
		||||
    } else {
 | 
			
		||||
      const lastTrade = this.tradeDataByMarket[market];
 | 
			
		||||
      if (!lastTrade) {
 | 
			
		||||
        return null;
 | 
			
		||||
      }
 | 
			
		||||
      const tradePrice = lastTrade[0].primaryMarketTradePrice * Math.pow(10, 8 - currencyRight.precision);
 | 
			
		||||
 | 
			
		||||
      const lastTradePrice = this.intToBtc(tradePrice);
 | 
			
		||||
      ticker = {
 | 
			
		||||
        'last': lastTradePrice,
 | 
			
		||||
        'high': lastTradePrice,
 | 
			
		||||
        'low': lastTradePrice,
 | 
			
		||||
        'volume_left': '0',
 | 
			
		||||
        'volume_right': '0',
 | 
			
		||||
        'buy': null,
 | 
			
		||||
        'sell': null,
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const timestampFromMilli = timestamp_from * 1000;
 | 
			
		||||
    const timestampToMilli = timestamp_to * 1000;
 | 
			
		||||
 | 
			
		||||
    const currencyPair = market.replace('_', '/').toUpperCase();
 | 
			
		||||
    const offersData = this.offersData.slice().sort((a, b) => a.price - b.price);
 | 
			
		||||
 | 
			
		||||
    const buy = offersData.find((offer) => offer.currencyPair === currencyPair
 | 
			
		||||
                                          && offer.primaryMarketDirection === 'BUY'
 | 
			
		||||
                                          && offer.date >= timestampFromMilli
 | 
			
		||||
                                          && offer.date <= timestampToMilli
 | 
			
		||||
                                        );
 | 
			
		||||
    const sell = offersData.find((offer) => offer.currencyPair === currencyPair
 | 
			
		||||
                                            && offer.primaryMarketDirection === 'SELL'
 | 
			
		||||
                                            && offer.date >= timestampFromMilli
 | 
			
		||||
                                            && offer.date <= timestampToMilli
 | 
			
		||||
                                          );
 | 
			
		||||
 | 
			
		||||
    if (buy) {
 | 
			
		||||
      ticker.buy = this.intToBtc(buy.primaryMarketPrice * Math.pow(10, 8 - currencyRight.precision));
 | 
			
		||||
    }
 | 
			
		||||
    if (sell) {
 | 
			
		||||
      ticker.sell = this.intToBtc(sell.primaryMarketPrice * Math.pow(10, 8 - currencyRight.precision));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return ticker;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getHloc(
 | 
			
		||||
    market: string,
 | 
			
		||||
    interval: Interval = 'auto',
 | 
			
		||||
    timestamp_from?: number,
 | 
			
		||||
    timestamp_to?: number,
 | 
			
		||||
    milliseconds?: boolean,
 | 
			
		||||
  ): HighLowOpenClose[] {
 | 
			
		||||
    if (milliseconds) {
 | 
			
		||||
      timestamp_from = timestamp_from ? timestamp_from / 1000 : timestamp_from;
 | 
			
		||||
      timestamp_to = timestamp_to ? timestamp_to / 1000 : timestamp_to;
 | 
			
		||||
    }
 | 
			
		||||
    if (!timestamp_from) {
 | 
			
		||||
      timestamp_from = new Date('2016-01-01').getTime() / 1000;
 | 
			
		||||
    }
 | 
			
		||||
    if (!timestamp_to) {
 | 
			
		||||
      timestamp_to = new Date().getTime() / 1000;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const trades = this.getTradesByCriteria(market, timestamp_to, timestamp_from,
 | 
			
		||||
      undefined, undefined, undefined, 'asc', Number.MAX_SAFE_INTEGER);
 | 
			
		||||
 | 
			
		||||
    if (interval === 'auto') {
 | 
			
		||||
      const range = timestamp_to - timestamp_from;
 | 
			
		||||
      interval = this.getIntervalFromRange(range);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const intervals = this.getTradesSummarized(trades, timestamp_from, interval);
 | 
			
		||||
 | 
			
		||||
    const hloc: HighLowOpenClose[] = [];
 | 
			
		||||
 | 
			
		||||
    for (const p in intervals) {
 | 
			
		||||
      if (intervals.hasOwnProperty(p)) {
 | 
			
		||||
        const period = intervals[p];
 | 
			
		||||
        hloc.push({
 | 
			
		||||
          period_start: period['period_start'],
 | 
			
		||||
          open: this.intToBtc(period['open']),
 | 
			
		||||
          close: this.intToBtc(period['close']),
 | 
			
		||||
          high: this.intToBtc(period['high']),
 | 
			
		||||
          low: this.intToBtc(period['low']),
 | 
			
		||||
          avg: this.intToBtc(period['avg']),
 | 
			
		||||
          volume_right: this.intToBtc(period['volume_right']),
 | 
			
		||||
          volume_left: this.intToBtc(period['volume_left']),
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return hloc;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private getIntervalFromRange(range: number): Interval {
 | 
			
		||||
    // two days range loads minute data
 | 
			
		||||
    if (range <= 3600) {
 | 
			
		||||
      // up to one hour range loads minutely data
 | 
			
		||||
      return 'minute';
 | 
			
		||||
    } else if (range <= 1 * 24 * 3600) {
 | 
			
		||||
      // up to one day range loads half-hourly data
 | 
			
		||||
      return 'half_hour';
 | 
			
		||||
    } else if (range <= 3 * 24 * 3600) {
 | 
			
		||||
      // up to 3 day range loads hourly data
 | 
			
		||||
      return 'hour';
 | 
			
		||||
    } else if (range <= 7 * 24 * 3600) {
 | 
			
		||||
      // up to 7 day range loads half-daily data
 | 
			
		||||
      return 'half_day';
 | 
			
		||||
    } else if (range <= 60 * 24 * 3600) {
 | 
			
		||||
      // up to 2 month range loads daily data
 | 
			
		||||
      return 'day';
 | 
			
		||||
    } else if (range <= 12 * 31 * 24 * 3600) {
 | 
			
		||||
      // up to one year range loads weekly data
 | 
			
		||||
      return 'week';
 | 
			
		||||
    } else if (range <= 12 * 31 * 24 * 3600) {
 | 
			
		||||
      // up to 5 year range loads monthly data
 | 
			
		||||
      return 'month';
 | 
			
		||||
    } else {
 | 
			
		||||
      // greater range loads yearly data
 | 
			
		||||
      return 'year';
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private getTradesSummarized(trades: TradesData[], timestamp_from: number, interval?: string): SummarizedIntervals {
 | 
			
		||||
    const intervals: any = {};
 | 
			
		||||
    const intervals_prices: any = {};
 | 
			
		||||
 | 
			
		||||
    for (const trade of trades) {
 | 
			
		||||
      const traded_at = trade.tradeDate / 1000;
 | 
			
		||||
      const interval_start = !interval ? timestamp_from : this.intervalStart(traded_at, interval);
 | 
			
		||||
 | 
			
		||||
      if (!intervals[interval_start]) {
 | 
			
		||||
          intervals[interval_start] = {
 | 
			
		||||
            'open': 0,
 | 
			
		||||
            'close': 0,
 | 
			
		||||
            'high': 0,
 | 
			
		||||
            'low': 0,
 | 
			
		||||
            'avg': 0,
 | 
			
		||||
            'volume_right': 0,
 | 
			
		||||
            'volume_left': 0,
 | 
			
		||||
        };
 | 
			
		||||
        intervals_prices[interval_start] = [];
 | 
			
		||||
      }
 | 
			
		||||
      const period = intervals[interval_start];
 | 
			
		||||
      const price = trade._tradePrice;
 | 
			
		||||
 | 
			
		||||
      if (!intervals_prices[interval_start]['leftvol']) {
 | 
			
		||||
        intervals_prices[interval_start]['leftvol'] = [];
 | 
			
		||||
      }
 | 
			
		||||
      if (!intervals_prices[interval_start]['rightvol']) {
 | 
			
		||||
        intervals_prices[interval_start]['rightvol'] = [];
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      intervals_prices[interval_start]['leftvol'].push(trade._tradeAmount);
 | 
			
		||||
      intervals_prices[interval_start]['rightvol'].push(trade._tradeVolume);
 | 
			
		||||
 | 
			
		||||
      if (price) {
 | 
			
		||||
          const plow = period['low'];
 | 
			
		||||
          period['period_start'] = interval_start;
 | 
			
		||||
          period['open'] = period['open'] || price;
 | 
			
		||||
          period['close'] = price;
 | 
			
		||||
          period['high'] = price > period['high'] ? price : period['high'];
 | 
			
		||||
          period['low'] = (plow && price > plow) ? period['low'] : price;
 | 
			
		||||
          period['avg'] = intervals_prices[interval_start]['rightvol'].reduce((p: number, c: number) => c + p, 0)
 | 
			
		||||
            / intervals_prices[interval_start]['leftvol'].reduce((c: number, p: number) => c + p, 0) * 100000000;
 | 
			
		||||
          period['volume_left'] += trade._tradeAmount;
 | 
			
		||||
          period['volume_right'] += trade._tradeVolume;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return intervals;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private getTradesByCriteria(
 | 
			
		||||
    market: string | undefined,
 | 
			
		||||
    timestamp_to: number,
 | 
			
		||||
    timestamp_from: number,
 | 
			
		||||
    trade_id_to: string | undefined,
 | 
			
		||||
    trade_id_from: string | undefined,
 | 
			
		||||
    direction: 'buy' | 'sell' | undefined,
 | 
			
		||||
    sort: string, limit: number,
 | 
			
		||||
    integerAmounts: boolean = true,
 | 
			
		||||
 ): TradesData[] {
 | 
			
		||||
    let trade_id_from_ts: number | null = null;
 | 
			
		||||
    let trade_id_to_ts: number | null = null;
 | 
			
		||||
    const allCurrencies = this.getCurrencies();
 | 
			
		||||
 | 
			
		||||
    const timestampFromMilli = timestamp_from * 1000;
 | 
			
		||||
    const timestampToMilli = timestamp_to * 1000;
 | 
			
		||||
 | 
			
		||||
    // note: the offer_id_from/to depends on iterating over trades in
 | 
			
		||||
    // descending chronological order.
 | 
			
		||||
    const tradesDataSorted = this.tradesData.slice();
 | 
			
		||||
    if (sort === 'asc') {
 | 
			
		||||
      tradesDataSorted.reverse();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let matches: TradesData[] = [];
 | 
			
		||||
    for (const trade of tradesDataSorted) {
 | 
			
		||||
      if (trade_id_from === trade.offerId) {
 | 
			
		||||
        trade_id_from_ts = trade.tradeDate;
 | 
			
		||||
      }
 | 
			
		||||
      if (trade_id_to === trade.offerId) {
 | 
			
		||||
        trade_id_to_ts = trade.tradeDate;
 | 
			
		||||
      }
 | 
			
		||||
      if (trade_id_to && trade_id_to_ts === null) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      if (trade_id_from && trade_id_from_ts != null && trade_id_from_ts !== trade.tradeDate) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      if (market && market !== trade._market) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      if (timestampFromMilli && timestampFromMilli > trade.tradeDate) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      if (timestampToMilli && timestampToMilli < trade.tradeDate) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      if (direction && direction !== trade.direction.toLowerCase()) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Filter out bogus trades with BTC/BTC or XXX/XXX market.
 | 
			
		||||
      // See github issue: https://github.com/bitsquare/bitsquare/issues/883
 | 
			
		||||
      const currencyPairs = trade.currencyPair.split('/');
 | 
			
		||||
      if (currencyPairs[0] === currencyPairs[1]) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const currencyLeft = allCurrencies[currencyPairs[0]];
 | 
			
		||||
      const currencyRight = allCurrencies[currencyPairs[1]];
 | 
			
		||||
 | 
			
		||||
      if (!currencyLeft || !currencyRight) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const tradePrice = trade.primaryMarketTradePrice * Math.pow(10, 8 - currencyRight.precision);
 | 
			
		||||
      const tradeAmount = trade.primaryMarketTradeAmount * Math.pow(10, 8 - currencyLeft.precision);
 | 
			
		||||
      const tradeVolume = trade.primaryMarketTradeVolume * Math.pow(10, 8 - currencyRight.precision);
 | 
			
		||||
 | 
			
		||||
      if (integerAmounts) {
 | 
			
		||||
        trade._tradePrice = tradePrice;
 | 
			
		||||
        trade._tradeAmount = tradeAmount;
 | 
			
		||||
        trade._tradeVolume = tradeVolume;
 | 
			
		||||
        trade._offerAmount = trade.offerAmount;
 | 
			
		||||
      } else {
 | 
			
		||||
        trade._tradePriceStr = this.intToBtc(tradePrice);
 | 
			
		||||
        trade._tradeAmountStr = this.intToBtc(tradeAmount);
 | 
			
		||||
        trade._tradeVolumeStr = this.intToBtc(tradeVolume);
 | 
			
		||||
        trade._offerAmountStr = this.intToBtc(trade.offerAmount);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      matches.push(trade);
 | 
			
		||||
 | 
			
		||||
      if (matches.length >= limit) {
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ((trade_id_from && !trade_id_from_ts) || (trade_id_to && !trade_id_to_ts)) {
 | 
			
		||||
      matches = [];
 | 
			
		||||
    }
 | 
			
		||||
    return matches;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private intervalStart(ts: number, interval: string): number {
 | 
			
		||||
    switch (interval) {
 | 
			
		||||
        case 'minute':
 | 
			
		||||
            return (ts - (ts % 60));
 | 
			
		||||
        case '10_minute':
 | 
			
		||||
            return (ts - (ts % 600));
 | 
			
		||||
        case 'half_hour':
 | 
			
		||||
            return (ts - (ts % 1800));
 | 
			
		||||
        case 'hour':
 | 
			
		||||
            return (ts - (ts % 3600));
 | 
			
		||||
        case 'half_day':
 | 
			
		||||
            return (ts - (ts % (3600 * 12)));
 | 
			
		||||
        case 'day':
 | 
			
		||||
            return datetime.strtotime('midnight today', ts);
 | 
			
		||||
        case 'week':
 | 
			
		||||
            return datetime.strtotime('midnight sunday last week', ts);
 | 
			
		||||
        case 'month':
 | 
			
		||||
            return datetime.strtotime('midnight first day of this month', ts);
 | 
			
		||||
        case 'year':
 | 
			
		||||
            return datetime.strtotime('midnight first day of january', ts);
 | 
			
		||||
        default:
 | 
			
		||||
            throw new Error('Unsupported interval: ' + interval);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  private offerDataToOffer(offer: OffsersData): Offer {
 | 
			
		||||
    return {
 | 
			
		||||
      offer_id: offer.id,
 | 
			
		||||
      offer_date: offer.date,
 | 
			
		||||
      direction: offer.direction,
 | 
			
		||||
      min_amount: this.intToBtc(offer.minAmount),
 | 
			
		||||
      amount: this.intToBtc(offer.amount),
 | 
			
		||||
      price: this.intToBtc(offer.price),
 | 
			
		||||
      volume: this.intToBtc(offer.primaryMarketVolume),
 | 
			
		||||
      payment_method: offer.paymentMethod,
 | 
			
		||||
      offer_fee_txid: null,
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private intToBtc(val: number): string {
 | 
			
		||||
    return (val / 100000000).toFixed(8);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default new BisqMarketsApi();
 | 
			
		||||
							
								
								
									
										90
									
								
								backend/src/api/bisq/markets.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								backend/src/api/bisq/markets.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,90 @@
 | 
			
		||||
const config = require('../../../mempool-config.json');
 | 
			
		||||
import * as fs from 'fs';
 | 
			
		||||
import { OffsersData, TradesData, Currency } from './interfaces';
 | 
			
		||||
import bisqMarket from './markets-api';
 | 
			
		||||
 | 
			
		||||
class Bisq {
 | 
			
		||||
  private static MARKET_JSON_PATH = config.BISQ_MARKETS_DATA_PATH + '/btc_mainnet/db';
 | 
			
		||||
  private static MARKET_JSON_FILE_PATHS = {
 | 
			
		||||
    cryptoCurrency: '/crypto_currency_list.json',
 | 
			
		||||
    fiatCurrency: '/fiat_currency_list.json',
 | 
			
		||||
    offers: '/offers_statistics.json',
 | 
			
		||||
    trades: '/trade_statistics.json',
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  private subdirectoryWatcher: fs.FSWatcher | undefined;
 | 
			
		||||
 | 
			
		||||
  constructor() {}
 | 
			
		||||
 | 
			
		||||
  startBisqService(): void {
 | 
			
		||||
    this.checkForBisqDataFolder();
 | 
			
		||||
    this.loadBisqDumpFile();
 | 
			
		||||
    this.startBisqDirectoryWatcher();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private checkForBisqDataFolder() {
 | 
			
		||||
    if (!fs.existsSync(Bisq.MARKET_JSON_PATH + Bisq.MARKET_JSON_FILE_PATHS.cryptoCurrency)) {
 | 
			
		||||
      console.log(Bisq.MARKET_JSON_PATH + Bisq.MARKET_JSON_FILE_PATHS.cryptoCurrency + ` doesn't exist. Make sure Bisq is running and the config is correct before starting the server.`);
 | 
			
		||||
      return process.exit(1);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private startBisqDirectoryWatcher() {
 | 
			
		||||
    if (this.subdirectoryWatcher) {
 | 
			
		||||
      this.subdirectoryWatcher.close();
 | 
			
		||||
    }
 | 
			
		||||
    if (!fs.existsSync(Bisq.MARKET_JSON_PATH + Bisq.MARKET_JSON_FILE_PATHS.cryptoCurrency)) {
 | 
			
		||||
      console.log(Bisq.MARKET_JSON_PATH + Bisq.MARKET_JSON_FILE_PATHS.cryptoCurrency + ` doesn't exist. Trying to restart sub directory watcher again in 3 minutes.`);
 | 
			
		||||
      setTimeout(() => this.startBisqDirectoryWatcher(), 180000);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    let fsWait: NodeJS.Timeout | null = null;
 | 
			
		||||
    this.subdirectoryWatcher = fs.watch(Bisq.MARKET_JSON_PATH, () => {
 | 
			
		||||
      if (fsWait) {
 | 
			
		||||
        clearTimeout(fsWait);
 | 
			
		||||
      }
 | 
			
		||||
      fsWait = setTimeout(() => {
 | 
			
		||||
        console.log(`Change detected in the Bisq market data folder.`);
 | 
			
		||||
        this.loadBisqDumpFile();
 | 
			
		||||
      }, 2000);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async loadBisqDumpFile(): Promise<void> {
 | 
			
		||||
    const start = new Date().getTime();
 | 
			
		||||
    console.log('Processing Bisq market data...');
 | 
			
		||||
    try {
 | 
			
		||||
      const cryptoCurrencyData = await this.loadData<Currency[]>(Bisq.MARKET_JSON_FILE_PATHS.cryptoCurrency);
 | 
			
		||||
      const fiatCurrencyData = await this.loadData<Currency[]>(Bisq.MARKET_JSON_FILE_PATHS.fiatCurrency);
 | 
			
		||||
      const offersData = await this.loadData<OffsersData[]>(Bisq.MARKET_JSON_FILE_PATHS.offers);
 | 
			
		||||
      const tradesData = await this.loadData<TradesData[]>(Bisq.MARKET_JSON_FILE_PATHS.trades);
 | 
			
		||||
 | 
			
		||||
      bisqMarket.setData(cryptoCurrencyData, fiatCurrencyData, offersData, tradesData);
 | 
			
		||||
      const time = new Date().getTime() - start;
 | 
			
		||||
      console.log('Bisq market data processed in ' + time + ' ms');
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.log('loadBisqMarketDataDumpFile() error.', e.message);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private loadData<T>(path: string): Promise<T> {
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
      if (!fs.existsSync(Bisq.MARKET_JSON_PATH + path)) {
 | 
			
		||||
        return reject(path + ` doesn't exist`);
 | 
			
		||||
      }
 | 
			
		||||
      fs.readFile(Bisq.MARKET_JSON_PATH + path, 'utf8', (err, data) => {
 | 
			
		||||
        if (err) {
 | 
			
		||||
          reject(err);
 | 
			
		||||
        }
 | 
			
		||||
        try {
 | 
			
		||||
          const parsedData = JSON.parse(data);
 | 
			
		||||
          resolve(parsedData);
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
          reject('JSON parse error (' + path + ')');
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default new Bisq();
 | 
			
		||||
@ -15,7 +15,8 @@ import diskCache from './api/disk-cache';
 | 
			
		||||
import statistics from './api/statistics';
 | 
			
		||||
import websocketHandler from './api/websocket-handler';
 | 
			
		||||
import fiatConversion from './api/fiat-conversion';
 | 
			
		||||
import bisq from './api/bisq';
 | 
			
		||||
import bisq from './api/bisq/bisq';
 | 
			
		||||
import bisqMarkets from './api/bisq/markets';
 | 
			
		||||
 | 
			
		||||
class Server {
 | 
			
		||||
  wss: WebSocket.Server;
 | 
			
		||||
@ -61,6 +62,10 @@ class Server {
 | 
			
		||||
      bisq.setPriceCallbackFunction((price) => websocketHandler.setExtraInitProperties('bsq-price', price));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (config.BISQ_MARKET_ENABLED) {
 | 
			
		||||
      bisqMarkets.startBisqService();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.server.listen(config.HTTP_PORT, () => {
 | 
			
		||||
      console.log(`Server started on port ${config.HTTP_PORT}`);
 | 
			
		||||
    });
 | 
			
		||||
@ -107,6 +112,19 @@ class Server {
 | 
			
		||||
        .get(config.API_ENDPOINT + 'bisq/txs/:index/:length', routes.getBisqTransactions)
 | 
			
		||||
      ;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (config.BISQ_MARKET_ENABLED) {
 | 
			
		||||
      this.app
 | 
			
		||||
        .get(config.API_ENDPOINT + 'bisq/markets/currencies', routes.getBisqMarketCurrencies.bind(routes))
 | 
			
		||||
        .get(config.API_ENDPOINT + 'bisq/markets/depth', routes.getBisqMarketDepth.bind(routes))
 | 
			
		||||
        .get(config.API_ENDPOINT + 'bisq/markets/hloc', routes.getBisqMarketHloc.bind(routes))
 | 
			
		||||
        .get(config.API_ENDPOINT + 'bisq/markets/markets', routes.getBisqMarketMarkets.bind(routes))
 | 
			
		||||
        .get(config.API_ENDPOINT + 'bisq/markets/offers', routes.getBisqMarketOffers.bind(routes))
 | 
			
		||||
        .get(config.API_ENDPOINT + 'bisq/markets/ticker', routes.getBisqMarketTicker.bind(routes))
 | 
			
		||||
        .get(config.API_ENDPOINT + 'bisq/markets/trades', routes.getBisqMarketTrades.bind(routes))
 | 
			
		||||
        .get(config.API_ENDPOINT + 'bisq/markets/volumes', routes.getBisqMarketVolumes.bind(routes))
 | 
			
		||||
        ;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -231,94 +231,9 @@ export interface VbytesPerSecond {
 | 
			
		||||
  vSize: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface BisqBlocks {
 | 
			
		||||
  chainHeight: number;
 | 
			
		||||
  blocks: BisqBlock[];
 | 
			
		||||
}
 | 
			
		||||
export interface RequiredSpec { [name: string]: RequiredParams; }
 | 
			
		||||
 | 
			
		||||
export interface BisqBlock {
 | 
			
		||||
  height: number;
 | 
			
		||||
  time: number;
 | 
			
		||||
  hash: string;
 | 
			
		||||
  previousBlockHash: string;
 | 
			
		||||
  txs: BisqTransaction[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface BisqTransaction {
 | 
			
		||||
  txVersion: string;
 | 
			
		||||
  id: string;
 | 
			
		||||
  blockHeight: number;
 | 
			
		||||
  blockHash: string;
 | 
			
		||||
  time: number;
 | 
			
		||||
  inputs: BisqInput[];
 | 
			
		||||
  outputs: BisqOutput[];
 | 
			
		||||
  txType: string;
 | 
			
		||||
  txTypeDisplayString: string;
 | 
			
		||||
  burntFee: number;
 | 
			
		||||
  invalidatedBsq: number;
 | 
			
		||||
  unlockBlockHeight: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface BisqStats {
 | 
			
		||||
  minted: number;
 | 
			
		||||
  burnt: number;
 | 
			
		||||
  addresses: number;
 | 
			
		||||
  unspent_txos: number;
 | 
			
		||||
  spent_txos: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface BisqInput {
 | 
			
		||||
  spendingTxOutputIndex: number;
 | 
			
		||||
  spendingTxId: string;
 | 
			
		||||
  bsqAmount: number;
 | 
			
		||||
  isVerified: boolean;
 | 
			
		||||
  address: string;
 | 
			
		||||
  time: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface BisqOutput {
 | 
			
		||||
  txVersion: string;
 | 
			
		||||
  txId: string;
 | 
			
		||||
  index: number;
 | 
			
		||||
  bsqAmount: number;
 | 
			
		||||
  btcAmount: number;
 | 
			
		||||
  height: number;
 | 
			
		||||
  isVerified: boolean;
 | 
			
		||||
  burntFee: number;
 | 
			
		||||
  invalidatedBsq: number;
 | 
			
		||||
  address: string;
 | 
			
		||||
  scriptPubKey: BisqScriptPubKey;
 | 
			
		||||
  time: any;
 | 
			
		||||
  txType: string;
 | 
			
		||||
  txTypeDisplayString: string;
 | 
			
		||||
  txOutputType: string;
 | 
			
		||||
  txOutputTypeDisplayString: string;
 | 
			
		||||
  lockTime: number;
 | 
			
		||||
  isUnspent: boolean;
 | 
			
		||||
  spentInfo: SpentInfo;
 | 
			
		||||
  opReturn?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface BisqScriptPubKey {
 | 
			
		||||
  addresses: string[];
 | 
			
		||||
  asm: string;
 | 
			
		||||
  hex: string;
 | 
			
		||||
  reqSigs: number;
 | 
			
		||||
  type: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface SpentInfo {
 | 
			
		||||
  height: number;
 | 
			
		||||
  inputIndex: number;
 | 
			
		||||
  txId: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface BisqTrade {
 | 
			
		||||
  direction: string;
 | 
			
		||||
  price: string;
 | 
			
		||||
  amount: string;
 | 
			
		||||
  volume: string;
 | 
			
		||||
  payment_method: string;
 | 
			
		||||
  trade_id: string;
 | 
			
		||||
  trade_date: number;
 | 
			
		||||
interface RequiredParams {
 | 
			
		||||
  required: boolean;
 | 
			
		||||
  types: ('@string' | '@number' | '@boolean' | string)[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,10 @@ import feeApi from './api/fee-api';
 | 
			
		||||
import backendInfo from './api/backend-info';
 | 
			
		||||
import mempoolBlocks from './api/mempool-blocks';
 | 
			
		||||
import mempool from './api/mempool';
 | 
			
		||||
import bisq from './api/bisq';
 | 
			
		||||
import bisq from './api/bisq/bisq';
 | 
			
		||||
import bisqMarket from './api/bisq/markets-api';
 | 
			
		||||
import { RequiredSpec } from './interfaces';
 | 
			
		||||
import { MarketsApiError } from './api/bisq/interfaces';
 | 
			
		||||
 | 
			
		||||
class Routes {
 | 
			
		||||
  private cache = {};
 | 
			
		||||
@ -161,6 +164,269 @@ class Routes {
 | 
			
		||||
      res.status(404).send('Bisq address not found');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public getBisqMarketCurrencies(req: Request, res: Response) {
 | 
			
		||||
    const constraints: RequiredSpec = {
 | 
			
		||||
      'type': {
 | 
			
		||||
        required: false,
 | 
			
		||||
        types: ['crypto', 'fiat', 'all']
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const p = this.parseRequestParameters(req, constraints);
 | 
			
		||||
    if (p.error) {
 | 
			
		||||
      res.status(400).json(this.getBisqMarketErrorResponse(p.error));
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const result = bisqMarket.getCurrencies(p.type);
 | 
			
		||||
    if (result) {
 | 
			
		||||
      res.json(result);
 | 
			
		||||
    } else {
 | 
			
		||||
      res.status(500).json(this.getBisqMarketErrorResponse('getBisqMarketCurrencies error'));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public getBisqMarketDepth(req: Request, res: Response) {
 | 
			
		||||
    const constraints: RequiredSpec = {
 | 
			
		||||
      'market': {
 | 
			
		||||
        required: true,
 | 
			
		||||
        types: ['@string']
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const p = this.parseRequestParameters(req, constraints);
 | 
			
		||||
    if (p.error) {
 | 
			
		||||
      res.status(400).json(this.getBisqMarketErrorResponse(p.error));
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const result = bisqMarket.getDepth(p.market);
 | 
			
		||||
    if (result) {
 | 
			
		||||
      res.json(result);
 | 
			
		||||
    } else {
 | 
			
		||||
      res.status(500).json(this.getBisqMarketErrorResponse('getBisqMarketDepth error'));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public getBisqMarketMarkets(req: Request, res: Response) {
 | 
			
		||||
    const result = bisqMarket.getMarkets();
 | 
			
		||||
    if (result) {
 | 
			
		||||
      res.json(result);
 | 
			
		||||
    } else {
 | 
			
		||||
      res.status(500).json(this.getBisqMarketErrorResponse('getBisqMarketMarkets error'));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public 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, constraints);
 | 
			
		||||
    if (p.error) {
 | 
			
		||||
      res.status(400).json(this.getBisqMarketErrorResponse(p.error));
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const result = bisqMarket.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'));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public getBisqMarketOffers(req: Request, res: Response) {
 | 
			
		||||
    const constraints: RequiredSpec = {
 | 
			
		||||
      'market': {
 | 
			
		||||
        required: true,
 | 
			
		||||
        types: ['@string']
 | 
			
		||||
      },
 | 
			
		||||
      'direction': {
 | 
			
		||||
        required: false,
 | 
			
		||||
        types: ['buy', 'sell']
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const p = this.parseRequestParameters(req, constraints);
 | 
			
		||||
    if (p.error) {
 | 
			
		||||
      res.status(400).json(this.getBisqMarketErrorResponse(p.error));
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const result = bisqMarket.getOffers(p.market, p.direction);
 | 
			
		||||
    if (result) {
 | 
			
		||||
      res.json(result);
 | 
			
		||||
    } else {
 | 
			
		||||
      res.status(500).json(this.getBisqMarketErrorResponse('getBisqMarketOffers error'));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public 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']
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const p = this.parseRequestParameters(req, constraints);
 | 
			
		||||
    if (p.error) {
 | 
			
		||||
      res.status(400).json(this.getBisqMarketErrorResponse(p.error));
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const result = bisqMarket.getVolumes(p.market, p.timestamp_from, p.timestamp_to, p.interval, p.milliseconds);
 | 
			
		||||
    if (result) {
 | 
			
		||||
      res.json(result);
 | 
			
		||||
    } else {
 | 
			
		||||
      res.status(500).json(this.getBisqMarketErrorResponse('getBisqMarketVolumes error'));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public 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']
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const p = this.parseRequestParameters(req, constraints);
 | 
			
		||||
    if (p.error) {
 | 
			
		||||
      res.status(400).json(this.getBisqMarketErrorResponse(p.error));
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const result = bisqMarket.getHloc(p.market, p.interval, p.timestamp_from, p.timestamp_to, p.milliseconds);
 | 
			
		||||
    if (result) {
 | 
			
		||||
      res.json(result);
 | 
			
		||||
    } else {
 | 
			
		||||
      res.status(500).json(this.getBisqMarketErrorResponse('getBisqMarketHloc error'));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public getBisqMarketTicker(req: Request, res: Response) {
 | 
			
		||||
    const constraints: RequiredSpec = {
 | 
			
		||||
      'market': {
 | 
			
		||||
        required: false,
 | 
			
		||||
        types: ['@string']
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const p = this.parseRequestParameters(req, constraints);
 | 
			
		||||
    if (p.error) {
 | 
			
		||||
      res.status(400).json(this.getBisqMarketErrorResponse(p.error));
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const result = bisqMarket.getTicker(p.market);
 | 
			
		||||
    if (result) {
 | 
			
		||||
      res.json(result);
 | 
			
		||||
    } else {
 | 
			
		||||
      res.status(500).json(this.getBisqMarketErrorResponse('getBisqMarketTicker error'));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private parseRequestParameters(req: Request, params: RequiredSpec): { [name: string]: any; } {
 | 
			
		||||
    const final = {};
 | 
			
		||||
    for (const i in params) {
 | 
			
		||||
      if (params.hasOwnProperty(i)) {
 | 
			
		||||
        if (params[i].required && !req.query[i]) {
 | 
			
		||||
          return { error: i + ' parameter missing'};
 | 
			
		||||
        }
 | 
			
		||||
        if (typeof req.query[i] === 'string') {
 | 
			
		||||
          const str = (req.query[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'};
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return final;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private getBisqMarketErrorResponse(message: string): MarketsApiError {
 | 
			
		||||
    return {
 | 
			
		||||
      'success': 0,
 | 
			
		||||
      'error': message
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default new Routes();
 | 
			
		||||
 | 
			
		||||
@ -14,7 +14,7 @@
 | 
			
		||||
  "TX_PER_SECOND_SPAN_SECONDS": 150,
 | 
			
		||||
  "ELECTRS_API_URL": "http://[::1]:3000",
 | 
			
		||||
  "BISQ_ENABLED": true,
 | 
			
		||||
  "BSQ_BLOCKS_DATA_PATH": "/bisq/data",
 | 
			
		||||
  "BISQ_BLOCKS_DATA_PATH": "/bisq/data",
 | 
			
		||||
  "SSL": false,
 | 
			
		||||
  "SSL_CERT_FILE_PATH": "/etc/letsencrypt/live/mysite/fullchain.pem",
 | 
			
		||||
  "SSL_KEY_FILE_PATH": "/etc/letsencrypt/live/mysite/privkey.pem"
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user