Merge pull request #4747 from mempool/natsoni/more-fiat-currencies
Use fx rates to add more currencies support and improve fiat prices
This commit is contained in:
commit
81c8c8dafb
@ -155,5 +155,9 @@
|
|||||||
"MEMPOOL_SERVICES": {
|
"MEMPOOL_SERVICES": {
|
||||||
"API": "https://mempool.space/api",
|
"API": "https://mempool.space/api",
|
||||||
"ACCELERATIONS": false
|
"ACCELERATIONS": false
|
||||||
|
},
|
||||||
|
"FIAT_PRICE": {
|
||||||
|
"ENABLED": true,
|
||||||
|
"API_KEY": "your-api-key-from-freecurrencyapi.com"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -147,5 +147,9 @@
|
|||||||
"ENABLED": false,
|
"ENABLED": false,
|
||||||
"UNIX_SOCKET_PATH": "/tmp/redis.sock",
|
"UNIX_SOCKET_PATH": "/tmp/redis.sock",
|
||||||
"BATCH_QUERY_BASE_SIZE": 5000
|
"BATCH_QUERY_BASE_SIZE": 5000
|
||||||
|
},
|
||||||
|
"FIAT_PRICE": {
|
||||||
|
"ENABLED": true,
|
||||||
|
"API_KEY": "__MEMPOOL_CURRENCY_API_KEY__"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -152,6 +152,11 @@ describe('Mempool Backend Config', () => {
|
|||||||
UNIX_SOCKET_PATH: '',
|
UNIX_SOCKET_PATH: '',
|
||||||
BATCH_QUERY_BASE_SIZE: 5000,
|
BATCH_QUERY_BASE_SIZE: 5000,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
expect(config.FIAT_PRICE).toStrictEqual({
|
||||||
|
ENABLED: true,
|
||||||
|
API_KEY: '',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import cpfpRepository from '../repositories/CpfpRepository';
|
|||||||
import { RowDataPacket } from 'mysql2';
|
import { RowDataPacket } from 'mysql2';
|
||||||
|
|
||||||
class DatabaseMigration {
|
class DatabaseMigration {
|
||||||
private static currentVersion = 74;
|
private static currentVersion = 75;
|
||||||
private queryTimeout = 3600_000;
|
private queryTimeout = 3600_000;
|
||||||
private statisticsAddedIndexed = false;
|
private statisticsAddedIndexed = false;
|
||||||
private uniqueLogs: string[] = [];
|
private uniqueLogs: string[] = [];
|
||||||
@ -608,7 +608,7 @@ class DatabaseMigration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (databaseSchemaVersion < 72 && isBitcoin === true) {
|
if (databaseSchemaVersion < 72 && isBitcoin === true) {
|
||||||
// reindex Goggles flags for mined block templates above height 833000
|
// reindex Goggles flags for mined block templates above height 832000
|
||||||
await this.$executeQuery('UPDATE blocks_summaries SET version = 0 WHERE height >= 832000;');
|
await this.$executeQuery('UPDATE blocks_summaries SET version = 0 WHERE height >= 832000;');
|
||||||
await this.updateToSchemaVersion(72);
|
await this.updateToSchemaVersion(72);
|
||||||
}
|
}
|
||||||
@ -624,6 +624,36 @@ class DatabaseMigration {
|
|||||||
await this.$executeQuery(`INSERT INTO state(name, number) VALUE ('last_acceleration_block', 0);`);
|
await this.$executeQuery(`INSERT INTO state(name, number) VALUE ('last_acceleration_block', 0);`);
|
||||||
await this.updateToSchemaVersion(74);
|
await this.updateToSchemaVersion(74);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (databaseSchemaVersion < 75) {
|
||||||
|
await this.$executeQuery('ALTER TABLE `prices` ADD `BGN` float DEFAULT "-1"');
|
||||||
|
await this.$executeQuery('ALTER TABLE `prices` ADD `BRL` float DEFAULT "-1"');
|
||||||
|
await this.$executeQuery('ALTER TABLE `prices` ADD `CNY` float DEFAULT "-1"');
|
||||||
|
await this.$executeQuery('ALTER TABLE `prices` ADD `CZK` float DEFAULT "-1"');
|
||||||
|
await this.$executeQuery('ALTER TABLE `prices` ADD `DKK` float DEFAULT "-1"');
|
||||||
|
await this.$executeQuery('ALTER TABLE `prices` ADD `HKD` float DEFAULT "-1"');
|
||||||
|
await this.$executeQuery('ALTER TABLE `prices` ADD `HRK` float DEFAULT "-1"');
|
||||||
|
await this.$executeQuery('ALTER TABLE `prices` ADD `HUF` float DEFAULT "-1"');
|
||||||
|
await this.$executeQuery('ALTER TABLE `prices` ADD `IDR` float DEFAULT "-1"');
|
||||||
|
await this.$executeQuery('ALTER TABLE `prices` ADD `ILS` float DEFAULT "-1"');
|
||||||
|
await this.$executeQuery('ALTER TABLE `prices` ADD `INR` float DEFAULT "-1"');
|
||||||
|
await this.$executeQuery('ALTER TABLE `prices` ADD `ISK` float DEFAULT "-1"');
|
||||||
|
await this.$executeQuery('ALTER TABLE `prices` ADD `KRW` float DEFAULT "-1"');
|
||||||
|
await this.$executeQuery('ALTER TABLE `prices` ADD `MXN` float DEFAULT "-1"');
|
||||||
|
await this.$executeQuery('ALTER TABLE `prices` ADD `MYR` float DEFAULT "-1"');
|
||||||
|
await this.$executeQuery('ALTER TABLE `prices` ADD `NOK` float DEFAULT "-1"');
|
||||||
|
await this.$executeQuery('ALTER TABLE `prices` ADD `NZD` float DEFAULT "-1"');
|
||||||
|
await this.$executeQuery('ALTER TABLE `prices` ADD `PHP` float DEFAULT "-1"');
|
||||||
|
await this.$executeQuery('ALTER TABLE `prices` ADD `PLN` float DEFAULT "-1"');
|
||||||
|
await this.$executeQuery('ALTER TABLE `prices` ADD `RON` float DEFAULT "-1"');
|
||||||
|
await this.$executeQuery('ALTER TABLE `prices` ADD `RUB` float DEFAULT "-1"');
|
||||||
|
await this.$executeQuery('ALTER TABLE `prices` ADD `SEK` float DEFAULT "-1"');
|
||||||
|
await this.$executeQuery('ALTER TABLE `prices` ADD `SGD` float DEFAULT "-1"');
|
||||||
|
await this.$executeQuery('ALTER TABLE `prices` ADD `THB` float DEFAULT "-1"');
|
||||||
|
await this.$executeQuery('ALTER TABLE `prices` ADD `TRY` float DEFAULT "-1"');
|
||||||
|
await this.$executeQuery('ALTER TABLE `prices` ADD `ZAR` float DEFAULT "-1"');
|
||||||
|
await this.updateToSchemaVersion(75);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -51,13 +51,20 @@ class MiningRoutes {
|
|||||||
res.status(400).send('Prices are not available on testnets.');
|
res.status(400).send('Prices are not available on testnets.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (req.query.timestamp) {
|
const timestamp = parseInt(req.query.timestamp as string, 10) || 0;
|
||||||
res.status(200).send(await PricesRepository.$getNearestHistoricalPrice(
|
const currency = req.query.currency as string;
|
||||||
parseInt(<string>req.query.timestamp ?? 0, 10)
|
|
||||||
));
|
let response;
|
||||||
|
if (timestamp && currency) {
|
||||||
|
response = await PricesRepository.$getNearestHistoricalPrice(timestamp, currency);
|
||||||
|
} else if (timestamp) {
|
||||||
|
response = await PricesRepository.$getNearestHistoricalPrice(timestamp);
|
||||||
|
} else if (currency) {
|
||||||
|
response = await PricesRepository.$getHistoricalPrices(currency);
|
||||||
} else {
|
} else {
|
||||||
res.status(200).send(await PricesRepository.$getHistoricalPrices());
|
response = await PricesRepository.$getHistoricalPrices();
|
||||||
}
|
}
|
||||||
|
res.status(200).send(response);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
res.status(500).send(e instanceof Error ? e.message : e);
|
res.status(500).send(e instanceof Error ? e.message : e);
|
||||||
}
|
}
|
||||||
|
@ -158,6 +158,10 @@ interface IConfig {
|
|||||||
UNIX_SOCKET_PATH: string;
|
UNIX_SOCKET_PATH: string;
|
||||||
BATCH_QUERY_BASE_SIZE: number;
|
BATCH_QUERY_BASE_SIZE: number;
|
||||||
},
|
},
|
||||||
|
FIAT_PRICE: {
|
||||||
|
ENABLED: boolean;
|
||||||
|
API_KEY: string;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaults: IConfig = {
|
const defaults: IConfig = {
|
||||||
@ -316,6 +320,10 @@ const defaults: IConfig = {
|
|||||||
'UNIX_SOCKET_PATH': '',
|
'UNIX_SOCKET_PATH': '',
|
||||||
'BATCH_QUERY_BASE_SIZE': 5000,
|
'BATCH_QUERY_BASE_SIZE': 5000,
|
||||||
},
|
},
|
||||||
|
'FIAT_PRICE': {
|
||||||
|
'ENABLED': true,
|
||||||
|
'API_KEY': '',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
class Config implements IConfig {
|
class Config implements IConfig {
|
||||||
@ -337,6 +345,7 @@ class Config implements IConfig {
|
|||||||
REPLICATION: IConfig['REPLICATION'];
|
REPLICATION: IConfig['REPLICATION'];
|
||||||
MEMPOOL_SERVICES: IConfig['MEMPOOL_SERVICES'];
|
MEMPOOL_SERVICES: IConfig['MEMPOOL_SERVICES'];
|
||||||
REDIS: IConfig['REDIS'];
|
REDIS: IConfig['REDIS'];
|
||||||
|
FIAT_PRICE: IConfig['FIAT_PRICE'];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
const configs = this.merge(configFromFile, defaults);
|
const configs = this.merge(configFromFile, defaults);
|
||||||
@ -358,6 +367,7 @@ class Config implements IConfig {
|
|||||||
this.REPLICATION = configs.REPLICATION;
|
this.REPLICATION = configs.REPLICATION;
|
||||||
this.MEMPOOL_SERVICES = configs.MEMPOOL_SERVICES;
|
this.MEMPOOL_SERVICES = configs.MEMPOOL_SERVICES;
|
||||||
this.REDIS = configs.REDIS;
|
this.REDIS = configs.REDIS;
|
||||||
|
this.FIAT_PRICE = configs.FIAT_PRICE;
|
||||||
}
|
}
|
||||||
|
|
||||||
merge = (...objects: object[]): IConfig => {
|
merge = (...objects: object[]): IConfig => {
|
||||||
|
@ -131,7 +131,7 @@ class Server {
|
|||||||
.use(express.text({ type: ['text/plain', 'application/base64'] }))
|
.use(express.text({ type: ['text/plain', 'application/base64'] }))
|
||||||
;
|
;
|
||||||
|
|
||||||
if (config.DATABASE.ENABLED) {
|
if (config.DATABASE.ENABLED && config.FIAT_PRICE.ENABLED) {
|
||||||
await priceUpdater.$initializeLatestPriceWithDb();
|
await priceUpdater.$initializeLatestPriceWithDb();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,7 +168,9 @@ class Server {
|
|||||||
setInterval(refreshIcons, 3600_000);
|
setInterval(refreshIcons, 3600_000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.FIAT_PRICE.ENABLED) {
|
||||||
priceUpdater.$run();
|
priceUpdater.$run();
|
||||||
|
}
|
||||||
await chainTips.updateOrphanedBlocks();
|
await chainTips.updateOrphanedBlocks();
|
||||||
|
|
||||||
this.setUpHttpApiRoutes();
|
this.setUpHttpApiRoutes();
|
||||||
@ -220,7 +222,9 @@ class Server {
|
|||||||
await memPool.$updateMempool(newMempool, newAccelerations, pollRate);
|
await memPool.$updateMempool(newMempool, newAccelerations, pollRate);
|
||||||
}
|
}
|
||||||
indexer.$run();
|
indexer.$run();
|
||||||
|
if (config.FIAT_PRICE.ENABLED) {
|
||||||
priceUpdater.$run();
|
priceUpdater.$run();
|
||||||
|
}
|
||||||
|
|
||||||
// rerun immediately if we skipped the mempool update, otherwise wait POLL_RATE_MS
|
// rerun immediately if we skipped the mempool update, otherwise wait POLL_RATE_MS
|
||||||
const elapsed = Date.now() - start;
|
const elapsed = Date.now() - start;
|
||||||
@ -284,7 +288,9 @@ class Server {
|
|||||||
memPool.setAsyncMempoolChangedCallback(websocketHandler.$handleMempoolChange.bind(websocketHandler));
|
memPool.setAsyncMempoolChangedCallback(websocketHandler.$handleMempoolChange.bind(websocketHandler));
|
||||||
blocks.setNewAsyncBlockCallback(websocketHandler.handleNewBlock.bind(websocketHandler));
|
blocks.setNewAsyncBlockCallback(websocketHandler.handleNewBlock.bind(websocketHandler));
|
||||||
}
|
}
|
||||||
|
if (config.FIAT_PRICE.ENABLED) {
|
||||||
priceUpdater.setRatesChangedCallback(websocketHandler.handleNewConversionRates.bind(websocketHandler));
|
priceUpdater.setRatesChangedCallback(websocketHandler.handleNewConversionRates.bind(websocketHandler));
|
||||||
|
}
|
||||||
loadingIndicators.setProgressChangedCallback(websocketHandler.handleLoadingChanged.bind(websocketHandler));
|
loadingIndicators.setProgressChangedCallback(websocketHandler.handleLoadingChanged.bind(websocketHandler));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@ class Indexer {
|
|||||||
|
|
||||||
switch (task) {
|
switch (task) {
|
||||||
case 'blocksPrices': {
|
case 'blocksPrices': {
|
||||||
if (!['testnet', 'signet'].includes(config.MEMPOOL.NETWORK)) {
|
if (!['testnet', 'signet'].includes(config.MEMPOOL.NETWORK) && config.FIAT_PRICE.ENABLED) {
|
||||||
let lastestPriceId;
|
let lastestPriceId;
|
||||||
try {
|
try {
|
||||||
lastestPriceId = await PricesRepository.$getLatestPriceId();
|
lastestPriceId = await PricesRepository.$getLatestPriceId();
|
||||||
@ -149,11 +149,13 @@ class Indexer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.FIAT_PRICE.ENABLED) {
|
||||||
try {
|
try {
|
||||||
await priceUpdater.$run();
|
await priceUpdater.$run();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.err(`Running priceUpdater failed. Reason: ` + (e instanceof Error ? e.message : e));
|
logger.err(`Running priceUpdater failed. Reason: ` + (e instanceof Error ? e.message : e));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Do not attempt to index anything unless Bitcoin Core is fully synced
|
// Do not attempt to index anything unless Bitcoin Core is fully synced
|
||||||
const blockchainInfo = await bitcoinClient.getBlockchainInfo();
|
const blockchainInfo = await bitcoinClient.getBlockchainInfo();
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import DB from '../database';
|
import DB from '../database';
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
|
import config from '../config';
|
||||||
import priceUpdater from '../tasks/price-updater';
|
import priceUpdater from '../tasks/price-updater';
|
||||||
|
|
||||||
export interface ApiPrice {
|
export interface ApiPrice {
|
||||||
@ -11,8 +12,72 @@ export interface ApiPrice {
|
|||||||
CHF: number,
|
CHF: number,
|
||||||
AUD: number,
|
AUD: number,
|
||||||
JPY: number,
|
JPY: number,
|
||||||
|
BGN: number,
|
||||||
|
BRL: number,
|
||||||
|
CNY: number,
|
||||||
|
CZK: number,
|
||||||
|
DKK: number,
|
||||||
|
HKD: number,
|
||||||
|
HRK: number,
|
||||||
|
HUF: number,
|
||||||
|
IDR: number,
|
||||||
|
ILS: number,
|
||||||
|
INR: number,
|
||||||
|
ISK: number,
|
||||||
|
KRW: number,
|
||||||
|
MXN: number,
|
||||||
|
MYR: number,
|
||||||
|
NOK: number,
|
||||||
|
NZD: number,
|
||||||
|
PHP: number,
|
||||||
|
PLN: number,
|
||||||
|
RON: number,
|
||||||
|
RUB: number,
|
||||||
|
SEK: number,
|
||||||
|
SGD: number,
|
||||||
|
THB: number,
|
||||||
|
TRY: number,
|
||||||
|
ZAR: number,
|
||||||
}
|
}
|
||||||
const ApiPriceFields = `
|
|
||||||
|
const ApiPriceFields = config.FIAT_PRICE.API_KEY ?
|
||||||
|
`
|
||||||
|
UNIX_TIMESTAMP(time) as time,
|
||||||
|
USD,
|
||||||
|
EUR,
|
||||||
|
GBP,
|
||||||
|
CAD,
|
||||||
|
CHF,
|
||||||
|
AUD,
|
||||||
|
JPY,
|
||||||
|
BGN,
|
||||||
|
BRL,
|
||||||
|
CNY,
|
||||||
|
CZK,
|
||||||
|
DKK,
|
||||||
|
HKD,
|
||||||
|
HRK,
|
||||||
|
HUF,
|
||||||
|
IDR,
|
||||||
|
ILS,
|
||||||
|
INR,
|
||||||
|
ISK,
|
||||||
|
KRW,
|
||||||
|
MXN,
|
||||||
|
MYR,
|
||||||
|
NOK,
|
||||||
|
NZD,
|
||||||
|
PHP,
|
||||||
|
PLN,
|
||||||
|
RON,
|
||||||
|
RUB,
|
||||||
|
SEK,
|
||||||
|
SGD,
|
||||||
|
THB,
|
||||||
|
TRY,
|
||||||
|
ZAR
|
||||||
|
`:
|
||||||
|
`
|
||||||
UNIX_TIMESTAMP(time) as time,
|
UNIX_TIMESTAMP(time) as time,
|
||||||
USD,
|
USD,
|
||||||
EUR,
|
EUR,
|
||||||
@ -21,7 +86,7 @@ const ApiPriceFields = `
|
|||||||
CHF,
|
CHF,
|
||||||
AUD,
|
AUD,
|
||||||
JPY
|
JPY
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export interface ExchangeRates {
|
export interface ExchangeRates {
|
||||||
USDEUR: number,
|
USDEUR: number,
|
||||||
@ -30,6 +95,32 @@ export interface ExchangeRates {
|
|||||||
USDCHF: number,
|
USDCHF: number,
|
||||||
USDAUD: number,
|
USDAUD: number,
|
||||||
USDJPY: number,
|
USDJPY: number,
|
||||||
|
USDBGN?: number,
|
||||||
|
USDBRL?: number,
|
||||||
|
USDCNY?: number,
|
||||||
|
USDCZK?: number,
|
||||||
|
USDDKK?: number,
|
||||||
|
USDHKD?: number,
|
||||||
|
USDHRK?: number,
|
||||||
|
USDHUF?: number,
|
||||||
|
USDIDR?: number,
|
||||||
|
USDILS?: number,
|
||||||
|
USDINR?: number,
|
||||||
|
USDISK?: number,
|
||||||
|
USDKRW?: number,
|
||||||
|
USDMXN?: number,
|
||||||
|
USDMYR?: number,
|
||||||
|
USDNOK?: number,
|
||||||
|
USDNZD?: number,
|
||||||
|
USDPHP?: number,
|
||||||
|
USDPLN?: number,
|
||||||
|
USDRON?: number,
|
||||||
|
USDRUB?: number,
|
||||||
|
USDSEK?: number,
|
||||||
|
USDSGD?: number,
|
||||||
|
USDTHB?: number,
|
||||||
|
USDTRY?: number,
|
||||||
|
USDZAR?: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Conversion {
|
export interface Conversion {
|
||||||
@ -45,6 +136,32 @@ export const MAX_PRICES = {
|
|||||||
CHF: 100000000,
|
CHF: 100000000,
|
||||||
AUD: 100000000,
|
AUD: 100000000,
|
||||||
JPY: 10000000000,
|
JPY: 10000000000,
|
||||||
|
BGN: 1000000000,
|
||||||
|
BRL: 1000000000,
|
||||||
|
CNY: 1000000000,
|
||||||
|
CZK: 10000000000,
|
||||||
|
DKK: 1000000000,
|
||||||
|
HKD: 1000000000,
|
||||||
|
HRK: 1000000000,
|
||||||
|
HUF: 10000000000,
|
||||||
|
IDR: 100000000000,
|
||||||
|
ILS: 1000000000,
|
||||||
|
INR: 10000000000,
|
||||||
|
ISK: 10000000000,
|
||||||
|
KRW: 100000000000,
|
||||||
|
MXN: 1000000000,
|
||||||
|
MYR: 1000000000,
|
||||||
|
NOK: 1000000000,
|
||||||
|
NZD: 1000000000,
|
||||||
|
PHP: 10000000000,
|
||||||
|
PLN: 1000000000,
|
||||||
|
RON: 1000000000,
|
||||||
|
RUB: 10000000000,
|
||||||
|
SEK: 1000000000,
|
||||||
|
SGD: 100000000,
|
||||||
|
THB: 10000000000,
|
||||||
|
TRY: 10000000000,
|
||||||
|
ZAR: 10000000000,
|
||||||
};
|
};
|
||||||
|
|
||||||
class PricesRepository {
|
class PricesRepository {
|
||||||
@ -64,17 +181,49 @@ class PricesRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (!config.FIAT_PRICE.API_KEY) { // Store only the 7 main currencies
|
||||||
await DB.query(`
|
await DB.query(`
|
||||||
INSERT INTO prices(time, USD, EUR, GBP, CAD, CHF, AUD, JPY)
|
INSERT INTO prices(time, USD, EUR, GBP, CAD, CHF, AUD, JPY)
|
||||||
VALUE (FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?, ? )`,
|
VALUE (FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?, ? )`,
|
||||||
[time, prices.USD, prices.EUR, prices.GBP, prices.CAD, prices.CHF, prices.AUD, prices.JPY]
|
[time, prices.USD, prices.EUR, prices.GBP, prices.CAD, prices.CHF, prices.AUD, prices.JPY]
|
||||||
);
|
);
|
||||||
|
} else { // Store all 7 main currencies + all the currencies obtained with the external API
|
||||||
|
await DB.query(`
|
||||||
|
INSERT INTO prices(time, USD, EUR, GBP, CAD, CHF, AUD, JPY, BGN, BRL, CNY, CZK, DKK, HKD, HRK, HUF, IDR, ILS, INR, ISK, KRW, MXN, MYR, NOK, NZD, PHP, PLN, RON, RUB, SEK, SGD, THB, TRY, ZAR)
|
||||||
|
VALUE (FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?, ?, ? , ?, ?, ?, ?, ?, ?, ?, ? , ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? , ? )`,
|
||||||
|
[time, prices.USD, prices.EUR, prices.GBP, prices.CAD, prices.CHF, prices.AUD, prices.JPY, prices.BGN, prices.BRL, prices.CNY, prices.CZK, prices.DKK,
|
||||||
|
prices.HKD, prices.HRK, prices.HUF, prices.IDR, prices.ILS, prices.INR, prices.ISK, prices.KRW, prices.MXN, prices.MYR, prices.NOK, prices.NZD,
|
||||||
|
prices.PHP, prices.PLN, prices.RON, prices.RUB, prices.SEK, prices.SGD, prices.THB, prices.TRY, prices.ZAR]
|
||||||
|
);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.err(`Cannot save exchange rate into db. Reason: ` + (e instanceof Error ? e.message : e));
|
logger.err(`Cannot save exchange rate into db. Reason: ` + (e instanceof Error ? e.message : e));
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async $saveAdditionalCurrencyPrices(time: number, prices: ApiPrice, legacyCurrencies: string[]): Promise<void> {
|
||||||
|
try {
|
||||||
|
await DB.query(`
|
||||||
|
UPDATE prices
|
||||||
|
SET BGN = ?, BRL = ?, CNY = ?, CZK = ?, DKK = ?, HKD = ?, HRK = ?, HUF = ?, IDR = ?, ILS = ?, INR = ?, ISK = ?, KRW = ?, MXN = ?, MYR = ?, NOK = ?, NZD = ?, PHP = ?, PLN = ?, RON = ?, RUB = ?, SEK = ?, SGD = ?, THB = ?, TRY = ?, ZAR = ?
|
||||||
|
WHERE UNIX_TIMESTAMP(time) = ?`,
|
||||||
|
[prices.BGN, prices.BRL, prices.CNY, prices.CZK, prices.DKK, prices.HKD, prices.HRK, prices.HUF, prices.IDR, prices.ILS, prices.INR, prices.ISK, prices.KRW, prices.MXN, prices.MYR, prices.NOK, prices.NZD, prices.PHP, prices.PLN, prices.RON, prices.RUB, prices.SEK, prices.SGD, prices.THB, prices.TRY, prices.ZAR, time]
|
||||||
|
);
|
||||||
|
for (const currency of legacyCurrencies) {
|
||||||
|
await DB.query(`
|
||||||
|
UPDATE prices
|
||||||
|
SET ${currency} = ?
|
||||||
|
WHERE UNIX_TIMESTAMP(time) = ?`,
|
||||||
|
[prices[currency], time]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger.err(`Cannot update exchange rate into db. Reason: ` + (e instanceof Error ? e.message : e));
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async $getOldestPriceTime(): Promise<number> {
|
public async $getOldestPriceTime(): Promise<number> {
|
||||||
const [oldestRow] = await DB.query(`
|
const [oldestRow] = await DB.query(`
|
||||||
SELECT UNIX_TIMESTAMP(time) AS time
|
SELECT UNIX_TIMESTAMP(time) AS time
|
||||||
@ -118,6 +267,28 @@ class PricesRepository {
|
|||||||
return times.map(time => time.time);
|
return times.map(time => time.time);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async $getPricesTimesWithMissingFields(): Promise<{time: number, USD: number, eur_missing: boolean, gbp_missing: boolean, cad_missing: boolean, chf_missing: boolean, aud_missing: boolean, jpy_missing: boolean}[]> {
|
||||||
|
const [times] = await DB.query(`
|
||||||
|
SELECT UNIX_TIMESTAMP(time) AS time,
|
||||||
|
USD,
|
||||||
|
CASE WHEN EUR = -1 THEN TRUE ELSE FALSE END AS eur_missing,
|
||||||
|
CASE WHEN GBP = -1 THEN TRUE ELSE FALSE END AS gbp_missing,
|
||||||
|
CASE WHEN CAD = -1 THEN TRUE ELSE FALSE END AS cad_missing,
|
||||||
|
CASE WHEN CHF = -1 THEN TRUE ELSE FALSE END AS chf_missing,
|
||||||
|
CASE WHEN AUD = -1 THEN TRUE ELSE FALSE END AS aud_missing,
|
||||||
|
CASE WHEN JPY = -1 THEN TRUE ELSE FALSE END AS jpy_missing
|
||||||
|
FROM prices
|
||||||
|
WHERE USD != -1
|
||||||
|
AND -1 IN (EUR, GBP, CAD, CHF, AUD, JPY, BGN, BRL, CNY, CZK, DKK, HKD, HRK, HUF, IDR, ILS, INR, ISK, KRW,
|
||||||
|
MXN, MYR, NOK, NZD, PHP, PLN, RON, RUB, SEK, SGD, THB, TRY, ZAR)
|
||||||
|
ORDER BY time DESC
|
||||||
|
`);
|
||||||
|
if (!Array.isArray(times)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return times as {time: number, USD: number, eur_missing: boolean, gbp_missing: boolean, cad_missing: boolean, chf_missing: boolean, aud_missing: boolean, jpy_missing: boolean}[];
|
||||||
|
}
|
||||||
|
|
||||||
public async $getPricesTimesAndId(): Promise<{time: number, id: number, USD: number}[]> {
|
public async $getPricesTimesAndId(): Promise<{time: number, id: number, USD: number}[]> {
|
||||||
const [times] = await DB.query(`
|
const [times] = await DB.query(`
|
||||||
SELECT
|
SELECT
|
||||||
@ -144,7 +315,7 @@ class PricesRepository {
|
|||||||
return rates[0] as ApiPrice;
|
return rates[0] as ApiPrice;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async $getNearestHistoricalPrice(timestamp: number | undefined): Promise<Conversion | null> {
|
public async $getNearestHistoricalPrice(timestamp: number | undefined, currency?: string): Promise<Conversion | null> {
|
||||||
try {
|
try {
|
||||||
const [rates] = await DB.query(`
|
const [rates] = await DB.query(`
|
||||||
SELECT ${ApiPriceFields}
|
SELECT ${ApiPriceFields}
|
||||||
@ -158,16 +329,59 @@ class PricesRepository {
|
|||||||
throw Error(`Cannot get single historical price from the database`);
|
throw Error(`Cannot get single historical price from the database`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [latestPrices] = await DB.query(`
|
||||||
|
SELECT ${ApiPriceFields}
|
||||||
|
FROM prices
|
||||||
|
ORDER BY time DESC
|
||||||
|
LIMIT 1
|
||||||
|
`);
|
||||||
|
if (!Array.isArray(latestPrices)) {
|
||||||
|
throw Error(`Cannot get single historical price from the database`);
|
||||||
|
}
|
||||||
|
|
||||||
// Compute fiat exchange rates
|
// Compute fiat exchange rates
|
||||||
let latestPrice = rates[0] as ApiPrice;
|
let latestPrice = latestPrices[0] as ApiPrice;
|
||||||
if (!latestPrice || latestPrice.USD === -1) {
|
if (!latestPrice || latestPrice.USD === -1) {
|
||||||
latestPrice = priceUpdater.getEmptyPricesObj();
|
latestPrice = priceUpdater.getEmptyPricesObj();
|
||||||
}
|
}
|
||||||
|
|
||||||
const computeFx = (usd: number, other: number): number =>
|
const computeFx = (usd: number, other: number): number => usd <= 0.05 ? 0 : Math.round(Math.max(other, 0) / usd * 100) / 100;
|
||||||
Math.round(Math.max(other, 0) / Math.max(usd, 1) * 100) / 100;
|
|
||||||
|
|
||||||
const exchangeRates: ExchangeRates = {
|
const exchangeRates: ExchangeRates = config.FIAT_PRICE.API_KEY ?
|
||||||
|
{
|
||||||
|
USDEUR: computeFx(latestPrice.USD, latestPrice.EUR),
|
||||||
|
USDGBP: computeFx(latestPrice.USD, latestPrice.GBP),
|
||||||
|
USDCAD: computeFx(latestPrice.USD, latestPrice.CAD),
|
||||||
|
USDCHF: computeFx(latestPrice.USD, latestPrice.CHF),
|
||||||
|
USDAUD: computeFx(latestPrice.USD, latestPrice.AUD),
|
||||||
|
USDJPY: computeFx(latestPrice.USD, latestPrice.JPY),
|
||||||
|
USDBGN: computeFx(latestPrice.USD, latestPrice.BGN),
|
||||||
|
USDBRL: computeFx(latestPrice.USD, latestPrice.BRL),
|
||||||
|
USDCNY: computeFx(latestPrice.USD, latestPrice.CNY),
|
||||||
|
USDCZK: computeFx(latestPrice.USD, latestPrice.CZK),
|
||||||
|
USDDKK: computeFx(latestPrice.USD, latestPrice.DKK),
|
||||||
|
USDHKD: computeFx(latestPrice.USD, latestPrice.HKD),
|
||||||
|
USDHRK: computeFx(latestPrice.USD, latestPrice.HRK),
|
||||||
|
USDHUF: computeFx(latestPrice.USD, latestPrice.HUF),
|
||||||
|
USDIDR: computeFx(latestPrice.USD, latestPrice.IDR),
|
||||||
|
USDILS: computeFx(latestPrice.USD, latestPrice.ILS),
|
||||||
|
USDINR: computeFx(latestPrice.USD, latestPrice.INR),
|
||||||
|
USDISK: computeFx(latestPrice.USD, latestPrice.ISK),
|
||||||
|
USDKRW: computeFx(latestPrice.USD, latestPrice.KRW),
|
||||||
|
USDMXN: computeFx(latestPrice.USD, latestPrice.MXN),
|
||||||
|
USDMYR: computeFx(latestPrice.USD, latestPrice.MYR),
|
||||||
|
USDNOK: computeFx(latestPrice.USD, latestPrice.NOK),
|
||||||
|
USDNZD: computeFx(latestPrice.USD, latestPrice.NZD),
|
||||||
|
USDPHP: computeFx(latestPrice.USD, latestPrice.PHP),
|
||||||
|
USDPLN: computeFx(latestPrice.USD, latestPrice.PLN),
|
||||||
|
USDRON: computeFx(latestPrice.USD, latestPrice.RON),
|
||||||
|
USDRUB: computeFx(latestPrice.USD, latestPrice.RUB),
|
||||||
|
USDSEK: computeFx(latestPrice.USD, latestPrice.SEK),
|
||||||
|
USDSGD: computeFx(latestPrice.USD, latestPrice.SGD),
|
||||||
|
USDTHB: computeFx(latestPrice.USD, latestPrice.THB),
|
||||||
|
USDTRY: computeFx(latestPrice.USD, latestPrice.TRY),
|
||||||
|
USDZAR: computeFx(latestPrice.USD, latestPrice.ZAR),
|
||||||
|
} : {
|
||||||
USDEUR: computeFx(latestPrice.USD, latestPrice.EUR),
|
USDEUR: computeFx(latestPrice.USD, latestPrice.EUR),
|
||||||
USDGBP: computeFx(latestPrice.USD, latestPrice.GBP),
|
USDGBP: computeFx(latestPrice.USD, latestPrice.GBP),
|
||||||
USDCAD: computeFx(latestPrice.USD, latestPrice.CAD),
|
USDCAD: computeFx(latestPrice.USD, latestPrice.CAD),
|
||||||
@ -176,6 +390,30 @@ class PricesRepository {
|
|||||||
USDJPY: computeFx(latestPrice.USD, latestPrice.JPY),
|
USDJPY: computeFx(latestPrice.USD, latestPrice.JPY),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (currency) {
|
||||||
|
if (!latestPrice[currency]) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const filteredRates = rates.map((rate: any) => {
|
||||||
|
return {
|
||||||
|
time: rate.time,
|
||||||
|
[currency]: rate[currency],
|
||||||
|
['USD']: rate['USD']
|
||||||
|
};
|
||||||
|
});
|
||||||
|
if (filteredRates.length === 0) { // No price data before 2010-07-19: add a fake entry
|
||||||
|
filteredRates.push({
|
||||||
|
time: 1279497600,
|
||||||
|
[currency]: 0,
|
||||||
|
['USD']: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
prices: filteredRates as ApiPrice[],
|
||||||
|
exchangeRates: exchangeRates
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
prices: rates as ApiPrice[],
|
prices: rates as ApiPrice[],
|
||||||
exchangeRates: exchangeRates
|
exchangeRates: exchangeRates
|
||||||
@ -186,7 +424,7 @@ class PricesRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async $getHistoricalPrices(): Promise<Conversion | null> {
|
public async $getHistoricalPrices(currency?: string): Promise<Conversion | null> {
|
||||||
try {
|
try {
|
||||||
const [rates] = await DB.query(`
|
const [rates] = await DB.query(`
|
||||||
SELECT ${ApiPriceFields}
|
SELECT ${ApiPriceFields}
|
||||||
@ -204,9 +442,43 @@ class PricesRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const computeFx = (usd: number, other: number): number =>
|
const computeFx = (usd: number, other: number): number =>
|
||||||
Math.round(Math.max(other, 0) / Math.max(usd, 1) * 100) / 100;
|
usd <= 0 ? 0 : Math.round(Math.max(other, 0) / usd * 100) / 100;
|
||||||
|
|
||||||
const exchangeRates: ExchangeRates = {
|
const exchangeRates: ExchangeRates = config.FIAT_PRICE.API_KEY ?
|
||||||
|
{
|
||||||
|
USDEUR: computeFx(latestPrice.USD, latestPrice.EUR),
|
||||||
|
USDGBP: computeFx(latestPrice.USD, latestPrice.GBP),
|
||||||
|
USDCAD: computeFx(latestPrice.USD, latestPrice.CAD),
|
||||||
|
USDCHF: computeFx(latestPrice.USD, latestPrice.CHF),
|
||||||
|
USDAUD: computeFx(latestPrice.USD, latestPrice.AUD),
|
||||||
|
USDJPY: computeFx(latestPrice.USD, latestPrice.JPY),
|
||||||
|
USDBGN: computeFx(latestPrice.USD, latestPrice.BGN),
|
||||||
|
USDBRL: computeFx(latestPrice.USD, latestPrice.BRL),
|
||||||
|
USDCNY: computeFx(latestPrice.USD, latestPrice.CNY),
|
||||||
|
USDCZK: computeFx(latestPrice.USD, latestPrice.CZK),
|
||||||
|
USDDKK: computeFx(latestPrice.USD, latestPrice.DKK),
|
||||||
|
USDHKD: computeFx(latestPrice.USD, latestPrice.HKD),
|
||||||
|
USDHRK: computeFx(latestPrice.USD, latestPrice.HRK),
|
||||||
|
USDHUF: computeFx(latestPrice.USD, latestPrice.HUF),
|
||||||
|
USDIDR: computeFx(latestPrice.USD, latestPrice.IDR),
|
||||||
|
USDILS: computeFx(latestPrice.USD, latestPrice.ILS),
|
||||||
|
USDINR: computeFx(latestPrice.USD, latestPrice.INR),
|
||||||
|
USDISK: computeFx(latestPrice.USD, latestPrice.ISK),
|
||||||
|
USDKRW: computeFx(latestPrice.USD, latestPrice.KRW),
|
||||||
|
USDMXN: computeFx(latestPrice.USD, latestPrice.MXN),
|
||||||
|
USDMYR: computeFx(latestPrice.USD, latestPrice.MYR),
|
||||||
|
USDNOK: computeFx(latestPrice.USD, latestPrice.NOK),
|
||||||
|
USDNZD: computeFx(latestPrice.USD, latestPrice.NZD),
|
||||||
|
USDPHP: computeFx(latestPrice.USD, latestPrice.PHP),
|
||||||
|
USDPLN: computeFx(latestPrice.USD, latestPrice.PLN),
|
||||||
|
USDRON: computeFx(latestPrice.USD, latestPrice.RON),
|
||||||
|
USDRUB: computeFx(latestPrice.USD, latestPrice.RUB),
|
||||||
|
USDSEK: computeFx(latestPrice.USD, latestPrice.SEK),
|
||||||
|
USDSGD: computeFx(latestPrice.USD, latestPrice.SGD),
|
||||||
|
USDTHB: computeFx(latestPrice.USD, latestPrice.THB),
|
||||||
|
USDTRY: computeFx(latestPrice.USD, latestPrice.TRY),
|
||||||
|
USDZAR: computeFx(latestPrice.USD, latestPrice.ZAR),
|
||||||
|
} : {
|
||||||
USDEUR: computeFx(latestPrice.USD, latestPrice.EUR),
|
USDEUR: computeFx(latestPrice.USD, latestPrice.EUR),
|
||||||
USDGBP: computeFx(latestPrice.USD, latestPrice.GBP),
|
USDGBP: computeFx(latestPrice.USD, latestPrice.GBP),
|
||||||
USDCAD: computeFx(latestPrice.USD, latestPrice.CAD),
|
USDCAD: computeFx(latestPrice.USD, latestPrice.CAD),
|
||||||
@ -215,6 +487,23 @@ class PricesRepository {
|
|||||||
USDJPY: computeFx(latestPrice.USD, latestPrice.JPY),
|
USDJPY: computeFx(latestPrice.USD, latestPrice.JPY),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (currency) {
|
||||||
|
if (!latestPrice[currency]) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const filteredRates = rates.map((rate: any) => {
|
||||||
|
return {
|
||||||
|
time: rate.time,
|
||||||
|
[currency]: rate[currency],
|
||||||
|
['USD']: rate['USD']
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
prices: filteredRates as ApiPrice[],
|
||||||
|
exchangeRates: exchangeRates
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
prices: rates as ApiPrice[],
|
prices: rates as ApiPrice[],
|
||||||
exchangeRates: exchangeRates
|
exchangeRates: exchangeRates
|
||||||
|
73
backend/src/tasks/price-feeds/free-currency-api.ts
Normal file
73
backend/src/tasks/price-feeds/free-currency-api.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { query } from '../../utils/axios-query';
|
||||||
|
import { ConversionFeed, ConversionRates } from '../price-updater';
|
||||||
|
|
||||||
|
const emptyRates = {
|
||||||
|
AUD: -1,
|
||||||
|
BGN: -1,
|
||||||
|
BRL: -1,
|
||||||
|
CAD: -1,
|
||||||
|
CHF: -1,
|
||||||
|
CNY: -1,
|
||||||
|
CZK: -1,
|
||||||
|
DKK: -1,
|
||||||
|
EUR: -1,
|
||||||
|
GBP: -1,
|
||||||
|
HKD: -1,
|
||||||
|
HRK: -1,
|
||||||
|
HUF: -1,
|
||||||
|
IDR: -1,
|
||||||
|
ILS: -1,
|
||||||
|
INR: -1,
|
||||||
|
ISK: -1,
|
||||||
|
JPY: -1,
|
||||||
|
KRW: -1,
|
||||||
|
MXN: -1,
|
||||||
|
MYR: -1,
|
||||||
|
NOK: -1,
|
||||||
|
NZD: -1,
|
||||||
|
PHP: -1,
|
||||||
|
PLN: -1,
|
||||||
|
RON: -1,
|
||||||
|
RUB: -1,
|
||||||
|
SEK: -1,
|
||||||
|
SGD: -1,
|
||||||
|
THB: -1,
|
||||||
|
TRY: -1,
|
||||||
|
USD: -1,
|
||||||
|
ZAR: -1,
|
||||||
|
};
|
||||||
|
|
||||||
|
class FreeCurrencyApi implements ConversionFeed {
|
||||||
|
private API_KEY: string;
|
||||||
|
|
||||||
|
constructor(apiKey: string) {
|
||||||
|
this.API_KEY = apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async $getQuota(): Promise<any> {
|
||||||
|
const response = await query(`https://api.freecurrencyapi.com/v1/status?apikey=${this.API_KEY}`);
|
||||||
|
if (response && response['quotas']) {
|
||||||
|
return response['quotas'];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async $fetchLatestConversionRates(): Promise<ConversionRates> {
|
||||||
|
const response = await query(`https://api.freecurrencyapi.com/v1/latest?apikey=${this.API_KEY}`);
|
||||||
|
if (response && response['data']) {
|
||||||
|
return response['data'];
|
||||||
|
}
|
||||||
|
return emptyRates;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async $fetchConversionRates(date: string): Promise<ConversionRates> {
|
||||||
|
const response = await query(`https://api.freecurrencyapi.com/v1/historical?date=${date}&apikey=${this.API_KEY}`);
|
||||||
|
if (response && response['data'] && response['data'][date]) {
|
||||||
|
return response['data'][date];
|
||||||
|
}
|
||||||
|
return emptyRates;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FreeCurrencyApi;
|
@ -8,6 +8,7 @@ import BitflyerApi from './price-feeds/bitflyer-api';
|
|||||||
import CoinbaseApi from './price-feeds/coinbase-api';
|
import CoinbaseApi from './price-feeds/coinbase-api';
|
||||||
import GeminiApi from './price-feeds/gemini-api';
|
import GeminiApi from './price-feeds/gemini-api';
|
||||||
import KrakenApi from './price-feeds/kraken-api';
|
import KrakenApi from './price-feeds/kraken-api';
|
||||||
|
import FreeCurrencyApi from './price-feeds/free-currency-api';
|
||||||
|
|
||||||
export interface PriceFeed {
|
export interface PriceFeed {
|
||||||
name: string;
|
name: string;
|
||||||
@ -23,6 +24,16 @@ export interface PriceHistory {
|
|||||||
[timestamp: number]: ApiPrice;
|
[timestamp: number]: ApiPrice;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ConversionFeed {
|
||||||
|
$getQuota(): Promise<any>;
|
||||||
|
$fetchLatestConversionRates(): Promise<ConversionRates>;
|
||||||
|
$fetchConversionRates(date: string): Promise<ConversionRates>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConversionRates {
|
||||||
|
[currency: string]: number
|
||||||
|
}
|
||||||
|
|
||||||
function getMedian(arr: number[]): number {
|
function getMedian(arr: number[]): number {
|
||||||
const sortedArr = arr.slice().sort((a, b) => a - b);
|
const sortedArr = arr.slice().sort((a, b) => a - b);
|
||||||
const mid = Math.floor(sortedArr.length / 2);
|
const mid = Math.floor(sortedArr.length / 2);
|
||||||
@ -33,6 +44,9 @@ function getMedian(arr: number[]): number {
|
|||||||
|
|
||||||
class PriceUpdater {
|
class PriceUpdater {
|
||||||
public historyInserted = false;
|
public historyInserted = false;
|
||||||
|
private additionalCurrenciesHistoryInserted = false;
|
||||||
|
private additionalCurrenciesHistoryRunning = false;
|
||||||
|
private lastFailedHistoricalRun = 0;
|
||||||
private timeBetweenUpdatesMs = 360_0000 / config.MEMPOOL.PRICE_UPDATES_PER_HOUR;
|
private timeBetweenUpdatesMs = 360_0000 / config.MEMPOOL.PRICE_UPDATES_PER_HOUR;
|
||||||
private cyclePosition = -1;
|
private cyclePosition = -1;
|
||||||
private firstRun = true;
|
private firstRun = true;
|
||||||
@ -42,6 +56,10 @@ class PriceUpdater {
|
|||||||
private feeds: PriceFeed[] = [];
|
private feeds: PriceFeed[] = [];
|
||||||
private currencies: string[] = ['USD', 'EUR', 'GBP', 'CAD', 'CHF', 'AUD', 'JPY'];
|
private currencies: string[] = ['USD', 'EUR', 'GBP', 'CAD', 'CHF', 'AUD', 'JPY'];
|
||||||
private latestPrices: ApiPrice;
|
private latestPrices: ApiPrice;
|
||||||
|
private currencyConversionFeed: ConversionFeed | undefined;
|
||||||
|
private newCurrencies: string[] = ['BGN', 'BRL', 'CNY', 'CZK', 'DKK', 'HKD', 'HRK', 'HUF', 'IDR', 'ILS', 'INR', 'ISK', 'KRW', 'MXN', 'MYR', 'NOK', 'NZD', 'PHP', 'PLN', 'RON', 'RUB', 'SEK', 'SGD', 'THB', 'TRY', 'ZAR'];
|
||||||
|
private lastTimeConversionsRatesFetched: number = 0;
|
||||||
|
private latestConversionsRatesFromFeed: ConversionRates = {};
|
||||||
private ratesChangedCallback: ((rates: ApiPrice) => void) | undefined;
|
private ratesChangedCallback: ((rates: ApiPrice) => void) | undefined;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -53,6 +71,7 @@ class PriceUpdater {
|
|||||||
this.feeds.push(new BitfinexApi());
|
this.feeds.push(new BitfinexApi());
|
||||||
this.feeds.push(new GeminiApi());
|
this.feeds.push(new GeminiApi());
|
||||||
|
|
||||||
|
this.currencyConversionFeed = new FreeCurrencyApi(config.FIAT_PRICE.API_KEY);
|
||||||
this.setCyclePosition();
|
this.setCyclePosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,6 +89,32 @@ class PriceUpdater {
|
|||||||
CHF: -1,
|
CHF: -1,
|
||||||
AUD: -1,
|
AUD: -1,
|
||||||
JPY: -1,
|
JPY: -1,
|
||||||
|
BGN: -1,
|
||||||
|
BRL: -1,
|
||||||
|
CNY: -1,
|
||||||
|
CZK: -1,
|
||||||
|
DKK: -1,
|
||||||
|
HKD: -1,
|
||||||
|
HRK: -1,
|
||||||
|
HUF: -1,
|
||||||
|
IDR: -1,
|
||||||
|
ILS: -1,
|
||||||
|
INR: -1,
|
||||||
|
ISK: -1,
|
||||||
|
KRW: -1,
|
||||||
|
MXN: -1,
|
||||||
|
MYR: -1,
|
||||||
|
NOK: -1,
|
||||||
|
NZD: -1,
|
||||||
|
PHP: -1,
|
||||||
|
PLN: -1,
|
||||||
|
RON: -1,
|
||||||
|
RUB: -1,
|
||||||
|
SEK: -1,
|
||||||
|
SGD: -1,
|
||||||
|
THB: -1,
|
||||||
|
TRY: -1,
|
||||||
|
ZAR: -1,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,6 +144,23 @@ class PriceUpdater {
|
|||||||
if ((Math.round(new Date().getTime() / 1000) - this.lastHistoricalRun) > 3600 * 24) {
|
if ((Math.round(new Date().getTime() / 1000) - this.lastHistoricalRun) > 3600 * 24) {
|
||||||
// Once a day, look for missing prices (could happen due to network connectivity issues)
|
// Once a day, look for missing prices (could happen due to network connectivity issues)
|
||||||
this.historyInserted = false;
|
this.historyInserted = false;
|
||||||
|
this.additionalCurrenciesHistoryInserted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.lastFailedHistoricalRun > 0 && (Math.round(new Date().getTime() / 1000) - this.lastFailedHistoricalRun) > 60) {
|
||||||
|
// If the last attempt to insert missing prices failed, we try again after 60 seconds
|
||||||
|
this.additionalCurrenciesHistoryInserted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.FIAT_PRICE.API_KEY && this.currencyConversionFeed && (Math.round(new Date().getTime() / 1000) - this.lastTimeConversionsRatesFetched) > 3600 * 24) {
|
||||||
|
// Once a day, fetch conversion rates from api: we don't need more granularity for fiat currencies and have a limited number of requests
|
||||||
|
try {
|
||||||
|
this.latestConversionsRatesFromFeed = await this.currencyConversionFeed.$fetchLatestConversionRates();
|
||||||
|
this.lastTimeConversionsRatesFetched = Math.round(new Date().getTime() / 1000);
|
||||||
|
logger.debug(`Fetched currencies conversion rates from external API: ${JSON.stringify(this.latestConversionsRatesFromFeed)}`);
|
||||||
|
} catch (e) {
|
||||||
|
logger.err(`Cannot fetch conversion rates from the API. Reason: ${(e instanceof Error ? e.message : e)}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -106,6 +168,10 @@ class PriceUpdater {
|
|||||||
if (this.historyInserted === false && config.DATABASE.ENABLED === true) {
|
if (this.historyInserted === false && config.DATABASE.ENABLED === true) {
|
||||||
await this.$insertHistoricalPrices();
|
await this.$insertHistoricalPrices();
|
||||||
}
|
}
|
||||||
|
if (this.additionalCurrenciesHistoryInserted === false && config.DATABASE.ENABLED === true && config.FIAT_PRICE.API_KEY && !this.additionalCurrenciesHistoryRunning) {
|
||||||
|
await this.$insertMissingAdditionalPrices();
|
||||||
|
}
|
||||||
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
logger.err(`Cannot save BTC prices in db. Reason: ${e instanceof Error ? e.message : e}`, logger.tags.mining);
|
logger.err(`Cannot save BTC prices in db. Reason: ${e instanceof Error ? e.message : e}`, logger.tags.mining);
|
||||||
}
|
}
|
||||||
@ -185,6 +251,14 @@ class PriceUpdater {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.FIAT_PRICE.API_KEY && this.latestPrices.USD > 0 && Object.keys(this.latestConversionsRatesFromFeed).length > 0) {
|
||||||
|
for (const conversionCurrency of this.newCurrencies) {
|
||||||
|
if (this.latestConversionsRatesFromFeed[conversionCurrency] > 0 && this.latestPrices.USD * this.latestConversionsRatesFromFeed[conversionCurrency] < MAX_PRICES[conversionCurrency]) {
|
||||||
|
this.latestPrices[conversionCurrency] = Math.round(this.latestPrices.USD * this.latestConversionsRatesFromFeed[conversionCurrency]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (config.DATABASE.ENABLED === true && this.cyclePosition === 0) {
|
if (config.DATABASE.ENABLED === true && this.cyclePosition === 0) {
|
||||||
// Save everything in db
|
// Save everything in db
|
||||||
try {
|
try {
|
||||||
@ -253,7 +327,7 @@ class PriceUpdater {
|
|||||||
await this.$insertMissingRecentPrices('hour');
|
await this.$insertMissingRecentPrices('hour');
|
||||||
|
|
||||||
this.historyInserted = true;
|
this.historyInserted = true;
|
||||||
this.lastHistoricalRun = new Date().getTime();
|
this.lastHistoricalRun = Math.round(new Date().getTime() / 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -320,6 +394,83 @@ class PriceUpdater {
|
|||||||
logger.debug(`Inserted ${totalInserted} ${type === 'day' ? 'dai' : 'hour'}ly historical prices into the db`, logger.tags.mining);
|
logger.debug(`Inserted ${totalInserted} ${type === 'day' ? 'dai' : 'hour'}ly historical prices into the db`, logger.tags.mining);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find missing prices for additional currencies and insert them in the database
|
||||||
|
* We calculate the additional prices from the USD price and the conversion rates
|
||||||
|
*/
|
||||||
|
private async $insertMissingAdditionalPrices(): Promise<void> {
|
||||||
|
this.lastFailedHistoricalRun = 0;
|
||||||
|
const priceTimesToFill = await PricesRepository.$getPricesTimesWithMissingFields();
|
||||||
|
if (priceTimesToFill.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const remainingQuota = await this.currencyConversionFeed?.$getQuota();
|
||||||
|
if (remainingQuota['month']['remaining'] < 500) { // We need some calls left for the daily updates
|
||||||
|
logger.debug(`Not enough currency API credit to insert missing prices in ${priceTimesToFill.length} rows (${remainingQuota['month']['remaining']} calls left).`, logger.tags.mining);
|
||||||
|
this.additionalCurrenciesHistoryInserted = true; // Do not try again until next day
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger.err(`Cannot fetch currency API credit, insertion of missing prices aborted. Reason: ${(e instanceof Error ? e.message : e)}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.additionalCurrenciesHistoryRunning = true;
|
||||||
|
logger.debug(`Fetching missing conversion rates from external API to fill ${priceTimesToFill.length} rows`, logger.tags.mining);
|
||||||
|
|
||||||
|
let conversionRates: { [timestamp: number]: ConversionRates } = {};
|
||||||
|
let totalInserted = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < priceTimesToFill.length; i++) {
|
||||||
|
const priceTime = priceTimesToFill[i];
|
||||||
|
const missingLegacyCurrencies = this.getMissingLegacyCurrencies(priceTime); // In the case a legacy currency (EUR, GBP, CAD, CHF, AUD, JPY)
|
||||||
|
const year = new Date(priceTime.time * 1000).getFullYear(); // is missing, we use the same process as for the new currencies
|
||||||
|
const month = new Date(priceTime.time * 1000).getMonth();
|
||||||
|
const yearMonthTimestamp = new Date(year, month, 1).getTime() / 1000;
|
||||||
|
if (conversionRates[yearMonthTimestamp] === undefined) {
|
||||||
|
conversionRates[yearMonthTimestamp] = await this.currencyConversionFeed?.$fetchConversionRates(`${year}-${month + 1 < 10 ? `0${month + 1}` : `${month + 1}`}-01`) || { USD: -1 };
|
||||||
|
if (conversionRates[yearMonthTimestamp]['USD'] < 0) {
|
||||||
|
logger.err(`Cannot fetch conversion rates from the API for ${year}-${month + 1 < 10 ? `0${month + 1}` : `${month + 1}`}-01. Aborting insertion of missing prices.`, logger.tags.mining);
|
||||||
|
this.lastFailedHistoricalRun = Math.round(new Date().getTime() / 1000);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const prices: ApiPrice = this.getEmptyPricesObj();
|
||||||
|
|
||||||
|
let willInsert = false;
|
||||||
|
for (const conversionCurrency of this.newCurrencies.concat(missingLegacyCurrencies)) {
|
||||||
|
if (conversionRates[yearMonthTimestamp][conversionCurrency] > 0 && priceTime.USD * conversionRates[yearMonthTimestamp][conversionCurrency] < MAX_PRICES[conversionCurrency]) {
|
||||||
|
prices[conversionCurrency] = year >= 2013 ? Math.round(priceTime.USD * conversionRates[yearMonthTimestamp][conversionCurrency]) : Math.round(priceTime.USD * conversionRates[yearMonthTimestamp][conversionCurrency] * 100) / 100;
|
||||||
|
willInsert = true;
|
||||||
|
} else {
|
||||||
|
prices[conversionCurrency] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (willInsert) {
|
||||||
|
await PricesRepository.$saveAdditionalCurrencyPrices(priceTime.time, prices, missingLegacyCurrencies);
|
||||||
|
++totalInserted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(`Inserted ${totalInserted} missing additional currency prices into the db`, logger.tags.mining);
|
||||||
|
this.additionalCurrenciesHistoryInserted = true;
|
||||||
|
this.additionalCurrenciesHistoryRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to get legacy missing currencies in a row (EUR, GBP, CAD, CHF, AUD, JPY)
|
||||||
|
private getMissingLegacyCurrencies(priceTime: any): string[] {
|
||||||
|
const missingCurrencies: string[] = [];
|
||||||
|
['eur', 'gbp', 'cad', 'chf', 'aud', 'jpy'].forEach(currency => {
|
||||||
|
if (priceTime[`${currency}_missing`]) {
|
||||||
|
missingCurrencies.push(currency.toUpperCase());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return missingCurrencies;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new PriceUpdater();
|
export default new PriceUpdater();
|
||||||
|
@ -153,5 +153,9 @@
|
|||||||
"ENABLED": __REDIS_ENABLED__,
|
"ENABLED": __REDIS_ENABLED__,
|
||||||
"UNIX_SOCKET_PATH": "__REDIS_UNIX_SOCKET_PATH__",
|
"UNIX_SOCKET_PATH": "__REDIS_UNIX_SOCKET_PATH__",
|
||||||
"BATCH_QUERY_BASE_SIZE": __REDIS_BATCH_QUERY_BASE_SIZE__
|
"BATCH_QUERY_BASE_SIZE": __REDIS_BATCH_QUERY_BASE_SIZE__
|
||||||
|
},
|
||||||
|
"FIAT_PRICE": {
|
||||||
|
"ENABLED": __FIAT_PRICE_ENABLED__,
|
||||||
|
"API_KEY": "__FIAT_PRICE_API_KEY__"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -155,6 +155,10 @@ __REDIS_ENABLED__=${REDIS_ENABLED:=false}
|
|||||||
__REDIS_UNIX_SOCKET_PATH__=${REDIS_UNIX_SOCKET_PATH:=true}
|
__REDIS_UNIX_SOCKET_PATH__=${REDIS_UNIX_SOCKET_PATH:=true}
|
||||||
__REDIS_BATCH_QUERY_BASE_SIZE__=${REDIS_BATCH_QUERY_BASE_SIZE:=5000}
|
__REDIS_BATCH_QUERY_BASE_SIZE__=${REDIS_BATCH_QUERY_BASE_SIZE:=5000}
|
||||||
|
|
||||||
|
# FIAT_PRICE
|
||||||
|
__FIAT_PRICE_ENABLED__=${FIAT_PRICE_ENABLED:=true}
|
||||||
|
__FIAT_PRICE_API_KEY__=${FIAT_PRICE_API_KEY:=""}
|
||||||
|
|
||||||
mkdir -p "${__MEMPOOL_CACHE_DIR__}"
|
mkdir -p "${__MEMPOOL_CACHE_DIR__}"
|
||||||
|
|
||||||
sed -i "s!__MEMPOOL_NETWORK__!${__MEMPOOL_NETWORK__}!g" mempool-config.json
|
sed -i "s!__MEMPOOL_NETWORK__!${__MEMPOOL_NETWORK__}!g" mempool-config.json
|
||||||
@ -301,4 +305,8 @@ sed -i "s!__REDIS_ENABLED__!${__REDIS_ENABLED__}!g" mempool-config.json
|
|||||||
sed -i "s!__REDIS_UNIX_SOCKET_PATH__!${__REDIS_UNIX_SOCKET_PATH__}!g" mempool-config.json
|
sed -i "s!__REDIS_UNIX_SOCKET_PATH__!${__REDIS_UNIX_SOCKET_PATH__}!g" mempool-config.json
|
||||||
sed -i "s!__REDIS_BATCH_QUERY_BASE_SIZE__!${__REDIS_BATCH_QUERY_BASE_SIZE__}!g" mempool-config.json
|
sed -i "s!__REDIS_BATCH_QUERY_BASE_SIZE__!${__REDIS_BATCH_QUERY_BASE_SIZE__}!g" mempool-config.json
|
||||||
|
|
||||||
|
# FIAT_PRICE
|
||||||
|
sed -i "s!__FIAT_PRICE_ENABLED__!${__FIAT_PRICE_ENABLED__}!g" mempool-config.json
|
||||||
|
sed -i "s!__FIAT_PRICE_API_KEY__!${__FIAT_PRICE_API_KEY__}!g" mempool-config.json
|
||||||
|
|
||||||
node /backend/package/index.js
|
node /backend/package/index.js
|
||||||
|
@ -268,4 +268,134 @@ export const fiatCurrencies = {
|
|||||||
code: 'USD',
|
code: 'USD',
|
||||||
indexed: true,
|
indexed: true,
|
||||||
},
|
},
|
||||||
|
BGN: {
|
||||||
|
name: 'Bulgarian Lev',
|
||||||
|
code: 'BGN',
|
||||||
|
indexed: true,
|
||||||
|
},
|
||||||
|
BRL: {
|
||||||
|
name: 'Brazilian Real',
|
||||||
|
code: 'BRL',
|
||||||
|
indexed: true,
|
||||||
|
},
|
||||||
|
CNY: {
|
||||||
|
name: 'Chinese Yuan',
|
||||||
|
code: 'CNY',
|
||||||
|
indexed: true,
|
||||||
|
},
|
||||||
|
CZK: {
|
||||||
|
name: 'Czech Koruna',
|
||||||
|
code: 'CZK',
|
||||||
|
indexed: true,
|
||||||
|
},
|
||||||
|
DKK: {
|
||||||
|
name: 'Danish Krone',
|
||||||
|
code: 'DKK',
|
||||||
|
indexed: true,
|
||||||
|
},
|
||||||
|
HKD: {
|
||||||
|
name: 'Hong Kong Dollar',
|
||||||
|
code: 'HKD',
|
||||||
|
indexed: true,
|
||||||
|
},
|
||||||
|
HRK: {
|
||||||
|
name: 'Croatian Kuna',
|
||||||
|
code: 'HRK',
|
||||||
|
indexed: true,
|
||||||
|
},
|
||||||
|
HUF: {
|
||||||
|
name: 'Hungarian Forint',
|
||||||
|
code: 'HUF',
|
||||||
|
indexed: true,
|
||||||
|
},
|
||||||
|
IDR: {
|
||||||
|
name: 'Indonesian Rupiah',
|
||||||
|
code: 'IDR',
|
||||||
|
indexed: true,
|
||||||
|
},
|
||||||
|
ILS: {
|
||||||
|
name: 'Israeli Shekel',
|
||||||
|
code: 'ILS',
|
||||||
|
indexed: true,
|
||||||
|
},
|
||||||
|
INR: {
|
||||||
|
name: 'Indian Rupee',
|
||||||
|
code: 'INR',
|
||||||
|
indexed: true,
|
||||||
|
},
|
||||||
|
ISK: {
|
||||||
|
name: 'Icelandic Krona',
|
||||||
|
code: 'ISK',
|
||||||
|
indexed: true,
|
||||||
|
},
|
||||||
|
KRW: {
|
||||||
|
name: 'South Korean Won',
|
||||||
|
code: 'KRW',
|
||||||
|
indexed: true,
|
||||||
|
},
|
||||||
|
MXN: {
|
||||||
|
name: 'Mexican Peso',
|
||||||
|
code: 'MXN',
|
||||||
|
indexed: true,
|
||||||
|
},
|
||||||
|
MYR: {
|
||||||
|
name: 'Malaysian Ringgit',
|
||||||
|
code: 'MYR',
|
||||||
|
indexed: true,
|
||||||
|
},
|
||||||
|
NOK: {
|
||||||
|
name: 'Norwegian Krone',
|
||||||
|
code: 'NOK',
|
||||||
|
indexed: true,
|
||||||
|
},
|
||||||
|
NZD: {
|
||||||
|
name: 'New Zealand Dollar',
|
||||||
|
code: 'NZD',
|
||||||
|
indexed: true,
|
||||||
|
},
|
||||||
|
PHP: {
|
||||||
|
name: 'Philippine Peso',
|
||||||
|
code: 'PHP',
|
||||||
|
indexed: true,
|
||||||
|
},
|
||||||
|
PLN: {
|
||||||
|
name: 'Polish Zloty',
|
||||||
|
code: 'PLN',
|
||||||
|
indexed: true,
|
||||||
|
},
|
||||||
|
RON: {
|
||||||
|
name: 'Romanian Leu',
|
||||||
|
code: 'RON',
|
||||||
|
indexed: true,
|
||||||
|
},
|
||||||
|
RUB: {
|
||||||
|
name: 'Russian Ruble',
|
||||||
|
code: 'RUB',
|
||||||
|
indexed: true,
|
||||||
|
},
|
||||||
|
SEK: {
|
||||||
|
name: 'Swedish Krona',
|
||||||
|
code: 'SEK',
|
||||||
|
indexed: true,
|
||||||
|
},
|
||||||
|
SGD: {
|
||||||
|
name: 'Singapore Dollar',
|
||||||
|
code: 'SGD',
|
||||||
|
indexed: true,
|
||||||
|
},
|
||||||
|
THB: {
|
||||||
|
name: 'Thai Baht',
|
||||||
|
code: 'THB',
|
||||||
|
indexed: true,
|
||||||
|
},
|
||||||
|
TRY: {
|
||||||
|
name: 'Turkish Lira',
|
||||||
|
code: 'TRY',
|
||||||
|
indexed: true,
|
||||||
|
},
|
||||||
|
ZAR: {
|
||||||
|
name: 'South African Rand',
|
||||||
|
code: 'ZAR',
|
||||||
|
indexed: true,
|
||||||
|
},
|
||||||
};
|
};
|
@ -335,7 +335,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
|
|
||||||
<app-transactions-list [transactions]="transactions" [paginated]="true"></app-transactions-list>
|
<app-transactions-list [transactions]="transactions" [paginated]="true" [blockTime]="block.timestamp"></app-transactions-list>
|
||||||
|
|
||||||
<ng-template [ngIf]="transactionsError">
|
<ng-template [ngIf]="transactionsError">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
|
@ -533,9 +533,9 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
if (this.priceSubscription) {
|
if (this.priceSubscription) {
|
||||||
this.priceSubscription.unsubscribe();
|
this.priceSubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
this.priceSubscription = block$.pipe(
|
this.priceSubscription = combineLatest([this.stateService.fiatCurrency$, block$]).pipe(
|
||||||
switchMap((block) => {
|
switchMap(([currency, block]) => {
|
||||||
return this.priceService.getBlockPrice$(block.timestamp).pipe(
|
return this.priceService.getBlockPrice$(block.timestamp, true, currency).pipe(
|
||||||
tap((price) => {
|
tap((price) => {
|
||||||
this.blockConversion = price;
|
this.blockConversion = price;
|
||||||
})
|
})
|
||||||
|
@ -35,6 +35,11 @@ export class FiatSelectorComponent implements OnInit {
|
|||||||
this.stateService.fiatCurrency$.subscribe((fiat) => {
|
this.stateService.fiatCurrency$.subscribe((fiat) => {
|
||||||
this.fiatForm.get('fiat')?.setValue(fiat);
|
this.fiatForm.get('fiat')?.setValue(fiat);
|
||||||
});
|
});
|
||||||
|
if (!this.stateService.env.ADDITIONAL_CURRENCIES) {
|
||||||
|
this.currencies = this.currencies.filter((currency: any) => {
|
||||||
|
return ['AUD', 'CAD', 'EUR', 'JPY', 'GBP', 'CHF', 'USD'].includes(currency[0]);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
changeFiat() {
|
changeFiat() {
|
||||||
|
@ -533,7 +533,7 @@
|
|||||||
<tr *ngIf="isMobile && (network === 'liquid' || network === 'liquidtestnet' || !featuresEnabled || network === '')"></tr>
|
<tr *ngIf="isMobile && (network === 'liquid' || network === 'liquidtestnet' || !featuresEnabled || network === '')"></tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="td-width" i18n="transaction.fee|Transaction fee">Fee</td>
|
<td class="td-width" i18n="transaction.fee|Transaction fee">Fee</td>
|
||||||
<td>{{ tx.fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span> <span class="fiat"><app-fiat [blockConversion]="blockConversion" [value]="tx.fee"></app-fiat></span></td>
|
<td>{{ tx.fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span> <span class="fiat"><app-fiat [blockConversion]="tx.price" [value]="tx.fee"></app-fiat></span></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n="transaction.fee-rate|Transaction fee rate">Fee rate</td>
|
<td i18n="transaction.fee-rate|Transaction fee rate">Fee rate</td>
|
||||||
|
@ -76,6 +76,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
mempoolBlocksSubscription: Subscription;
|
mempoolBlocksSubscription: Subscription;
|
||||||
blocksSubscription: Subscription;
|
blocksSubscription: Subscription;
|
||||||
miningSubscription: Subscription;
|
miningSubscription: Subscription;
|
||||||
|
currencyChangeSubscription: Subscription;
|
||||||
fragmentParams: URLSearchParams;
|
fragmentParams: URLSearchParams;
|
||||||
rbfTransaction: undefined | Transaction;
|
rbfTransaction: undefined | Transaction;
|
||||||
replaced: boolean = false;
|
replaced: boolean = false;
|
||||||
@ -108,7 +109,6 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
hideFlow: boolean = this.stateService.hideFlow.value;
|
hideFlow: boolean = this.stateService.hideFlow.value;
|
||||||
overrideFlowPreference: boolean = null;
|
overrideFlowPreference: boolean = null;
|
||||||
flowEnabled: boolean;
|
flowEnabled: boolean;
|
||||||
blockConversion: Price;
|
|
||||||
tooltipPosition: { x: number, y: number };
|
tooltipPosition: { x: number, y: number };
|
||||||
isMobile: boolean;
|
isMobile: boolean;
|
||||||
|
|
||||||
@ -493,10 +493,12 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.fetchRbfHistory$.next(this.tx.txid);
|
this.fetchRbfHistory$.next(this.tx.txid);
|
||||||
|
this.currencyChangeSubscription?.unsubscribe();
|
||||||
this.priceService.getBlockPrice$(tx.status?.block_time, true).pipe(
|
this.currencyChangeSubscription = this.stateService.fiatCurrency$.pipe(
|
||||||
tap((price) => {
|
switchMap((currency) => {
|
||||||
this.blockConversion = price;
|
return tx.status.block_time ? this.priceService.getBlockPrice$(tx.status.block_time, true, currency).pipe(
|
||||||
|
tap((price) => tx['price'] = price),
|
||||||
|
) : of(undefined);
|
||||||
})
|
})
|
||||||
).subscribe();
|
).subscribe();
|
||||||
|
|
||||||
@ -810,6 +812,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
this.mempoolBlocksSubscription.unsubscribe();
|
this.mempoolBlocksSubscription.unsubscribe();
|
||||||
this.blocksSubscription.unsubscribe();
|
this.blocksSubscription.unsubscribe();
|
||||||
this.miningSubscription?.unsubscribe();
|
this.miningSubscription?.unsubscribe();
|
||||||
|
this.currencyChangeSubscription?.unsubscribe();
|
||||||
this.leaveTransaction();
|
this.leaveTransaction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,11 +32,14 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
|||||||
@Input() outputIndex: number;
|
@Input() outputIndex: number;
|
||||||
@Input() address: string = '';
|
@Input() address: string = '';
|
||||||
@Input() rowLimit = 12;
|
@Input() rowLimit = 12;
|
||||||
|
@Input() blockTime: number = 0; // Used for price calculation if all the transactions are in the same block
|
||||||
|
|
||||||
@Output() loadMore = new EventEmitter();
|
@Output() loadMore = new EventEmitter();
|
||||||
|
|
||||||
latestBlock$: Observable<BlockExtended>;
|
latestBlock$: Observable<BlockExtended>;
|
||||||
outspendsSubscription: Subscription;
|
outspendsSubscription: Subscription;
|
||||||
|
currencyChangeSubscription: Subscription;
|
||||||
|
currency: string;
|
||||||
refreshOutspends$: ReplaySubject<string[]> = new ReplaySubject();
|
refreshOutspends$: ReplaySubject<string[]> = new ReplaySubject();
|
||||||
refreshChannels$: ReplaySubject<string[]> = new ReplaySubject();
|
refreshChannels$: ReplaySubject<string[]> = new ReplaySubject();
|
||||||
showDetails$ = new BehaviorSubject<boolean>(false);
|
showDetails$ = new BehaviorSubject<boolean>(false);
|
||||||
@ -125,6 +128,35 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
|||||||
)
|
)
|
||||||
,
|
,
|
||||||
).subscribe(() => this.ref.markForCheck());
|
).subscribe(() => this.ref.markForCheck());
|
||||||
|
|
||||||
|
this.currencyChangeSubscription = this.stateService.fiatCurrency$
|
||||||
|
.subscribe(currency => {
|
||||||
|
this.currency = currency;
|
||||||
|
this.refreshPrice();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshPrice(): void {
|
||||||
|
// Loop over all transactions
|
||||||
|
if (!this.transactions || !this.transactions.length || !this.currency) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const confirmedTxs = this.transactions.filter((tx) => tx.status.confirmed).length;
|
||||||
|
if (!this.blockTime) {
|
||||||
|
this.transactions.forEach((tx) => {
|
||||||
|
if (!this.blockTime) {
|
||||||
|
if (tx.status.block_time) {
|
||||||
|
this.priceService.getBlockPrice$(tx.status.block_time, confirmedTxs < 10, this.currency).pipe(
|
||||||
|
tap((price) => tx['price'] = price),
|
||||||
|
).subscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.priceService.getBlockPrice$(this.blockTime, true, this.currency).pipe(
|
||||||
|
tap((price) => this.transactions.forEach((tx) => tx['price'] = price)),
|
||||||
|
).subscribe();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(changes): void {
|
ngOnChanges(changes): void {
|
||||||
@ -148,6 +180,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
|||||||
this.transactionsLength = this.transactions.length;
|
this.transactionsLength = this.transactions.length;
|
||||||
this.cacheService.setTxCache(this.transactions);
|
this.cacheService.setTxCache(this.transactions);
|
||||||
|
|
||||||
|
const confirmedTxs = this.transactions.filter((tx) => tx.status.confirmed).length;
|
||||||
this.transactions.forEach((tx) => {
|
this.transactions.forEach((tx) => {
|
||||||
tx['@voutLimit'] = true;
|
tx['@voutLimit'] = true;
|
||||||
tx['@vinLimit'] = true;
|
tx['@vinLimit'] = true;
|
||||||
@ -197,10 +230,18 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.priceService.getBlockPrice$(tx.status.block_time).pipe(
|
if (!this.blockTime && tx.status.block_time && this.currency) {
|
||||||
tap((price) => tx['price'] = price)
|
this.priceService.getBlockPrice$(tx.status.block_time, confirmedTxs < 10, this.currency).pipe(
|
||||||
|
tap((price) => tx['price'] = price),
|
||||||
).subscribe();
|
).subscribe();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (this.blockTime && this.transactions?.length && this.currency) {
|
||||||
|
this.priceService.getBlockPrice$(this.blockTime, true, this.currency).pipe(
|
||||||
|
tap((price) => this.transactions.forEach((tx) => tx['price'] = price)),
|
||||||
|
).subscribe();
|
||||||
|
}
|
||||||
const txIds = this.transactions.filter((tx) => !tx._outspends).map((tx) => tx.txid);
|
const txIds = this.transactions.filter((tx) => !tx._outspends).map((tx) => tx.txid);
|
||||||
if (txIds.length && !this.cached) {
|
if (txIds.length && !this.cached) {
|
||||||
this.refreshOutspends$.next(txIds);
|
this.refreshOutspends$.next(txIds);
|
||||||
@ -308,5 +349,6 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
|||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.outspendsSubscription.unsubscribe();
|
this.outspendsSubscription.unsubscribe();
|
||||||
|
this.currencyChangeSubscription?.unsubscribe();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Component, ElementRef, ViewChild, Input, OnChanges, OnInit } from '@angular/core';
|
import { Component, ElementRef, ViewChild, Input, OnChanges, OnInit } from '@angular/core';
|
||||||
import { tap } from 'rxjs';
|
import { Subscription, of, switchMap, tap } from 'rxjs';
|
||||||
import { Price, PriceService } from '../../services/price.service';
|
import { Price, PriceService } from '../../services/price.service';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
@ -35,6 +35,7 @@ export class TxBowtieGraphTooltipComponent implements OnChanges {
|
|||||||
|
|
||||||
tooltipPosition = { x: 0, y: 0 };
|
tooltipPosition = { x: 0, y: 0 };
|
||||||
blockConversion: Price;
|
blockConversion: Price;
|
||||||
|
currencyChangeSubscription: Subscription;
|
||||||
|
|
||||||
nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId;
|
nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId;
|
||||||
|
|
||||||
@ -47,9 +48,12 @@ export class TxBowtieGraphTooltipComponent implements OnChanges {
|
|||||||
|
|
||||||
ngOnChanges(changes): void {
|
ngOnChanges(changes): void {
|
||||||
if (changes.line?.currentValue) {
|
if (changes.line?.currentValue) {
|
||||||
this.priceService.getBlockPrice$(changes.line?.currentValue.timestamp, true).pipe(
|
this.currencyChangeSubscription?.unsubscribe();
|
||||||
tap((price) => {
|
this.currencyChangeSubscription = this.stateService.fiatCurrency$.pipe(
|
||||||
this.blockConversion = price;
|
switchMap((currency) => {
|
||||||
|
return changes.line?.currentValue.timestamp ? this.priceService.getBlockPrice$(changes.line?.currentValue.timestamp, true, currency).pipe(
|
||||||
|
tap((price) => this.blockConversion = price),
|
||||||
|
) : of(undefined);
|
||||||
})
|
})
|
||||||
).subscribe();
|
).subscribe();
|
||||||
}
|
}
|
||||||
|
@ -405,7 +405,7 @@ export class ApiService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getHistoricalPrice$(timestamp: number | undefined): Observable<Conversion> {
|
getHistoricalPrice$(timestamp: number | undefined, currency?: string): Observable<Conversion> {
|
||||||
if (this.stateService.isAnyTestnet()) {
|
if (this.stateService.isAnyTestnet()) {
|
||||||
return of({
|
return of({
|
||||||
prices: [],
|
prices: [],
|
||||||
@ -416,12 +416,47 @@ export class ApiService {
|
|||||||
USDCHF: 0,
|
USDCHF: 0,
|
||||||
USDAUD: 0,
|
USDAUD: 0,
|
||||||
USDJPY: 0,
|
USDJPY: 0,
|
||||||
|
USDBGN: 0,
|
||||||
|
USDBRL: 0,
|
||||||
|
USDCNY: 0,
|
||||||
|
USDCZK: 0,
|
||||||
|
USDDKK: 0,
|
||||||
|
USDHKD: 0,
|
||||||
|
USDHRK: 0,
|
||||||
|
USDHUF: 0,
|
||||||
|
USDIDR: 0,
|
||||||
|
USDILS: 0,
|
||||||
|
USDINR: 0,
|
||||||
|
USDISK: 0,
|
||||||
|
USDKRW: 0,
|
||||||
|
USDMXN: 0,
|
||||||
|
USDMYR: 0,
|
||||||
|
USDNOK: 0,
|
||||||
|
USDNZD: 0,
|
||||||
|
USDPHP: 0,
|
||||||
|
USDPLN: 0,
|
||||||
|
USDRON: 0,
|
||||||
|
USDRUB: 0,
|
||||||
|
USDSEK: 0,
|
||||||
|
USDSGD: 0,
|
||||||
|
USDTHB: 0,
|
||||||
|
USDTRY: 0,
|
||||||
|
USDZAR: 0,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const queryParams = [];
|
||||||
|
|
||||||
|
if (timestamp) {
|
||||||
|
queryParams.push(`timestamp=${timestamp}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currency) {
|
||||||
|
queryParams.push(`currency=${currency}`);
|
||||||
|
}
|
||||||
return this.httpClient.get<Conversion>(
|
return this.httpClient.get<Conversion>(
|
||||||
this.apiBaseUrl + this.apiBasePath + '/api/v1/historical-price' +
|
`${this.apiBaseUrl}${this.apiBasePath}/api/v1/historical-price` +
|
||||||
(timestamp ? `?timestamp=${timestamp}` : '')
|
(queryParams.length > 0 ? `?${queryParams.join('&')}` : '')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,32 @@ export interface ApiPrice {
|
|||||||
CHF: number,
|
CHF: number,
|
||||||
AUD: number,
|
AUD: number,
|
||||||
JPY: number,
|
JPY: number,
|
||||||
|
BGN?: number,
|
||||||
|
BRL?: number,
|
||||||
|
CNY?: number,
|
||||||
|
CZK?: number,
|
||||||
|
DKK?: number,
|
||||||
|
HKD?: number,
|
||||||
|
HRK?: number,
|
||||||
|
HUF?: number,
|
||||||
|
IDR?: number,
|
||||||
|
ILS?: number,
|
||||||
|
INR?: number,
|
||||||
|
ISK?: number,
|
||||||
|
KRW?: number,
|
||||||
|
MXN?: number,
|
||||||
|
MYR?: number,
|
||||||
|
NOK?: number,
|
||||||
|
NZD?: number,
|
||||||
|
PHP?: number,
|
||||||
|
PLN?: number,
|
||||||
|
RON?: number,
|
||||||
|
RUB?: number,
|
||||||
|
SEK?: number,
|
||||||
|
SGD?: number,
|
||||||
|
THB?: number,
|
||||||
|
TRY?: number,
|
||||||
|
ZAR?: number,
|
||||||
}
|
}
|
||||||
export interface ExchangeRates {
|
export interface ExchangeRates {
|
||||||
USDEUR: number,
|
USDEUR: number,
|
||||||
@ -21,6 +47,32 @@ export interface ExchangeRates {
|
|||||||
USDCHF: number,
|
USDCHF: number,
|
||||||
USDAUD: number,
|
USDAUD: number,
|
||||||
USDJPY: number,
|
USDJPY: number,
|
||||||
|
USDBGN?: number,
|
||||||
|
USDBRL?: number,
|
||||||
|
USDCNY?: number,
|
||||||
|
USDCZK?: number,
|
||||||
|
USDDKK?: number,
|
||||||
|
USDHKD?: number,
|
||||||
|
USDHRK?: number,
|
||||||
|
USDHUF?: number,
|
||||||
|
USDIDR?: number,
|
||||||
|
USDILS?: number,
|
||||||
|
USDINR?: number,
|
||||||
|
USDISK?: number,
|
||||||
|
USDKRW?: number,
|
||||||
|
USDMXN?: number,
|
||||||
|
USDMYR?: number,
|
||||||
|
USDNOK?: number,
|
||||||
|
USDNZD?: number,
|
||||||
|
USDPHP?: number,
|
||||||
|
USDPLN?: number,
|
||||||
|
USDRON?: number,
|
||||||
|
USDRUB?: number,
|
||||||
|
USDSEK?: number,
|
||||||
|
USDSGD?: number,
|
||||||
|
USDTHB?: number,
|
||||||
|
USDTRY?: number,
|
||||||
|
USDZAR?: number,
|
||||||
}
|
}
|
||||||
export interface Conversion {
|
export interface Conversion {
|
||||||
prices: ApiPrice[],
|
prices: ApiPrice[],
|
||||||
@ -46,6 +98,8 @@ export class PriceService {
|
|||||||
|
|
||||||
lastQueriedTimestamp: number;
|
lastQueriedTimestamp: number;
|
||||||
lastPriceHistoryUpdate: number;
|
lastPriceHistoryUpdate: number;
|
||||||
|
lastQueriedCurrency: string;
|
||||||
|
lastQueriedHistoricalCurrency: string;
|
||||||
|
|
||||||
historicalPrice: ConversionDict = {
|
historicalPrice: ConversionDict = {
|
||||||
prices: null,
|
prices: null,
|
||||||
@ -60,16 +114,25 @@ export class PriceService {
|
|||||||
|
|
||||||
getEmptyPrice(): Price {
|
getEmptyPrice(): Price {
|
||||||
return {
|
return {
|
||||||
price: {
|
price: this.stateService.env.ADDITIONAL_CURRENCIES ? {
|
||||||
|
USD: 0, EUR: 0, GBP: 0, CAD: 0, CHF: 0, AUD: 0, JPY: 0, BGN: 0, BRL: 0, CNY: 0, CZK: 0, DKK: 0, HKD: 0, HRK: 0, HUF: 0, IDR: 0,
|
||||||
|
ILS: 0, INR: 0, ISK: 0, KRW: 0, MXN: 0, MYR: 0, NOK: 0, NZD: 0, PHP: 0, PLN: 0, RON: 0, RUB: 0, SEK: 0, SGD: 0, THB: 0, TRY: 0,
|
||||||
|
ZAR: 0
|
||||||
|
} :
|
||||||
|
{
|
||||||
USD: 0, EUR: 0, GBP: 0, CAD: 0, CHF: 0, AUD: 0, JPY: 0,
|
USD: 0, EUR: 0, GBP: 0, CAD: 0, CHF: 0, AUD: 0, JPY: 0,
|
||||||
},
|
},
|
||||||
exchangeRates: {
|
exchangeRates: this.stateService.env.ADDITIONAL_CURRENCIES ? {
|
||||||
|
USDEUR: 0, USDGBP: 0, USDCAD: 0, USDCHF: 0, USDAUD: 0, USDJPY: 0, USDBGN: 0, USDBRL: 0, USDCNY: 0, USDCZK: 0, USDDKK: 0, USDHKD: 0,
|
||||||
|
USDHRK: 0, USDHUF: 0, USDIDR: 0, USDILS: 0, USDINR: 0, USDISK: 0, USDKRW: 0, USDMXN: 0, USDMYR: 0, USDNOK: 0, USDNZD: 0, USDPHP: 0,
|
||||||
|
USDPLN: 0, USDRON: 0, USDRUB: 0, USDSEK: 0, USDSGD: 0, USDTHB: 0, USDTRY: 0, USDZAR: 0
|
||||||
|
} : {
|
||||||
USDEUR: 0, USDGBP: 0, USDCAD: 0, USDCHF: 0, USDAUD: 0, USDJPY: 0,
|
USDEUR: 0, USDGBP: 0, USDCAD: 0, USDCHF: 0, USDAUD: 0, USDJPY: 0,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getBlockPrice$(blockTimestamp: number, singlePrice = false): Observable<Price | undefined> {
|
getBlockPrice$(blockTimestamp: number, singlePrice = false, currency: string): Observable<Price | undefined> {
|
||||||
if (this.stateService.env.BASE_MODULE !== 'mempool' || !this.stateService.env.HISTORICAL_PRICE) {
|
if (this.stateService.env.BASE_MODULE !== 'mempool' || !this.stateService.env.HISTORICAL_PRICE) {
|
||||||
return of(undefined);
|
return of(undefined);
|
||||||
}
|
}
|
||||||
@ -81,9 +144,10 @@ export class PriceService {
|
|||||||
* query a different timestamp than the last one
|
* query a different timestamp than the last one
|
||||||
*/
|
*/
|
||||||
if (singlePrice) {
|
if (singlePrice) {
|
||||||
if (!this.singlePriceObservable$ || (this.singlePriceObservable$ && blockTimestamp !== this.lastQueriedTimestamp)) {
|
if (!this.singlePriceObservable$ || (this.singlePriceObservable$ && (blockTimestamp !== this.lastQueriedTimestamp || currency !== this.lastQueriedCurrency))) {
|
||||||
this.singlePriceObservable$ = this.apiService.getHistoricalPrice$(blockTimestamp).pipe(shareReplay());
|
this.singlePriceObservable$ = this.apiService.getHistoricalPrice$(blockTimestamp, currency).pipe(shareReplay());
|
||||||
this.lastQueriedTimestamp = blockTimestamp;
|
this.lastQueriedTimestamp = blockTimestamp;
|
||||||
|
this.lastQueriedCurrency = currency;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.singlePriceObservable$.pipe(
|
return this.singlePriceObservable$.pipe(
|
||||||
@ -92,7 +156,17 @@ export class PriceService {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
price: {
|
price: this.stateService.env.ADDITIONAL_CURRENCIES ? {
|
||||||
|
USD: conversion.prices[0].USD, EUR: conversion.prices[0].EUR, GBP: conversion.prices[0].GBP, CAD: conversion.prices[0].CAD,
|
||||||
|
CHF: conversion.prices[0].CHF, AUD: conversion.prices[0].AUD, JPY: conversion.prices[0].JPY, BGN: conversion.prices[0].BGN,
|
||||||
|
BRL: conversion.prices[0].BRL, CNY: conversion.prices[0].CNY, CZK: conversion.prices[0].CZK, DKK: conversion.prices[0].DKK,
|
||||||
|
HKD: conversion.prices[0].HKD, HRK: conversion.prices[0].HRK, HUF: conversion.prices[0].HUF, IDR: conversion.prices[0].IDR,
|
||||||
|
ILS: conversion.prices[0].ILS, INR: conversion.prices[0].INR, ISK: conversion.prices[0].ISK, KRW: conversion.prices[0].KRW,
|
||||||
|
MXN: conversion.prices[0].MXN, MYR: conversion.prices[0].MYR, NOK: conversion.prices[0].NOK, NZD: conversion.prices[0].NZD,
|
||||||
|
PHP: conversion.prices[0].PHP, PLN: conversion.prices[0].PLN, RON: conversion.prices[0].RON, RUB: conversion.prices[0].RUB,
|
||||||
|
SEK: conversion.prices[0].SEK, SGD: conversion.prices[0].SGD, THB: conversion.prices[0].THB, TRY: conversion.prices[0].TRY,
|
||||||
|
ZAR: conversion.prices[0].ZAR
|
||||||
|
} : {
|
||||||
USD: conversion.prices[0].USD, EUR: conversion.prices[0].EUR, GBP: conversion.prices[0].GBP, CAD: conversion.prices[0].CAD,
|
USD: conversion.prices[0].USD, EUR: conversion.prices[0].EUR, GBP: conversion.prices[0].GBP, CAD: conversion.prices[0].CAD,
|
||||||
CHF: conversion.prices[0].CHF, AUD: conversion.prices[0].AUD, JPY: conversion.prices[0].JPY
|
CHF: conversion.prices[0].CHF, AUD: conversion.prices[0].AUD, JPY: conversion.prices[0].JPY
|
||||||
},
|
},
|
||||||
@ -106,9 +180,10 @@ export class PriceService {
|
|||||||
* Query all price history only once. The observable is invalidated after 1 hour
|
* Query all price history only once. The observable is invalidated after 1 hour
|
||||||
*/
|
*/
|
||||||
else {
|
else {
|
||||||
if (!this.priceObservable$ || (this.priceObservable$ && (now - this.lastPriceHistoryUpdate > 3600))) {
|
if (!this.priceObservable$ || (this.priceObservable$ && (now - this.lastPriceHistoryUpdate > 3600 || currency !== this.lastQueriedHistoricalCurrency))) {
|
||||||
this.priceObservable$ = this.apiService.getHistoricalPrice$(undefined).pipe(shareReplay());
|
this.priceObservable$ = this.apiService.getHistoricalPrice$(undefined, currency).pipe(shareReplay());
|
||||||
this.lastPriceHistoryUpdate = new Date().getTime() / 1000;
|
this.lastPriceHistoryUpdate = new Date().getTime() / 1000;
|
||||||
|
this.lastQueriedHistoricalCurrency = currency;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.priceObservable$.pipe(
|
return this.priceObservable$.pipe(
|
||||||
@ -122,9 +197,15 @@ export class PriceService {
|
|||||||
exchangeRates: conversion.exchangeRates,
|
exchangeRates: conversion.exchangeRates,
|
||||||
};
|
};
|
||||||
for (const price of conversion.prices) {
|
for (const price of conversion.prices) {
|
||||||
historicalPrice.prices[price.time] = {
|
historicalPrice.prices[price.time] = this.stateService.env.ADDITIONAL_CURRENCIES ? {
|
||||||
USD: price.USD, EUR: price.EUR, GBP: price.GBP, CAD: price.CAD,
|
USD: price.USD, EUR: price.EUR, GBP: price.GBP, CAD: price.CAD, CHF: price.CHF, AUD: price.AUD,
|
||||||
CHF: price.CHF, AUD: price.AUD, JPY: price.JPY
|
JPY: price.JPY, BGN: price.BGN, BRL: price.BRL, CNY: price.CNY, CZK: price.CZK, DKK: price.DKK,
|
||||||
|
HKD: price.HKD, HRK: price.HRK, HUF: price.HUF, IDR: price.IDR, ILS: price.ILS, INR: price.INR,
|
||||||
|
ISK: price.ISK, KRW: price.KRW, MXN: price.MXN, MYR: price.MYR, NOK: price.NOK, NZD: price.NZD,
|
||||||
|
PHP: price.PHP, PLN: price.PLN, RON: price.RON, RUB: price.RUB, SEK: price.SEK, SGD: price.SGD,
|
||||||
|
THB: price.THB, TRY: price.TRY, ZAR: price.ZAR
|
||||||
|
} : {
|
||||||
|
USD: price.USD, EUR: price.EUR, GBP: price.GBP, CAD: price.CAD, CHF: price.CHF, AUD: price.AUD, JPY: price.JPY
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,6 +49,7 @@ export interface Env {
|
|||||||
SIGNET_BLOCK_AUDIT_START_HEIGHT: number;
|
SIGNET_BLOCK_AUDIT_START_HEIGHT: number;
|
||||||
HISTORICAL_PRICE: boolean;
|
HISTORICAL_PRICE: boolean;
|
||||||
ACCELERATOR: boolean;
|
ACCELERATOR: boolean;
|
||||||
|
ADDITIONAL_CURRENCIES: boolean;
|
||||||
GIT_COMMIT_HASH_MEMPOOL_SPACE?: string;
|
GIT_COMMIT_HASH_MEMPOOL_SPACE?: string;
|
||||||
PACKAGE_JSON_VERSION_MEMPOOL_SPACE?: string;
|
PACKAGE_JSON_VERSION_MEMPOOL_SPACE?: string;
|
||||||
}
|
}
|
||||||
@ -82,6 +83,7 @@ const defaultEnv: Env = {
|
|||||||
'SIGNET_BLOCK_AUDIT_START_HEIGHT': 0,
|
'SIGNET_BLOCK_AUDIT_START_HEIGHT': 0,
|
||||||
'HISTORICAL_PRICE': true,
|
'HISTORICAL_PRICE': true,
|
||||||
'ACCELERATOR': false,
|
'ACCELERATOR': false,
|
||||||
|
'ADDITIONAL_CURRENCIES': false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
|
Loading…
x
Reference in New Issue
Block a user