From 1b33081c3137a8835f76c8b7e393a642b77edbef Mon Sep 17 00:00:00 2001 From: michael1011 Date: Fri, 28 Dec 2018 14:02:27 +0100 Subject: [PATCH] feat: update rates regularly (#22) --- bin/boltzm | 4 ++++ lib/BoltzMiddleware.ts | 2 +- lib/Config.ts | 2 ++ lib/Utils.ts | 23 +++++++++++++++++----- lib/api/Api.ts | 2 ++ lib/rates/RateProvider.ts | 41 +++++++++++++++++++++++++-------------- lib/service/Service.ts | 26 ++++++++----------------- 7 files changed, 61 insertions(+), 39 deletions(-) diff --git a/bin/boltzm b/bin/boltzm index 4881a74..a844d51 100755 --- a/bin/boltzm +++ b/bin/boltzm @@ -33,6 +33,10 @@ const { argv } = require('yargs').options({ describe: 'Port of the boltz-middleware REST API', type: 'number', }, + 'api.interval': { + descibe: 'The interval at which your rates should be updated in minutes', + type: 'string', + }, 'boltz.host': { describe: 'Host of the Boltz gRPC interface', type: 'string', diff --git a/lib/BoltzMiddleware.ts b/lib/BoltzMiddleware.ts index 615758f..0eb2d96 100644 --- a/lib/BoltzMiddleware.ts +++ b/lib/BoltzMiddleware.ts @@ -26,7 +26,7 @@ class BoltzMiddleware { this.db = new Database(this.logger, this.config.dbpath); this.boltzClient = new BoltzClient(this.logger, this.config.boltz); - this.service = new Service(this.logger, this.db, this.boltzClient); + this.service = new Service(this.logger, this.db, this.boltzClient, this.config.api.interval); this.api = new Api(this.logger, this.config.api, this.service); } diff --git a/lib/Config.ts b/lib/Config.ts index 4284374..47a336b 100644 --- a/lib/Config.ts +++ b/lib/Config.ts @@ -37,6 +37,8 @@ class Config { this.api = { host: '127.0.0.1', port: 9001, + + interval: 15, }; this.boltz = { diff --git a/lib/Utils.ts b/lib/Utils.ts index 60ae2ce..4386554 100644 --- a/lib/Utils.ts +++ b/lib/Utils.ts @@ -22,31 +22,44 @@ export const splitPairId = (pairId: string): { quote: string, base: string } => }; /** - * Gets the current date in the LocaleString format. + * Get the current date in the LocaleString format. */ export const getTsString = (): string => (new Date()).toLocaleString('en-US', { hour12: false }); /** - * Concats an error code and its prefix + * Concat an error code and its prefix */ export const concatErrorCode = (prefix: number, code: number) => { return `${prefix}.${code}`; }; /** - * Stringifies any object or array + * Stringify any object or array */ export const stringify = (object: any) => { return JSON.stringify(object, undefined, 2); }; /** - * Turns a map into an array + * Turn a map into an array */ export const mapToArray = (map: Map) => { return Array.from(map.entries()); }; +/** + * Turn a map into an object + */ +export const mapToObject = (map: Map) => { + const object: any = {}; + + map.forEach((value, index) => { + object[index] = value; + }); + + return object; +}; + /** * Check whether a variable is a non-array object */ @@ -56,7 +69,7 @@ export const isObject = (val: any): boolean => { /** * Recursively merge properties from different sources into a target object, overriding any - * existing properties. + * existing properties * * @param target The destination object to merge into. * @param sources The sources objects to copy from. diff --git a/lib/api/Api.ts b/lib/api/Api.ts index 1a0e10b..2bbb0e3 100644 --- a/lib/api/Api.ts +++ b/lib/api/Api.ts @@ -8,6 +8,8 @@ import Service from '../service/Service'; type ApiConfig = { host: string; port: number; + + interval: number; }; class Api { diff --git a/lib/rates/RateProvider.ts b/lib/rates/RateProvider.ts index 6309503..44fa30e 100644 --- a/lib/rates/RateProvider.ts +++ b/lib/rates/RateProvider.ts @@ -4,42 +4,54 @@ import { PairInstance } from 'lib/consts/Database'; import { getPairId, stringify, mapToArray } from '../Utils'; // TODO: add unit tests +// TODO: make rate update interval configurable class RateProvider { - private cryptoCompare: CryptoCompare; + // A map between pair ids and their rates + public rates = new Map(); - constructor(private logger: Logger) { + // A map between quote and their base assets + private baseAssetsMap = new Map(); + + private cryptoCompare = new CryptoCompare(); + + constructor(private logger: Logger, private rateUpdateInterval: number) { this.cryptoCompare = new CryptoCompare(); } /** * Gets a map of of rates for the provided pairs */ - public getRates = async (pairs: PairInstance[]) => { - // A map between the quote and their base assets - const baseAssetsMap = new Map(); - + public init = async (pairs: PairInstance[]) => { pairs.forEach((pair) => { - const baseAssets = baseAssetsMap.get(pair.quote); + const baseAssets = this.baseAssetsMap.get(pair.quote); if (baseAssets) { baseAssets.push(pair.base); } else { - baseAssetsMap.set(pair.quote, [pair.base]); + this.baseAssetsMap.set(pair.quote, [pair.base]); } }); - this.logger.silly(`Prepared data for requests to CryptoCompare: ${stringify(mapToArray(baseAssetsMap))}`); + this.logger.silly(`Prepared data for requests to CryptoCompare: ${stringify(mapToArray(this.baseAssetsMap))}`); + + await this.updateRates(); - const rates = new Map(); + this.logger.silly(`Updating rates every ${this.rateUpdateInterval} minutes`); + setInterval(async () => { + await this.updateRates(); + }, this.rateUpdateInterval * 60 * 1000); + } + + private updateRates = async () => { const promises: Promise[] = []; - baseAssetsMap.forEach((baseAssets, quoteAsset) => { + this.baseAssetsMap.forEach((baseAssets, quoteAsset) => { promises.push(new Promise(async (resolve) => { const baseAssetsRates = await this.cryptoCompare.getPriceMulti(baseAssets, [quoteAsset]); baseAssets.forEach((baseAsset) => { - rates.set(getPairId({ base: baseAsset, quote: quoteAsset }), baseAssetsRates[baseAsset][quoteAsset]); + this.rates.set(getPairId({ base: baseAsset, quote: quoteAsset }), baseAssetsRates[baseAsset][quoteAsset]); }); resolve(); @@ -48,10 +60,9 @@ class RateProvider { await Promise.all(promises); - this.logger.debug(`Got updated rates: ${stringify(mapToArray(rates))}`); - - return rates; + this.logger.debug(`Updated rates: ${stringify(mapToArray(this.rates))}`); } + } export default RateProvider; diff --git a/lib/service/Service.ts b/lib/service/Service.ts index 74a95f6..b3eba9b 100644 --- a/lib/service/Service.ts +++ b/lib/service/Service.ts @@ -7,7 +7,7 @@ import { OrderSide, OutputType, CurrencyInfo } from '../proto/boltzrpc_pb'; import PairRepository from './PairRepository'; import RateProvider from '../rates/RateProvider'; import { PairInstance, PairFactory } from '../consts/Database'; -import { splitPairId, stringify, mapToArray } from '../Utils'; +import { splitPairId, stringify, mapToArray, mapToObject } from '../Utils'; import Errors from './Errors'; type PairConfig = { @@ -19,8 +19,6 @@ type Pair = { id: string; base: string; quote: string; - - rate: number; }; type PendingSwap = { @@ -43,14 +41,10 @@ class Service extends EventEmitter { private pairs = new Map(); - // This object is needed because a stringifyied Map is an empty object - // tslint:disable-next-line:no-null-keyword - private pairsObject = {}; - - constructor(private logger: Logger, db: Database, private boltz: BoltzClient) { + constructor(private logger: Logger, db: Database, private boltz: BoltzClient, rateInterval: number) { super(); - this.rateProvider = new RateProvider(this.logger); + this.rateProvider = new RateProvider(this.logger, rateInterval); this.pairRepository = new PairRepository(db.models); } @@ -110,24 +104,19 @@ class Service extends EventEmitter { } }; - const rates = await this.rateProvider.getRates(dbPairs); + await this.rateProvider.init(dbPairs); dbPairs.forEach((pair) => { try { verifyBackendSupport(pair.base); verifyBackendSupport(pair.quote); - const rate = rates.get(pair.id)!; - this.pairs.set(pair.id, { // The values have to be set manually to avoid "TypeError: Converting circular structure to JSON" errors - rate, id: pair.id, base: pair.base, quote: pair.quote, }); - - this.pairsObject[pair.id] = rate; } catch (error) { this.logger.warn(`Could not initialise pair ${pair.id}: ${error.message}`); } @@ -158,7 +147,7 @@ class Service extends EventEmitter { * Gets all supported pairs and their conversion rates */ public getPairs = () => { - return this.pairsObject; + return mapToObject(this.rateProvider.rates); } /** @@ -182,12 +171,13 @@ class Service extends EventEmitter { const { base, quote } = splitPairId(pairId); const pair = this.pairs.get(pairId); + const rate = this.rateProvider.rates.get(pairId); - if (pair === undefined) { + if (!pair || !rate) { throw Errors.PAIR_NOT_SUPPORTED(pairId); } - const swapResponse = await this.boltz.createSwap(base, quote, orderSide, pair.rate, invoice, refundPublicKey, OutputType.COMPATIBILITY); + const swapResponse = await this.boltz.createSwap(base, quote, orderSide, rate, invoice, refundPublicKey, OutputType.COMPATIBILITY); await this.boltz.listenOnAddress(this.getChainCurrency(orderSide, base, quote), swapResponse.address); const id = uuidv4();