commit
c1c0521ab4
@ -15,7 +15,9 @@
|
|||||||
"TX_PER_SECOND_SPAN_SECONDS": 150,
|
"TX_PER_SECOND_SPAN_SECONDS": 150,
|
||||||
"ELECTRS_API_URL": "http://localhost:50001",
|
"ELECTRS_API_URL": "http://localhost:50001",
|
||||||
"BISQ_ENABLED": false,
|
"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": false,
|
||||||
"SSL_CERT_FILE_PATH": "/etc/letsencrypt/live/mysite/fullchain.pem",
|
"SSL_CERT_FILE_PATH": "/etc/letsencrypt/live/mysite/fullchain.pem",
|
||||||
"SSL_KEY_FILE_PATH": "/etc/letsencrypt/live/mysite/privkey.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/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": {
|
"@types/mime": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||||
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
|
"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": {
|
"escape-html": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||||
@ -733,6 +743,14 @@
|
|||||||
"verror": "1.10.0"
|
"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": {
|
"long": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"mysql2": "^1.6.1",
|
"mysql2": "^1.6.1",
|
||||||
"request": "^2.88.2",
|
"request": "^2.88.2",
|
||||||
|
"locutus": "^2.0.12",
|
||||||
"ws": "^7.3.1"
|
"ws": "^7.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -36,6 +37,7 @@
|
|||||||
"@types/express": "^4.17.2",
|
"@types/express": "^4.17.2",
|
||||||
"@types/request": "^2.48.2",
|
"@types/request": "^2.48.2",
|
||||||
"@types/ws": "^6.0.4",
|
"@types/ws": "^6.0.4",
|
||||||
|
"@types/locutus": "^0.0.6",
|
||||||
"tslint": "~6.1.0",
|
"tslint": "~6.1.0",
|
||||||
"typescript": "~3.9.7"
|
"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 fs from 'fs';
|
||||||
import * as request from 'request';
|
import * as request from 'request';
|
||||||
import { BisqBlocks, BisqBlock, BisqTransaction, BisqStats, BisqTrade } from '../interfaces';
|
import { BisqBlocks, BisqBlock, BisqTransaction, BisqStats, BisqTrade } from './interfaces';
|
||||||
import { Common } from './common';
|
import { Common } from '../common';
|
||||||
|
|
||||||
class Bisq {
|
class Bisq {
|
||||||
private static BLOCKS_JSON_FILE_PATH = '/all/blocks.json';
|
private static BLOCKS_JSON_FILE_PATH = '/all/blocks.json';
|
||||||
@ -72,8 +72,8 @@ class Bisq {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private checkForBisqDataFolder() {
|
private checkForBisqDataFolder() {
|
||||||
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)) {
|
||||||
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.`);
|
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);
|
return process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -83,7 +83,7 @@ class Bisq {
|
|||||||
this.topDirectoryWatcher.close();
|
this.topDirectoryWatcher.close();
|
||||||
}
|
}
|
||||||
let fsWait: NodeJS.Timeout | null = null;
|
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) {
|
if (fsWait) {
|
||||||
clearTimeout(fsWait);
|
clearTimeout(fsWait);
|
||||||
}
|
}
|
||||||
@ -105,13 +105,13 @@ class Bisq {
|
|||||||
if (this.subdirectoryWatcher) {
|
if (this.subdirectoryWatcher) {
|
||||||
this.subdirectoryWatcher.close();
|
this.subdirectoryWatcher.close();
|
||||||
}
|
}
|
||||||
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)) {
|
||||||
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.`);
|
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);
|
setTimeout(() => this.startSubDirectoryWatcher(), 180000);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let fsWait: NodeJS.Timeout | null = null;
|
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) {
|
if (fsWait) {
|
||||||
clearTimeout(fsWait);
|
clearTimeout(fsWait);
|
||||||
}
|
}
|
||||||
@ -243,10 +243,10 @@ class Bisq {
|
|||||||
|
|
||||||
private loadData(): Promise<string> {
|
private loadData(): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
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`);
|
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) {
|
if (err) {
|
||||||
reject(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 statistics from './api/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';
|
import bisq from './api/bisq/bisq';
|
||||||
|
import bisqMarkets from './api/bisq/markets';
|
||||||
|
|
||||||
class Server {
|
class Server {
|
||||||
wss: WebSocket.Server;
|
wss: WebSocket.Server;
|
||||||
@ -61,6 +62,10 @@ class Server {
|
|||||||
bisq.setPriceCallbackFunction((price) => websocketHandler.setExtraInitProperties('bsq-price', price));
|
bisq.setPriceCallbackFunction((price) => websocketHandler.setExtraInitProperties('bsq-price', price));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.BISQ_MARKET_ENABLED) {
|
||||||
|
bisqMarkets.startBisqService();
|
||||||
|
}
|
||||||
|
|
||||||
this.server.listen(config.HTTP_PORT, () => {
|
this.server.listen(config.HTTP_PORT, () => {
|
||||||
console.log(`Server started on port ${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)
|
.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;
|
vSize: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BisqBlocks {
|
export interface RequiredSpec { [name: string]: RequiredParams; }
|
||||||
chainHeight: number;
|
|
||||||
blocks: BisqBlock[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BisqBlock {
|
interface RequiredParams {
|
||||||
height: number;
|
required: boolean;
|
||||||
time: number;
|
types: ('@string' | '@number' | '@boolean' | string)[];
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,10 @@ import feeApi from './api/fee-api';
|
|||||||
import backendInfo from './api/backend-info';
|
import backendInfo from './api/backend-info';
|
||||||
import mempoolBlocks from './api/mempool-blocks';
|
import mempoolBlocks from './api/mempool-blocks';
|
||||||
import mempool from './api/mempool';
|
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 {
|
class Routes {
|
||||||
private cache = {};
|
private cache = {};
|
||||||
@ -161,6 +164,269 @@ class Routes {
|
|||||||
res.status(404).send('Bisq address not found');
|
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();
|
export default new Routes();
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
"TX_PER_SECOND_SPAN_SECONDS": 150,
|
"TX_PER_SECOND_SPAN_SECONDS": 150,
|
||||||
"ELECTRS_API_URL": "http://[::1]:3000",
|
"ELECTRS_API_URL": "http://[::1]:3000",
|
||||||
"BISQ_ENABLED": true,
|
"BISQ_ENABLED": true,
|
||||||
"BSQ_BLOCKS_DATA_PATH": "/bisq/data",
|
"BISQ_BLOCKS_DATA_PATH": "/bisq/data",
|
||||||
"SSL": false,
|
"SSL": false,
|
||||||
"SSL_CERT_FILE_PATH": "/etc/letsencrypt/live/mysite/fullchain.pem",
|
"SSL_CERT_FILE_PATH": "/etc/letsencrypt/live/mysite/fullchain.pem",
|
||||||
"SSL_KEY_FILE_PATH": "/etc/letsencrypt/live/mysite/privkey.pem"
|
"SSL_KEY_FILE_PATH": "/etc/letsencrypt/live/mysite/privkey.pem"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user