From fb9a84f77e7279e3ad2063aac4b72cc34715f80f Mon Sep 17 00:00:00 2001 From: Stultus Mundi Date: Wed, 5 Aug 2026 12:21:15 +0300 Subject: [PATCH 01/12] feat: Binance public-API provider client Adds a Binance singleton provider (mirroring CoinGecko's shape) that fetches the tokenised bStocks universe and Spot 24h/7d tickers from Binance's public endpoints, no API key required. Exposes getTokenisedAssets (1h cache, BSC-only filter), getTradingSymbols (1h cache, TRADING-status filter from exchangeInfo), getTicker24h (60s cache, single batched call), and getTicker7d (60s cache, chunked to 20 symbols/request via the existing arraySplit util). Ticker fetches keep a last-known-good value per symbol so a halted/omitted symbol (e.g. during a stock-split trading break) or an outright failed refresh still serves the previous price instead of dropping the entry. Fixtures are built from live curls of both Binance endpoints rather than assumed shapes. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01UnpQSycV6gfbF9JqRf4x1f --- config/index.ts | 3 + src/services/providers/binance.ts | 243 ++++++++++++++++++++++++++++++ src/services/providers/index.ts | 3 +- src/types.ts | 15 ++ tests/binanceProvider.spec.ts | 155 +++++++++++++++++++ 5 files changed, 418 insertions(+), 1 deletion(-) create mode 100644 src/services/providers/binance.ts create mode 100644 tests/binanceProvider.spec.ts diff --git a/config/index.ts b/config/index.ts index d320eba..a5c18cf 100644 --- a/config/index.ts +++ b/config/index.ts @@ -11,6 +11,9 @@ export const config = { liveCoinWatchUrl: 'https://api.livecoinwatch.com/', zelCoinsUrl: 'https://raw.githubusercontent.com/ZelCore-io/Zelcore/master/coins.json', zelCoinInfoUrl: 'https://raw.githubusercontent.com/ZelCore-io/Zelcore/master/coininfo.json', + binanceApiUrl: 'https://api.binance.com/', + binanceAssetUrl: 'https://www.binance.com/', + bStocksEnabled: true, }; export default config; \ No newline at end of file diff --git a/src/services/providers/binance.ts b/src/services/providers/binance.ts new file mode 100644 index 0000000..6b56e42 --- /dev/null +++ b/src/services/providers/binance.ts @@ -0,0 +1,243 @@ +import { LRUCache as LRU } from 'lru-cache'; +import config from '../../../config'; +import * as log from '../../lib/log'; +import { AxiosWrapper } from '../../lib/axios'; +import { arraySplit } from '../../lib/utils'; +import type { BinanceTicker, BinanceTokenisedAsset } from '../../types'; + +// 7d ticker window is fetched per-symbol; stay far under Binance's 200-weight/request cap. +const TICKER_CHUNK = 20; + +/** + * Singleton class to interact with Binance's public (no-API-key) endpoints. + * + * Provides the tokenised-asset universe (bStocks with a BSC contract) and Spot + * 24h/7d tickers, quoted in USDT. Mirrors `CoinGecko`'s shape: an `AxiosWrapper` + * per base URL, an `LRUCache` per refresh cadence, and defensive error handling + * that never lets a single failed refresh drop a symbol that was previously + * known good (e.g. during a CEX trading halt around a stock split). + * + * @example + * ```typescript + * import { Binance } from './binance'; + * + * async function fetchBStocks() { + * const binance = Binance.getInstance(); + * const assets = await binance.getTokenisedAssets(); + * const trading = await binance.getTradingSymbols(); + * const tickers = await binance.getTicker24h([...trading]); + * console.log(tickers); + * } + * ``` + */ +export class Binance { + /** + * The singleton instance of the Binance class. + * @private + */ + private static instance: Binance; + + /** + * AxiosWrapper for the api.binance.com host (exchangeInfo, tickers). + * @private + */ + private api = new AxiosWrapper(config.binanceApiUrl); + + /** + * AxiosWrapper for the www.binance.com host (tokenised-asset listing). + * @private + */ + private assetApi = new AxiosWrapper(config.binanceAssetUrl); + + /** + * Cache for slow-moving data (tokenised-asset list, trading symbol set): 1 hour. + * @private + */ + private longCache = new LRU({ max: 10, ttl: 60 * 60 * 1000 }); + + /** + * Cache for ticker quotes, keyed by requested symbol set: 60 seconds. + * @private + */ + private quoteCache = new LRU({ max: 50, ttl: 60 * 1000 }); + + /** + * Last-known-good ticker per symbol, independent of `quoteCache`'s TTL. Used to + * backfill a symbol that a refresh omitted or that an entire refresh request + * failed for (e.g. a CEX trading halt during a stock split), so a transient + * gap upstream never drops the symbol from the response. + * @private + */ + private lastGoodTicker = new Map(); + + /** + * Returns the singleton instance of the Binance class. + * + * @returns The singleton instance of Binance. + */ + static getInstance(): Binance { + if (!Binance.instance) Binance.instance = new Binance(); + return Binance.instance; + } + + /** + * Filters tokenised assets down to those with a BSC (BNB Smart Chain) contract listed. + * + * @param assets - The raw tokenised-asset list from Binance. + * @returns Only the assets with at least one BSC entry in `caList`. + */ + filterBscAssets(assets: BinanceTokenisedAsset[]): BinanceTokenisedAsset[] { + return (assets || []).filter((a) => (a.caList || []) + .some((c) => String(c.network).toUpperCase() === 'BSC' && !!c.ca)); + } + + /** + * Splits a symbol list into chunks of at most `TICKER_CHUNK` symbols, to stay + * under Binance's per-request weight cap on the 7d rolling-window ticker. + * + * @param symbols - The full symbol list to split. + * @returns An array of symbol chunks. + */ + chunkSymbols(symbols: string[]): string[][] { + return arraySplit(symbols, TICKER_CHUNK); + } + + /** + * Merges a freshly-fetched ticker batch into the last-known-good store, then + * returns the requested symbols using the fresh value where available and + * falling back to the last-known-good value otherwise (halted/omitted symbol, + * or the whole request failed and `fetched` is empty). + * + * @private + * @param symbols - The symbols that were requested. + * @param fetched - Whatever tickers were actually returned (possibly a subset, possibly empty on error). + * @returns One ticker per requested symbol that has ever been seen; halted/never-seen symbols are omitted. + */ + private mergeTickers(symbols: string[], fetched: BinanceTicker[]): BinanceTicker[] { + fetched.forEach((t) => this.lastGoodTicker.set(t.symbol, t)); + const bySymbol = new Map(fetched.map((t) => [t.symbol, t])); + return symbols + .map((s) => bySymbol.get(s) ?? this.lastGoodTicker.get(s)) + .filter((t): t is BinanceTicker => !!t); + } + + /** + * Retrieves the tokenised-asset universe (bStocks), filtered to those with a BSC contract. + * + * Cached for 1 hour. + * + * @returns The BSC-listed tokenised assets. + */ + async getTokenisedAssets(): Promise { + const key = 'tokenised'; + if (this.longCache.has(key)) return this.longCache.get(key) as BinanceTokenisedAsset[]; + + try { + const res = await this.assetApi.get('bapi/asset/v2/public/asset/asset/get-tokenised-asset'); + const assets = this.filterBscAssets(res.data?.data ?? []); + this.longCache.set(key, assets); + return assets; + } catch (err) { + log.error('Error getting tokenised assets from Binance'); + log.error(err); + return []; + } + } + + /** + * Retrieves the set of Spot symbols currently in `TRADING` status. + * + * A symbol dropping to `BREAK` (as happens during trading halts, e.g. around + * a stock split) simply falls out of this set on the next refresh; callers + * should keep serving the last-known-good ticker for it rather than treating + * its absence here as "delisted". + * + * Cached for 1 hour. + * + * @returns The set of currently-trading symbols. + */ + async getTradingSymbols(): Promise> { + const key = 'trading'; + if (this.longCache.has(key)) return this.longCache.get(key) as Set; + + try { + const res = await this.api.get('api/v3/exchangeInfo?permissions=SPOT'); + const set = new Set( + (res.data?.symbols ?? []) + .filter((s: { status: string }) => s.status === 'TRADING') + .map((s: { symbol: string }) => s.symbol), + ); + this.longCache.set(key, set); + return set; + } catch (err) { + log.error('Error getting trading symbols from Binance'); + log.error(err); + return new Set(); + } + } + + /** + * Retrieves 24h tickers for the given symbols in a single request. + * + * On a failed or partial refresh, missing symbols are backfilled from the + * last-known-good store rather than dropped. Cached for 60 seconds per + * requested symbol set. + * + * @param symbols - The Spot symbols to fetch (e.g. `TSLABUSDT`). + * @returns One ticker per requested symbol that has ever been seen. + */ + async getTicker24h(symbols: string[]): Promise { + const key = `t24:${symbols.join(',')}`; + if (this.quoteCache.has(key)) return this.quoteCache.get(key) as BinanceTicker[]; + + let fetched: BinanceTicker[] = []; + try { + const res = await this.api.get(`api/v3/ticker/24hr?symbols=${encodeURIComponent(JSON.stringify(symbols))}`); + fetched = res.data ?? []; + } catch (err) { + log.error('Error getting 24h tickers from Binance'); + log.error(err); + } + + const merged = this.mergeTickers(symbols, fetched); + this.quoteCache.set(key, merged); + return merged; + } + + /** + * Retrieves 7d rolling-window tickers for the given symbols, chunked to stay + * under Binance's per-request weight cap. + * + * Each chunk is fetched independently, so one failing chunk never drops the + * symbols in the others; any symbol whose chunk failed (or that was omitted, + * e.g. a halt) is backfilled from the last-known-good store. Cached for 60 + * seconds per requested symbol set. + * + * @param symbols - The Spot symbols to fetch (e.g. `TSLABUSDT`). + * @returns One ticker per requested symbol that has ever been seen. + */ + async getTicker7d(symbols: string[]): Promise { + const key = `t7d:${symbols.join(',')}`; + if (this.quoteCache.has(key)) return this.quoteCache.get(key) as BinanceTicker[]; + + const chunks = this.chunkSymbols(symbols); + const fetched: BinanceTicker[] = []; + /* eslint-disable no-await-in-loop */ + for (const chunk of chunks) { + try { + const res = await this.api.get(`api/v3/ticker?symbols=${encodeURIComponent(JSON.stringify(chunk))}&windowSize=7d`); + fetched.push(...(res.data ?? [])); + } catch (err) { + log.error('Error getting 7d tickers from Binance'); + log.error(err); + } + } + /* eslint-enable no-await-in-loop */ + + const merged = this.mergeTickers(symbols, fetched); + this.quoteCache.set(key, merged); + return merged; + } +} + +export default Binance; diff --git a/src/services/providers/index.ts b/src/services/providers/index.ts index 2f0dc80..17f8911 100644 --- a/src/services/providers/index.ts +++ b/src/services/providers/index.ts @@ -1,4 +1,5 @@ export { CoinGecko } from './coinGecko'; export { CryptoCompare } from './cryptoCompare'; export { BitPay } from './bitpay'; -export { LiveCoinWatch } from './liveCoinWatch'; \ No newline at end of file +export { LiveCoinWatch } from './liveCoinWatch'; +export { Binance } from './binance'; \ No newline at end of file diff --git a/src/types.ts b/src/types.ts index 1c06661..225eeb6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -126,6 +126,21 @@ export type CoinGeckoPrice = { price_change_percentage_7d_in_currency: number; }; +export type BinanceTokenisedAsset = { + assetCode: string; + assetName: string; + uq?: string; + logo?: string; + caList?: { network: string; ca: string }[]; +}; + +export type BinanceTicker = { + symbol: string; + lastPrice: string; + priceChangePercent: string; + quoteVolume: string; +}; + export type CryptoComparePrice = { [key: string]: { [key: string]: number; diff --git a/tests/binanceProvider.spec.ts b/tests/binanceProvider.spec.ts new file mode 100644 index 0000000..d7a9e59 --- /dev/null +++ b/tests/binanceProvider.spec.ts @@ -0,0 +1,155 @@ +import type { AxiosResponse } from 'axios'; +import { AxiosWrapper } from '../src/lib/axios'; +import { Binance } from '../src/services/providers/binance'; +import type { BinanceTicker } from '../src/types'; + +/** Builds a minimal AxiosResponse-shaped object so mockResolvedValue satisfies AxiosWrapper.get's return type. */ +const axiosResponse = (data: T): AxiosResponse => ({ + data, + status: 200, + statusText: 'OK', + headers: {}, + config: {} as AxiosResponse['config'], +}); + +describe('Binance provider', () => { + const binance = Binance.getInstance(); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('is a singleton', () => { + expect(Binance.getInstance()).toBe(binance); + }); + + it('filters tokenised assets to those with a BSC contract', () => { + const assets = binance.filterBscAssets([ + { assetCode: 'TSLAB', assetName: 'Tesla', caList: [{ network: 'BSC', ca: '0x5b19' }] }, + { assetCode: 'ALABB', assetName: 'Unlaunched', caList: [] }, + ]); + expect(assets.map((a) => a.assetCode)).toEqual(['TSLAB']); + }); + + it('chunks 7d ticker requests to 20 symbols', () => { + const chunks = binance.chunkSymbols(Array.from({ length: 45 }, (_, i) => `S${i}USDT`)); + expect(chunks.length).toBe(3); + expect(chunks[0].length).toBe(20); + expect(chunks[2].length).toBe(5); + }); + + describe('getTokenisedAssets', () => { + it('unwraps the {data:[...]} envelope, keeps only BSC-listed assets, and caches the result', async () => { + const getSpy = jest.spyOn(AxiosWrapper.prototype, 'get').mockResolvedValue(axiosResponse({ + code: '000000', + message: null, + messageDetail: null, + data: [ + { assetCode: 'TSLAB', assetName: 'Tesla (bStocks)', logo: 'https://x/logo.png', uq: 'TSLA', caList: [{ network: 'BSC', ca: '0x5b1910eaad6450e50f816082aa078c41f10c292f' }] }, + { assetCode: 'TEST1B', assetName: 'Bstock TEST1B', uq: 'BNKK', caList: [] as { network: string; ca: string }[] }, + ], + })); + + const result = await binance.getTokenisedAssets(); + expect(result.map((a) => a.assetCode)).toEqual(['TSLAB']); + expect(getSpy).toHaveBeenCalledWith('bapi/asset/v2/public/asset/asset/get-tokenised-asset'); + + // second call within the 1h TTL must be served from cache, not the network + await binance.getTokenisedAssets(); + expect(getSpy).toHaveBeenCalledTimes(1); + }); + }); + + describe('getTradingSymbols', () => { + it('returns only symbols whose status is TRADING (BREAK/halted symbols excluded) and caches the result', async () => { + const getSpy = jest.spyOn(AxiosWrapper.prototype, 'get').mockResolvedValue(axiosResponse({ + timezone: 'UTC', + serverTime: 1785921304221, + symbols: [ + { symbol: 'TSLABUSDT', status: 'TRADING', baseAsset: 'TSLAB', quoteAsset: 'USDT' }, + { symbol: 'USDSBUSDT', status: 'BREAK', baseAsset: 'USDSB', quoteAsset: 'USDT' }, + { symbol: 'NVDABUSDT', status: 'TRADING', baseAsset: 'NVDAB', quoteAsset: 'USDT' }, + ], + })); + + const set = await binance.getTradingSymbols(); + expect(set).toEqual(new Set(['TSLABUSDT', 'NVDABUSDT'])); + expect(getSpy).toHaveBeenCalledWith('api/v3/exchangeInfo?permissions=SPOT'); + + await binance.getTradingSymbols(); + expect(getSpy).toHaveBeenCalledTimes(1); + }); + }); + + describe('getTicker24h', () => { + it('requests all symbols in a single call and caches the result for 60s', async () => { + const raw: BinanceTicker[] = [ + { symbol: 'TSLABUSDT', lastPrice: '324.92000000', priceChangePercent: '0.247', quoteVolume: '3149396.46686000' }, + { symbol: 'NVDABUSDT', lastPrice: '216.05000000', priceChangePercent: '3.626', quoteVolume: '3539230.84326000' }, + ]; + const getSpy = jest.spyOn(AxiosWrapper.prototype, 'get').mockResolvedValue(axiosResponse(raw)); + + const result = await binance.getTicker24h(['TSLABUSDT', 'NVDABUSDT']); + expect(result).toEqual(raw); + expect(getSpy).toHaveBeenCalledTimes(1); + const calledUrl = getSpy.mock.calls[0][0]; + expect(calledUrl).toBe(`api/v3/ticker/24hr?symbols=${encodeURIComponent(JSON.stringify(['TSLABUSDT', 'NVDABUSDT']))}`); + + await binance.getTicker24h(['TSLABUSDT', 'NVDABUSDT']); + expect(getSpy).toHaveBeenCalledTimes(1); + }); + + it('falls back to the last-known-good price for a symbol omitted on refresh (CEX halt)', async () => { + const good: BinanceTicker = { symbol: 'MSTRBUSDT', lastPrice: '400.00000000', priceChangePercent: '1.0', quoteVolume: '1000000' }; + const getSpy = jest.spyOn(AxiosWrapper.prototype, 'get'); + + // first request establishes a known-good price for MSTRBUSDT + getSpy.mockResolvedValueOnce(axiosResponse([good])); + const first = await binance.getTicker24h(['MSTRBUSDT']); + expect(first).toEqual([good]); + + // second request (different symbol set -> new cache key) halts MSTRBUSDT: Binance + // omits it from the payload entirely, as happens on trading breaks/splits + const other: BinanceTicker = { symbol: 'AMDBUSDT', lastPrice: '150.00000000', priceChangePercent: '2.0', quoteVolume: '500000' }; + getSpy.mockResolvedValueOnce(axiosResponse([other])); + const second = await binance.getTicker24h(['MSTRBUSDT', 'AMDBUSDT']); + + expect(second).toEqual(expect.arrayContaining([good, other])); + expect(second.find((t) => t.symbol === 'MSTRBUSDT')).toEqual(good); + }); + + it('serves the last-known-good ticker for requested symbols when the whole refresh request rejects', async () => { + const good: BinanceTicker = { symbol: 'CRCLBUSDT', lastPrice: '90.00000000', priceChangePercent: '0.5', quoteVolume: '2000000' }; + const getSpy = jest.spyOn(AxiosWrapper.prototype, 'get'); + + getSpy.mockResolvedValueOnce(axiosResponse([good])); + await binance.getTicker24h(['CRCLBUSDT']); + + // a different symbol combination forces a fresh network call, which this time fails outright + getSpy.mockRejectedValueOnce(new Error('network blip')); + const result = await binance.getTicker24h(['CRCLBUSDT', 'ZZZUSDT']); + + expect(result).toEqual([good]); + }); + }); + + describe('getTicker7d', () => { + it('chunks requests to <=20 symbols per call, tags windowSize=7d, and merges all chunk results', async () => { + const symbols = Array.from({ length: 25 }, (_, i) => `S${i}USDT`); + const getSpy = jest.spyOn(AxiosWrapper.prototype, 'get').mockImplementation(async (url?: string) => { + const match = (url || '').match(/symbols=([^&]+)/); + const requested: string[] = JSON.parse(decodeURIComponent(match ? match[1] : '[]')); + const data: BinanceTicker[] = requested.map((symbol) => ({ + symbol, lastPrice: '1.00', priceChangePercent: '0.0', quoteVolume: '1', + })); + return axiosResponse(data); + }); + + const result = await binance.getTicker7d(symbols); + expect(getSpy).toHaveBeenCalledTimes(2); + expect(getSpy.mock.calls[0][0]).toContain('windowSize=7d'); + expect(result.length).toBe(25); + expect(result.map((t) => t.symbol).sort()).toEqual([...symbols].sort()); + }); + }); +}); From 18aa05835cee0dbd17e657a41582c85ac70e78c3 Mon Sep 17 00:00:00 2001 From: Stultus Mundi Date: Wed, 5 Aug 2026 12:32:24 +0300 Subject: [PATCH 02/12] fix: treat a zero-priced halted symbol as unusable, not as a fresh price Binance does not omit a halted symbol from the ticker response - it returns the symbol present with lastPrice "0.00000000". Measured live: of 20 BREAK-status symbols requested, 20 came back present and 9 were priced at zero. So the absent-symbol fallback was dead code for the exact scenario it was written for, and the zero was accepted as fresh AND written to the last-known-good store - poisoning it, so a later outage would serve $0 forever rather than the real last price. mergeTickers now admits only finite, strictly positive prices, both to the served set and to the store. Adds lastGoodAgeMs() so a caller can tell a live price from one carried through a long halt, and sorts the ticker cache keys so the same request in a different symbol order still hits cache. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01UnpQSycV6gfbF9JqRf4x1f --- src/services/providers/binance.ts | 62 ++++++++++++++++++++++++++----- tests/binanceProvider.spec.ts | 41 +++++++++++++++++++- 2 files changed, 91 insertions(+), 12 deletions(-) diff --git a/src/services/providers/binance.ts b/src/services/providers/binance.ts index 6b56e42..0e12521 100644 --- a/src/services/providers/binance.ts +++ b/src/services/providers/binance.ts @@ -62,13 +62,34 @@ export class Binance { private quoteCache = new LRU({ max: 50, ttl: 60 * 1000 }); /** - * Last-known-good ticker per symbol, independent of `quoteCache`'s TTL. Used to - * backfill a symbol that a refresh omitted or that an entire refresh request - * failed for (e.g. a CEX trading halt during a stock split), so a transient - * gap upstream never drops the symbol from the response. + * Last-known-good ticker per symbol, independent of `quoteCache`'s TTL, with + * the epoch-ms timestamp at which it was accepted. Used to backfill a symbol + * whose fresh value is unusable — a halted symbol priced at zero, a symbol + * omitted from the batch, or an entire request that failed — so a transient + * gap upstream never drops the symbol or fabricates a price for it. * @private */ - private lastGoodTicker = new Map(); + private lastGoodTicker = new Map(); + + /** + * Whether a freshly-fetched ticker carries a usable price. + * + * Binance does NOT omit a halted symbol from the ticker response: it returns + * the symbol present with `lastPrice: "0.00000000"`. Measured against live + * data, 20 of 20 requested BREAK-status symbols came back present and 9 of + * those 20 were priced at zero. So a presence check alone never triggers the + * last-known-good fallback, and accepting the zero would both serve $0 and + * overwrite the good value — worse than dropping the entry. + * + * @private + * @param ticker - A ticker straight from Binance. + * @returns True when the ticker has a finite, strictly positive last price. + */ + private static isUsable(ticker: BinanceTicker | undefined): ticker is BinanceTicker { + if (!ticker) return false; + const px = parseFloat(String(ticker.lastPrice)); + return Number.isFinite(px) && px > 0; + } /** * Returns the singleton instance of the Binance class. @@ -114,13 +135,34 @@ export class Binance { * @returns One ticker per requested symbol that has ever been seen; halted/never-seen symbols are omitted. */ private mergeTickers(symbols: string[], fetched: BinanceTicker[]): BinanceTicker[] { - fetched.forEach((t) => this.lastGoodTicker.set(t.symbol, t)); - const bySymbol = new Map(fetched.map((t) => [t.symbol, t])); + const now = Date.now(); + const bySymbol = new Map(); + fetched.forEach((t) => { + // Only a usable price is allowed to become the new last-known-good. + // A halted symbol comes back present but priced at zero; letting it + // through would overwrite the real price and serve $0 from then on. + if (!Binance.isUsable(t)) return; + bySymbol.set(t.symbol, t); + this.lastGoodTicker.set(t.symbol, { ticker: t, at: now }); + }); return symbols - .map((s) => bySymbol.get(s) ?? this.lastGoodTicker.get(s)) + .map((s) => bySymbol.get(s) ?? this.lastGoodTicker.get(s)?.ticker) .filter((t): t is BinanceTicker => !!t); } + /** + * Age in milliseconds of the last-known-good price for a symbol, or null if + * none has ever been recorded. Lets a caller distinguish a live price from + * one carried through a long halt, which the ticker itself cannot express. + * + * @param symbol - The Binance symbol, e.g. `TSLABUSDT`. + * @returns Age in ms, or null when the symbol has never priced successfully. + */ + lastGoodAgeMs(symbol: string): number | null { + const entry = this.lastGoodTicker.get(symbol); + return entry ? Date.now() - entry.at : null; + } + /** * Retrieves the tokenised-asset universe (bStocks), filtered to those with a BSC contract. * @@ -187,7 +229,7 @@ export class Binance { * @returns One ticker per requested symbol that has ever been seen. */ async getTicker24h(symbols: string[]): Promise { - const key = `t24:${symbols.join(',')}`; + const key = `t24:${[...symbols].sort().join(',')}`; if (this.quoteCache.has(key)) return this.quoteCache.get(key) as BinanceTicker[]; let fetched: BinanceTicker[] = []; @@ -217,7 +259,7 @@ export class Binance { * @returns One ticker per requested symbol that has ever been seen. */ async getTicker7d(symbols: string[]): Promise { - const key = `t7d:${symbols.join(',')}`; + const key = `t7d:${[...symbols].sort().join(',')}`; if (this.quoteCache.has(key)) return this.quoteCache.get(key) as BinanceTicker[]; const chunks = this.chunkSymbols(symbols); diff --git a/tests/binanceProvider.spec.ts b/tests/binanceProvider.spec.ts index d7a9e59..3721494 100644 --- a/tests/binanceProvider.spec.ts +++ b/tests/binanceProvider.spec.ts @@ -108,8 +108,9 @@ describe('Binance provider', () => { const first = await binance.getTicker24h(['MSTRBUSDT']); expect(first).toEqual([good]); - // second request (different symbol set -> new cache key) halts MSTRBUSDT: Binance - // omits it from the payload entirely, as happens on trading breaks/splits + // second request (different symbol set -> new cache key) omits MSTRBUSDT. + // NOTE: omission is NOT what a real halt looks like -- see the zero-price + // test below. This covers the genuinely-absent case only. const other: BinanceTicker = { symbol: 'AMDBUSDT', lastPrice: '150.00000000', priceChangePercent: '2.0', quoteVolume: '500000' }; getSpy.mockResolvedValueOnce(axiosResponse([other])); const second = await binance.getTicker24h(['MSTRBUSDT', 'AMDBUSDT']); @@ -118,6 +119,42 @@ describe('Binance provider', () => { expect(second.find((t) => t.symbol === 'MSTRBUSDT')).toEqual(good); }); + it('treats a halted symbol priced at zero as unusable and keeps the last-known-good price', async () => { + // Measured against live Binance data: requesting 20 BREAK-status symbols + // returned all 20 PRESENT, and 9 of them carried lastPrice "0.00000000". + // Binance does not omit a halted symbol -- so a presence check alone never + // triggers the fallback, and accepting the zero would both serve $0 and + // overwrite the real price for good. + const good: BinanceTicker = { symbol: 'GOOGLBUSDT', lastPrice: '180.00000000', priceChangePercent: '1.0', quoteVolume: '900000' }; + const getSpy = jest.spyOn(AxiosWrapper.prototype, 'get'); + + getSpy.mockResolvedValueOnce(axiosResponse([good])); + expect(await binance.getTicker24h(['GOOGLBUSDT'])).toEqual([good]); + + // The halt: symbol present, price zeroed. + const halted: BinanceTicker = { symbol: 'GOOGLBUSDT', lastPrice: '0.00000000', priceChangePercent: '0.0', quoteVolume: '0' }; + const filler: BinanceTicker = { symbol: 'METABUSDT', lastPrice: '500.00000000', priceChangePercent: '0.2', quoteVolume: '100000' }; + getSpy.mockResolvedValueOnce(axiosResponse([halted, filler])); + const during = await binance.getTicker24h(['GOOGLBUSDT', 'METABUSDT']); + expect(during.find((t) => t.symbol === 'GOOGLBUSDT')).toEqual(good); + + // ...and the zero must not have poisoned the store: a later total failure + // still serves the real price rather than $0. + getSpy.mockRejectedValueOnce(new Error('network down')); + const after = await binance.getTicker24h(['GOOGLBUSDT', 'AMZNBUSDT']); + expect(after.find((t) => t.symbol === 'GOOGLBUSDT')).toEqual(good); + }); + + it('reports the age of a last-known-good price, and null for a symbol never priced', async () => { + const good: BinanceTicker = { symbol: 'ORCLBUSDT', lastPrice: '120.00000000', priceChangePercent: '1.0', quoteVolume: '10000' }; + const getSpy = jest.spyOn(AxiosWrapper.prototype, 'get'); + getSpy.mockResolvedValueOnce(axiosResponse([good])); + await binance.getTicker24h(['ORCLBUSDT']); + + expect(binance.lastGoodAgeMs('ORCLBUSDT')).toBeGreaterThanOrEqual(0); + expect(binance.lastGoodAgeMs('NEVERSEENUSDT')).toBeNull(); + }); + it('serves the last-known-good ticker for requested symbols when the whole refresh request rejects', async () => { const good: BinanceTicker = { symbol: 'CRCLBUSDT', lastPrice: '90.00000000', priceChangePercent: '0.5', quoteVolume: '2000000' }; const getSpy = jest.spyOn(AxiosWrapper.prototype, 'get'); From 2cf3f0034aa0896a1441b0f513f944de00e82c45 Mon Sep 17 00:00:00 2001 From: Stultus Mundi Date: Wed, 5 Aug 2026 12:35:13 +0300 Subject: [PATCH 03/12] feat: bStocks synthetic market assembler with last-known-good MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Intersects the Binance tokenised-asset universe (BSC-listed, from Task 1) with TRADING Spot symbols to emit CryptoPrice[] entries for bStocks, quoted against BTCUSDT fetched in the same batch so both legs share one venue. Ids are bstock- under provider "coingecko" per the cross-repo id contract with the sibling api repo. A module-level last-known-good map keeps serving a symbol's previous price across a refresh where it's omitted (CEX halt around a stock split), rather than dropping it. Verified against live Binance endpoints: 66 tokenised assets are all BSC-listed, and intersecting with exchangeInfo's TRADING Spot symbols yields 56/66 tradable today (the other 10, e.g. NFLXB/ASMLB, aren't listed on Spot at all yet) — matching the brief's expected ~56/66. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01UnpQSycV6gfbF9JqRf4x1f --- src/services/bstocks.ts | 77 +++++++++++++++++++++++++++++++++++++++++ tests/bstocks.spec.ts | 69 ++++++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 src/services/bstocks.ts create mode 100644 tests/bstocks.spec.ts diff --git a/src/services/bstocks.ts b/src/services/bstocks.ts new file mode 100644 index 0000000..2b4a4fa --- /dev/null +++ b/src/services/bstocks.ts @@ -0,0 +1,77 @@ +import config from '../../config'; +import { Binance } from './providers/binance'; +import type { CryptoPrice } from '../types'; + +// Last-known-good per bStock id — during CEX halts (stock splits) the ticker +// omits the symbol; we keep serving the previous price (display-only per the +// bStocks partner guide). +let lastGood = new Map(); + +export function _clearLastGoodForTests(): void { + lastGood = new Map(); +} + +/** + * Assembles the bStocks synthetic market: the intersection of Binance's + * tokenised-asset universe (already filtered to BSC-listed assets by + * `Binance.getTokenisedAssets`) with Spot symbols currently in `TRADING` + * status, quoted in USDT. + * + * BTC/USD conversion uses BTCUSDT fetched in the same 24h-ticker batch as the + * bStock symbols, so both legs come from the same venue and no cross-venue + * basis is introduced. + * + * Emitted ids are `bstock-` under `provider: "coingecko"` + * — NOT `"binance"` — because the ZelCore client's `use-fiat.js` only resolves + * the `coingecko|cryptocompare|coinmarketcap|livecoinwatch` provider prefixes, + * and the sibling `api` repo serves `coinInfo.coingeckoID = "bstock-"` to + * match. This id/provider pairing is a cross-repo contract — do not change it + * here in isolation. + * + * A module-level last-known-good map means a symbol that drops out of a given + * refresh (CEX halt, e.g. around a stock split) keeps being served at its + * previous price rather than disappearing from the response. + * + * @returns One `CryptoPrice` per tradable bStock (BSC contract + TRADING + * `USDT` Spot symbol), including any carried over from a prior refresh. + */ +export async function getBstockPrices(): Promise { + if (!config.bStocksEnabled) return []; + const binance = Binance.getInstance(); + const [assets, trading] = await Promise.all([ + binance.getTokenisedAssets(), + binance.getTradingSymbols(), + ]); + const tradable = assets.filter((a) => trading.has(`${a.assetCode}USDT`)); + const symbols = tradable.map((a) => `${a.assetCode}USDT`); + const withBtc = symbols.includes('BTCUSDT') ? symbols : [...symbols, 'BTCUSDT']; + const [t24, t7d] = await Promise.all([ + binance.getTicker24h(withBtc), + binance.getTicker7d(symbols), + ]); + const t24Map = new Map(t24.map((t) => [t.symbol, t])); + const t7dMap = new Map(t7d.map((t) => [t.symbol, t])); + const btcUsd = Number(t24Map.get('BTCUSDT')?.lastPrice); + + tradable.forEach((asset) => { + const id = `bstock-${asset.assetCode.toLowerCase()}`; + const ticker = t24Map.get(`${asset.assetCode}USDT`); + const px = Number(ticker?.lastPrice); + if (!ticker || !Number.isFinite(px) || px <= 0 || !Number.isFinite(btcUsd) || btcUsd <= 0) { + return; // keep lastGood entry as-is + } + lastGood.set(id, { + id, + provider: 'coingecko', + rates: { btc: px / btcUsd, usd: px }, + supply: 0, + volume: Number(ticker.quoteVolume) || 0, + change24h: Number(ticker.priceChangePercent) || 0, + market: 0, + rank: 0, + total_supply: 0, + change7d: Number(t7dMap.get(`${asset.assetCode}USDT`)?.priceChangePercent) || 0, + }); + }); + return Array.from(lastGood.values()); +} diff --git a/tests/bstocks.spec.ts b/tests/bstocks.spec.ts new file mode 100644 index 0000000..7f32630 --- /dev/null +++ b/tests/bstocks.spec.ts @@ -0,0 +1,69 @@ +import { Binance } from '../src/services/providers/binance'; +import { getBstockPrices, _clearLastGoodForTests } from '../src/services/bstocks'; + +jest.mock('../src/services/providers/binance'); + +const MockedBinance = Binance as jest.Mocked; + +function mockBinance({ assets, trading, t24, t7d }: { + assets: unknown[]; trading: string[]; t24: unknown[]; t7d: unknown[]; +}) { + MockedBinance.getInstance.mockReturnValue({ + getTokenisedAssets: jest.fn().mockResolvedValue(assets), + getTradingSymbols: jest.fn().mockResolvedValue(new Set(trading)), + getTicker24h: jest.fn().mockResolvedValue(t24), + getTicker7d: jest.fn().mockResolvedValue(t7d), + } as never); +} + +const TSLAB = { assetCode: 'TSLAB', assetName: 'Tesla', caList: [{ network: 'BSC', ca: '0x5b19' }] }; + +describe('bStocks assembler', () => { + beforeEach(() => _clearLastGoodForTests()); + + it('emits coingecko-provider entries with BTC and USD rates', async () => { + mockBinance({ + assets: [TSLAB], + trading: ['TSLABUSDT', 'BTCUSDT'], + t24: [ + { symbol: 'TSLABUSDT', lastPrice: '326.11', priceChangePercent: '2.5', quoteVolume: '1000000' }, + { symbol: 'BTCUSDT', lastPrice: '65222.00', priceChangePercent: '1.0', quoteVolume: '9' }, + ], + t7d: [{ symbol: 'TSLABUSDT', lastPrice: '326.11', priceChangePercent: '7.1', quoteVolume: '0' }], + }); + const prices = await getBstockPrices(); + expect(prices).toHaveLength(1); + expect(prices[0].id).toBe('bstock-tslab'); + expect(prices[0].provider).toBe('coingecko'); + expect(prices[0].rates.usd).toBeCloseTo(326.11); + expect(prices[0].rates.btc).toBeCloseTo(326.11 / 65222); + expect(prices[0].change24h).toBeCloseTo(2.5); + expect(prices[0].change7d).toBeCloseTo(7.1); + }); + + it('skips assets without a TRADING symbol', async () => { + mockBinance({ assets: [TSLAB], trading: ['BTCUSDT'], t24: [ + { symbol: 'BTCUSDT', lastPrice: '65222.00', priceChangePercent: '1.0', quoteVolume: '9' }, + ], t7d: [] }); + expect(await getBstockPrices()).toHaveLength(0); + }); + + it('serves last-known-good when a symbol disappears (CEX halt)', async () => { + mockBinance({ + assets: [TSLAB], trading: ['TSLABUSDT', 'BTCUSDT'], + t24: [ + { symbol: 'TSLABUSDT', lastPrice: '326.11', priceChangePercent: '2.5', quoteVolume: '1000000' }, + { symbol: 'BTCUSDT', lastPrice: '65222.00', priceChangePercent: '1.0', quoteVolume: '9' }, + ], + t7d: [], + }); + await getBstockPrices(); + // Halt: ticker omits TSLABUSDT this round + mockBinance({ assets: [TSLAB], trading: ['TSLABUSDT', 'BTCUSDT'], t24: [ + { symbol: 'BTCUSDT', lastPrice: '65000.00', priceChangePercent: '0.5', quoteVolume: '9' }, + ], t7d: [] }); + const prices = await getBstockPrices(); + expect(prices).toHaveLength(1); + expect(prices[0].rates.usd).toBeCloseTo(326.11); + }); +}); From e07f1ca8f80af43510262c657ee2197b0dd79ad5 Mon Sep 17 00:00:00 2001 From: Stultus Mundi Date: Wed, 5 Aug 2026 12:42:48 +0300 Subject: [PATCH 04/12] test: pin the BTC-divisor guard, per-asset isolation and the disabled path The guard against a missing or zero BTCUSDT was correct but untested, so a later simplification could silently put Infinity or NaN into rates.btc with nothing in CI to catch it - the same shape of bug that reached a signing path in the sibling api repo. Mutation-tested: dropping the btcUsd half of the guard fails both new tests. Also pins that one halted asset does not affect its siblings in the same batch, and that the feature flag actually suppresses output. Corrects two JSDoc claims that came from the plan text: the client does no provider-prefix parsing (applyMarkets and use-fiat.js each build the literal `${provider}-${id}` and the strings simply have to match), and Binance does not omit halted symbols - it returns them present at zero. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01UnpQSycV6gfbF9JqRf4x1f --- src/services/bstocks.ts | 22 ++++++++++------ tests/bstocks.spec.ts | 58 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 8 deletions(-) diff --git a/src/services/bstocks.ts b/src/services/bstocks.ts index 2b4a4fa..e75c198 100644 --- a/src/services/bstocks.ts +++ b/src/services/bstocks.ts @@ -2,9 +2,12 @@ import config from '../../config'; import { Binance } from './providers/binance'; import type { CryptoPrice } from '../types'; -// Last-known-good per bStock id — during CEX halts (stock splits) the ticker -// omits the symbol; we keep serving the previous price (display-only per the -// bStocks partner guide). +// Last-known-good per bStock id. During a CEX halt (stock splits) Binance +// returns the symbol PRESENT with lastPrice "0.00000000" rather than omitting +// it — measured live, 20/20 halted symbols came back present, 9 priced zero — +// so the guard below is on the price being finite and positive, not on the +// ticker being absent. We keep serving the previous price (display-only per +// the bStocks partner guide). let lastGood = new Map(); export function _clearLastGoodForTests(): void { @@ -22,11 +25,14 @@ export function _clearLastGoodForTests(): void { * basis is introduced. * * Emitted ids are `bstock-` under `provider: "coingecko"` - * — NOT `"binance"` — because the ZelCore client's `use-fiat.js` only resolves - * the `coingecko|cryptocompare|coinmarketcap|livecoinwatch` provider prefixes, - * and the sibling `api` repo serves `coinInfo.coingeckoID = "bstock-"` to - * match. This id/provider pairing is a cross-repo contract — do not change it - * here in isolation. + * — NOT `"binance"`. The client does no prefix parsing: ZelCore's + * `store/actions.js` (`applyMarkets`) keys the market store on the literal + * string `${provider}-${id}`, and `use-fiat.js` builds the same literal from + * `coininfo.json`'s `coingeckoID` as `coingecko-${coingeckoID}`. The sibling + * `api` repo serves `coinInfo.coingeckoID = "bstock-"`, so the two + * literals only meet if the provider here is exactly `"coingecko"`. Any other + * value makes the lookup miss silently — no error, just no price. This + * id/provider pairing is a cross-repo contract; do not change it in isolation. * * A module-level last-known-good map means a symbol that drops out of a given * refresh (CEX halt, e.g. around a stock split) keeps being served at its diff --git a/tests/bstocks.spec.ts b/tests/bstocks.spec.ts index 7f32630..e6cb6c2 100644 --- a/tests/bstocks.spec.ts +++ b/tests/bstocks.spec.ts @@ -66,4 +66,62 @@ describe('bStocks assembler', () => { expect(prices).toHaveLength(1); expect(prices[0].rates.usd).toBeCloseTo(326.11); }); + + // The BTC divisor is the one place an unusable upstream value can turn into + // Infinity/NaN in an emitted rate. A near-identical zero-divisor bug reached + // a transaction-signing path in the sibling `api` repo, so pin both shapes. + it('emits nothing rather than Infinity when BTCUSDT is missing from the batch', async () => { + mockBinance({ + assets: [TSLAB], + trading: ['TSLABUSDT', 'BTCUSDT'], + t24: [{ symbol: 'TSLABUSDT', lastPrice: '326.11', priceChangePercent: '2.5', quoteVolume: '1000000' }], + t7d: [{ symbol: 'TSLABUSDT', lastPrice: '326.11', priceChangePercent: '7.1', quoteVolume: '0' }], + }); + expect(await getBstockPrices()).toHaveLength(0); + }); + + it('emits nothing rather than Infinity when BTCUSDT is priced at zero', async () => { + mockBinance({ + assets: [TSLAB], + trading: ['TSLABUSDT', 'BTCUSDT'], + t24: [ + { symbol: 'TSLABUSDT', lastPrice: '326.11', priceChangePercent: '2.5', quoteVolume: '1000000' }, + { symbol: 'BTCUSDT', lastPrice: '0.00000000', priceChangePercent: '0', quoteVolume: '0' }, + ], + t7d: [{ symbol: 'TSLABUSDT', lastPrice: '326.11', priceChangePercent: '7.1', quoteVolume: '0' }], + }); + expect(await getBstockPrices()).toHaveLength(0); + }); + + it('isolates a bad ticker to its own asset, leaving siblings in the batch priced', async () => { + const NVDAB = { assetCode: 'NVDAB', assetName: 'Nvidia', caList: [{ network: 'BSC', ca: '0xabcd' }] }; + mockBinance({ + assets: [TSLAB, NVDAB], + trading: ['TSLABUSDT', 'NVDABUSDT', 'BTCUSDT'], + t24: [ + // TSLAB halted (present, zeroed); NVDAB healthy. + { symbol: 'TSLABUSDT', lastPrice: '0.00000000', priceChangePercent: '0', quoteVolume: '0' }, + { symbol: 'NVDABUSDT', lastPrice: '120.00', priceChangePercent: '3.0', quoteVolume: '2000' }, + { symbol: 'BTCUSDT', lastPrice: '65222.00', priceChangePercent: '1.0', quoteVolume: '9' }, + ], + t7d: [{ symbol: 'NVDABUSDT', lastPrice: '120.00', priceChangePercent: '4.0', quoteVolume: '0' }], + }); + const prices = await getBstockPrices(); + expect(prices.map((p) => p.id)).toEqual(['bstock-nvdab']); + expect(prices[0].rates.usd).toBeCloseTo(120); + }); + + it('emits nothing at all when the feature is disabled', async () => { + const config = await import('../config'); + const original = config.default.bStocksEnabled; + config.default.bStocksEnabled = false; + try { + mockBinance({ + assets: [TSLAB], trading: ['TSLABUSDT', 'BTCUSDT'], t24: [], t7d: [], + }); + expect(await getBstockPrices()).toHaveLength(0); + } finally { + config.default.bStocksEnabled = original; + } + }); }); From 8c296f1463648287f648283282b36ab726365e90 Mon Sep 17 00:00:00 2001 From: Stultus Mundi Date: Wed, 5 Aug 2026 12:49:04 +0300 Subject: [PATCH 05/12] feat: bStock entries in /v2/rates; key-based crypto merge Adds a fifth try/catch provider block to zelcoreRatesV2.getAll() that appends getBstockPrices() (Task 2) into the crypto array, wrapped so a Binance/bStocks outage sets errors.binance = true instead of throwing out of getAll(). Also excludes synthetic `bstock-*` ids from the CoinGecko harvest in coinAggregatorIDs.ts so rates-api never queries CoinGecko for them. Fixes a pre-existing positional-merge bug in apiServices.ts: the crypto refresh merge used mergeDeep, which walks target/source arrays by index. Since `processed` in zelcoreRatesV2.getAll() is a concatenation of four independent try/catch provider blocks, its length and per-index identity shift between refresh cycles whenever any block throws or an upstream API returns a different row count -- both routine occurrences. Concretely: if CoinGecko's block (rank/change7d present) fails one cycle while CryptoCompare's block (no rank/change7d) succeeds, mergeDeep deep-merges the old CoinGecko entry at index 0 with the new CryptoCompare entry at index 0 -- id/provider/rates get correctly overwritten, but `rank` survives from the stale CoinGecko entry, producing a CryptoCompare coin wearing a foreign coin's rank. Separately, if the new array is shorter than the old one, mergeDeep's source.forEach never visits the trailing old indices, so entries missing from the new fetch persist in the output forever with frozen data instead of being dropped. Replaces the crypto merge with mergeCryptoByKey, which discards the stale target and rebuilds the array from source only, keyed by `${provider}-${id}`. tests/mergeCrypto.spec.ts pins this: one test documents mergeDeep's corruption (still used unchanged for fiat/rates/ marketsUSD) on realistic shape-mismatched input, one confirms mergeCryptoByKey fixes it, and one confirms mergeCryptoByKey produces identical output to mergeDeep for a normal, well-ordered refresh so existing consumers see no behavioural change. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01UnpQSycV6gfbF9JqRf4x1f --- src/lib/objects.ts | 14 ++++ src/services/apiServices.ts | 4 +- src/services/coinAggregatorIDs.ts | 3 +- src/services/zelcoreRatesV2.ts | 11 +++ tests/mergeCrypto.spec.ts | 119 ++++++++++++++++++++++++++++++ 5 files changed, 148 insertions(+), 3 deletions(-) create mode 100644 tests/mergeCrypto.spec.ts diff --git a/src/lib/objects.ts b/src/lib/objects.ts index 0e3d809..1c3b4a8 100644 --- a/src/lib/objects.ts +++ b/src/lib/objects.ts @@ -45,3 +45,17 @@ export function mergeDeep(target: any, source: any) { } return target; } + +/** + * Replaces the crypto array wholesale, keyed by `${provider}-${id}`. + * The previous positional mergeDeep corrupted entries when provider block + * lengths shifted between refreshes (duplicate/stale-field bug). + */ +export function mergeCryptoByKey( + _target: T[], + source: T[], +): T[] { + const byKey = new Map(); + for (const entry of source) byKey.set(`${entry.provider}-${entry.id}`, entry); + return Array.from(byKey.values()); +} diff --git a/src/services/apiServices.ts b/src/services/apiServices.ts index 14adbe3..d3b7c63 100644 --- a/src/services/apiServices.ts +++ b/src/services/apiServices.ts @@ -1,7 +1,7 @@ import { Request, Response } from 'express'; import zlib from 'zlib'; import * as log from '../lib/log'; -import { mergeDeep } from '../lib/objects'; +import { mergeDeep, mergeCryptoByKey } from '../lib/objects'; import zelcoreRates from './zelcoreRates'; import zelcoreMarketsUSD from './zelcoreMarketsUSD'; import zelcoreRatesV2 from './zelcoreRatesV2'; @@ -246,7 +246,7 @@ export async function serviceRefresher(): Promise { if (ratesV2Fetched && ratesV2Fetched.fiat.length > 20 && ratesV2Fetched.crypto.length > 300) { ratesV2.fiat = mergeDeep(ratesV2.fiat, ratesV2Fetched.fiat); - ratesV2.crypto = mergeDeep(ratesV2.crypto, ratesV2Fetched.crypto); + ratesV2.crypto = mergeCryptoByKey(ratesV2.crypto, ratesV2Fetched.crypto); ratesV2.errors = ratesV2Fetched.errors; } diff --git a/src/services/coinAggregatorIDs.ts b/src/services/coinAggregatorIDs.ts index 07dc71c..00e00aa 100644 --- a/src/services/coinAggregatorIDs.ts +++ b/src/services/coinAggregatorIDs.ts @@ -94,7 +94,8 @@ export async function getLatestCoinInfo(): Promise { const coinInfo: Record = (await axios.get(config.zelCoinInfoUrl)).data; const coinGeckoKeys = Object.values(coinInfo) .map((coin) => coin.coingeckoID) - .filter((id) => !!id); + .filter((id) => !!id) + .filter((id: string) => !id.startsWith('bstock-')); const uniqueCoinGeckoKeys = [...new Set(coinGeckoKeys)]; coinAggregatorIDs.coingecko = [...new Set([...coinAggregatorIDs.coingecko, ...uniqueCoinGeckoKeys])]; zelData.coinInfo = coinInfo; diff --git a/src/services/zelcoreRatesV2.ts b/src/services/zelcoreRatesV2.ts index f958670..7dbf4aa 100644 --- a/src/services/zelcoreRatesV2.ts +++ b/src/services/zelcoreRatesV2.ts @@ -1,6 +1,7 @@ import { coinAggregatorIDs } from './coinAggregatorIDs'; import * as log from '../lib/log'; import { CoinGecko, BitPay, CryptoCompare, LiveCoinWatch } from './providers'; +import { getBstockPrices } from './bstocks'; import { PricesResponse, CryptoPrice, ICurrencyRate } from '../types'; /** @@ -132,6 +133,16 @@ export async function getAll(): Promise { errors.livecoinwatch = true; } + // Fetch bStock prices from Binance + try { + const bstocks = await getBstockPrices(); + processed.push(...bstocks); + } catch (e) { + log.error('bStocks error'); + log.error(e); + errors.binance = true; + } + return { crypto: processed, fiat, diff --git a/tests/mergeCrypto.spec.ts b/tests/mergeCrypto.spec.ts new file mode 100644 index 0000000..1000f2b --- /dev/null +++ b/tests/mergeCrypto.spec.ts @@ -0,0 +1,119 @@ +import { mergeDeep, mergeCryptoByKey } from '../src/lib/objects'; +import { CryptoPrice } from '../src/types'; + +describe('mergeCryptoByKey', () => { + it('replaces entries by provider-id key, not by index', () => { + const target = [ + { id: 'bitcoin', provider: 'coingecko', rates: { btc: 1 } }, + { id: 'stale', provider: 'livecoinwatch', rates: { btc: 9 } }, + ]; + const source = [ + { id: 'bstock-tslab', provider: 'coingecko', rates: { btc: 0.005 } }, + { id: 'bitcoin', provider: 'coingecko', rates: { btc: 1.0001 } }, + ]; + const merged = mergeCryptoByKey(target, source); + expect(merged).toHaveLength(2); + expect(merged.find((e) => e.id === 'bitcoin')!.rates.btc).toBe(1.0001); + expect(merged.find((e) => e.id === 'stale')).toBeUndefined(); // stale tails dropped + }); +}); + +/** + * Regression coverage for the pre-existing positional-merge bug in + * `mergeDeep`, which `apiServices.ts` used to use for the crypto array too. + * + * `processed` in `zelcoreRatesV2.getAll()` is built by concatenating four + * independent provider blocks (coingecko, cryptocompare, livecoinwatch, and + * now bstocks), each wrapped in its own try/catch. If any block throws or an + * upstream API returns a different number of rows than last cycle -- both + * routine, expected occurrences, not edge cases -- the total array length and + * the identity of "whatever happens to be at index N" shift between refresh + * cycles. `mergeDeep` merges purely by array index, so: + * + * 1. "Frankenstein" records: entry N from the OLD cycle gets deep-merged + * with entry N from the NEW cycle. Fields present on both are correctly + * overwritten (id, provider, rates), but fields present only on the OLD + * entry's shape (e.g. `rank`/`change7d`, which CoinGecko sends but + * CryptoCompare does not) survive untouched -- a CryptoCompare coin ends + * up wearing a stale CoinGecko coin's rank. + * 2. Stale tails: when the NEW array is shorter than the OLD one, + * `mergeDeep`'s `source.forEach` never visits the trailing OLD indices, + * so those entries -- which the new fetch no longer produced at all -- + * persist in the output forever with frozen, increasingly stale data. + */ +describe('positional-merge bug (apiServices.ts crypto merge)', () => { + // Cycle 1 result: three coingecko-shaped entries (has `rank`/`change7d`). + const target: CryptoPrice[] = [ + { + id: 'bitcoin', provider: 'coingecko', rates: { btc: 1, usd: 65000 }, supply: 1, volume: 1, change24h: 1, market: 1, rank: 1, total_supply: 21000000, change7d: 1, + }, + { + id: 'ethereum', provider: 'coingecko', rates: { btc: 0.05, usd: 3200 }, supply: 1, volume: 1, change24h: 1, market: 1, rank: 2, total_supply: 1, change7d: 1, + }, + { + id: 'litecoin', provider: 'coingecko', rates: { btc: 0.002, usd: 130 }, supply: 1, volume: 1, change24h: 1, market: 1, rank: 3, total_supply: 1, change7d: 1, + }, + ]; + // Cycle 2: CoinGecko's block threw this cycle (ethereum/litecoin absent + // entirely), leaving only a single CryptoCompare-shaped entry (no + // `rank`/`change7d`) landing at index 0. + const source: CryptoPrice[] = [ + { + id: 'CONI', provider: 'cryptocompare', rates: { btc: 0.00001, usd: 0.5 }, supply: 1, volume: 1, change24h: 1, market: 1, total_supply: 1, + }, + ]; + + it('mergeDeep (pre-existing, still used for fiat/rates/marketsUSD) corrupts: frankenstein fields + stale tail survive', () => { + const merged = mergeDeep(JSON.parse(JSON.stringify(target)), source) as CryptoPrice[]; + const slot0 = merged.find((e) => e.provider === 'cryptocompare'); + // WRONG: CONI has no rank of its own -- this is bitcoin's stale rank, + // left over because CryptoCompare's shape doesn't carry a `rank` key for + // mergeDeep to overwrite it with. + expect(slot0?.id).toBe('CONI'); + expect(slot0?.rank).toBe(1); + // WRONG: ethereum/litecoin were not part of this cycle's fetch at all, + // but the stale tail (indices 1, 2) was never touched by the positional + // merge, so they survive in the output indefinitely. + expect(merged).toHaveLength(3); + expect(merged.find((e) => e.id === 'litecoin')).toBeDefined(); + }); + + it('mergeCryptoByKey (fixed) replaces wholesale: no stale fields, no stale tail', () => { + const merged = mergeCryptoByKey(JSON.parse(JSON.stringify(target)), source); + expect(merged).toHaveLength(1); + const [only] = merged; + expect(only.id).toBe('CONI'); + expect(only.rank).toBeUndefined(); // no bitcoin leftover + expect(merged.find((e: CryptoPrice) => e.id === 'litecoin')).toBeUndefined(); // dropped, not frozen + }); +}); + +/** + * For the common case the merge runs under every 30s -- a fresh cycle with + * the same providers succeeding, same ids, same order, same shapes -- the + * key-based merge must produce output identical to the old positional one so + * existing consumers see no behavioural change. + */ +describe('mergeCryptoByKey vs mergeDeep -- identical for well-ordered input', () => { + it('produces the same array for a normal, non-corrupting refresh', () => { + const target: CryptoPrice[] = [ + { + id: 'bitcoin', provider: 'coingecko', rates: { btc: 1, usd: 64000 }, supply: 1, volume: 1, change24h: 1, market: 1, rank: 1, total_supply: 21000000, change7d: 1, + }, + { + id: 'ethereum', provider: 'coingecko', rates: { btc: 0.05, usd: 3100 }, supply: 1, volume: 1, change24h: 1, market: 1, rank: 2, total_supply: 1, change7d: 1, + }, + ]; + const source: CryptoPrice[] = [ + { + id: 'bitcoin', provider: 'coingecko', rates: { btc: 1, usd: 65000 }, supply: 1, volume: 1, change24h: 1.2, market: 1, rank: 1, total_supply: 21000000, change7d: 1.2, + }, + { + id: 'ethereum', provider: 'coingecko', rates: { btc: 0.05, usd: 3200 }, supply: 1, volume: 1, change24h: 1.2, market: 1, rank: 2, total_supply: 1, change7d: 1.2, + }, + ]; + const viaMergeDeep = mergeDeep(JSON.parse(JSON.stringify(target)), source); + const viaKeyMerge = mergeCryptoByKey(JSON.parse(JSON.stringify(target)), source); + expect(viaKeyMerge).toEqual(viaMergeDeep); + }); +}); From 7a2468a8cade5eda737ea12d5da2661a8f76b82f Mon Sep 17 00:00:00 2001 From: Stultus Mundi Date: Wed, 5 Aug 2026 13:01:44 +0300 Subject: [PATCH 06/12] fix: stop bStock rows inflating the degraded-response guard; pin the merge key The crypto.length > 300 sanity floor was calibrated before bStocks existed. With ~56 synthetic entries added, a degraded CoinGecko response (250 rows instead of ~342, which it returns silently rather than throwing) now clears the floor where it used to be blocked: 250+39+2 = 291 was rejected, 250+39+2+56 = 347 is accepted. Combined with the wholesale replace, that truncates /v2/rates and drops ~100 coins for the cycle. The floor now counts real-provider rows only. mergeCryptoByKey is renamed replaceCryptoByKey and its unused _target parameter dropped. It never merged - it discards target entirely - and the old signature invited a maintainer to wire target back in, which would throw on the first refresh cycle where ratesV2.crypto is genuinely undefined. A mutation run showed a bare spread of source passed every test in the file: nothing pinned that provider is part of the key, that duplicates collapse, or that the last write wins. Two tests now cover those; all three surviving mutants are killed. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01UnpQSycV6gfbF9JqRf4x1f --- src/lib/objects.ts | 29 ++++++++++++++++++++----- src/services/apiServices.ts | 13 +++++++++--- tests/mergeCrypto.spec.ts | 42 ++++++++++++++++++++++++++++++------- 3 files changed, 69 insertions(+), 15 deletions(-) diff --git a/src/lib/objects.ts b/src/lib/objects.ts index 1c3b4a8..e87b4f5 100644 --- a/src/lib/objects.ts +++ b/src/lib/objects.ts @@ -47,12 +47,31 @@ export function mergeDeep(target: any, source: any) { } /** - * Replaces the crypto array wholesale, keyed by `${provider}-${id}`. - * The previous positional mergeDeep corrupted entries when provider block - * lengths shifted between refreshes (duplicate/stale-field bug). + * Rebuilds the crypto array from `source` alone, de-duplicated by + * `${provider}-${id}`, preserving source order with last-write-wins. + * + * This deliberately does NOT merge with the previous array — hence the name. + * The positional `mergeDeep` it replaced overlaid the new array onto the old + * one index by index, which is only correct while every provider block returns + * exactly the same number of rows in the same order. When a block shrank (a + * provider outage, a delisted coin), two things went wrong: fields from the + * old entry at that index survived onto a different coin — a CryptoCompare row + * inheriting CoinGecko's `rank` and `change7d` — and entries past the new + * length lived on as stale duplicates. Because the ZelCore client re-keys on + * `${provider}-${id}` with last-write-wins, and the stale duplicates sat after + * the fresh ones, wallet users were served the STALE price on any cycle where + * a block's row count shifted. + * + * Two behaviour changes a caller should know about: + * - entries repeating the same `provider`+`id` collapse to one, keeping the + * last value at the first occurrence's position; + * - an entry the fetch no longer produces disappears immediately, rather than + * persisting from the previous cycle. + * + * @param source - The freshly fetched entries. + * @returns The de-duplicated entries, in source order. */ -export function mergeCryptoByKey( - _target: T[], +export function replaceCryptoByKey( source: T[], ): T[] { const byKey = new Map(); diff --git a/src/services/apiServices.ts b/src/services/apiServices.ts index d3b7c63..285cf8e 100644 --- a/src/services/apiServices.ts +++ b/src/services/apiServices.ts @@ -1,7 +1,7 @@ import { Request, Response } from 'express'; import zlib from 'zlib'; import * as log from '../lib/log'; -import { mergeDeep, mergeCryptoByKey } from '../lib/objects'; +import { mergeDeep, replaceCryptoByKey } from '../lib/objects'; import zelcoreRates from './zelcoreRates'; import zelcoreMarketsUSD from './zelcoreMarketsUSD'; import zelcoreRatesV2 from './zelcoreRatesV2'; @@ -244,9 +244,16 @@ export async function serviceRefresher(): Promise { } } - if (ratesV2Fetched && ratesV2Fetched.fiat.length > 20 && ratesV2Fetched.crypto.length > 300) { + // Count only real-provider rows. The floor was calibrated before bStocks + // existed, and ~56 synthetic bStock entries would otherwise mask a + // degraded provider response that the floor is meant to reject — pushing + // an under-strength payload past the guard and truncating /v2/rates. + const providerCryptoCount = ratesV2Fetched + ? ratesV2Fetched.crypto.filter((c) => !c.id.startsWith('bstock-')).length + : 0; + if (ratesV2Fetched && ratesV2Fetched.fiat.length > 20 && providerCryptoCount > 300) { ratesV2.fiat = mergeDeep(ratesV2.fiat, ratesV2Fetched.fiat); - ratesV2.crypto = mergeCryptoByKey(ratesV2.crypto, ratesV2Fetched.crypto); + ratesV2.crypto = replaceCryptoByKey(ratesV2Fetched.crypto); ratesV2.errors = ratesV2Fetched.errors; } diff --git a/tests/mergeCrypto.spec.ts b/tests/mergeCrypto.spec.ts index 1000f2b..994da42 100644 --- a/tests/mergeCrypto.spec.ts +++ b/tests/mergeCrypto.spec.ts @@ -1,7 +1,7 @@ -import { mergeDeep, mergeCryptoByKey } from '../src/lib/objects'; +import { mergeDeep, replaceCryptoByKey } from '../src/lib/objects'; import { CryptoPrice } from '../src/types'; -describe('mergeCryptoByKey', () => { +describe('replaceCryptoByKey', () => { it('replaces entries by provider-id key, not by index', () => { const target = [ { id: 'bitcoin', provider: 'coingecko', rates: { btc: 1 } }, @@ -11,7 +11,7 @@ describe('mergeCryptoByKey', () => { { id: 'bstock-tslab', provider: 'coingecko', rates: { btc: 0.005 } }, { id: 'bitcoin', provider: 'coingecko', rates: { btc: 1.0001 } }, ]; - const merged = mergeCryptoByKey(target, source); + const merged = replaceCryptoByKey(source); expect(merged).toHaveLength(2); expect(merged.find((e) => e.id === 'bitcoin')!.rates.btc).toBe(1.0001); expect(merged.find((e) => e.id === 'stale')).toBeUndefined(); // stale tails dropped @@ -78,8 +78,8 @@ describe('positional-merge bug (apiServices.ts crypto merge)', () => { expect(merged.find((e) => e.id === 'litecoin')).toBeDefined(); }); - it('mergeCryptoByKey (fixed) replaces wholesale: no stale fields, no stale tail', () => { - const merged = mergeCryptoByKey(JSON.parse(JSON.stringify(target)), source); + it('replaceCryptoByKey (fixed) replaces wholesale: no stale fields, no stale tail', () => { + const merged = replaceCryptoByKey(source); expect(merged).toHaveLength(1); const [only] = merged; expect(only.id).toBe('CONI'); @@ -94,7 +94,7 @@ describe('positional-merge bug (apiServices.ts crypto merge)', () => { * key-based merge must produce output identical to the old positional one so * existing consumers see no behavioural change. */ -describe('mergeCryptoByKey vs mergeDeep -- identical for well-ordered input', () => { +describe('replaceCryptoByKey vs mergeDeep -- identical for well-ordered input', () => { it('produces the same array for a normal, non-corrupting refresh', () => { const target: CryptoPrice[] = [ { @@ -113,7 +113,35 @@ describe('mergeCryptoByKey vs mergeDeep -- identical for well-ordered input', () }, ]; const viaMergeDeep = mergeDeep(JSON.parse(JSON.stringify(target)), source); - const viaKeyMerge = mergeCryptoByKey(JSON.parse(JSON.stringify(target)), source); + const viaKeyMerge = replaceCryptoByKey(source); expect(viaKeyMerge).toEqual(viaMergeDeep); }); }); + +describe('replaceCryptoByKey -- the key is provider AND id, last write wins', () => { + // A mutation run showed `return [...source]` passed every earlier test here: + // none of them pinned that provider is part of the key, that duplicates + // collapse, or that the LAST duplicate wins. These two do. + it('keeps the same id under two different providers as separate entries', () => { + const out = replaceCryptoByKey([ + { id: 'bitcoin', provider: 'coingecko', rates: { usd: 100 } }, + { id: 'bitcoin', provider: 'cryptocompare', rates: { usd: 101 } }, + ] as never); + expect(out).toHaveLength(2); + expect(out.map((e) => (e as { provider: string }).provider).sort()) + .toEqual(['coingecko', 'cryptocompare']); + }); + + it('collapses a repeated provider+id to one entry carrying the LAST value', () => { + const out = replaceCryptoByKey([ + { id: 'bitcoin', provider: 'coingecko', rates: { usd: 100 } }, + { id: 'ethereum', provider: 'coingecko', rates: { usd: 5 } }, + { id: 'bitcoin', provider: 'coingecko', rates: { usd: 999 } }, + ] as never); + expect(out).toHaveLength(2); + const btc = out.find((e) => (e as { id: string }).id === 'bitcoin') as unknown as { rates: { usd: number } }; + expect(btc.rates.usd).toBe(999); + // ...and it stays at the first occurrence's position, so ordering is stable. + expect((out[0] as { id: string }).id).toBe('bitcoin'); + }); +}); From cc6bd3d2fdfc2eb8b8622d2a6098e3edf8a2975a Mon Sep 17 00:00:00 2001 From: Stultus Mundi Date: Wed, 5 Aug 2026 13:24:42 +0300 Subject: [PATCH 07/12] docs: document bStocks in swagger.yaml/README, refresh typedoc output Documents the synthetic Binance bStock entries in /v2/rates: universe selection (BSC contract + TRADING Spot symbol), USDT quoting, the provider:"coingecko" id contract with the client and the sibling api repo, and the halted-symbol/last-known-good behavior. Regenerates the committed docs/ typedoc tree to pick up the new bstocks/binance modules and types. Bumped the typedoc devDependency ^0.26.7 -> ^0.28.0: typedoc-plugin- markdown@4.12 (already the installed version, satisfying the existing ^4.2.7 range) requires typedoc 0.28.x as a peer, so `npx typedoc` - the docs-refresh step this same README documents - was silently broken on a clean install before this change. Dev-only, no runtime effect. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01UnpQSycV6gfbF9JqRf4x1f --- README.md | 30 +++ docs/README.md | 32 ++- docs/index/README.md | 4 +- docs/modules.md | 4 +- docs/src/lib/axios/README.md | 8 +- docs/src/lib/axios/classes/AxiosWrapper.md | 102 ++++---- docs/src/lib/objects/README.md | 9 +- docs/src/lib/objects/functions/mergeDeep.md | 18 +- .../objects/functions/replaceCryptoByKey.md | 52 ++++ docs/src/lib/server/README.md | 10 +- docs/src/lib/server/functions/default.md | 100 -------- docs/src/lib/server/variables/default.md | 29 +++ docs/src/lib/utils/README.md | 8 +- docs/src/lib/utils/functions/arraySplit.md | 18 +- .../lib/utils/functions/makeRequestStrings.md | 18 +- docs/src/routes/README.md | 10 +- .../{functions => variables}/default.md | 18 +- docs/src/services/apiServices/README.md | 10 +- .../apiServices/functions/checkContractsV2.md | 18 +- .../apiServices/functions/dataRefresher.md | 10 +- .../services/apiServices/functions/getData.md | 25 +- .../functions/getFoundContracts.md | 10 +- .../apiServices/functions/getMarketsUsd.md | 18 +- .../apiServices/functions/getRates.md | 18 +- .../apiServices/functions/getRatesV2.md | 18 +- .../functions/getRatesV2Compressed.md | 18 +- .../apiServices/functions/serviceRefresher.md | 10 +- .../services/apiServices/variables/default.md | 85 ++++--- docs/src/services/bstocks/README.md | 12 + .../functions/clearLastGoodForTests.md | 15 ++ .../bstocks/functions/getBstockPrices.md | 41 ++++ docs/src/services/coinAggregatorIDs/README.md | 10 +- .../functions/getLatestCoinInfo.md | 10 +- .../variables/cgContractMap.md | 10 +- .../coinAggregatorIDs/variables/cgTokens.md | 10 +- .../variables/coinAggregatorIDs.md | 12 +- .../coinAggregatorIDs/variables/zelData.md | 12 +- docs/src/services/newContracts/README.md | 10 +- .../newContracts/functions/checkContracts.md | 14 +- .../newContracts/variables/foundContracts.md | 10 +- docs/src/services/providers/README.md | 10 +- docs/src/services/providers/binance/README.md | 17 ++ .../providers/binance/classes/Binance.md | 231 ++++++++++++++++++ docs/src/services/providers/bitpay/README.md | 8 +- .../providers/bitpay/classes/BitPay.md | 34 ++- .../services/providers/coinGecko/README.md | 8 +- .../providers/coinGecko/classes/CoinGecko.md | 72 +++--- .../providers/cryptoCompare/README.md | 8 +- .../cryptoCompare/classes/CryptoCompare.md | 60 ++--- .../providers/liveCoinWatch/README.md | 8 +- .../liveCoinWatch/classes/LiveCoinWatch.md | 44 ++-- docs/src/services/zelcoreMarketsUSD/README.md | 10 +- .../zelcoreMarketsUSD/functions/getAll.md | 10 +- .../zelcoreMarketsUSD/variables/default.md | 14 +- docs/src/services/zelcoreRates/README.md | 10 +- .../services/zelcoreRates/functions/getAll.md | 10 +- .../zelcoreRates/variables/default.md | 14 +- docs/src/services/zelcoreRatesV2/README.md | 10 +- .../zelcoreRatesV2/functions/getAll.md | 10 +- .../zelcoreRatesV2/variables/default.md | 14 +- docs/src/types/README.md | 12 +- docs/src/types/interfaces/ICurrencyData.md | 40 ++- docs/src/types/interfaces/ICurrencyRate.md | 18 +- docs/src/types/interfaces/IErrorObject.md | 14 +- docs/src/types/type-aliases/BinanceTicker.md | 43 ++++ .../type-aliases/BinanceTokenisedAsset.md | 59 +++++ docs/src/types/type-aliases/CodeRates.md | 14 +- docs/src/types/type-aliases/CoinGeckoPrice.md | 120 ++++++++- docs/src/types/type-aliases/CoinGeckoToken.md | 26 +- docs/src/types/type-aliases/CoinInfo.md | 102 +++++++- .../types/type-aliases/ContractWithType.md | 18 +- .../type-aliases/CryptoCompareMarkets.md | 14 +- .../types/type-aliases/CryptoComparePrice.md | 14 +- docs/src/types/type-aliases/CryptoPrice.md | 56 ++++- docs/src/types/type-aliases/CurrencyMap.md | 14 +- docs/src/types/type-aliases/FiatPrice.md | 28 ++- .../types/type-aliases/FoundContractStore.md | 10 +- .../types/type-aliases/LiveCoinWatchMarket.md | 142 ++++++++--- docs/src/types/type-aliases/MarketsData.md | 10 +- docs/src/types/type-aliases/PricesResponse.md | 24 +- docs/src/types/type-aliases/RatesData.md | 10 +- package.json | 2 +- swagger.yaml | 53 +++- 83 files changed, 1582 insertions(+), 739 deletions(-) create mode 100644 docs/src/lib/objects/functions/replaceCryptoByKey.md delete mode 100644 docs/src/lib/server/functions/default.md create mode 100644 docs/src/lib/server/variables/default.md rename docs/src/routes/{functions => variables}/default.md (52%) create mode 100644 docs/src/services/bstocks/README.md create mode 100644 docs/src/services/bstocks/functions/clearLastGoodForTests.md create mode 100644 docs/src/services/bstocks/functions/getBstockPrices.md create mode 100644 docs/src/services/providers/binance/README.md create mode 100644 docs/src/services/providers/binance/classes/Binance.md create mode 100644 docs/src/types/type-aliases/BinanceTicker.md create mode 100644 docs/src/types/type-aliases/BinanceTokenisedAsset.md diff --git a/README.md b/README.md index bd95b1a..3264571 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,36 @@ Example: http://localhost:3333/rates docker run -e API_KEY=yourApiKey -p 4444:3333 zelcash/rates-api ``` +## bStocks (Binance tokenized equities) + +`GET /v2/rates` emits one synthetic `crypto` entry per Binance bStock — a +tokenized US equity on BNB Smart Chain (e.g. `bstock-tslab` for Tesla). Prices +come straight from Binance's public Spot API (no API key required): + +- Universe: the intersection of Binance's tokenised-asset list + (`GET https://www.binance.com/bapi/asset/v2/public/asset/asset/get-tokenised-asset`, + filtered to assets with a BSC contract) with Spot symbols currently in + `TRADING` status — about 56 of the ~66 listed assets qualify today. +- Quote currency: **USDT**, not USDC — verified live, no USDC pairs exist for + these symbols. +- `rates.usd` = `USDT` last price; `rates.btc` = that price divided by + `BTCUSDT` from the same ticker batch (same venue, no cross-venue basis). + `change24h`/`change7d` come from Binance's 24h ticker and 7d rolling-window + ticker respectively. +- `provider` is always the literal string `"coingecko"`, never `"binance"`. + The ZelCore client keys its market store on `${provider}-${id}` and the + sibling `api` repo advertises each bStock's `coinInfo.coingeckoID` as + `bstock-`; the two literals only meet if the provider here is exactly + `"coingecko"`. This is a cross-repo contract — do not change it in + isolation. +- Binance does **not** omit a halted symbol (e.g. during a stock split) from + its ticker response — it returns the symbol present with + `lastPrice: "0.00000000"`. Prices are therefore accepted only when finite + and strictly positive; a halted/zero-priced symbol keeps serving its last + known-good price rather than a stale zero or a dropped entry, per the + bStocks partner guide's "display-only during halts is acceptable" allowance. +- Toggle via `config.bStocksEnabled` (`config/index.ts`). + ## Update Documentation To update typedoc documentation please run. diff --git a/docs/README.md b/docs/README.md index 1fdedbc..901a611 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,4 +1,4 @@ -**rates-api v3.0.0** • [**Docs**](modules.md) +**rates-api v3.0.0** *** @@ -35,6 +35,36 @@ Example: http://localhost:3333/rates docker run -e API_KEY=yourApiKey -p 4444:3333 zelcash/rates-api ``` +## bStocks (Binance tokenized equities) + +`GET /v2/rates` emits one synthetic `crypto` entry per Binance bStock — a +tokenized US equity on BNB Smart Chain (e.g. `bstock-tslab` for Tesla). Prices +come straight from Binance's public Spot API (no API key required): + +- Universe: the intersection of Binance's tokenised-asset list + (`GET https://www.binance.com/bapi/asset/v2/public/asset/asset/get-tokenised-asset`, + filtered to assets with a BSC contract) with Spot symbols currently in + `TRADING` status — about 56 of the ~66 listed assets qualify today. +- Quote currency: **USDT**, not USDC — verified live, no USDC pairs exist for + these symbols. +- `rates.usd` = `USDT` last price; `rates.btc` = that price divided by + `BTCUSDT` from the same ticker batch (same venue, no cross-venue basis). + `change24h`/`change7d` come from Binance's 24h ticker and 7d rolling-window + ticker respectively. +- `provider` is always the literal string `"coingecko"`, never `"binance"`. + The ZelCore client keys its market store on `${provider}-${id}` and the + sibling `api` repo advertises each bStock's `coinInfo.coingeckoID` as + `bstock-`; the two literals only meet if the provider here is exactly + `"coingecko"`. This is a cross-repo contract — do not change it in + isolation. +- Binance does **not** omit a halted symbol (e.g. during a stock split) from + its ticker response — it returns the symbol present with + `lastPrice: "0.00000000"`. Prices are therefore accepted only when finite + and strictly positive; a halted/zero-priced symbol keeps serving its last + known-good price rather than a stale zero or a dropped entry, per the + bStocks partner guide's "display-only during halts is acceptable" allowance. +- Toggle via `config.bStocksEnabled` (`config/index.ts`). + ## Update Documentation To update typedoc documentation please run. diff --git a/docs/index/README.md b/docs/index/README.md index 96cb1bd..1100c05 100644 --- a/docs/index/README.md +++ b/docs/index/README.md @@ -1,7 +1,7 @@ -[**rates-api v3.0.0**](../README.md) • **Docs** +[**rates-api v3.0.0**](../README.md) *** -[rates-api v3.0.0](../modules.md) / index +[rates-api](../modules.md) / index # index diff --git a/docs/modules.md b/docs/modules.md index 675e24d..1a06768 100644 --- a/docs/modules.md +++ b/docs/modules.md @@ -1,4 +1,4 @@ -[**rates-api v3.0.0**](README.md) • **Docs** +[**rates-api v3.0.0**](README.md) *** @@ -14,9 +14,11 @@ - [src/lib/utils](src/lib/utils/README.md) - [src/routes](src/routes/README.md) - [src/services/apiServices](src/services/apiServices/README.md) +- [src/services/bstocks](src/services/bstocks/README.md) - [src/services/coinAggregatorIDs](src/services/coinAggregatorIDs/README.md) - [src/services/newContracts](src/services/newContracts/README.md) - [src/services/providers](src/services/providers/README.md) +- [src/services/providers/binance](src/services/providers/binance/README.md) - [src/services/providers/bitpay](src/services/providers/bitpay/README.md) - [src/services/providers/coinGecko](src/services/providers/coinGecko/README.md) - [src/services/providers/cryptoCompare](src/services/providers/cryptoCompare/README.md) diff --git a/docs/src/lib/axios/README.md b/docs/src/lib/axios/README.md index f97f5f7..b520a32 100644 --- a/docs/src/lib/axios/README.md +++ b/docs/src/lib/axios/README.md @@ -1,14 +1,12 @@ -[**rates-api v3.0.0**](../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../README.md) *** -[rates-api v3.0.0](../../../modules.md) / src/lib/axios +[rates-api](../../../modules.md) / src/lib/axios # src/lib/axios -## Index - -### Classes +## Classes - [AxiosWrapper](classes/AxiosWrapper.md) diff --git a/docs/src/lib/axios/classes/AxiosWrapper.md b/docs/src/lib/axios/classes/AxiosWrapper.md index c06f42b..51ae47b 100644 --- a/docs/src/lib/axios/classes/AxiosWrapper.md +++ b/docs/src/lib/axios/classes/AxiosWrapper.md @@ -1,11 +1,13 @@ -[**rates-api v3.0.0**](../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../README.md) *** -[rates-api v3.0.0](../../../../modules.md) / [src/lib/axios](../README.md) / AxiosWrapper +[rates-api](../../../../modules.md) / [src/lib/axios](../README.md) / AxiosWrapper # Class: AxiosWrapper +Defined in: src/lib/axios.ts:26 + A wrapper around Axios to handle automatic retries and customizable configurations. This class provides a simplified interface over Axios, adding automatic retry functionality @@ -31,29 +33,37 @@ apiClient.post('/users', { name: 'John Doe' }) ## Constructors -### new AxiosWrapper() +### Constructor + +> **new AxiosWrapper**(`baseURL`, `maxRetries?`, `timeout?`): `AxiosWrapper` -> **new AxiosWrapper**(`baseURL`, `maxRetries`, `timeout`): [`AxiosWrapper`](AxiosWrapper.md) +Defined in: src/lib/axios.ts:43 Creates an instance of AxiosWrapper. #### Parameters -• **baseURL**: `string` +##### baseURL + +`string` The base URL for all requests. -• **maxRetries**: `number` = `3` +##### maxRetries? + +`number` = `3` The maximum number of retry attempts for failed requests (default is 3). -• **timeout**: `number` = `5000` +##### timeout? + +`number` = `5000` The timeout in milliseconds for requests (default is 5000 ms). #### Returns -[`AxiosWrapper`](AxiosWrapper.md) +`AxiosWrapper` #### Example @@ -61,25 +71,27 @@ The timeout in milliseconds for requests (default is 5000 ms). const apiClient = new AxiosWrapper('https://api.example.com', 5, 10000); ``` -#### Defined in - -[src/lib/axios.ts:43](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/lib/axios.ts#L43) - ## Methods ### delete() -> **delete**(`url`, `config`?): `Promise`\<`AxiosResponse`\<`any`, `any`\>\> +> **delete**(`url`, `config?`): `Promise`\<`AxiosResponse`\<`any`, `any`\>\> + +Defined in: src/lib/axios.ts:171 Performs a DELETE request. #### Parameters -• **url**: `string` +##### url + +`string` The URL to send the DELETE request to. -• **config?**: `AxiosRequestConfig`\<`any`\> +##### config? + +`AxiosRequestConfig`\<`any`\> Optional Axios request configuration. @@ -97,25 +109,27 @@ apiClient.delete('/users/123') .catch(error => console.error(error)); ``` -#### Defined in - -[src/lib/axios.ts:171](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/lib/axios.ts#L171) - *** ### get() -> **get**(`url`, `config`?): `Promise`\<`AxiosResponse`\<`any`, `any`\>\> +> **get**(`url`, `config?`): `Promise`\<`AxiosResponse`\<`any`, `any`\>\> + +Defined in: src/lib/axios.ts:115 Performs a GET request. #### Parameters -• **url**: `string` +##### url + +`string` The URL to send the GET request to. -• **config?**: `AxiosRequestConfig`\<`any`\> +##### config? + +`AxiosRequestConfig`\<`any`\> Optional Axios request configuration. @@ -133,29 +147,33 @@ apiClient.get('/users') .catch(error => console.error(error)); ``` -#### Defined in - -[src/lib/axios.ts:115](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/lib/axios.ts#L115) - *** ### post() -> **post**(`url`, `data`?, `config`?): `Promise`\<`AxiosResponse`\<`any`, `any`\>\> +> **post**(`url`, `data?`, `config?`): `Promise`\<`AxiosResponse`\<`any`, `any`\>\> + +Defined in: src/lib/axios.ts:134 Performs a POST request. #### Parameters -• **url**: `string` +##### url + +`string` The URL to send the POST request to. -• **data?**: `any` +##### data? + +`any` The data to send with the POST request. -• **config?**: `AxiosRequestConfig`\<`any`\> +##### config? + +`AxiosRequestConfig`\<`any`\> Optional Axios request configuration. @@ -173,29 +191,33 @@ apiClient.post('/users', { name: 'John Doe' }) .catch(error => console.error(error)); ``` -#### Defined in - -[src/lib/axios.ts:134](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/lib/axios.ts#L134) - *** ### put() -> **put**(`url`, `data`?, `config`?): `Promise`\<`AxiosResponse`\<`any`, `any`\>\> +> **put**(`url`, `data?`, `config?`): `Promise`\<`AxiosResponse`\<`any`, `any`\>\> + +Defined in: src/lib/axios.ts:153 Performs a PUT request. #### Parameters -• **url**: `string` +##### url + +`string` The URL to send the PUT request to. -• **data?**: `any` +##### data? + +`any` The data to send with the PUT request. -• **config?**: `AxiosRequestConfig`\<`any`\> +##### config? + +`AxiosRequestConfig`\<`any`\> Optional Axios request configuration. @@ -212,7 +234,3 @@ apiClient.put('/users/123', { name: 'Jane Doe' }) .then(response => console.log(response.data)) .catch(error => console.error(error)); ``` - -#### Defined in - -[src/lib/axios.ts:153](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/lib/axios.ts#L153) diff --git a/docs/src/lib/objects/README.md b/docs/src/lib/objects/README.md index c1c1472..274164e 100644 --- a/docs/src/lib/objects/README.md +++ b/docs/src/lib/objects/README.md @@ -1,13 +1,12 @@ -[**rates-api v3.0.0**](../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../README.md) *** -[rates-api v3.0.0](../../../modules.md) / src/lib/objects +[rates-api](../../../modules.md) / src/lib/objects # src/lib/objects -## Index - -### Functions +## Functions - [mergeDeep](functions/mergeDeep.md) +- [replaceCryptoByKey](functions/replaceCryptoByKey.md) diff --git a/docs/src/lib/objects/functions/mergeDeep.md b/docs/src/lib/objects/functions/mergeDeep.md index 4a2b428..d3aef62 100644 --- a/docs/src/lib/objects/functions/mergeDeep.md +++ b/docs/src/lib/objects/functions/mergeDeep.md @@ -1,13 +1,15 @@ -[**rates-api v3.0.0**](../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../README.md) *** -[rates-api v3.0.0](../../../../modules.md) / [src/lib/objects](../README.md) / mergeDeep +[rates-api](../../../../modules.md) / [src/lib/objects](../README.md) / mergeDeep # Function: mergeDeep() > **mergeDeep**(`target`, `source`): `any` +Defined in: src/lib/objects.ts:20 + Deeply merges two objects or arrays. This function takes a target and a source and recursively merges properties. @@ -16,11 +18,15 @@ This function takes a target and a source and recursively merges properties. ## Parameters -• **target**: `any` +### target + +`any` The target object or array to merge into. -• **source**: `any` +### source + +`any` The source object or array to merge from. @@ -38,7 +44,3 @@ const obj2 = { b: { d: 3 }, e: 4 }; const result = mergeDeep(obj1, obj2); // result: { a: 1, b: { c: 2, d: 3 }, e: 4 } ``` - -## Defined in - -[src/lib/objects.ts:20](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/lib/objects.ts#L20) diff --git a/docs/src/lib/objects/functions/replaceCryptoByKey.md b/docs/src/lib/objects/functions/replaceCryptoByKey.md new file mode 100644 index 0000000..35e9b93 --- /dev/null +++ b/docs/src/lib/objects/functions/replaceCryptoByKey.md @@ -0,0 +1,52 @@ +[**rates-api v3.0.0**](../../../../README.md) + +*** + +[rates-api](../../../../modules.md) / [src/lib/objects](../README.md) / replaceCryptoByKey + +# Function: replaceCryptoByKey() + +> **replaceCryptoByKey**\<`T`\>(`source`): `T`[] + +Defined in: src/lib/objects.ts:74 + +Rebuilds the crypto array from `source` alone, de-duplicated by +`${provider}-${id}`, preserving source order with last-write-wins. + +This deliberately does NOT merge with the previous array — hence the name. +The positional `mergeDeep` it replaced overlaid the new array onto the old +one index by index, which is only correct while every provider block returns +exactly the same number of rows in the same order. When a block shrank (a +provider outage, a delisted coin), two things went wrong: fields from the +old entry at that index survived onto a different coin — a CryptoCompare row +inheriting CoinGecko's `rank` and `change7d` — and entries past the new +length lived on as stale duplicates. Because the ZelCore client re-keys on +`${provider}-${id}` with last-write-wins, and the stale duplicates sat after +the fresh ones, wallet users were served the STALE price on any cycle where +a block's row count shifted. + +Two behaviour changes a caller should know about: + - entries repeating the same `provider`+`id` collapse to one, keeping the + last value at the first occurrence's position; + - an entry the fetch no longer produces disappears immediately, rather than + persisting from the previous cycle. + +## Type Parameters + +### T + +`T` *extends* `object` + +## Parameters + +### source + +`T`[] + +The freshly fetched entries. + +## Returns + +`T`[] + +The de-duplicated entries, in source order. diff --git a/docs/src/lib/server/README.md b/docs/src/lib/server/README.md index 1bd3634..1620b5c 100644 --- a/docs/src/lib/server/README.md +++ b/docs/src/lib/server/README.md @@ -1,13 +1,11 @@ -[**rates-api v3.0.0**](../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../README.md) *** -[rates-api v3.0.0](../../../modules.md) / src/lib/server +[rates-api](../../../modules.md) / src/lib/server # src/lib/server -## Index +## Variables -### Functions - -- [default](functions/default.md) +- [default](variables/default.md) diff --git a/docs/src/lib/server/functions/default.md b/docs/src/lib/server/functions/default.md deleted file mode 100644 index b52dfad..0000000 --- a/docs/src/lib/server/functions/default.md +++ /dev/null @@ -1,100 +0,0 @@ -[**rates-api v3.0.0**](../../../../README.md) • **Docs** - -*** - -[rates-api v3.0.0](../../../../modules.md) / [src/lib/server](../README.md) / default - -# Function: default() - -The main Express application instance. - -## Remarks - -This instance is configured with middleware and routes and is exported for use in the server. - -## Example - -```typescript -import app from './server'; - -const port = process.env.PORT || 3000; - -app.listen(port, () => { - console.log(`Server is running on port ${port}`); -}); -``` - -## default(req, res) - -> **default**(`req`, `res`): `any` - -Express instance itself is a request handler, which could be invoked without -third argument. - -### Parameters - -• **req**: `IncomingMessage` \| `Request`\<`ParamsDictionary`, `any`, `any`, `ParsedQs`, `Record`\<`string`, `any`\>\> - -• **res**: `ServerResponse`\<`IncomingMessage`\> \| `Response`\<`any`, `Record`\<`string`, `any`\>, `number`\> - -### Returns - -`any` - -### Remarks - -This instance is configured with middleware and routes and is exported for use in the server. - -### Example - -```typescript -import app from './server'; - -const port = process.env.PORT || 3000; - -app.listen(port, () => { - console.log(`Server is running on port ${port}`); -}); -``` - -### Defined in - -[src/lib/server.ts:32](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/lib/server.ts#L32) - -## default(req, res, next) - -> **default**(`req`, `res`, `next`): `void` - -The main Express application instance. - -### Parameters - -• **req**: `Request`\<`ParamsDictionary`, `any`, `any`, `ParsedQs`, `Record`\<`string`, `any`\>\> - -• **res**: `Response`\<`any`, `Record`\<`string`, `any`\>, `number`\> - -• **next**: `NextFunction` - -### Returns - -`void` - -### Remarks - -This instance is configured with middleware and routes and is exported for use in the server. - -### Example - -```typescript -import app from './server'; - -const port = process.env.PORT || 3000; - -app.listen(port, () => { - console.log(`Server is running on port ${port}`); -}); -``` - -### Defined in - -[src/lib/server.ts:32](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/lib/server.ts#L32) diff --git a/docs/src/lib/server/variables/default.md b/docs/src/lib/server/variables/default.md new file mode 100644 index 0000000..c5d997b --- /dev/null +++ b/docs/src/lib/server/variables/default.md @@ -0,0 +1,29 @@ +[**rates-api v3.0.0**](../../../../README.md) + +*** + +[rates-api](../../../../modules.md) / [src/lib/server](../README.md) / default + +# Variable: default + +> `const` **default**: `Express` + +Defined in: src/lib/server.ts:33 + +The main Express application instance. + +## Remarks + +This instance is configured with middleware and routes and is exported for use in the server. + +## Example + +```typescript +import app from './server'; + +const port = process.env.PORT || 3000; + +app.listen(port, () => { + console.log(`Server is running on port ${port}`); +}); +``` diff --git a/docs/src/lib/utils/README.md b/docs/src/lib/utils/README.md index 0801b2a..30efb7b 100644 --- a/docs/src/lib/utils/README.md +++ b/docs/src/lib/utils/README.md @@ -1,14 +1,12 @@ -[**rates-api v3.0.0**](../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../README.md) *** -[rates-api v3.0.0](../../../modules.md) / src/lib/utils +[rates-api](../../../modules.md) / src/lib/utils # src/lib/utils -## Index - -### Functions +## Functions - [arraySplit](functions/arraySplit.md) - [makeRequestStrings](functions/makeRequestStrings.md) diff --git a/docs/src/lib/utils/functions/arraySplit.md b/docs/src/lib/utils/functions/arraySplit.md index 0becf82..9329cda 100644 --- a/docs/src/lib/utils/functions/arraySplit.md +++ b/docs/src/lib/utils/functions/arraySplit.md @@ -1,22 +1,28 @@ -[**rates-api v3.0.0**](../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../README.md) *** -[rates-api v3.0.0](../../../../modules.md) / [src/lib/utils](../README.md) / arraySplit +[rates-api](../../../../modules.md) / [src/lib/utils](../README.md) / arraySplit # Function: arraySplit() > **arraySplit**(`arr`, `size`): `string`[][] +Defined in: src/lib/utils.ts:15 + Splits an array into chunks of a specified size. ## Parameters -• **arr**: `string`[] +### arr + +`string`[] The array to split. -• **size**: `number` +### size + +`number` The maximum size of each chunk. @@ -33,7 +39,3 @@ const array = ['a', 'b', 'c', 'd', 'e']; const chunks = arraySplit(array, 2); // chunks: [['a', 'b'], ['c', 'd'], ['e']] ``` - -## Defined in - -[src/lib/utils.ts:15](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/lib/utils.ts#L15) diff --git a/docs/src/lib/utils/functions/makeRequestStrings.md b/docs/src/lib/utils/functions/makeRequestStrings.md index 40bcf79..f3195e0 100644 --- a/docs/src/lib/utils/functions/makeRequestStrings.md +++ b/docs/src/lib/utils/functions/makeRequestStrings.md @@ -1,13 +1,15 @@ -[**rates-api v3.0.0**](../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../README.md) *** -[rates-api v3.0.0](../../../../modules.md) / [src/lib/utils](../README.md) / makeRequestStrings +[rates-api](../../../../modules.md) / [src/lib/utils](../README.md) / makeRequestStrings # Function: makeRequestStrings() > **makeRequestStrings**(`elements`, `maxLength`): `string`[] +Defined in: src/lib/utils.ts:41 + Combines elements of a string array into comma-separated strings, ensuring that each combined string does not exceed a specified maximum length. This function iterates over the input `elements` and concatenates them with commas. @@ -15,11 +17,15 @@ If adding another element would exceed the `maxLength`, it pushes the current st ## Parameters -• **elements**: `string`[] +### elements + +`string`[] The array of strings to combine. -• **maxLength**: `number` +### maxLength + +`number` The maximum length of each combined string. @@ -37,7 +43,3 @@ const maxLength = 15; const result = makeRequestStrings(elements, maxLength); // result: ['apple,banana', 'cherry,date', 'fig'] ``` - -## Defined in - -[src/lib/utils.ts:41](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/lib/utils.ts#L41) diff --git a/docs/src/routes/README.md b/docs/src/routes/README.md index 2d8673c..95643ca 100644 --- a/docs/src/routes/README.md +++ b/docs/src/routes/README.md @@ -1,13 +1,11 @@ -[**rates-api v3.0.0**](../../README.md) • **Docs** +[**rates-api v3.0.0**](../../README.md) *** -[rates-api v3.0.0](../../modules.md) / src/routes +[rates-api](../../modules.md) / src/routes # src/routes -## Index +## Variables -### Functions - -- [default](functions/default.md) +- [default](variables/default.md) diff --git a/docs/src/routes/functions/default.md b/docs/src/routes/variables/default.md similarity index 52% rename from docs/src/routes/functions/default.md rename to docs/src/routes/variables/default.md index c1e0287..c999765 100644 --- a/docs/src/routes/functions/default.md +++ b/docs/src/routes/variables/default.md @@ -1,18 +1,22 @@ -[**rates-api v3.0.0**](../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../README.md) *** -[rates-api v3.0.0](../../../modules.md) / [src/routes](../README.md) / default +[rates-api](../../../modules.md) / [src/routes](../README.md) / default -# Function: default() +# Variable: default -> **default**(`app`): `void` +> **default**: (`app`) => `void` + +Defined in: src/routes.ts:26 Configures the Express application by setting up routes, middleware, and caching. ## Parameters -• **app**: `Application` +### app + +`Application` The Express application instance. @@ -33,7 +37,3 @@ app.listen(3000, () => { console.log('Server is running on port 3000'); }); ``` - -## Defined in - -[src/routes.ts:26](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/routes.ts#L26) diff --git a/docs/src/services/apiServices/README.md b/docs/src/services/apiServices/README.md index eaa4660..1f7fc04 100644 --- a/docs/src/services/apiServices/README.md +++ b/docs/src/services/apiServices/README.md @@ -1,18 +1,16 @@ -[**rates-api v3.0.0**](../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../README.md) *** -[rates-api v3.0.0](../../../modules.md) / src/services/apiServices +[rates-api](../../../modules.md) / src/services/apiServices # src/services/apiServices -## Index - -### Variables +## Variables - [default](variables/default.md) -### Functions +## Functions - [checkContractsV2](functions/checkContractsV2.md) - [dataRefresher](functions/dataRefresher.md) diff --git a/docs/src/services/apiServices/functions/checkContractsV2.md b/docs/src/services/apiServices/functions/checkContractsV2.md index 7a20b11..f22f830 100644 --- a/docs/src/services/apiServices/functions/checkContractsV2.md +++ b/docs/src/services/apiServices/functions/checkContractsV2.md @@ -1,22 +1,28 @@ -[**rates-api v3.0.0**](../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../README.md) *** -[rates-api v3.0.0](../../../../modules.md) / [src/services/apiServices](../README.md) / checkContractsV2 +[rates-api](../../../../modules.md) / [src/services/apiServices](../README.md) / checkContractsV2 # Function: checkContractsV2() > **checkContractsV2**(`req`, `res`): `Promise`\<`void`\> +Defined in: src/services/apiServices.ts:156 + Handles the request to check for new contracts. ## Parameters -• **req**: `Request`\<`ParamsDictionary`, `any`, `any`, `ParsedQs`, `Record`\<`string`, `any`\>\> +### req + +`Request` The Express request object containing `contracts` in the body. -• **res**: `Response`\<`any`, `Record`\<`string`, `any`\>\> +### res + +`Response` The Express response object. @@ -29,7 +35,3 @@ The Express response object. ```typescript app.post('/contracts/check', checkContractsV2); ``` - -## Defined in - -[src/services/apiServices.ts:156](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/apiServices.ts#L156) diff --git a/docs/src/services/apiServices/functions/dataRefresher.md b/docs/src/services/apiServices/functions/dataRefresher.md index 86e918b..0c29bb7 100644 --- a/docs/src/services/apiServices/functions/dataRefresher.md +++ b/docs/src/services/apiServices/functions/dataRefresher.md @@ -1,13 +1,15 @@ -[**rates-api v3.0.0**](../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../README.md) *** -[rates-api v3.0.0](../../../../modules.md) / [src/services/apiServices](../README.md) / dataRefresher +[rates-api](../../../../modules.md) / [src/services/apiServices](../README.md) / dataRefresher # Function: dataRefresher() > **dataRefresher**(): `Promise`\<`void`\> +Defined in: src/services/apiServices.ts:195 + Periodically refreshes coin information and aggregator IDs. This function logs the start of the refresh process, calls `getLatestCoinInfo`, @@ -23,7 +25,3 @@ logs the error and retries after 30 minutes. ```typescript dataRefresher(); ``` - -## Defined in - -[src/services/apiServices.ts:195](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/apiServices.ts#L195) diff --git a/docs/src/services/apiServices/functions/getData.md b/docs/src/services/apiServices/functions/getData.md index 97fda87..5d83b7e 100644 --- a/docs/src/services/apiServices/functions/getData.md +++ b/docs/src/services/apiServices/functions/getData.md @@ -1,36 +1,45 @@ -[**rates-api v3.0.0**](../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../README.md) *** -[rates-api v3.0.0](../../../../modules.md) / [src/services/apiServices](../README.md) / getData +[rates-api](../../../../modules.md) / [src/services/apiServices](../README.md) / getData # Function: getData() > **getData**(): `object` +Defined in: src/services/apiServices.ts:105 + Retrieves the current rates and market data. ## Returns -`object` - An object containing `rates` and `marketsUSD`. ### marketsUSD > **marketsUSD**: [`MarketsData`](../../../types/type-aliases/MarketsData.md) +Stores market data in USD. + +Structure: +- `marketsUSD[0]`: BTC to USD market data. +- `marketsUSD[1]`: Errors object. + ### rates > **rates**: [`RatesData`](../../../types/type-aliases/RatesData.md) +Stores exchange rates data. + +Structure: +- `rates[0]`: BTC to fiat exchange rates. +- `rates[1]`: Alternative coins to fiat exchange rates. +- `rates[2]`: Errors object. + ## Example ```typescript const data = getData(); console.log(data.rates, data.marketsUSD); ``` - -## Defined in - -[src/services/apiServices.ts:105](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/apiServices.ts#L105) diff --git a/docs/src/services/apiServices/functions/getFoundContracts.md b/docs/src/services/apiServices/functions/getFoundContracts.md index 37b8f3b..1e56dd7 100644 --- a/docs/src/services/apiServices/functions/getFoundContracts.md +++ b/docs/src/services/apiServices/functions/getFoundContracts.md @@ -1,13 +1,15 @@ -[**rates-api v3.0.0**](../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../README.md) *** -[rates-api v3.0.0](../../../../modules.md) / [src/services/apiServices](../README.md) / getFoundContracts +[rates-api](../../../../modules.md) / [src/services/apiServices](../README.md) / getFoundContracts # Function: getFoundContracts() > **getFoundContracts**(): [`FoundContractStore`](../../../types/type-aliases/FoundContractStore.md) +Defined in: src/services/apiServices.ts:141 + Retrieves the found contracts. ## Returns @@ -21,7 +23,3 @@ The `foundContracts` object. ```typescript const contracts = getFoundContracts(); ``` - -## Defined in - -[src/services/apiServices.ts:141](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/apiServices.ts#L141) diff --git a/docs/src/services/apiServices/functions/getMarketsUsd.md b/docs/src/services/apiServices/functions/getMarketsUsd.md index dead2ba..db420f0 100644 --- a/docs/src/services/apiServices/functions/getMarketsUsd.md +++ b/docs/src/services/apiServices/functions/getMarketsUsd.md @@ -1,22 +1,28 @@ -[**rates-api v3.0.0**](../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../README.md) *** -[rates-api v3.0.0](../../../../modules.md) / [src/services/apiServices](../README.md) / getMarketsUsd +[rates-api](../../../../modules.md) / [src/services/apiServices](../README.md) / getMarketsUsd # Function: getMarketsUsd() > **getMarketsUsd**(`req`, `res`): `Promise`\<`void`\> +Defined in: src/services/apiServices.ts:123 + Handles the GET request to retrieve market data in USD. ## Parameters -• **req**: `Request`\<`ParamsDictionary`, `any`, `any`, `ParsedQs`, `Record`\<`string`, `any`\>\> +### req + +`Request` The Express request object. -• **res**: `Response`\<`any`, `Record`\<`string`, `any`\>\> +### res + +`Response` The Express response object. @@ -29,7 +35,3 @@ The Express response object. ```typescript app.get('/markets/usd', getMarketsUsd); ``` - -## Defined in - -[src/services/apiServices.ts:123](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/apiServices.ts#L123) diff --git a/docs/src/services/apiServices/functions/getRates.md b/docs/src/services/apiServices/functions/getRates.md index 4f1cedf..0b1dddf 100644 --- a/docs/src/services/apiServices/functions/getRates.md +++ b/docs/src/services/apiServices/functions/getRates.md @@ -1,22 +1,28 @@ -[**rates-api v3.0.0**](../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../README.md) *** -[rates-api v3.0.0](../../../../modules.md) / [src/services/apiServices](../README.md) / getRates +[rates-api](../../../../modules.md) / [src/services/apiServices](../README.md) / getRates # Function: getRates() > **getRates**(`req`, `res`): `Promise`\<`void`\> +Defined in: src/services/apiServices.ts:47 + Handles the GET request to retrieve exchange rates. ## Parameters -• **req**: `Request`\<`ParamsDictionary`, `any`, `any`, `ParsedQs`, `Record`\<`string`, `any`\>\> +### req + +`Request` The Express request object. -• **res**: `Response`\<`any`, `Record`\<`string`, `any`\>\> +### res + +`Response` The Express response object. @@ -29,7 +35,3 @@ The Express response object. ```typescript app.get('/rates', getRates); ``` - -## Defined in - -[src/services/apiServices.ts:47](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/apiServices.ts#L47) diff --git a/docs/src/services/apiServices/functions/getRatesV2.md b/docs/src/services/apiServices/functions/getRatesV2.md index ce16bcf..b8615eb 100644 --- a/docs/src/services/apiServices/functions/getRatesV2.md +++ b/docs/src/services/apiServices/functions/getRatesV2.md @@ -1,22 +1,28 @@ -[**rates-api v3.0.0**](../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../README.md) *** -[rates-api v3.0.0](../../../../modules.md) / [src/services/apiServices](../README.md) / getRatesV2 +[rates-api](../../../../modules.md) / [src/services/apiServices](../README.md) / getRatesV2 # Function: getRatesV2() > **getRatesV2**(`req`, `res`): `Promise`\<`void`\> +Defined in: src/services/apiServices.ts:66 + Handles the GET request to retrieve version 2 of the exchange rates. ## Parameters -• **req**: `Request`\<`ParamsDictionary`, `any`, `any`, `ParsedQs`, `Record`\<`string`, `any`\>\> +### req + +`Request` The Express request object. -• **res**: `Response`\<`any`, `Record`\<`string`, `any`\>\> +### res + +`Response` The Express response object. @@ -29,7 +35,3 @@ The Express response object. ```typescript app.get('/rates/v2', getRatesV2); ``` - -## Defined in - -[src/services/apiServices.ts:66](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/apiServices.ts#L66) diff --git a/docs/src/services/apiServices/functions/getRatesV2Compressed.md b/docs/src/services/apiServices/functions/getRatesV2Compressed.md index 52b3800..66a9a05 100644 --- a/docs/src/services/apiServices/functions/getRatesV2Compressed.md +++ b/docs/src/services/apiServices/functions/getRatesV2Compressed.md @@ -1,22 +1,28 @@ -[**rates-api v3.0.0**](../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../README.md) *** -[rates-api v3.0.0](../../../../modules.md) / [src/services/apiServices](../README.md) / getRatesV2Compressed +[rates-api](../../../../modules.md) / [src/services/apiServices](../README.md) / getRatesV2Compressed # Function: getRatesV2Compressed() > **getRatesV2Compressed**(`req`, `res`): `Promise`\<`void`\> +Defined in: src/services/apiServices.ts:85 + Handles the GET request to retrieve compressed version of the exchange rates (version 2). ## Parameters -• **req**: `Request`\<`ParamsDictionary`, `any`, `any`, `ParsedQs`, `Record`\<`string`, `any`\>\> +### req + +`Request` The Express request object. -• **res**: `Response`\<`any`, `Record`\<`string`, `any`\>\> +### res + +`Response` The Express response object. @@ -29,7 +35,3 @@ The Express response object. ```typescript app.get('/rates/v2/compressed', getRatesV2Compressed); ``` - -## Defined in - -[src/services/apiServices.ts:85](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/apiServices.ts#L85) diff --git a/docs/src/services/apiServices/functions/serviceRefresher.md b/docs/src/services/apiServices/functions/serviceRefresher.md index 8d4e9bc..fa2fac8 100644 --- a/docs/src/services/apiServices/functions/serviceRefresher.md +++ b/docs/src/services/apiServices/functions/serviceRefresher.md @@ -1,13 +1,15 @@ -[**rates-api v3.0.0**](../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../README.md) *** -[rates-api v3.0.0](../../../../modules.md) / [src/services/apiServices](../README.md) / serviceRefresher +[rates-api](../../../../modules.md) / [src/services/apiServices](../README.md) / serviceRefresher # Function: serviceRefresher() > **serviceRefresher**(): `Promise`\<`void`\> +Defined in: src/services/apiServices.ts:224 + Periodically refreshes market data and exchange rates. Fetches data from `zelcoreRates`, `zelcoreMarketsUSD`, and `zelcoreRatesV2`, @@ -23,7 +25,3 @@ Sets a delay before calling itself again. ```typescript serviceRefresher(); ``` - -## Defined in - -[src/services/apiServices.ts:224](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/apiServices.ts#L224) diff --git a/docs/src/services/apiServices/variables/default.md b/docs/src/services/apiServices/variables/default.md index 50ba3b5..e187661 100644 --- a/docs/src/services/apiServices/variables/default.md +++ b/docs/src/services/apiServices/variables/default.md @@ -1,16 +1,18 @@ -[**rates-api v3.0.0**](../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../README.md) *** -[rates-api v3.0.0](../../../../modules.md) / [src/services/apiServices](../README.md) / default +[rates-api](../../../../modules.md) / [src/services/apiServices](../README.md) / default # Variable: default > **default**: `object` -## Type declaration +Defined in: src/services/apiServices.ts:270 -### checkContractsV2() +## Type Declaration + +### checkContractsV2 > **checkContractsV2**: (`req`, `res`) => `Promise`\<`void`\> @@ -18,11 +20,15 @@ Handles the request to check for new contracts. #### Parameters -• **req**: `Request`\<`ParamsDictionary`, `any`, `any`, `ParsedQs`, `Record`\<`string`, `any`\>\> +##### req + +`Request` The Express request object containing `contracts` in the body. -• **res**: `Response`\<`any`, `Record`\<`string`, `any`\>\> +##### res + +`Response` The Express response object. @@ -36,7 +42,7 @@ The Express response object. app.post('/contracts/check', checkContractsV2); ``` -### dataRefresher() +### dataRefresher > **dataRefresher**: () => `Promise`\<`void`\> @@ -56,7 +62,7 @@ logs the error and retries after 30 minutes. dataRefresher(); ``` -### getData() +### getData > **getData**: () => `object` @@ -64,18 +70,29 @@ Retrieves the current rates and market data. #### Returns -`object` - An object containing `rates` and `marketsUSD`. ##### marketsUSD > **marketsUSD**: [`MarketsData`](../../../types/type-aliases/MarketsData.md) +Stores market data in USD. + +Structure: +- `marketsUSD[0]`: BTC to USD market data. +- `marketsUSD[1]`: Errors object. + ##### rates > **rates**: [`RatesData`](../../../types/type-aliases/RatesData.md) +Stores exchange rates data. + +Structure: +- `rates[0]`: BTC to fiat exchange rates. +- `rates[1]`: Alternative coins to fiat exchange rates. +- `rates[2]`: Errors object. + #### Example ```typescript @@ -83,7 +100,7 @@ const data = getData(); console.log(data.rates, data.marketsUSD); ``` -### getFoundContracts() +### getFoundContracts > **getFoundContracts**: () => [`FoundContractStore`](../../../types/type-aliases/FoundContractStore.md) @@ -101,7 +118,7 @@ The `foundContracts` object. const contracts = getFoundContracts(); ``` -### getMarketsUsd() +### getMarketsUsd > **getMarketsUsd**: (`req`, `res`) => `Promise`\<`void`\> @@ -109,11 +126,15 @@ Handles the GET request to retrieve market data in USD. #### Parameters -• **req**: `Request`\<`ParamsDictionary`, `any`, `any`, `ParsedQs`, `Record`\<`string`, `any`\>\> +##### req + +`Request` The Express request object. -• **res**: `Response`\<`any`, `Record`\<`string`, `any`\>\> +##### res + +`Response` The Express response object. @@ -127,7 +148,7 @@ The Express response object. app.get('/markets/usd', getMarketsUsd); ``` -### getRates() +### getRates > **getRates**: (`req`, `res`) => `Promise`\<`void`\> @@ -135,11 +156,15 @@ Handles the GET request to retrieve exchange rates. #### Parameters -• **req**: `Request`\<`ParamsDictionary`, `any`, `any`, `ParsedQs`, `Record`\<`string`, `any`\>\> +##### req + +`Request` The Express request object. -• **res**: `Response`\<`any`, `Record`\<`string`, `any`\>\> +##### res + +`Response` The Express response object. @@ -153,7 +178,7 @@ The Express response object. app.get('/rates', getRates); ``` -### getRatesV2() +### getRatesV2 > **getRatesV2**: (`req`, `res`) => `Promise`\<`void`\> @@ -161,11 +186,15 @@ Handles the GET request to retrieve version 2 of the exchange rates. #### Parameters -• **req**: `Request`\<`ParamsDictionary`, `any`, `any`, `ParsedQs`, `Record`\<`string`, `any`\>\> +##### req + +`Request` The Express request object. -• **res**: `Response`\<`any`, `Record`\<`string`, `any`\>\> +##### res + +`Response` The Express response object. @@ -179,7 +208,7 @@ The Express response object. app.get('/rates/v2', getRatesV2); ``` -### getRatesV2Compressed() +### getRatesV2Compressed > **getRatesV2Compressed**: (`req`, `res`) => `Promise`\<`void`\> @@ -187,11 +216,15 @@ Handles the GET request to retrieve compressed version of the exchange rates (ve #### Parameters -• **req**: `Request`\<`ParamsDictionary`, `any`, `any`, `ParsedQs`, `Record`\<`string`, `any`\>\> +##### req + +`Request` The Express request object. -• **res**: `Response`\<`any`, `Record`\<`string`, `any`\>\> +##### res + +`Response` The Express response object. @@ -205,7 +238,7 @@ The Express response object. app.get('/rates/v2/compressed', getRatesV2Compressed); ``` -### serviceRefresher() +### serviceRefresher > **serviceRefresher**: () => `Promise`\<`void`\> @@ -224,7 +257,3 @@ Sets a delay before calling itself again. ```typescript serviceRefresher(); ``` - -## Defined in - -[src/services/apiServices.ts:263](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/apiServices.ts#L263) diff --git a/docs/src/services/bstocks/README.md b/docs/src/services/bstocks/README.md new file mode 100644 index 0000000..c663b5a --- /dev/null +++ b/docs/src/services/bstocks/README.md @@ -0,0 +1,12 @@ +[**rates-api v3.0.0**](../../../README.md) + +*** + +[rates-api](../../../modules.md) / src/services/bstocks + +# src/services/bstocks + +## Functions + +- [\_clearLastGoodForTests](functions/clearLastGoodForTests.md) +- [getBstockPrices](functions/getBstockPrices.md) diff --git a/docs/src/services/bstocks/functions/clearLastGoodForTests.md b/docs/src/services/bstocks/functions/clearLastGoodForTests.md new file mode 100644 index 0000000..c648803 --- /dev/null +++ b/docs/src/services/bstocks/functions/clearLastGoodForTests.md @@ -0,0 +1,15 @@ +[**rates-api v3.0.0**](../../../../README.md) + +*** + +[rates-api](../../../../modules.md) / [src/services/bstocks](../README.md) / \_clearLastGoodForTests + +# Function: \_clearLastGoodForTests() + +> **\_clearLastGoodForTests**(): `void` + +Defined in: src/services/bstocks.ts:13 + +## Returns + +`void` diff --git a/docs/src/services/bstocks/functions/getBstockPrices.md b/docs/src/services/bstocks/functions/getBstockPrices.md new file mode 100644 index 0000000..244ef95 --- /dev/null +++ b/docs/src/services/bstocks/functions/getBstockPrices.md @@ -0,0 +1,41 @@ +[**rates-api v3.0.0**](../../../../README.md) + +*** + +[rates-api](../../../../modules.md) / [src/services/bstocks](../README.md) / getBstockPrices + +# Function: getBstockPrices() + +> **getBstockPrices**(): `Promise`\<[`CryptoPrice`](../../../types/type-aliases/CryptoPrice.md)[]\> + +Defined in: src/services/bstocks.ts:44 + +Assembles the bStocks synthetic market: the intersection of Binance's +tokenised-asset universe (already filtered to BSC-listed assets by +`Binance.getTokenisedAssets`) with Spot symbols currently in `TRADING` +status, quoted in USDT. + +BTC/USD conversion uses BTCUSDT fetched in the same 24h-ticker batch as the +bStock symbols, so both legs come from the same venue and no cross-venue +basis is introduced. + +Emitted ids are `bstock-` under `provider: "coingecko"` +— NOT `"binance"`. The client does no prefix parsing: ZelCore's +`store/actions.js` (`applyMarkets`) keys the market store on the literal +string `${provider}-${id}`, and `use-fiat.js` builds the same literal from +`coininfo.json`'s `coingeckoID` as `coingecko-${coingeckoID}`. The sibling +`api` repo serves `coinInfo.coingeckoID = "bstock-"`, so the two +literals only meet if the provider here is exactly `"coingecko"`. Any other +value makes the lookup miss silently — no error, just no price. This +id/provider pairing is a cross-repo contract; do not change it in isolation. + +A module-level last-known-good map means a symbol that drops out of a given +refresh (CEX halt, e.g. around a stock split) keeps being served at its +previous price rather than disappearing from the response. + +## Returns + +`Promise`\<[`CryptoPrice`](../../../types/type-aliases/CryptoPrice.md)[]\> + +One `CryptoPrice` per tradable bStock (BSC contract + TRADING +`USDT` Spot symbol), including any carried over from a prior refresh. diff --git a/docs/src/services/coinAggregatorIDs/README.md b/docs/src/services/coinAggregatorIDs/README.md index cbc85b7..b89b0a3 100644 --- a/docs/src/services/coinAggregatorIDs/README.md +++ b/docs/src/services/coinAggregatorIDs/README.md @@ -1,20 +1,18 @@ -[**rates-api v3.0.0**](../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../README.md) *** -[rates-api v3.0.0](../../../modules.md) / src/services/coinAggregatorIDs +[rates-api](../../../modules.md) / src/services/coinAggregatorIDs # src/services/coinAggregatorIDs -## Index - -### Variables +## Variables - [cgContractMap](variables/cgContractMap.md) - [cgTokens](variables/cgTokens.md) - [coinAggregatorIDs](variables/coinAggregatorIDs.md) - [zelData](variables/zelData.md) -### Functions +## Functions - [getLatestCoinInfo](functions/getLatestCoinInfo.md) diff --git a/docs/src/services/coinAggregatorIDs/functions/getLatestCoinInfo.md b/docs/src/services/coinAggregatorIDs/functions/getLatestCoinInfo.md index 6ce46f4..e1deffd 100644 --- a/docs/src/services/coinAggregatorIDs/functions/getLatestCoinInfo.md +++ b/docs/src/services/coinAggregatorIDs/functions/getLatestCoinInfo.md @@ -1,13 +1,15 @@ -[**rates-api v3.0.0**](../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../README.md) *** -[rates-api v3.0.0](../../../../modules.md) / [src/services/coinAggregatorIDs](../README.md) / getLatestCoinInfo +[rates-api](../../../../modules.md) / [src/services/coinAggregatorIDs](../README.md) / getLatestCoinInfo # Function: getLatestCoinInfo() > **getLatestCoinInfo**(): `Promise`\<`void`\> +Defined in: src/services/coinAggregatorIDs.ts:92 + Fetches the latest coin information and updates the global data. This function retrieves coin information from a specified URL, updates the CoinGecko IDs, @@ -27,7 +29,3 @@ A promise that resolves when the operation is complete. await getLatestCoinInfo(); console.log(zelData.coinInfo); ``` - -## Defined in - -[src/services/coinAggregatorIDs.ts:92](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/coinAggregatorIDs.ts#L92) diff --git a/docs/src/services/coinAggregatorIDs/variables/cgContractMap.md b/docs/src/services/coinAggregatorIDs/variables/cgContractMap.md index 866065d..1cd2d0e 100644 --- a/docs/src/services/coinAggregatorIDs/variables/cgContractMap.md +++ b/docs/src/services/coinAggregatorIDs/variables/cgContractMap.md @@ -1,15 +1,13 @@ -[**rates-api v3.0.0**](../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../README.md) *** -[rates-api v3.0.0](../../../../modules.md) / [src/services/coinAggregatorIDs](../README.md) / cgContractMap +[rates-api](../../../../modules.md) / [src/services/coinAggregatorIDs](../README.md) / cgContractMap # Variable: cgContractMap > `const` **cgContractMap**: `Record`\<`string`, [`CoinGeckoToken`](../../../types/type-aliases/CoinGeckoToken.md)\> = `{}` -Map of contract addresses to CoinGecko tokens. - -## Defined in +Defined in: src/services/coinAggregatorIDs.ts:75 -[src/services/coinAggregatorIDs.ts:75](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/coinAggregatorIDs.ts#L75) +Map of contract addresses to CoinGecko tokens. diff --git a/docs/src/services/coinAggregatorIDs/variables/cgTokens.md b/docs/src/services/coinAggregatorIDs/variables/cgTokens.md index e41d01a..07a73b9 100644 --- a/docs/src/services/coinAggregatorIDs/variables/cgTokens.md +++ b/docs/src/services/coinAggregatorIDs/variables/cgTokens.md @@ -1,15 +1,13 @@ -[**rates-api v3.0.0**](../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../README.md) *** -[rates-api v3.0.0](../../../../modules.md) / [src/services/coinAggregatorIDs](../README.md) / cgTokens +[rates-api](../../../../modules.md) / [src/services/coinAggregatorIDs](../README.md) / cgTokens # Variable: cgTokens > **cgTokens**: [`CoinGeckoToken`](../../../types/type-aliases/CoinGeckoToken.md)[] = `cgCoins` -Array of CoinGecko tokens. - -## Defined in +Defined in: src/services/coinAggregatorIDs.ts:70 -[src/services/coinAggregatorIDs.ts:70](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/coinAggregatorIDs.ts#L70) +Array of CoinGecko tokens. diff --git a/docs/src/services/coinAggregatorIDs/variables/coinAggregatorIDs.md b/docs/src/services/coinAggregatorIDs/variables/coinAggregatorIDs.md index a9cbde1..4dfd07e 100644 --- a/docs/src/services/coinAggregatorIDs/variables/coinAggregatorIDs.md +++ b/docs/src/services/coinAggregatorIDs/variables/coinAggregatorIDs.md @@ -1,16 +1,18 @@ -[**rates-api v3.0.0**](../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../README.md) *** -[rates-api v3.0.0](../../../../modules.md) / [src/services/coinAggregatorIDs](../README.md) / coinAggregatorIDs +[rates-api](../../../../modules.md) / [src/services/coinAggregatorIDs](../README.md) / coinAggregatorIDs # Variable: coinAggregatorIDs > `const` **coinAggregatorIDs**: `object` +Defined in: src/services/coinAggregatorIDs.ts:14 + An object containing arrays of cryptocurrency IDs used by different data aggregators. -## Type declaration +## Type Declaration ### coingecko @@ -36,7 +38,3 @@ Add the CryptoCompare IDs at the end of this list. LiveCoinWatch API IDs. ## Const - -## Defined in - -[src/services/coinAggregatorIDs.ts:14](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/coinAggregatorIDs.ts#L14) diff --git a/docs/src/services/coinAggregatorIDs/variables/zelData.md b/docs/src/services/coinAggregatorIDs/variables/zelData.md index 7c79465..f0e3a29 100644 --- a/docs/src/services/coinAggregatorIDs/variables/zelData.md +++ b/docs/src/services/coinAggregatorIDs/variables/zelData.md @@ -1,21 +1,19 @@ -[**rates-api v3.0.0**](../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../README.md) *** -[rates-api v3.0.0](../../../../modules.md) / [src/services/coinAggregatorIDs](../README.md) / zelData +[rates-api](../../../../modules.md) / [src/services/coinAggregatorIDs](../README.md) / zelData # Variable: zelData > `const` **zelData**: `object` +Defined in: src/services/coinAggregatorIDs.ts:61 + Global object to store coin information. -## Type declaration +## Type Declaration ### coinInfo > **coinInfo**: `Record`\<`string`, [`CoinInfo`](../../../types/type-aliases/CoinInfo.md)\> - -## Defined in - -[src/services/coinAggregatorIDs.ts:61](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/coinAggregatorIDs.ts#L61) diff --git a/docs/src/services/newContracts/README.md b/docs/src/services/newContracts/README.md index 345ae48..3a960da 100644 --- a/docs/src/services/newContracts/README.md +++ b/docs/src/services/newContracts/README.md @@ -1,17 +1,15 @@ -[**rates-api v3.0.0**](../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../README.md) *** -[rates-api v3.0.0](../../../modules.md) / src/services/newContracts +[rates-api](../../../modules.md) / src/services/newContracts # src/services/newContracts -## Index - -### Variables +## Variables - [foundContracts](variables/foundContracts.md) -### Functions +## Functions - [checkContracts](functions/checkContracts.md) diff --git a/docs/src/services/newContracts/functions/checkContracts.md b/docs/src/services/newContracts/functions/checkContracts.md index 4c82e91..ae2ee9d 100644 --- a/docs/src/services/newContracts/functions/checkContracts.md +++ b/docs/src/services/newContracts/functions/checkContracts.md @@ -1,13 +1,15 @@ -[**rates-api v3.0.0**](../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../README.md) *** -[rates-api v3.0.0](../../../../modules.md) / [src/services/newContracts](../README.md) / checkContracts +[rates-api](../../../../modules.md) / [src/services/newContracts](../README.md) / checkContracts # Function: checkContracts() > **checkContracts**(`contracts`): `boolean` +Defined in: src/services/newContracts.ts:32 + Checks the provided contracts against the CoinGecko contract map and updates the `foundContracts` store. This function iterates over an array of contracts, checks if they exist in the CoinGecko contract map, @@ -15,7 +17,9 @@ and updates the `foundContracts` object by incrementing the count or adding a ne ## Parameters -• **contracts**: [`ContractWithType`](../../../types/type-aliases/ContractWithType.md)[] +### contracts + +[`ContractWithType`](../../../types/type-aliases/ContractWithType.md)[] An array of contracts with their types. @@ -38,7 +42,3 @@ const contracts = [ const success = checkContracts(contracts); console.log('Contracts checked:', success); ``` - -## Defined in - -[src/services/newContracts.ts:32](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/newContracts.ts#L32) diff --git a/docs/src/services/newContracts/variables/foundContracts.md b/docs/src/services/newContracts/variables/foundContracts.md index bc92307..acbd6a8 100644 --- a/docs/src/services/newContracts/variables/foundContracts.md +++ b/docs/src/services/newContracts/variables/foundContracts.md @@ -1,15 +1,13 @@ -[**rates-api v3.0.0**](../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../README.md) *** -[rates-api v3.0.0](../../../../modules.md) / [src/services/newContracts](../README.md) / foundContracts +[rates-api](../../../../modules.md) / [src/services/newContracts](../README.md) / foundContracts # Variable: foundContracts > `const` **foundContracts**: [`FoundContractStore`](../../../types/type-aliases/FoundContractStore.md) = `{}` -Stores the found contracts with their occurrence count. - -## Defined in +Defined in: src/services/newContracts.ts:8 -[src/services/newContracts.ts:8](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/newContracts.ts#L8) +Stores the found contracts with their occurrence count. diff --git a/docs/src/services/providers/README.md b/docs/src/services/providers/README.md index b80fc59..0707039 100644 --- a/docs/src/services/providers/README.md +++ b/docs/src/services/providers/README.md @@ -1,13 +1,19 @@ -[**rates-api v3.0.0**](../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../README.md) *** -[rates-api v3.0.0](../../../modules.md) / src/services/providers +[rates-api](../../../modules.md) / src/services/providers # src/services/providers ## References +### Binance + +Re-exports [Binance](binance/classes/Binance.md) + +*** + ### BitPay Re-exports [BitPay](bitpay/classes/BitPay.md) diff --git a/docs/src/services/providers/binance/README.md b/docs/src/services/providers/binance/README.md new file mode 100644 index 0000000..b343326 --- /dev/null +++ b/docs/src/services/providers/binance/README.md @@ -0,0 +1,17 @@ +[**rates-api v3.0.0**](../../../../README.md) + +*** + +[rates-api](../../../../modules.md) / src/services/providers/binance + +# src/services/providers/binance + +## Classes + +- [Binance](classes/Binance.md) + +## References + +### default + +Renames and re-exports [Binance](classes/Binance.md) diff --git a/docs/src/services/providers/binance/classes/Binance.md b/docs/src/services/providers/binance/classes/Binance.md new file mode 100644 index 0000000..46aa0ab --- /dev/null +++ b/docs/src/services/providers/binance/classes/Binance.md @@ -0,0 +1,231 @@ +[**rates-api v3.0.0**](../../../../../README.md) + +*** + +[rates-api](../../../../../modules.md) / [src/services/providers/binance](../README.md) / Binance + +# Class: Binance + +Defined in: src/services/providers/binance.ts:33 + +Singleton class to interact with Binance's public (no-API-key) endpoints. + +Provides the tokenised-asset universe (bStocks with a BSC contract) and Spot +24h/7d tickers, quoted in USDT. Mirrors `CoinGecko`'s shape: an `AxiosWrapper` +per base URL, an `LRUCache` per refresh cadence, and defensive error handling +that never lets a single failed refresh drop a symbol that was previously +known good (e.g. during a CEX trading halt around a stock split). + +## Example + +```typescript +import { Binance } from './binance'; + +async function fetchBStocks() { + const binance = Binance.getInstance(); + const assets = await binance.getTokenisedAssets(); + const trading = await binance.getTradingSymbols(); + const tickers = await binance.getTicker24h([...trading]); + console.log(tickers); +} +``` + +## Constructors + +### Constructor + +> **new Binance**(): `Binance` + +#### Returns + +`Binance` + +## Methods + +### chunkSymbols() + +> **chunkSymbols**(`symbols`): `string`[][] + +Defined in: src/services/providers/binance.ts:122 + +Splits a symbol list into chunks of at most `TICKER_CHUNK` symbols, to stay +under Binance's per-request weight cap on the 7d rolling-window ticker. + +#### Parameters + +##### symbols + +`string`[] + +The full symbol list to split. + +#### Returns + +`string`[][] + +An array of symbol chunks. + +*** + +### filterBscAssets() + +> **filterBscAssets**(`assets`): [`BinanceTokenisedAsset`](../../../../types/type-aliases/BinanceTokenisedAsset.md)[] + +Defined in: src/services/providers/binance.ts:110 + +Filters tokenised assets down to those with a BSC (BNB Smart Chain) contract listed. + +#### Parameters + +##### assets + +[`BinanceTokenisedAsset`](../../../../types/type-aliases/BinanceTokenisedAsset.md)[] + +The raw tokenised-asset list from Binance. + +#### Returns + +[`BinanceTokenisedAsset`](../../../../types/type-aliases/BinanceTokenisedAsset.md)[] + +Only the assets with at least one BSC entry in `caList`. + +*** + +### getTicker24h() + +> **getTicker24h**(`symbols`): `Promise`\<[`BinanceTicker`](../../../../types/type-aliases/BinanceTicker.md)[]\> + +Defined in: src/services/providers/binance.ts:231 + +Retrieves 24h tickers for the given symbols in a single request. + +On a failed or partial refresh, missing symbols are backfilled from the +last-known-good store rather than dropped. Cached for 60 seconds per +requested symbol set. + +#### Parameters + +##### symbols + +`string`[] + +The Spot symbols to fetch (e.g. `TSLABUSDT`). + +#### Returns + +`Promise`\<[`BinanceTicker`](../../../../types/type-aliases/BinanceTicker.md)[]\> + +One ticker per requested symbol that has ever been seen. + +*** + +### getTicker7d() + +> **getTicker7d**(`symbols`): `Promise`\<[`BinanceTicker`](../../../../types/type-aliases/BinanceTicker.md)[]\> + +Defined in: src/services/providers/binance.ts:261 + +Retrieves 7d rolling-window tickers for the given symbols, chunked to stay +under Binance's per-request weight cap. + +Each chunk is fetched independently, so one failing chunk never drops the +symbols in the others; any symbol whose chunk failed (or that was omitted, +e.g. a halt) is backfilled from the last-known-good store. Cached for 60 +seconds per requested symbol set. + +#### Parameters + +##### symbols + +`string`[] + +The Spot symbols to fetch (e.g. `TSLABUSDT`). + +#### Returns + +`Promise`\<[`BinanceTicker`](../../../../types/type-aliases/BinanceTicker.md)[]\> + +One ticker per requested symbol that has ever been seen. + +*** + +### getTokenisedAssets() + +> **getTokenisedAssets**(): `Promise`\<[`BinanceTokenisedAsset`](../../../../types/type-aliases/BinanceTokenisedAsset.md)[]\> + +Defined in: src/services/providers/binance.ts:173 + +Retrieves the tokenised-asset universe (bStocks), filtered to those with a BSC contract. + +Cached for 1 hour. + +#### Returns + +`Promise`\<[`BinanceTokenisedAsset`](../../../../types/type-aliases/BinanceTokenisedAsset.md)[]\> + +The BSC-listed tokenised assets. + +*** + +### getTradingSymbols() + +> **getTradingSymbols**(): `Promise`\<`Set`\<`string`\>\> + +Defined in: src/services/providers/binance.ts:201 + +Retrieves the set of Spot symbols currently in `TRADING` status. + +A symbol dropping to `BREAK` (as happens during trading halts, e.g. around +a stock split) simply falls out of this set on the next refresh; callers +should keep serving the last-known-good ticker for it rather than treating +its absence here as "delisted". + +Cached for 1 hour. + +#### Returns + +`Promise`\<`Set`\<`string`\>\> + +The set of currently-trading symbols. + +*** + +### lastGoodAgeMs() + +> **lastGoodAgeMs**(`symbol`): `number` \| `null` + +Defined in: src/services/providers/binance.ts:161 + +Age in milliseconds of the last-known-good price for a symbol, or null if +none has ever been recorded. Lets a caller distinguish a live price from +one carried through a long halt, which the ticker itself cannot express. + +#### Parameters + +##### symbol + +`string` + +The Binance symbol, e.g. `TSLABUSDT`. + +#### Returns + +`number` \| `null` + +Age in ms, or null when the symbol has never priced successfully. + +*** + +### getInstance() + +> `static` **getInstance**(): `Binance` + +Defined in: src/services/providers/binance.ts:99 + +Returns the singleton instance of the Binance class. + +#### Returns + +`Binance` + +The singleton instance of Binance. diff --git a/docs/src/services/providers/bitpay/README.md b/docs/src/services/providers/bitpay/README.md index 55b02a1..2a78cda 100644 --- a/docs/src/services/providers/bitpay/README.md +++ b/docs/src/services/providers/bitpay/README.md @@ -1,13 +1,11 @@ -[**rates-api v3.0.0**](../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../README.md) *** -[rates-api v3.0.0](../../../../modules.md) / src/services/providers/bitpay +[rates-api](../../../../modules.md) / src/services/providers/bitpay # src/services/providers/bitpay -## Index - -### Classes +## Classes - [BitPay](classes/BitPay.md) diff --git a/docs/src/services/providers/bitpay/classes/BitPay.md b/docs/src/services/providers/bitpay/classes/BitPay.md index 12f72dd..7ea766a 100644 --- a/docs/src/services/providers/bitpay/classes/BitPay.md +++ b/docs/src/services/providers/bitpay/classes/BitPay.md @@ -1,11 +1,13 @@ -[**rates-api v3.0.0**](../../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../../README.md) *** -[rates-api v3.0.0](../../../../../modules.md) / [src/services/providers/bitpay](../README.md) / BitPay +[rates-api](../../../../../modules.md) / [src/services/providers/bitpay](../README.md) / BitPay # Class: BitPay +Defined in: src/services/providers/bitpay.ts:24 + Singleton class to interact with the BitPay API. This class provides methods to retrieve fiat currency exchange rates from the BitPay API. @@ -27,9 +29,11 @@ fetchRates(); ## Constructors -### new BitPay() +### Constructor + +> **new BitPay**(): `BitPay` -> **new BitPay**(): [`BitPay`](BitPay.md) +Defined in: src/services/providers/bitpay.ts:58 Private constructor to enforce the singleton pattern. @@ -37,22 +41,20 @@ Initializes the AxiosWrapper and the LRU cache. #### Returns -[`BitPay`](BitPay.md) +`BitPay` #### Throws If an instance already exists. -#### Defined in - -[src/services/providers/bitpay.ts:58](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/providers/bitpay.ts#L58) - ## Methods ### getFiatRates() > **getFiatRates**(): `Promise`\<`any`\> +Defined in: src/services/providers/bitpay.ts:115 + Retrieves fiat currency exchange rates from the BitPay API. Utilizes caching to prevent unnecessary API calls. If the rates are cached and valid, @@ -72,21 +74,19 @@ const rates = await bitPay.getFiatRates(); console.log(rates); ``` -#### Defined in - -[src/services/providers/bitpay.ts:115](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/providers/bitpay.ts#L115) - *** ### getInstance() -> `static` **getInstance**(): [`BitPay`](BitPay.md) +> `static` **getInstance**(): `BitPay` + +Defined in: src/services/providers/bitpay.ts:81 Returns the singleton instance of the BitPay class. #### Returns -[`BitPay`](BitPay.md) +`BitPay` The singleton instance of BitPay. @@ -95,7 +95,3 @@ The singleton instance of BitPay. ```typescript const bitPay = BitPay.getInstance(); ``` - -#### Defined in - -[src/services/providers/bitpay.ts:81](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/providers/bitpay.ts#L81) diff --git a/docs/src/services/providers/coinGecko/README.md b/docs/src/services/providers/coinGecko/README.md index bed55e4..3f5a61d 100644 --- a/docs/src/services/providers/coinGecko/README.md +++ b/docs/src/services/providers/coinGecko/README.md @@ -1,13 +1,11 @@ -[**rates-api v3.0.0**](../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../README.md) *** -[rates-api v3.0.0](../../../../modules.md) / src/services/providers/coinGecko +[rates-api](../../../../modules.md) / src/services/providers/coinGecko # src/services/providers/coinGecko -## Index - -### Classes +## Classes - [CoinGecko](classes/CoinGecko.md) diff --git a/docs/src/services/providers/coinGecko/classes/CoinGecko.md b/docs/src/services/providers/coinGecko/classes/CoinGecko.md index c7853bb..b7efa8f 100644 --- a/docs/src/services/providers/coinGecko/classes/CoinGecko.md +++ b/docs/src/services/providers/coinGecko/classes/CoinGecko.md @@ -1,11 +1,13 @@ -[**rates-api v3.0.0**](../../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../../README.md) *** -[rates-api v3.0.0](../../../../../modules.md) / [src/services/providers/coinGecko](../README.md) / CoinGecko +[rates-api](../../../../../modules.md) / [src/services/providers/coinGecko](../README.md) / CoinGecko # Class: CoinGecko +Defined in: src/services/providers/coinGecko.ts:40 + Singleton class to interact with the CoinGecko API. This class provides methods to retrieve cryptocurrency data from CoinGecko. @@ -27,9 +29,11 @@ fetchRates(); ## Constructors -### new CoinGecko() +### Constructor + +> **new CoinGecko**(): `CoinGecko` -> **new CoinGecko**(): [`CoinGecko`](CoinGecko.md) +Defined in: src/services/providers/coinGecko.ts:81 Private constructor to enforce the singleton pattern. @@ -37,22 +41,20 @@ Initializes the AxiosWrapper and the LRU cache. #### Returns -[`CoinGecko`](CoinGecko.md) +`CoinGecko` #### Throws If an instance already exists. -#### Defined in - -[src/services/providers/coinGecko.ts:81](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/providers/coinGecko.ts#L81) - ## Methods ### getAssetPlatformData() > **getAssetPlatformData**(): `Promise`\<`any`\> +Defined in: src/services/providers/coinGecko.ts:207 + Retrieves asset platform data from CoinGecko. #### Returns @@ -69,21 +71,21 @@ const assetPlatforms = await coinGecko.getAssetPlatformData(); console.log('Asset Platforms:', assetPlatforms); ``` -#### Defined in - -[src/services/providers/coinGecko.ts:207](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/providers/coinGecko.ts#L207) - *** ### getCoinsList() -> **getCoinsList**(`includePlatform`): `Promise`\<`any`\> +> **getCoinsList**(`includePlatform?`): `Promise`\<`any`\> + +Defined in: src/services/providers/coinGecko.ts:173 Retrieves a list of all coins supported by CoinGecko. #### Parameters -• **includePlatform**: `boolean` = `true` +##### includePlatform? + +`boolean` = `true` Whether to include platform data in the response (default is `true`). @@ -101,15 +103,13 @@ const coinsList = await coinGecko.getCoinsList(); console.log('Coins List:', coinsList); ``` -#### Defined in - -[src/services/providers/coinGecko.ts:173](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/providers/coinGecko.ts#L173) - *** ### getExchangeRates() -> **getExchangeRates**(`ids`, `vsCurrency`): `Promise`\<[`CoinGeckoPrice`](../../../../types/type-aliases/CoinGeckoPrice.md)[]\> +> **getExchangeRates**(`ids`, `vsCurrency?`): `Promise`\<[`CoinGeckoPrice`](../../../../types/type-aliases/CoinGeckoPrice.md)[]\> + +Defined in: src/services/providers/coinGecko.ts:278 Retrieves exchange rates for an array of coin IDs. @@ -117,11 +117,15 @@ Handles splitting the IDs into batches to comply with API limitations. #### Parameters -• **ids**: `string`[] +##### ids + +`string`[] An array of coin IDs. -• **vsCurrency**: `string` = `'btc'` +##### vsCurrency? + +`string` = `'btc'` The target currency (default is 'btc'). @@ -139,15 +143,13 @@ const rates = await coinGecko.getExchangeRates(['bitcoin', 'ethereum', 'litecoin console.log('Exchange Rates:', rates); ``` -#### Defined in - -[src/services/providers/coinGecko.ts:278](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/providers/coinGecko.ts#L278) - *** ### getKeyUsage() -> **getKeyUsage**(): `Promise`\<`null` \| `KeyUsage`\> +> **getKeyUsage**(): `Promise`\<`KeyUsage` \| `null`\> + +Defined in: src/services/providers/coinGecko.ts:138 Retrieves the usage statistics of the CoinGecko API key. @@ -156,7 +158,7 @@ it returns it directly from the cache. Otherwise, it fetches new data from the A #### Returns -`Promise`\<`null` \| `KeyUsage`\> +`Promise`\<`KeyUsage` \| `null`\> The key usage data or `null` if an error occurs. @@ -168,21 +170,19 @@ const usage = await coinGecko.getKeyUsage(); console.log('API Key Usage:', usage); ``` -#### Defined in - -[src/services/providers/coinGecko.ts:138](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/providers/coinGecko.ts#L138) - *** ### getInstance() -> `static` **getInstance**(): [`CoinGecko`](CoinGecko.md) +> `static` **getInstance**(): `CoinGecko` + +Defined in: src/services/providers/coinGecko.ts:104 Returns the singleton instance of the CoinGecko class. #### Returns -[`CoinGecko`](CoinGecko.md) +`CoinGecko` The singleton instance of CoinGecko. @@ -191,7 +191,3 @@ The singleton instance of CoinGecko. ```typescript const coinGecko = CoinGecko.getInstance(); ``` - -#### Defined in - -[src/services/providers/coinGecko.ts:104](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/providers/coinGecko.ts#L104) diff --git a/docs/src/services/providers/cryptoCompare/README.md b/docs/src/services/providers/cryptoCompare/README.md index 931e9ee..a974ec4 100644 --- a/docs/src/services/providers/cryptoCompare/README.md +++ b/docs/src/services/providers/cryptoCompare/README.md @@ -1,13 +1,11 @@ -[**rates-api v3.0.0**](../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../README.md) *** -[rates-api v3.0.0](../../../../modules.md) / src/services/providers/cryptoCompare +[rates-api](../../../../modules.md) / src/services/providers/cryptoCompare # src/services/providers/cryptoCompare -## Index - -### Classes +## Classes - [CryptoCompare](classes/CryptoCompare.md) diff --git a/docs/src/services/providers/cryptoCompare/classes/CryptoCompare.md b/docs/src/services/providers/cryptoCompare/classes/CryptoCompare.md index 7d95fb6..94cd451 100644 --- a/docs/src/services/providers/cryptoCompare/classes/CryptoCompare.md +++ b/docs/src/services/providers/cryptoCompare/classes/CryptoCompare.md @@ -1,11 +1,13 @@ -[**rates-api v3.0.0**](../../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../../README.md) *** -[rates-api v3.0.0](../../../../../modules.md) / [src/services/providers/cryptoCompare](../README.md) / CryptoCompare +[rates-api](../../../../../modules.md) / [src/services/providers/cryptoCompare](../README.md) / CryptoCompare # Class: CryptoCompare +Defined in: src/services/providers/cryptoCompare.ts:28 + Singleton class to interact with the CryptoCompare API. This class provides methods to retrieve cryptocurrency exchange rates and market data from CryptoCompare. @@ -27,9 +29,11 @@ fetchExchangeRates(); ## Constructors -### new CryptoCompare() +### Constructor + +> **new CryptoCompare**(): `CryptoCompare` -> **new CryptoCompare**(): [`CryptoCompare`](CryptoCompare.md) +Defined in: src/services/providers/cryptoCompare.ts:69 Private constructor to enforce the singleton pattern. @@ -37,21 +41,19 @@ Initializes the AxiosWrapper and the LRU cache. #### Returns -[`CryptoCompare`](CryptoCompare.md) +`CryptoCompare` #### Throws If an instance already exists. -#### Defined in - -[src/services/providers/cryptoCompare.ts:69](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/providers/cryptoCompare.ts#L69) - ## Methods ### getExchangeRates() -> **getExchangeRates**(`ids`, `vsCurrency`): `Promise`\<[`CryptoComparePrice`](../../../../types/type-aliases/CryptoComparePrice.md)\> +> **getExchangeRates**(`ids`, `vsCurrency?`): `Promise`\<[`CryptoComparePrice`](../../../../types/type-aliases/CryptoComparePrice.md)\> + +Defined in: src/services/providers/cryptoCompare.ts:163 Retrieves exchange rates for an array of cryptocurrency symbols. @@ -59,11 +61,15 @@ Handles splitting the symbols into batches to comply with API limitations. #### Parameters -• **ids**: `string`[] +##### ids + +`string`[] An array of cryptocurrency symbols (e.g., ['BTC', 'ETH']). -• **vsCurrency**: `string` = `'BTC'` +##### vsCurrency? + +`string` = `'BTC'` The target currency symbol (default is 'BTC'). @@ -81,15 +87,13 @@ const rates = await cryptoCompare.getExchangeRates(['BTC', 'ETH'], 'USD'); console.log('Exchange Rates:', rates); ``` -#### Defined in - -[src/services/providers/cryptoCompare.ts:163](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/providers/cryptoCompare.ts#L163) - *** ### getMarketData() -> **getMarketData**(`ids`, `vsCurrency`): `Promise`\<[`CryptoCompareMarkets`](../../../../types/type-aliases/CryptoCompareMarkets.md)\> +> **getMarketData**(`ids`, `vsCurrency?`): `Promise`\<[`CryptoCompareMarkets`](../../../../types/type-aliases/CryptoCompareMarkets.md)\> + +Defined in: src/services/providers/cryptoCompare.ts:226 Retrieves market data for an array of cryptocurrency symbols. @@ -97,11 +101,15 @@ Handles splitting the symbols into batches to comply with API limitations. #### Parameters -• **ids**: `string`[] +##### ids + +`string`[] An array of cryptocurrency symbols (e.g., ['BTC', 'ETH']). -• **vsCurrency**: `string` = `'BTC'` +##### vsCurrency? + +`string` = `'BTC'` The target currency symbol (default is 'BTC'). @@ -119,21 +127,19 @@ const marketData = await cryptoCompare.getMarketData(['BTC', 'ETH'], 'USD'); console.log('Market Data:', marketData); ``` -#### Defined in - -[src/services/providers/cryptoCompare.ts:226](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/providers/cryptoCompare.ts#L226) - *** ### getInstance() -> `static` **getInstance**(): [`CryptoCompare`](CryptoCompare.md) +> `static` **getInstance**(): `CryptoCompare` + +Defined in: src/services/providers/cryptoCompare.ts:92 Returns the singleton instance of the CryptoCompare class. #### Returns -[`CryptoCompare`](CryptoCompare.md) +`CryptoCompare` The singleton instance of CryptoCompare. @@ -142,7 +148,3 @@ The singleton instance of CryptoCompare. ```typescript const cryptoCompare = CryptoCompare.getInstance(); ``` - -#### Defined in - -[src/services/providers/cryptoCompare.ts:92](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/providers/cryptoCompare.ts#L92) diff --git a/docs/src/services/providers/liveCoinWatch/README.md b/docs/src/services/providers/liveCoinWatch/README.md index 1aebaf3..1b09805 100644 --- a/docs/src/services/providers/liveCoinWatch/README.md +++ b/docs/src/services/providers/liveCoinWatch/README.md @@ -1,13 +1,11 @@ -[**rates-api v3.0.0**](../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../README.md) *** -[rates-api v3.0.0](../../../../modules.md) / src/services/providers/liveCoinWatch +[rates-api](../../../../modules.md) / src/services/providers/liveCoinWatch # src/services/providers/liveCoinWatch -## Index - -### Classes +## Classes - [LiveCoinWatch](classes/LiveCoinWatch.md) diff --git a/docs/src/services/providers/liveCoinWatch/classes/LiveCoinWatch.md b/docs/src/services/providers/liveCoinWatch/classes/LiveCoinWatch.md index aab64b4..c789730 100644 --- a/docs/src/services/providers/liveCoinWatch/classes/LiveCoinWatch.md +++ b/docs/src/services/providers/liveCoinWatch/classes/LiveCoinWatch.md @@ -1,11 +1,13 @@ -[**rates-api v3.0.0**](../../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../../README.md) *** -[rates-api v3.0.0](../../../../../modules.md) / [src/services/providers/liveCoinWatch](../README.md) / LiveCoinWatch +[rates-api](../../../../../modules.md) / [src/services/providers/liveCoinWatch](../README.md) / LiveCoinWatch # Class: LiveCoinWatch +Defined in: src/services/providers/liveCoinWatch.ts:28 + Singleton class to interact with the LiveCoinWatch API. This class provides methods to retrieve cryptocurrency exchange rates from LiveCoinWatch. @@ -27,9 +29,11 @@ fetchExchangeRates(); ## Constructors -### new LiveCoinWatch() +### Constructor + +> **new LiveCoinWatch**(): `LiveCoinWatch` -> **new LiveCoinWatch**(): [`LiveCoinWatch`](LiveCoinWatch.md) +Defined in: src/services/providers/liveCoinWatch.ts:69 Private constructor to enforce the singleton pattern. @@ -37,21 +41,19 @@ Initializes the AxiosWrapper and the LRU cache. #### Returns -[`LiveCoinWatch`](LiveCoinWatch.md) +`LiveCoinWatch` #### Throws If an instance already exists. -#### Defined in - -[src/services/providers/liveCoinWatch.ts:69](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/providers/liveCoinWatch.ts#L69) - ## Methods ### getExchangeRates() -> **getExchangeRates**(`ids`, `vsCurrency`): `Promise`\<[`LiveCoinWatchMarket`](../../../../types/type-aliases/LiveCoinWatchMarket.md)[]\> +> **getExchangeRates**(`ids`, `vsCurrency?`): `Promise`\<[`LiveCoinWatchMarket`](../../../../types/type-aliases/LiveCoinWatchMarket.md)[]\> + +Defined in: src/services/providers/liveCoinWatch.ts:164 Retrieves exchange rates for an array of cryptocurrency symbols. @@ -59,11 +61,15 @@ Handles splitting the symbols into batches to comply with API limitations. #### Parameters -• **ids**: `string`[] +##### ids + +`string`[] An array of cryptocurrency symbols (e.g., ['BTC', 'ETH']). -• **vsCurrency**: `string` = `'BTC'` +##### vsCurrency? + +`string` = `'BTC'` The target currency symbol (default is 'BTC'). @@ -81,21 +87,19 @@ const rates = await liveCoinWatch.getExchangeRates(['BTC', 'ETH'], 'USD'); console.log('Exchange Rates:', rates); ``` -#### Defined in - -[src/services/providers/liveCoinWatch.ts:164](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/providers/liveCoinWatch.ts#L164) - *** ### getInstance() -> `static` **getInstance**(): [`LiveCoinWatch`](LiveCoinWatch.md) +> `static` **getInstance**(): `LiveCoinWatch` + +Defined in: src/services/providers/liveCoinWatch.ts:92 Returns the singleton instance of the LiveCoinWatch class. #### Returns -[`LiveCoinWatch`](LiveCoinWatch.md) +`LiveCoinWatch` The singleton instance of LiveCoinWatch. @@ -104,7 +108,3 @@ The singleton instance of LiveCoinWatch. ```typescript const liveCoinWatch = LiveCoinWatch.getInstance(); ``` - -#### Defined in - -[src/services/providers/liveCoinWatch.ts:92](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/providers/liveCoinWatch.ts#L92) diff --git a/docs/src/services/zelcoreMarketsUSD/README.md b/docs/src/services/zelcoreMarketsUSD/README.md index b789833..505013b 100644 --- a/docs/src/services/zelcoreMarketsUSD/README.md +++ b/docs/src/services/zelcoreMarketsUSD/README.md @@ -1,17 +1,15 @@ -[**rates-api v3.0.0**](../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../README.md) *** -[rates-api v3.0.0](../../../modules.md) / src/services/zelcoreMarketsUSD +[rates-api](../../../modules.md) / src/services/zelcoreMarketsUSD # src/services/zelcoreMarketsUSD -## Index - -### Variables +## Variables - [default](variables/default.md) -### Functions +## Functions - [getAll](functions/getAll.md) diff --git a/docs/src/services/zelcoreMarketsUSD/functions/getAll.md b/docs/src/services/zelcoreMarketsUSD/functions/getAll.md index f207b35..e0de134 100644 --- a/docs/src/services/zelcoreMarketsUSD/functions/getAll.md +++ b/docs/src/services/zelcoreMarketsUSD/functions/getAll.md @@ -1,13 +1,15 @@ -[**rates-api v3.0.0**](../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../README.md) *** -[rates-api v3.0.0](../../../../modules.md) / [src/services/zelcoreMarketsUSD](../README.md) / getAll +[rates-api](../../../../modules.md) / [src/services/zelcoreMarketsUSD](../README.md) / getAll # Function: getAll() > **getAll**(): `Promise`\<[`MarketsData`](../../../types/type-aliases/MarketsData.md)\> +Defined in: src/services/zelcoreMarketsUSD.ts:21 + Fetches market data from multiple providers and aggregates it. This function retrieves market data from CryptoCompare, CoinGecko, and LiveCoinWatch, @@ -27,7 +29,3 @@ The aggregated market data. const marketData = await getAll(); console.log(marketData); ``` - -## Defined in - -[src/services/zelcoreMarketsUSD.ts:21](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/zelcoreMarketsUSD.ts#L21) diff --git a/docs/src/services/zelcoreMarketsUSD/variables/default.md b/docs/src/services/zelcoreMarketsUSD/variables/default.md index ed8ccd0..2da8843 100644 --- a/docs/src/services/zelcoreMarketsUSD/variables/default.md +++ b/docs/src/services/zelcoreMarketsUSD/variables/default.md @@ -1,16 +1,18 @@ -[**rates-api v3.0.0**](../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../README.md) *** -[rates-api v3.0.0](../../../../modules.md) / [src/services/zelcoreMarketsUSD](../README.md) / default +[rates-api](../../../../modules.md) / [src/services/zelcoreMarketsUSD](../README.md) / default # Variable: default > **default**: `object` -## Type declaration +Defined in: src/services/zelcoreMarketsUSD.ts:139 -### getAll() +## Type Declaration + +### getAll > **getAll**: () => `Promise`\<[`MarketsData`](../../../types/type-aliases/MarketsData.md)\> @@ -33,7 +35,3 @@ The aggregated market data. const marketData = await getAll(); console.log(marketData); ``` - -## Defined in - -[src/services/zelcoreMarketsUSD.ts:137](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/zelcoreMarketsUSD.ts#L137) diff --git a/docs/src/services/zelcoreRates/README.md b/docs/src/services/zelcoreRates/README.md index 6533591..7a4815f 100644 --- a/docs/src/services/zelcoreRates/README.md +++ b/docs/src/services/zelcoreRates/README.md @@ -1,17 +1,15 @@ -[**rates-api v3.0.0**](../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../README.md) *** -[rates-api v3.0.0](../../../modules.md) / src/services/zelcoreRates +[rates-api](../../../modules.md) / src/services/zelcoreRates # src/services/zelcoreRates -## Index - -### Variables +## Variables - [default](variables/default.md) -### Functions +## Functions - [getAll](functions/getAll.md) diff --git a/docs/src/services/zelcoreRates/functions/getAll.md b/docs/src/services/zelcoreRates/functions/getAll.md index ff1d835..62a8134 100644 --- a/docs/src/services/zelcoreRates/functions/getAll.md +++ b/docs/src/services/zelcoreRates/functions/getAll.md @@ -1,13 +1,15 @@ -[**rates-api v3.0.0**](../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../README.md) *** -[rates-api v3.0.0](../../../../modules.md) / [src/services/zelcoreRates](../README.md) / getAll +[rates-api](../../../../modules.md) / [src/services/zelcoreRates](../README.md) / getAll # Function: getAll() > **getAll**(): `Promise`\<[`RatesData`](../../../types/type-aliases/RatesData.md)\> +Defined in: src/services/zelcoreRates.ts:34 + Fetches exchange rates and price data from various providers and aggregates them. This function retrieves fiat rates from BitPay and cryptocurrency prices from CoinGecko, @@ -34,7 +36,3 @@ async function fetchRates() { fetchRates(); ``` - -## Defined in - -[src/services/zelcoreRates.ts:34](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/zelcoreRates.ts#L34) diff --git a/docs/src/services/zelcoreRates/variables/default.md b/docs/src/services/zelcoreRates/variables/default.md index d9727e3..6bc0e12 100644 --- a/docs/src/services/zelcoreRates/variables/default.md +++ b/docs/src/services/zelcoreRates/variables/default.md @@ -1,16 +1,18 @@ -[**rates-api v3.0.0**](../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../README.md) *** -[rates-api v3.0.0](../../../../modules.md) / [src/services/zelcoreRates](../README.md) / default +[rates-api](../../../../modules.md) / [src/services/zelcoreRates](../README.md) / default # Variable: default > **default**: `object` -## Type declaration +Defined in: src/services/zelcoreRates.ts:153 -### getAll() +## Type Declaration + +### getAll > **getAll**: () => `Promise`\<[`RatesData`](../../../types/type-aliases/RatesData.md)\> @@ -40,7 +42,3 @@ async function fetchRates() { fetchRates(); ``` - -## Defined in - -[src/services/zelcoreRates.ts:151](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/zelcoreRates.ts#L151) diff --git a/docs/src/services/zelcoreRatesV2/README.md b/docs/src/services/zelcoreRatesV2/README.md index 565f0d4..1e19fa6 100644 --- a/docs/src/services/zelcoreRatesV2/README.md +++ b/docs/src/services/zelcoreRatesV2/README.md @@ -1,17 +1,15 @@ -[**rates-api v3.0.0**](../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../README.md) *** -[rates-api v3.0.0](../../../modules.md) / src/services/zelcoreRatesV2 +[rates-api](../../../modules.md) / src/services/zelcoreRatesV2 # src/services/zelcoreRatesV2 -## Index - -### Variables +## Variables - [default](variables/default.md) -### Functions +## Functions - [getAll](functions/getAll.md) diff --git a/docs/src/services/zelcoreRatesV2/functions/getAll.md b/docs/src/services/zelcoreRatesV2/functions/getAll.md index 6cb03da..1db60b4 100644 --- a/docs/src/services/zelcoreRatesV2/functions/getAll.md +++ b/docs/src/services/zelcoreRatesV2/functions/getAll.md @@ -1,13 +1,15 @@ -[**rates-api v3.0.0**](../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../README.md) *** -[rates-api v3.0.0](../../../../modules.md) / [src/services/zelcoreRatesV2](../README.md) / getAll +[rates-api](../../../../modules.md) / [src/services/zelcoreRatesV2](../README.md) / getAll # Function: getAll() > **getAll**(): `Promise`\<[`PricesResponse`](../../../types/type-aliases/PricesResponse.md)\> +Defined in: src/services/zelcoreRatesV2.ts:29 + Fetches and aggregates cryptocurrency prices and fiat rates from multiple providers. This function retrieves fiat rates from BitPay and cryptocurrency prices from CoinGecko, @@ -34,7 +36,3 @@ async function fetchPrices() { fetchPrices(); ``` - -## Defined in - -[src/services/zelcoreRatesV2.ts:28](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/zelcoreRatesV2.ts#L28) diff --git a/docs/src/services/zelcoreRatesV2/variables/default.md b/docs/src/services/zelcoreRatesV2/variables/default.md index 9c9a173..d1d9804 100644 --- a/docs/src/services/zelcoreRatesV2/variables/default.md +++ b/docs/src/services/zelcoreRatesV2/variables/default.md @@ -1,16 +1,18 @@ -[**rates-api v3.0.0**](../../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../../README.md) *** -[rates-api v3.0.0](../../../../modules.md) / [src/services/zelcoreRatesV2](../README.md) / default +[rates-api](../../../../modules.md) / [src/services/zelcoreRatesV2](../README.md) / default # Variable: default > **default**: `object` -## Type declaration +Defined in: src/services/zelcoreRatesV2.ts:153 -### getAll() +## Type Declaration + +### getAll > **getAll**: () => `Promise`\<[`PricesResponse`](../../../types/type-aliases/PricesResponse.md)\> @@ -40,7 +42,3 @@ async function fetchPrices() { fetchPrices(); ``` - -## Defined in - -[src/services/zelcoreRatesV2.ts:142](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/services/zelcoreRatesV2.ts#L142) diff --git a/docs/src/types/README.md b/docs/src/types/README.md index ab08f68..f64ef33 100644 --- a/docs/src/types/README.md +++ b/docs/src/types/README.md @@ -1,21 +1,21 @@ -[**rates-api v3.0.0**](../../README.md) • **Docs** +[**rates-api v3.0.0**](../../README.md) *** -[rates-api v3.0.0](../../modules.md) / src/types +[rates-api](../../modules.md) / src/types # src/types -## Index - -### Interfaces +## Interfaces - [ICurrencyData](interfaces/ICurrencyData.md) - [ICurrencyRate](interfaces/ICurrencyRate.md) - [IErrorObject](interfaces/IErrorObject.md) -### Type Aliases +## Type Aliases +- [BinanceTicker](type-aliases/BinanceTicker.md) +- [BinanceTokenisedAsset](type-aliases/BinanceTokenisedAsset.md) - [CodeRates](type-aliases/CodeRates.md) - [CoinGeckoPrice](type-aliases/CoinGeckoPrice.md) - [CoinGeckoToken](type-aliases/CoinGeckoToken.md) diff --git a/docs/src/types/interfaces/ICurrencyData.md b/docs/src/types/interfaces/ICurrencyData.md index 7b7ab2f..2bd14b4 100644 --- a/docs/src/types/interfaces/ICurrencyData.md +++ b/docs/src/types/interfaces/ICurrencyData.md @@ -1,30 +1,28 @@ -[**rates-api v3.0.0**](../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../README.md) *** -[rates-api v3.0.0](../../../modules.md) / [src/types](../README.md) / ICurrencyData +[rates-api](../../../modules.md) / [src/types](../README.md) / ICurrencyData # Interface: ICurrencyData +Defined in: src/types.ts:81 + ## Properties ### change > **change**: `number` -#### Defined in - -[src/types.ts:84](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/types.ts#L84) +Defined in: src/types.ts:84 *** ### change7d? -> `optional` **change7d**: `number` +> `optional` **change7d?**: `number` -#### Defined in - -[src/types.ts:88](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/types.ts#L88) +Defined in: src/types.ts:88 *** @@ -32,19 +30,15 @@ > **market**: `number` -#### Defined in - -[src/types.ts:85](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/types.ts#L85) +Defined in: src/types.ts:85 *** ### rank? -> `optional` **rank**: `number` +> `optional` **rank?**: `number` -#### Defined in - -[src/types.ts:86](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/types.ts#L86) +Defined in: src/types.ts:86 *** @@ -52,19 +46,15 @@ > **supply**: `number` -#### Defined in - -[src/types.ts:82](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/types.ts#L82) +Defined in: src/types.ts:82 *** ### total\_supply? -> `optional` **total\_supply**: `number` +> `optional` **total\_supply?**: `number` -#### Defined in - -[src/types.ts:87](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/types.ts#L87) +Defined in: src/types.ts:87 *** @@ -72,6 +62,4 @@ > **volume**: `number` -#### Defined in - -[src/types.ts:83](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/types.ts#L83) +Defined in: src/types.ts:83 diff --git a/docs/src/types/interfaces/ICurrencyRate.md b/docs/src/types/interfaces/ICurrencyRate.md index 896019a..7be564b 100644 --- a/docs/src/types/interfaces/ICurrencyRate.md +++ b/docs/src/types/interfaces/ICurrencyRate.md @@ -1,20 +1,20 @@ -[**rates-api v3.0.0**](../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../README.md) *** -[rates-api v3.0.0](../../../modules.md) / [src/types](../README.md) / ICurrencyRate +[rates-api](../../../modules.md) / [src/types](../README.md) / ICurrencyRate # Interface: ICurrencyRate +Defined in: src/types.ts:67 + ## Properties ### code > **code**: `string` -#### Defined in - -[src/types.ts:68](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/types.ts#L68) +Defined in: src/types.ts:68 *** @@ -22,9 +22,7 @@ > **name**: `string` -#### Defined in - -[src/types.ts:69](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/types.ts#L69) +Defined in: src/types.ts:69 *** @@ -32,6 +30,4 @@ > **rate**: `number` -#### Defined in - -[src/types.ts:70](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/types.ts#L70) +Defined in: src/types.ts:70 diff --git a/docs/src/types/interfaces/IErrorObject.md b/docs/src/types/interfaces/IErrorObject.md index b4137ea..93c6346 100644 --- a/docs/src/types/interfaces/IErrorObject.md +++ b/docs/src/types/interfaces/IErrorObject.md @@ -1,21 +1,21 @@ -[**rates-api v3.0.0**](../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../README.md) *** -[rates-api v3.0.0](../../../modules.md) / [src/types](../README.md) / IErrorObject +[rates-api](../../../modules.md) / [src/types](../README.md) / IErrorObject # Interface: IErrorObject +Defined in: src/types.ts:75 + ## Properties ### errors > **errors**: `object` -#### Index Signature - - \[`key`: `string`\]: `any` +Defined in: src/types.ts:76 -#### Defined in +#### Index Signature -[src/types.ts:76](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/types.ts#L76) +\[`key`: `string`\]: `any` diff --git a/docs/src/types/type-aliases/BinanceTicker.md b/docs/src/types/type-aliases/BinanceTicker.md new file mode 100644 index 0000000..0d0dca1 --- /dev/null +++ b/docs/src/types/type-aliases/BinanceTicker.md @@ -0,0 +1,43 @@ +[**rates-api v3.0.0**](../../../README.md) + +*** + +[rates-api](../../../modules.md) / [src/types](../README.md) / BinanceTicker + +# Type Alias: BinanceTicker + +> **BinanceTicker** = `object` + +Defined in: src/types.ts:137 + +## Properties + +### lastPrice + +> **lastPrice**: `string` + +Defined in: src/types.ts:139 + +*** + +### priceChangePercent + +> **priceChangePercent**: `string` + +Defined in: src/types.ts:140 + +*** + +### quoteVolume + +> **quoteVolume**: `string` + +Defined in: src/types.ts:141 + +*** + +### symbol + +> **symbol**: `string` + +Defined in: src/types.ts:138 diff --git a/docs/src/types/type-aliases/BinanceTokenisedAsset.md b/docs/src/types/type-aliases/BinanceTokenisedAsset.md new file mode 100644 index 0000000..7938f80 --- /dev/null +++ b/docs/src/types/type-aliases/BinanceTokenisedAsset.md @@ -0,0 +1,59 @@ +[**rates-api v3.0.0**](../../../README.md) + +*** + +[rates-api](../../../modules.md) / [src/types](../README.md) / BinanceTokenisedAsset + +# Type Alias: BinanceTokenisedAsset + +> **BinanceTokenisedAsset** = `object` + +Defined in: src/types.ts:129 + +## Properties + +### assetCode + +> **assetCode**: `string` + +Defined in: src/types.ts:130 + +*** + +### assetName + +> **assetName**: `string` + +Defined in: src/types.ts:131 + +*** + +### caList? + +> `optional` **caList?**: `object`[] + +Defined in: src/types.ts:134 + +#### ca + +> **ca**: `string` + +#### network + +> **network**: `string` + +*** + +### logo? + +> `optional` **logo?**: `string` + +Defined in: src/types.ts:133 + +*** + +### uq? + +> `optional` **uq?**: `string` + +Defined in: src/types.ts:132 diff --git a/docs/src/types/type-aliases/CodeRates.md b/docs/src/types/type-aliases/CodeRates.md index d14e504..bf9c8fc 100644 --- a/docs/src/types/type-aliases/CodeRates.md +++ b/docs/src/types/type-aliases/CodeRates.md @@ -1,17 +1,15 @@ -[**rates-api v3.0.0**](../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../README.md) *** -[rates-api v3.0.0](../../../modules.md) / [src/types](../README.md) / CodeRates +[rates-api](../../../modules.md) / [src/types](../README.md) / CodeRates # Type Alias: CodeRates -> **CodeRates**: `object` +> **CodeRates** = `object` -## Index Signature - - \[`code`: `string`\]: `number` \| `null` +Defined in: src/types.ts:73 -## Defined in +## Index Signature -[src/types.ts:73](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/types.ts#L73) +\[`code`: `string`\]: `number` \| `null` diff --git a/docs/src/types/type-aliases/CoinGeckoPrice.md b/docs/src/types/type-aliases/CoinGeckoPrice.md index f20e5e5..9ff603e 100644 --- a/docs/src/types/type-aliases/CoinGeckoPrice.md +++ b/docs/src/types/type-aliases/CoinGeckoPrice.md @@ -1,123 +1,227 @@ -[**rates-api v3.0.0**](../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../README.md) *** -[rates-api v3.0.0](../../../modules.md) / [src/types](../README.md) / CoinGeckoPrice +[rates-api](../../../modules.md) / [src/types](../README.md) / CoinGeckoPrice # Type Alias: CoinGeckoPrice -> **CoinGeckoPrice**: `object` +> **CoinGeckoPrice** = `object` -## Type declaration +Defined in: src/types.ts:95 + +## Properties ### ath > **ath**: `number` +Defined in: src/types.ts:114 + +*** + ### ath\_change\_percentage > **ath\_change\_percentage**: `number` +Defined in: src/types.ts:115 + +*** + ### ath\_date > **ath\_date**: `string` +Defined in: src/types.ts:116 + +*** + ### atl > **atl**: `number` +Defined in: src/types.ts:117 + +*** + ### atl\_change\_percentage > **atl\_change\_percentage**: `number` +Defined in: src/types.ts:118 + +*** + ### atl\_date > **atl\_date**: `string` +Defined in: src/types.ts:119 + +*** + ### circulating\_supply > **circulating\_supply**: `number` +Defined in: src/types.ts:111 + +*** + ### current\_price > **current\_price**: `number` +Defined in: src/types.ts:100 + +*** + ### fully\_diluted\_valuation > **fully\_diluted\_valuation**: `number` +Defined in: src/types.ts:103 + +*** + ### high\_24h > **high\_24h**: `number` +Defined in: src/types.ts:105 + +*** + ### id > **id**: `string` +Defined in: src/types.ts:96 + +*** + ### image > **image**: `string` +Defined in: src/types.ts:99 + +*** + ### last\_updated > **last\_updated**: `string` +Defined in: src/types.ts:125 + +*** + ### low\_24h > **low\_24h**: `number` +Defined in: src/types.ts:106 + +*** + ### market\_cap > **market\_cap**: `number` +Defined in: src/types.ts:101 + +*** + ### market\_cap\_change\_24h > **market\_cap\_change\_24h**: `number` +Defined in: src/types.ts:109 + +*** + ### market\_cap\_change\_percentage\_24h > **market\_cap\_change\_percentage\_24h**: `number` +Defined in: src/types.ts:110 + +*** + ### market\_cap\_rank > **market\_cap\_rank**: `number` +Defined in: src/types.ts:102 + +*** + ### max\_supply > **max\_supply**: `number` +Defined in: src/types.ts:113 + +*** + ### name > **name**: `string` +Defined in: src/types.ts:98 + +*** + ### price\_change\_24h > **price\_change\_24h**: `number` +Defined in: src/types.ts:107 + +*** + ### price\_change\_percentage\_24h > **price\_change\_percentage\_24h**: `number` +Defined in: src/types.ts:108 + +*** + ### price\_change\_percentage\_7d\_in\_currency > **price\_change\_percentage\_7d\_in\_currency**: `number` +Defined in: src/types.ts:126 + +*** + ### roi -> **roi**: `null` \| `object` +> **roi**: `null` \| \{ `currency`: `string`; `percentage`: `number`; `times`: `number`; \} + +Defined in: src/types.ts:120 + +*** ### symbol > **symbol**: `string` +Defined in: src/types.ts:97 + +*** + ### total\_supply > **total\_supply**: `number` +Defined in: src/types.ts:112 + +*** + ### total\_volume > **total\_volume**: `number` -## Defined in - -[src/types.ts:95](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/types.ts#L95) +Defined in: src/types.ts:104 diff --git a/docs/src/types/type-aliases/CoinGeckoToken.md b/docs/src/types/type-aliases/CoinGeckoToken.md index 1d419c8..29dcd3b 100644 --- a/docs/src/types/type-aliases/CoinGeckoToken.md +++ b/docs/src/types/type-aliases/CoinGeckoToken.md @@ -1,31 +1,43 @@ -[**rates-api v3.0.0**](../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../README.md) *** -[rates-api v3.0.0](../../../modules.md) / [src/types](../README.md) / CoinGeckoToken +[rates-api](../../../modules.md) / [src/types](../README.md) / CoinGeckoToken # Type Alias: CoinGeckoToken -> **CoinGeckoToken**: `object` +> **CoinGeckoToken** = `object` -## Type declaration +Defined in: src/types.ts:58 + +## Properties ### id > **id**: `string` +Defined in: src/types.ts:59 + +*** + ### name > **name**: `string` +Defined in: src/types.ts:61 + +*** + ### platforms > **platforms**: `Record`\<`string`, `string`\> +Defined in: src/types.ts:62 + +*** + ### symbol > **symbol**: `string` -## Defined in - -[src/types.ts:58](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/types.ts#L58) +Defined in: src/types.ts:60 diff --git a/docs/src/types/type-aliases/CoinInfo.md b/docs/src/types/type-aliases/CoinInfo.md index 01525f4..0cfdcee 100644 --- a/docs/src/types/type-aliases/CoinInfo.md +++ b/docs/src/types/type-aliases/CoinInfo.md @@ -1,107 +1,195 @@ -[**rates-api v3.0.0**](../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../README.md) *** -[rates-api v3.0.0](../../../modules.md) / [src/types](../README.md) / CoinInfo +[rates-api](../../../modules.md) / [src/types](../README.md) / CoinInfo # Type Alias: CoinInfo -> **CoinInfo**: `object` +> **CoinInfo** = `object` -## Type declaration +Defined in: src/types.ts:32 + +## Properties ### auditInfos > **auditInfos**: `string`[] +Defined in: src/types.ts:54 + +*** + ### bitcointalk > **bitcointalk**: `string` +Defined in: src/types.ts:41 + +*** + ### circulating\_supply > **circulating\_supply**: `number` \| `null` +Defined in: src/types.ts:35 + +*** + ### coingeckoID > **coingeckoID**: `string` +Defined in: src/types.ts:53 + +*** + ### coinMarketCapID > **coinMarketCapID**: `string` +Defined in: src/types.ts:52 + +*** + ### cryptoCompareID > **cryptoCompareID**: `string` +Defined in: src/types.ts:51 + +*** + ### description > **description**: `string` +Defined in: src/types.ts:33 + +*** + ### discord > **discord**: `string` +Defined in: src/types.ts:39 + +*** + ### explorers > **explorers**: `string`[] +Defined in: src/types.ts:37 + +*** + ### facebook > **facebook**: `string` +Defined in: src/types.ts:42 + +*** + ### instagram > **instagram**: `string` +Defined in: src/types.ts:47 + +*** + ### linkedin > **linkedin**: `string` +Defined in: src/types.ts:50 + +*** + ### medium > **medium**: `string` +Defined in: src/types.ts:38 + +*** + ### reddit > **reddit**: `string` +Defined in: src/types.ts:44 + +*** + ### repository > **repository**: `string` +Defined in: src/types.ts:45 + +*** + ### telegram > **telegram**: `string` +Defined in: src/types.ts:40 + +*** + ### tiktok > **tiktok**: `string` +Defined in: src/types.ts:48 + +*** + ### total\_supply > **total\_supply**: `number` \| `null` +Defined in: src/types.ts:34 + +*** + ### twitch > **twitch**: `string` +Defined in: src/types.ts:49 + +*** + ### twitter > **twitter**: `string` +Defined in: src/types.ts:43 + +*** + ### websites > **websites**: `string`[] +Defined in: src/types.ts:36 + +*** + ### whitepaper > **whitepaper**: `string`[] +Defined in: src/types.ts:55 + +*** + ### youtube > **youtube**: `string` -## Defined in - -[src/types.ts:32](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/types.ts#L32) +Defined in: src/types.ts:46 diff --git a/docs/src/types/type-aliases/ContractWithType.md b/docs/src/types/type-aliases/ContractWithType.md index 8683a4f..dc0faa1 100644 --- a/docs/src/types/type-aliases/ContractWithType.md +++ b/docs/src/types/type-aliases/ContractWithType.md @@ -1,23 +1,27 @@ -[**rates-api v3.0.0**](../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../README.md) *** -[rates-api v3.0.0](../../../modules.md) / [src/types](../README.md) / ContractWithType +[rates-api](../../../modules.md) / [src/types](../README.md) / ContractWithType # Type Alias: ContractWithType -> **ContractWithType**: `object` +> **ContractWithType** = `object` -## Type declaration +Defined in: src/types.ts:27 + +## Properties ### address > **address**: `string` +Defined in: src/types.ts:28 + +*** + ### type > **type**: `string` -## Defined in - -[src/types.ts:27](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/types.ts#L27) +Defined in: src/types.ts:29 diff --git a/docs/src/types/type-aliases/CryptoCompareMarkets.md b/docs/src/types/type-aliases/CryptoCompareMarkets.md index 64e3682..5f16e63 100644 --- a/docs/src/types/type-aliases/CryptoCompareMarkets.md +++ b/docs/src/types/type-aliases/CryptoCompareMarkets.md @@ -1,17 +1,15 @@ -[**rates-api v3.0.0**](../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../README.md) *** -[rates-api v3.0.0](../../../modules.md) / [src/types](../README.md) / CryptoCompareMarkets +[rates-api](../../../modules.md) / [src/types](../README.md) / CryptoCompareMarkets # Type Alias: CryptoCompareMarkets -> **CryptoCompareMarkets**: `object` +> **CryptoCompareMarkets** = `object` -## Index Signature - - \[`key`: `string`\]: `object` +Defined in: src/types.ts:149 -## Defined in +## Index Signature -[src/types.ts:134](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/types.ts#L134) +\[`key`: `string`\]: `object` diff --git a/docs/src/types/type-aliases/CryptoComparePrice.md b/docs/src/types/type-aliases/CryptoComparePrice.md index c60d105..8df7423 100644 --- a/docs/src/types/type-aliases/CryptoComparePrice.md +++ b/docs/src/types/type-aliases/CryptoComparePrice.md @@ -1,17 +1,15 @@ -[**rates-api v3.0.0**](../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../README.md) *** -[rates-api v3.0.0](../../../modules.md) / [src/types](../README.md) / CryptoComparePrice +[rates-api](../../../modules.md) / [src/types](../README.md) / CryptoComparePrice # Type Alias: CryptoComparePrice -> **CryptoComparePrice**: `object` +> **CryptoComparePrice** = `object` -## Index Signature - - \[`key`: `string`\]: `object` +Defined in: src/types.ts:144 -## Defined in +## Index Signature -[src/types.ts:129](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/types.ts#L129) +\[`key`: `string`\]: `object` diff --git a/docs/src/types/type-aliases/CryptoPrice.md b/docs/src/types/type-aliases/CryptoPrice.md index 9726dbb..69c611e 100644 --- a/docs/src/types/type-aliases/CryptoPrice.md +++ b/docs/src/types/type-aliases/CryptoPrice.md @@ -1,55 +1,91 @@ -[**rates-api v3.0.0**](../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../README.md) *** -[rates-api v3.0.0](../../../modules.md) / [src/types](../README.md) / CryptoPrice +[rates-api](../../../modules.md) / [src/types](../README.md) / CryptoPrice # Type Alias: CryptoPrice -> **CryptoPrice**: `object` +> **CryptoPrice** = `object` -## Type declaration +Defined in: src/types.ts:1 + +## Properties ### change24h > **change24h**: `number` +Defined in: src/types.ts:7 + +*** + ### change7d? -> `optional` **change7d**: `number` +> `optional` **change7d?**: `number` + +Defined in: src/types.ts:11 + +*** ### id > **id**: `string` +Defined in: src/types.ts:2 + +*** + ### market > **market**: `number` +Defined in: src/types.ts:8 + +*** + ### provider > **provider**: `string` +Defined in: src/types.ts:3 + +*** + ### rank? -> `optional` **rank**: `number` +> `optional` **rank?**: `number` + +Defined in: src/types.ts:9 + +*** ### rates > **rates**: `Record`\<`string`, `number`\> +Defined in: src/types.ts:4 + +*** + ### supply > **supply**: `number` +Defined in: src/types.ts:5 + +*** + ### total\_supply? -> `optional` **total\_supply**: `number` +> `optional` **total\_supply?**: `number` + +Defined in: src/types.ts:10 + +*** ### volume > **volume**: `number` -## Defined in - -[src/types.ts:1](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/types.ts#L1) +Defined in: src/types.ts:6 diff --git a/docs/src/types/type-aliases/CurrencyMap.md b/docs/src/types/type-aliases/CurrencyMap.md index c34d182..3af8338 100644 --- a/docs/src/types/type-aliases/CurrencyMap.md +++ b/docs/src/types/type-aliases/CurrencyMap.md @@ -1,17 +1,15 @@ -[**rates-api v3.0.0**](../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../README.md) *** -[rates-api v3.0.0](../../../modules.md) / [src/types](../README.md) / CurrencyMap +[rates-api](../../../modules.md) / [src/types](../README.md) / CurrencyMap # Type Alias: CurrencyMap -> **CurrencyMap**: `object` +> **CurrencyMap** = `object` -## Index Signature - - \[`code`: `string`\]: [`ICurrencyData`](../interfaces/ICurrencyData.md) +Defined in: src/types.ts:91 -## Defined in +## Index Signature -[src/types.ts:91](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/types.ts#L91) +\[`code`: `string`\]: [`ICurrencyData`](../interfaces/ICurrencyData.md) diff --git a/docs/src/types/type-aliases/FiatPrice.md b/docs/src/types/type-aliases/FiatPrice.md index 488363f..1b719b8 100644 --- a/docs/src/types/type-aliases/FiatPrice.md +++ b/docs/src/types/type-aliases/FiatPrice.md @@ -1,31 +1,43 @@ -[**rates-api v3.0.0**](../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../README.md) *** -[rates-api v3.0.0](../../../modules.md) / [src/types](../README.md) / FiatPrice +[rates-api](../../../modules.md) / [src/types](../README.md) / FiatPrice # Type Alias: FiatPrice -> **FiatPrice**: `object` +> **FiatPrice** = `object` -## Type declaration +Defined in: src/types.ts:14 + +## Properties ### code > **code**: `string` +Defined in: src/types.ts:15 + +*** + ### name > **name**: `string` +Defined in: src/types.ts:16 + +*** + ### provider? -> `optional` **provider**: `string` +> `optional` **provider?**: `string` + +Defined in: src/types.ts:18 + +*** ### rate > **rate**: `number` -## Defined in - -[src/types.ts:14](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/types.ts#L14) +Defined in: src/types.ts:17 diff --git a/docs/src/types/type-aliases/FoundContractStore.md b/docs/src/types/type-aliases/FoundContractStore.md index f263a13..095cd45 100644 --- a/docs/src/types/type-aliases/FoundContractStore.md +++ b/docs/src/types/type-aliases/FoundContractStore.md @@ -1,13 +1,11 @@ -[**rates-api v3.0.0**](../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../README.md) *** -[rates-api v3.0.0](../../../modules.md) / [src/types](../README.md) / FoundContractStore +[rates-api](../../../modules.md) / [src/types](../README.md) / FoundContractStore # Type Alias: FoundContractStore -> **FoundContractStore**: `Record`\<`string`, `object`\> +> **FoundContractStore** = `Record`\<`string`, \{ `cg`: [`CoinGeckoToken`](CoinGeckoToken.md); `count`: `number`; `zel`: [`ContractWithType`](ContractWithType.md); \}\> -## Defined in - -[src/types.ts:65](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/types.ts#L65) +Defined in: src/types.ts:65 diff --git a/docs/src/types/type-aliases/LiveCoinWatchMarket.md b/docs/src/types/type-aliases/LiveCoinWatchMarket.md index 6e4a101..c916a15 100644 --- a/docs/src/types/type-aliases/LiveCoinWatchMarket.md +++ b/docs/src/types/type-aliases/LiveCoinWatchMarket.md @@ -1,191 +1,275 @@ -[**rates-api v3.0.0**](../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../README.md) *** -[rates-api v3.0.0](../../../modules.md) / [src/types](../README.md) / LiveCoinWatchMarket +[rates-api](../../../modules.md) / [src/types](../README.md) / LiveCoinWatchMarket # Type Alias: LiveCoinWatchMarket -> **LiveCoinWatchMarket**: `object` +> **LiveCoinWatchMarket** = `object` -## Type declaration +Defined in: src/types.ts:204 + +## Properties ### age > **age**: `number` +Defined in: src/types.ts:207 + +*** + ### allTimeHighUSD > **allTimeHighUSD**: `number` +Defined in: src/types.ts:217 + +*** + ### cap > **cap**: `number` \| `null` +Defined in: src/types.ts:242 + +*** + ### categories > **categories**: `string`[] +Defined in: src/types.ts:216 + +*** + ### circulatingSupply > **circulatingSupply**: `number` \| `null` +Defined in: src/types.ts:218 + +*** + ### code > **code**: `string` +Defined in: src/types.ts:239 + +*** + ### color > **color**: `string` +Defined in: src/types.ts:208 + +*** + ### delta > **delta**: `object` -### delta.day +Defined in: src/types.ts:243 + +#### day > **day**: `number` \| `null` -### delta.hour +#### hour > **hour**: `number` \| `null` -### delta.month +#### month > **month**: `number` \| `null` -### delta.quarter +#### quarter > **quarter**: `number` \| `null` -### delta.week +#### week > **week**: `number` \| `null` -### delta.year +#### year > **year**: `number` \| `null` +*** + ### exchanges > **exchanges**: `number` +Defined in: src/types.ts:213 + +*** + ### links > **links**: `object` -### links.discord +Defined in: src/types.ts:221 + +#### discord > **discord**: `string` \| `null` -### links.instagram +#### instagram > **instagram**: `string` \| `null` -### links.linkedin +#### linkedin > **linkedin**: `string` \| `null` -### links.medium +#### medium > **medium**: `string` \| `null` -### links.naver +#### naver > **naver**: `string` \| `null` -### links.reddit +#### reddit > **reddit**: `string` \| `null` -### links.soundcloud +#### soundcloud > **soundcloud**: `string` \| `null` -### links.spotify +#### spotify > **spotify**: `string` \| `null` -### links.telegram +#### telegram > **telegram**: `string` \| `null` -### links.tiktok +#### tiktok > **tiktok**: `string` \| `null` -### links.twitch +#### twitch > **twitch**: `string` \| `null` -### links.twitter +#### twitter > **twitter**: `string` \| `null` -### links.website +#### website > **website**: `string` \| `null` -### links.wechat +#### wechat > **wechat**: `string` \| `null` -### links.whitepaper +#### whitepaper > **whitepaper**: `string` \| `null` -### links.youtube +#### youtube > **youtube**: `string` \| `null` +*** + ### markets > **markets**: `number` +Defined in: src/types.ts:214 + +*** + ### maxSupply > **maxSupply**: `number` \| `null` +Defined in: src/types.ts:220 + +*** + ### name > **name**: `string` +Defined in: src/types.ts:205 + +*** + ### pairs > **pairs**: `number` +Defined in: src/types.ts:215 + +*** + ### png32 > **png32**: `string` +Defined in: src/types.ts:209 + +*** + ### png64 > **png64**: `string` +Defined in: src/types.ts:210 + +*** + ### rank > **rank**: `number` +Defined in: src/types.ts:206 + +*** + ### rate > **rate**: `number` \| `null` +Defined in: src/types.ts:240 + +*** + ### totalSupply > **totalSupply**: `number` +Defined in: src/types.ts:219 + +*** + ### volume > **volume**: `number` \| `null` +Defined in: src/types.ts:241 + +*** + ### webp32 > **webp32**: `string` +Defined in: src/types.ts:211 + +*** + ### webp64 > **webp64**: `string` -## Defined in - -[src/types.ts:189](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/types.ts#L189) +Defined in: src/types.ts:212 diff --git a/docs/src/types/type-aliases/MarketsData.md b/docs/src/types/type-aliases/MarketsData.md index 3f8e707..fefa979 100644 --- a/docs/src/types/type-aliases/MarketsData.md +++ b/docs/src/types/type-aliases/MarketsData.md @@ -1,13 +1,11 @@ -[**rates-api v3.0.0**](../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../README.md) *** -[rates-api v3.0.0](../../../modules.md) / [src/types](../README.md) / MarketsData +[rates-api](../../../modules.md) / [src/types](../README.md) / MarketsData # Type Alias: MarketsData -> **MarketsData**: [[`CurrencyMap`](CurrencyMap.md), [`IErrorObject`](../interfaces/IErrorObject.md)] +> **MarketsData** = \[[`CurrencyMap`](CurrencyMap.md), [`IErrorObject`](../interfaces/IErrorObject.md)\] -## Defined in - -[src/types.ts:93](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/types.ts#L93) +Defined in: src/types.ts:93 diff --git a/docs/src/types/type-aliases/PricesResponse.md b/docs/src/types/type-aliases/PricesResponse.md index a9835b2..692b204 100644 --- a/docs/src/types/type-aliases/PricesResponse.md +++ b/docs/src/types/type-aliases/PricesResponse.md @@ -1,27 +1,35 @@ -[**rates-api v3.0.0**](../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../README.md) *** -[rates-api v3.0.0](../../../modules.md) / [src/types](../README.md) / PricesResponse +[rates-api](../../../modules.md) / [src/types](../README.md) / PricesResponse # Type Alias: PricesResponse -> **PricesResponse**: `object` +> **PricesResponse** = `object` -## Type declaration +Defined in: src/types.ts:21 + +## Properties ### crypto > **crypto**: [`CryptoPrice`](CryptoPrice.md)[] +Defined in: src/types.ts:22 + +*** + ### errors? -> `optional` **errors**: `Record`\<`string`, `any`\> +> `optional` **errors?**: `Record`\<`string`, `any`\> + +Defined in: src/types.ts:24 + +*** ### fiat > **fiat**: [`FiatPrice`](FiatPrice.md)[] -## Defined in - -[src/types.ts:21](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/types.ts#L21) +Defined in: src/types.ts:23 diff --git a/docs/src/types/type-aliases/RatesData.md b/docs/src/types/type-aliases/RatesData.md index 3cdde34..4b45a40 100644 --- a/docs/src/types/type-aliases/RatesData.md +++ b/docs/src/types/type-aliases/RatesData.md @@ -1,13 +1,11 @@ -[**rates-api v3.0.0**](../../../README.md) • **Docs** +[**rates-api v3.0.0**](../../../README.md) *** -[rates-api v3.0.0](../../../modules.md) / [src/types](../README.md) / RatesData +[rates-api](../../../modules.md) / [src/types](../README.md) / RatesData # Type Alias: RatesData -> **RatesData**: [[`ICurrencyRate`](../interfaces/ICurrencyRate.md)[], [`CodeRates`](CodeRates.md), [`IErrorObject`](../interfaces/IErrorObject.md)] +> **RatesData** = \[[`ICurrencyRate`](../interfaces/ICurrencyRate.md)[], [`CodeRates`](CodeRates.md), [`IErrorObject`](../interfaces/IErrorObject.md)\] -## Defined in - -[src/types.ts:79](https://github.com/ZelCore-io/rates-api/blob/6ee8192dea404fd0a0f6ba9b7352f3b7673523eb/src/types.ts#L79) +Defined in: src/types.ts:79 diff --git a/package.json b/package.json index 90555ac..4119757 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "eslint-plugin-react": "~7.35.2", "jest": "^29.7.0", "ts-jest": "^29.2.5", - "typedoc": "^0.26.7", + "typedoc": "^0.28.0", "typedoc-plugin-markdown": "^4.2.7" } } diff --git a/swagger.yaml b/swagger.yaml index 546230f..7c6e92f 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -40,7 +40,29 @@ paths: /v2/rates: get: summary: Get exchange rates (v2) - description: Retrieves version 2 of the exchange rates. + description: > + Retrieves version 2 of the exchange rates. The `crypto` array includes + one synthetic entry per Binance bStock (tokenized US equity on BNB + Smart Chain), alongside the regular CoinGecko/CryptoCompare/LiveCoinWatch + entries. + + + bStock entries are identified by `id` starting with `bstock-` (e.g. + `bstock-tslab` for the Tesla bStock). They are always tagged + `provider: "coingecko"` — never `"binance"` — because the ZelCore + client matches market entries on the literal string + `${provider}-${id}`, and the sibling `api` repo advertises each + bStock's `coinInfo.coingeckoID` as `bstock-`. Using any other + provider value would make the client-side lookup miss silently. + + + `rates.usd` and `rates.btc` are sourced from live Binance Spot + `USDT` tickers (bStocks are quoted in USDT, not USDC) divided by + the same batch's `BTCUSDT` price; `change24h`/`change7d` come from + Binance's 24h and 7d rolling-window tickers. During a CEX trading + halt (e.g. around a stock split) the last known-good price is served + rather than dropping the entry, per the bStocks partner guide's + display-only allowance. responses: '200': description: A list of exchange rates (v2). @@ -48,6 +70,35 @@ paths: application/json: schema: type: object + properties: + crypto: + type: array + items: + type: object + properties: + id: + type: string + example: bstock-tslab + provider: + type: string + example: coingecko + rates: + type: object + properties: + usd: + type: number + btc: + type: number + change24h: + type: number + change7d: + type: number + fiat: + type: array + items: + type: object + errors: + type: object /v2/rates-compressed: get: From 87b3888ebd23495da12421480e69218b6700a8b7 Mon Sep 17 00:00:00 2001 From: Stultus Mundi Date: Wed, 5 Aug 2026 13:59:25 +0300 Subject: [PATCH 08/12] fix: bStocks review findings -- window-scoped last-good, provider carry-forward, outage visibility, refresh bounding Addresses the whole-branch review of bstocks (PR #39): 1. binance.ts: lastGoodTicker was keyed on bare symbol, so getTicker24h and getTicker7d shared one fallback slot. Whichever window last wrote silently overwrote the other's priceChangePercent/quoteVolume, so a 24h fallback could serve the 7-day change (and ~7x-inflated volume) as the 24-hour figure. Now keyed per `${window}:${symbol}`; mergeTickers and lastGoodAgeMs take an explicit window. 2. apiServices.ts: serviceRefresher rebuilt ratesV2.crypto from the fresh fetch alone once the >300-row floor passed. A provider whose block failed this cycle (CryptoCompare's ~39 rows, LiveCoinWatch's 2) would vanish from /v2/rates outright whenever the remaining providers alone still cleared the floor -- new in this branch, since replaceCryptoByKey has no positional stale tail to fall back on. Now carries forward only the rows belonging to providers present in ratesV2Fetched.errors, then key-merges with fresh data last so fresh always wins. 3. bstocks.ts / zelcoreRatesV2.ts: every failure inside getBstockPrices is caught internally, so a total Binance outage looked identical to a healthy refresh while quietly re-serving frozen prices forever. Added isBstocksDegraded() (true when a refresh prices zero symbols fresh while last-known-good is non-empty), wired into errors.binance, and bounded last-known-good to config.bstocksLastGoodMaxAgeMs (7 days, chosen because the outage this exists for -- a stock split halt -- is naturally multi-day). 4. zelcoreRatesV2.ts / binance.ts: a Binance outage cost up to ~92s (AxiosWrapper's retry budget across getTokenisedAssets, getTradingSymbols, and both ticker windows), stalling the refresh of all other providers behind it every 30s cycle. getBstockPrices() is now raced against a 10s timeout, and getTokenisedAssets/ getTradingSymbols negatively-cache a failure for config.binanceFailureCacheMs (60s) so the retry storm doesn't repeat every cycle. The timeout's setTimeout is .unref()'d since Promise.race never cancels the losing branch. 5. config/index.ts: bStocksEnabled now reads process.env.BSTOCKS_ENABLED !== 'false' instead of a literal, so it can be disabled without a redeploy. 6. bstocks.ts: rank is no longer hardcoded to 0 (which sorted every bStock ahead of Bitcoin in ascending rank order) -- omitted, matching the convention CryptoCompare's rows already use elsewhere in this repo. 7. New test coverage: serviceRefresher (previously zero coverage) for the small-provider-outage guard/carry-forward interaction; a total-outage integration case pinning that getBstockPrices never rejects; and a multi-asset property assertion over the coingecko/bstock- cross-repo id contract. Each fix has a test that was verified to fail before the change and pass after (confirmed via targeted git-stash reverts per file). Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01UnpQSycV6gfbF9JqRf4x1f --- config/index.ts | 13 ++- src/services/apiServices.ts | 12 ++- src/services/bstocks.ts | 86 +++++++++++++---- src/services/providers/binance.ts | 66 +++++++++---- src/services/zelcoreRatesV2.ts | 36 +++++++- tests/binanceProvider.spec.ts | 59 ++++++++++++ tests/bstocks.spec.ts | 100 +++++++++++++++++++- tests/config.spec.ts | 39 ++++++++ tests/serviceRefresher.spec.ts | 148 ++++++++++++++++++++++++++++++ tests/zelcoreRatesV2.spec.ts | 105 +++++++++++++++++++++ 10 files changed, 621 insertions(+), 43 deletions(-) create mode 100644 tests/config.spec.ts create mode 100644 tests/serviceRefresher.spec.ts create mode 100644 tests/zelcoreRatesV2.spec.ts diff --git a/config/index.ts b/config/index.ts index a5c18cf..688f9d0 100644 --- a/config/index.ts +++ b/config/index.ts @@ -13,7 +13,18 @@ export const config = { zelCoinInfoUrl: 'https://raw.githubusercontent.com/ZelCore-io/Zelcore/master/coininfo.json', binanceApiUrl: 'https://api.binance.com/', binanceAssetUrl: 'https://www.binance.com/', - bStocksEnabled: true, + // Env kill switch: disabling in production should not require a code + // change + redeploy. Defaults to enabled when unset. + bStocksEnabled: process.env.BSTOCKS_ENABLED !== 'false', + // How long a failed Binance request (tokenised-asset list / trading-symbol + // set) is negatively-cached before retrying, so an outage doesn't re-spend + // the full AxiosWrapper retry budget on every 30s refresh cycle. + binanceFailureCacheMs: 60 * 1000, + // How long a bStock's last-known-good price is served after Binance stops + // pricing it fresh. The outage this exists for (a stock split halt, + // exchange maintenance) is naturally multi-day, so this is deliberately far + // longer than the ticker windows themselves. + bstocksLastGoodMaxAgeMs: 7 * 24 * 60 * 60 * 1000, }; export default config; \ No newline at end of file diff --git a/src/services/apiServices.ts b/src/services/apiServices.ts index 285cf8e..2d4c28a 100644 --- a/src/services/apiServices.ts +++ b/src/services/apiServices.ts @@ -253,7 +253,17 @@ export async function serviceRefresher(): Promise { : 0; if (ratesV2Fetched && ratesV2Fetched.fiat.length > 20 && providerCryptoCount > 300) { ratesV2.fiat = mergeDeep(ratesV2.fiat, ratesV2Fetched.fiat); - ratesV2.crypto = replaceCryptoByKey(ratesV2Fetched.crypto); + // Carry forward only the rows belonging to providers that errored THIS + // cycle, then key-merge with the fresh fetch last so fresh data always + // wins. Without this, a provider whose block failed (CryptoCompare, + // LiveCoinWatch) simply vanishes from /v2/rates the moment the + // remaining providers alone still clear the >300 floor -- there is no + // positional stale tail to fall back on any more (replaceCryptoByKey + // rebuilds from `source` alone). `ratesV2.crypto` is genuinely + // undefined on the first cycle, hence the `??`. + const failedProviders = new Set(Object.keys(ratesV2Fetched.errors ?? {})); + const carried = (ratesV2.crypto ?? []).filter((c) => failedProviders.has(c.provider)); + ratesV2.crypto = replaceCryptoByKey([...carried, ...ratesV2Fetched.crypto]); ratesV2.errors = ratesV2Fetched.errors; } diff --git a/src/services/bstocks.ts b/src/services/bstocks.ts index e75c198..20880c4 100644 --- a/src/services/bstocks.ts +++ b/src/services/bstocks.ts @@ -2,16 +2,40 @@ import config from '../../config'; import { Binance } from './providers/binance'; import type { CryptoPrice } from '../types'; -// Last-known-good per bStock id. During a CEX halt (stock splits) Binance -// returns the symbol PRESENT with lastPrice "0.00000000" rather than omitting -// it — measured live, 20/20 halted symbols came back present, 9 priced zero — -// so the guard below is on the price being finite and positive, not on the -// ticker being absent. We keep serving the previous price (display-only per -// the bStocks partner guide). -let lastGood = new Map(); +// Last-known-good per bStock id, with the epoch-ms timestamp it was accepted +// at. During a CEX halt (stock splits) Binance returns the symbol PRESENT +// with lastPrice "0.00000000" rather than omitting it — measured live, 20/20 +// halted symbols came back present, 9 priced zero — so the guard below is on +// the price being finite and positive, not on the ticker being absent. We +// keep serving the previous price (display-only per the bStocks partner +// guide) until it exceeds `config.bstocksLastGoodMaxAgeMs`. +let lastGood = new Map(); + +// How many symbols were priced FRESH (not carried from last-known-good) on +// the most recent call to getBstockPrices(). Every failure inside this +// module's Binance calls is caught internally (see the Binance class docs), +// so Promise.all never rejects and a total outage would otherwise look +// identical to a healthy refresh from the outside. isBstocksDegraded() below +// exposes that distinction so a caller can surface it (e.g. errors.binance). +let freshPricedLastRun = 0; export function _clearLastGoodForTests(): void { lastGood = new Map(); + freshPricedLastRun = 0; +} + +/** + * True when the most recent `getBstockPrices()` call priced nothing fresh + * (every underlying Binance call failed or returned unusable data) while + * there is still last-known-good data being served. Distinguishes "Binance is + * down and we're serving frozen prices" from a normal, healthy refresh, which + * `getBstockPrices()`'s return value alone cannot express since it never + * rejects and unconditionally re-emits `lastGood` either way. + * + * @returns Whether the bStocks pipeline is currently degraded. + */ +export function isBstocksDegraded(): boolean { + return freshPricedLastRun === 0 && lastGood.size > 0; } /** @@ -34,12 +58,19 @@ export function _clearLastGoodForTests(): void { * value makes the lookup miss silently — no error, just no price. This * id/provider pairing is a cross-repo contract; do not change it in isolation. * + * `rank` is intentionally omitted (not zeroed) to match CryptoCompare's rows + * elsewhere in this repo, which also carry no `rank`: a literal `rank: 0` + * would sort every bStock ahead of Bitcoin in any ascending rank-ordered list. + * * A module-level last-known-good map means a symbol that drops out of a given * refresh (CEX halt, e.g. around a stock split) keeps being served at its - * previous price rather than disappearing from the response. + * previous price rather than disappearing from the response, bounded by + * `config.bstocksLastGoodMaxAgeMs` (see the halting comment on `lastGood` + * above) so a permanently-delisted symbol doesn't get served forever. * * @returns One `CryptoPrice` per tradable bStock (BSC contract + TRADING - * `USDT` Spot symbol), including any carried over from a prior refresh. + * `USDT` Spot symbol) still within the staleness bound, including any + * carried over from a prior refresh. */ export async function getBstockPrices(): Promise { if (!config.bStocksEnabled) return []; @@ -59,6 +90,8 @@ export async function getBstockPrices(): Promise { const t7dMap = new Map(t7d.map((t) => [t.symbol, t])); const btcUsd = Number(t24Map.get('BTCUSDT')?.lastPrice); + const now = Date.now(); + let freshCount = 0; tradable.forEach((asset) => { const id = `bstock-${asset.assetCode.toLowerCase()}`; const ticker = t24Map.get(`${asset.assetCode}USDT`); @@ -66,18 +99,31 @@ export async function getBstockPrices(): Promise { if (!ticker || !Number.isFinite(px) || px <= 0 || !Number.isFinite(btcUsd) || btcUsd <= 0) { return; // keep lastGood entry as-is } + freshCount += 1; lastGood.set(id, { - id, - provider: 'coingecko', - rates: { btc: px / btcUsd, usd: px }, - supply: 0, - volume: Number(ticker.quoteVolume) || 0, - change24h: Number(ticker.priceChangePercent) || 0, - market: 0, - rank: 0, - total_supply: 0, - change7d: Number(t7dMap.get(`${asset.assetCode}USDT`)?.priceChangePercent) || 0, + at: now, + price: { + id, + provider: 'coingecko', + rates: { btc: px / btcUsd, usd: px }, + supply: 0, + volume: Number(ticker.quoteVolume) || 0, + change24h: Number(ticker.priceChangePercent) || 0, + market: 0, + total_supply: 0, + change7d: Number(t7dMap.get(`${asset.assetCode}USDT`)?.priceChangePercent) || 0, + }, }); }); - return Array.from(lastGood.values()); + freshPricedLastRun = freshCount; + + // Bound the staleness: drop any entry that hasn't priced fresh within the + // configured window rather than serving it forever. + lastGood.forEach((entry, id) => { + if (now - entry.at > config.bstocksLastGoodMaxAgeMs) { + lastGood.delete(id); + } + }); + + return Array.from(lastGood.values()).map((entry) => entry.price); } diff --git a/src/services/providers/binance.ts b/src/services/providers/binance.ts index 0e12521..4cff3cf 100644 --- a/src/services/providers/binance.ts +++ b/src/services/providers/binance.ts @@ -62,11 +62,19 @@ export class Binance { private quoteCache = new LRU({ max: 50, ttl: 60 * 1000 }); /** - * Last-known-good ticker per symbol, independent of `quoteCache`'s TTL, with - * the epoch-ms timestamp at which it was accepted. Used to backfill a symbol - * whose fresh value is unusable — a halted symbol priced at zero, a symbol - * omitted from the batch, or an entire request that failed — so a transient - * gap upstream never drops the symbol or fabricates a price for it. + * Last-known-good ticker per `${window}:${symbol}`, independent of + * `quoteCache`'s TTL, with the epoch-ms timestamp at which it was accepted. + * Used to backfill a symbol whose fresh value is unusable — a halted symbol + * priced at zero, a symbol omitted from the batch, or an entire request + * that failed — so a transient gap upstream never drops the symbol or + * fabricates a price for it. + * + * Keyed per window (not bare symbol) because `getTicker24h` and + * `getTicker7d` both resolve the same symbol but with window-scoped + * `priceChangePercent`/`quoteVolume`. A single symbol-keyed store would let + * whichever window last wrote silently overwrite the other's fallback — + * e.g. a 7d fetch populating the store, then a later 24h failure serving + * the 7-day change/volume as the 24-hour figure. * @private */ private lastGoodTicker = new Map(); @@ -129,12 +137,16 @@ export class Binance { * falling back to the last-known-good value otherwise (halted/omitted symbol, * or the whole request failed and `fetched` is empty). * + * `window` scopes both the write and the fallback read to `${window}:${symbol}` + * so the 24h and 7d stores never collide — see the `lastGoodTicker` doc. + * * @private + * @param window - Which ticker window this batch belongs to (`24h` or `7d`). * @param symbols - The symbols that were requested. * @param fetched - Whatever tickers were actually returned (possibly a subset, possibly empty on error). - * @returns One ticker per requested symbol that has ever been seen; halted/never-seen symbols are omitted. + * @returns One ticker per requested symbol that has ever been seen for this window; halted/never-seen symbols are omitted. */ - private mergeTickers(symbols: string[], fetched: BinanceTicker[]): BinanceTicker[] { + private mergeTickers(window: '24h' | '7d', symbols: string[], fetched: BinanceTicker[]): BinanceTicker[] { const now = Date.now(); const bySymbol = new Map(); fetched.forEach((t) => { @@ -143,24 +155,37 @@ export class Binance { // through would overwrite the real price and serve $0 from then on. if (!Binance.isUsable(t)) return; bySymbol.set(t.symbol, t); - this.lastGoodTicker.set(t.symbol, { ticker: t, at: now }); + this.lastGoodTicker.set(`${window}:${t.symbol}`, { ticker: t, at: now }); }); return symbols - .map((s) => bySymbol.get(s) ?? this.lastGoodTicker.get(s)?.ticker) + .map((s) => bySymbol.get(s) ?? this.lastGoodTicker.get(`${window}:${s}`)?.ticker) .filter((t): t is BinanceTicker => !!t); } /** * Age in milliseconds of the last-known-good price for a symbol, or null if - * none has ever been recorded. Lets a caller distinguish a live price from - * one carried through a long halt, which the ticker itself cannot express. + * none has ever been recorded for the requested window(s). Lets a caller + * distinguish a live price from one carried through a long halt, which the + * ticker itself cannot express. * * @param symbol - The Binance symbol, e.g. `TSLABUSDT`. - * @returns Age in ms, or null when the symbol has never priced successfully. + * @param window - Which window's last-known-good entry to check (`24h` or + * `7d`). Omit to get the freshest of the two — the age of whichever window + * priced most recently — which is what a caller asking "how stale is this + * symbol overall" generally wants. + * @returns Age in ms, or null when the symbol has never priced successfully + * for the requested window (or for either window, when unspecified). */ - lastGoodAgeMs(symbol: string): number | null { - const entry = this.lastGoodTicker.get(symbol); - return entry ? Date.now() - entry.at : null; + lastGoodAgeMs(symbol: string, window?: '24h' | '7d'): number | null { + if (window) { + const entry = this.lastGoodTicker.get(`${window}:${symbol}`); + return entry ? Date.now() - entry.at : null; + } + const ages = (['24h', '7d'] as const) + .map((w) => this.lastGoodTicker.get(`${w}:${symbol}`)) + .filter((e): e is { ticker: BinanceTicker; at: number } => !!e) + .map((e) => Date.now() - e.at); + return ages.length ? Math.min(...ages) : null; } /** @@ -182,6 +207,10 @@ export class Binance { } catch (err) { log.error('Error getting tokenised assets from Binance'); log.error(err); + // Negatively-cache the failure briefly (well under the 1h success TTL) + // so a Binance outage doesn't re-spend the full ~23s AxiosWrapper retry + // budget on every 30s refresh cycle, forever, until Binance recovers. + this.longCache.set(key, [], { ttl: config.binanceFailureCacheMs }); return []; } } @@ -214,6 +243,9 @@ export class Binance { } catch (err) { log.error('Error getting trading symbols from Binance'); log.error(err); + // See the matching comment in getTokenisedAssets: negatively-cache so + // the retry storm doesn't repeat every cycle while Binance is down. + this.longCache.set(key, new Set(), { ttl: config.binanceFailureCacheMs }); return new Set(); } } @@ -241,7 +273,7 @@ export class Binance { log.error(err); } - const merged = this.mergeTickers(symbols, fetched); + const merged = this.mergeTickers('24h', symbols, fetched); this.quoteCache.set(key, merged); return merged; } @@ -276,7 +308,7 @@ export class Binance { } /* eslint-enable no-await-in-loop */ - const merged = this.mergeTickers(symbols, fetched); + const merged = this.mergeTickers('7d', symbols, fetched); this.quoteCache.set(key, merged); return merged; } diff --git a/src/services/zelcoreRatesV2.ts b/src/services/zelcoreRatesV2.ts index 7dbf4aa..39de534 100644 --- a/src/services/zelcoreRatesV2.ts +++ b/src/services/zelcoreRatesV2.ts @@ -1,9 +1,27 @@ import { coinAggregatorIDs } from './coinAggregatorIDs'; import * as log from '../lib/log'; import { CoinGecko, BitPay, CryptoCompare, LiveCoinWatch } from './providers'; -import { getBstockPrices } from './bstocks'; +import { getBstockPrices, isBstocksDegraded } from './bstocks'; import { PricesResponse, CryptoPrice, ICurrencyRate } from '../types'; +/** + * Resolves after `ms` milliseconds, ignoring the value it's chained onto. + * Used to bound the bStocks fetch below: a total Binance outage can otherwise + * cost a single refresh cycle up to ~92s (the AxiosWrapper retry budget spent + * across `getTokenisedAssets`, `getTradingSymbols`, and both ticker windows), + * which would stall the refresh of all 364 non-bStock assets behind it. + * + * `Promise.race` never cancels the losing branch, so on the normal/healthy + * path (`getBstockPrices()` wins well under 10s) this timer is still live in + * the background for whatever remains of the 10s -- `.unref()` keeps it from + * holding the process open for that tail, since nothing depends on it firing. + * + * @param ms - Milliseconds to wait. + */ +function timeout(ms: number): Promise { + return new Promise((resolve) => { setTimeout(resolve, ms).unref(); }); +} + /** * Fetches and aggregates cryptocurrency prices and fiat rates from multiple providers. * @@ -133,10 +151,22 @@ export async function getAll(): Promise { errors.livecoinwatch = true; } - // Fetch bStock prices from Binance + // Fetch bStock prices from Binance. Bounded to 10s so a hung/slow Binance + // outage cannot stall the refresh of every other provider behind it -- the + // race losing simply means no bStock rows this cycle, same as any other + // degraded refresh (see isBstocksDegraded below). try { - const bstocks = await getBstockPrices(); + const bstocks = await Promise.race([ + getBstockPrices(), + timeout(10_000).then((): CryptoPrice[] => []), + ]); processed.push(...bstocks); + // getBstockPrices() never rejects -- every failure inside it is caught + // internally -- so a total Binance outage looks identical to a healthy + // refresh unless we ask it directly whether it degraded this cycle. + if (isBstocksDegraded()) { + errors.binance = true; + } } catch (e) { log.error('bStocks error'); log.error(e); diff --git a/tests/binanceProvider.spec.ts b/tests/binanceProvider.spec.ts index 3721494..59ca5be 100644 --- a/tests/binanceProvider.spec.ts +++ b/tests/binanceProvider.spec.ts @@ -189,4 +189,63 @@ describe('Binance provider', () => { expect(result.map((t) => t.symbol).sort()).toEqual([...symbols].sort()); }); }); + + // Regression coverage for the shared last-known-good store colliding across + // windows: `mergeTickers` used to key solely on `symbol`, so whichever of + // getTicker24h/getTicker7d ran (and priced successfully) LAST would silently + // overwrite the other window's fallback value -- serving a 7-day change + // and ~7x-inflated volume as the 24-hour figure, or vice versa. + describe('24h/7d last-known-good isolation', () => { + it('does not let a 7d last-known-good leak into a failed 24h fetch, or vice versa', async () => { + const getSpy = jest.spyOn(AxiosWrapper.prototype, 'get'); + + // Establish a 24h last-known-good for NVDAB: small change/volume. + const t24: BinanceTicker = { + symbol: 'NVDABUSDT', lastPrice: '120.00', priceChangePercent: '3.0', quoteVolume: '500000', + }; + getSpy.mockResolvedValueOnce(axiosResponse([t24])); + await binance.getTicker24h(['NVDABUSDT']); + + // Establish a 7d last-known-good for the SAME symbol: much larger + // (window-scoped) change/volume, as Binance's real 7d ticker would report. + const t7d: BinanceTicker = { + symbol: 'NVDABUSDT', lastPrice: '120.00', priceChangePercent: '9.0', quoteVolume: '3500000', + }; + getSpy.mockResolvedValueOnce(axiosResponse([t7d])); + await binance.getTicker7d(['NVDABUSDT']); + + // A fresh 24h fetch (different symbol combo -> new quoteCache key) now + // fails outright and must fall back to the 24h store -- NOT the 7d one. + getSpy.mockRejectedValueOnce(new Error('network down')); + const during24 = await binance.getTicker24h(['NVDABUSDT', 'FILLER1USDT']); + const fallback24 = during24.find((t) => t.symbol === 'NVDABUSDT'); + expect(fallback24?.priceChangePercent).toBe('3.0'); + expect(fallback24?.quoteVolume).toBe('500000'); + + // Symmetric check: a fresh 7d fetch failing must fall back to the 7d + // store, not whatever the 24h store now holds. + getSpy.mockRejectedValueOnce(new Error('network down')); + const during7d = await binance.getTicker7d(['NVDABUSDT', 'FILLER2USDT']); + const fallback7d = during7d.find((t) => t.symbol === 'NVDABUSDT'); + expect(fallback7d?.priceChangePercent).toBe('9.0'); + expect(fallback7d?.quoteVolume).toBe('3500000'); + }); + + it('lastGoodAgeMs reports a per-window age, and the freshest of the two when no window is given', async () => { + const getSpy = jest.spyOn(AxiosWrapper.prototype, 'get'); + + const t24: BinanceTicker = { + symbol: 'AMDBUSDT', lastPrice: '150.00', priceChangePercent: '2.0', quoteVolume: '500000', + }; + getSpy.mockResolvedValueOnce(axiosResponse([t24])); + await binance.getTicker24h(['AMDBUSDT']); + + // No 7d price has ever been recorded for AMDBUSDT. + expect(binance.lastGoodAgeMs('AMDBUSDT', '24h')).toBeGreaterThanOrEqual(0); + expect(binance.lastGoodAgeMs('AMDBUSDT', '7d')).toBeNull(); + // Falls back to whichever window exists when unspecified. + expect(binance.lastGoodAgeMs('AMDBUSDT')).toBeGreaterThanOrEqual(0); + expect(binance.lastGoodAgeMs('NEVERSEENUSDT')).toBeNull(); + }); + }); }); diff --git a/tests/bstocks.spec.ts b/tests/bstocks.spec.ts index e6cb6c2..4b23acb 100644 --- a/tests/bstocks.spec.ts +++ b/tests/bstocks.spec.ts @@ -1,5 +1,7 @@ import { Binance } from '../src/services/providers/binance'; -import { getBstockPrices, _clearLastGoodForTests } from '../src/services/bstocks'; +import { + getBstockPrices, _clearLastGoodForTests, isBstocksDegraded, +} from '../src/services/bstocks'; jest.mock('../src/services/providers/binance'); @@ -39,6 +41,10 @@ describe('bStocks assembler', () => { expect(prices[0].rates.btc).toBeCloseTo(326.11 / 65222); expect(prices[0].change24h).toBeCloseTo(2.5); expect(prices[0].change7d).toBeCloseTo(7.1); + // rank must be OMITTED (like CryptoCompare's rows elsewhere in this repo), + // not zeroed -- a literal `rank: 0` would sort every bStock ahead of + // Bitcoin in any ascending rank-ordered wallet list. + expect(prices[0].rank).toBeUndefined(); }); it('skips assets without a TRADING symbol', async () => { @@ -124,4 +130,96 @@ describe('bStocks assembler', () => { config.default.bStocksEnabled = original; } }); + + // Finding 3: a total Binance outage must be observable (not reported as a + // healthy service quietly serving frozen prices forever), and getBstockPrices + // must never reject even when every underlying Binance call fails -- every + // failure inside getTokenisedAssets/getTradingSymbols/getTicker24h/getTicker7d + // is already caught internally (see binance.ts), so a total outage looks + // like `{assets: [], trading: new Set(), t24: [], t7d: []}` from here. + it('marks the service degraded without throwing when a total outage prices nothing fresh, while still serving last-known-good within the staleness bound', async () => { + mockBinance({ + assets: [TSLAB], + trading: ['TSLABUSDT', 'BTCUSDT'], + t24: [ + { symbol: 'TSLABUSDT', lastPrice: '326.11', priceChangePercent: '2.5', quoteVolume: '1000000' }, + { symbol: 'BTCUSDT', lastPrice: '65222.00', priceChangePercent: '1.0', quoteVolume: '9' }, + ], + t7d: [{ symbol: 'TSLABUSDT', lastPrice: '326.11', priceChangePercent: '7.1', quoteVolume: '0' }], + }); + await getBstockPrices(); + expect(isBstocksDegraded()).toBe(false); + + // Total outage: every Binance call resolves the way the real client does + // after internally catching a network failure -- empty, never rejecting. + mockBinance({ + assets: [], trading: [], t24: [], t7d: [], + }); + await expect(getBstockPrices()).resolves.toHaveLength(1); // stale TSLAB still served + expect(isBstocksDegraded()).toBe(true); + }); + + it('drops a last-known-good entry once it exceeds the configured staleness bound, rather than serving it forever', async () => { + const config = (await import('../config')).default; + const dateSpy = jest.spyOn(Date, 'now'); + try { + dateSpy.mockReturnValue(1_000_000); + mockBinance({ + assets: [TSLAB], + trading: ['TSLABUSDT', 'BTCUSDT'], + t24: [ + { symbol: 'TSLABUSDT', lastPrice: '326.11', priceChangePercent: '2.5', quoteVolume: '1000000' }, + { symbol: 'BTCUSDT', lastPrice: '65222.00', priceChangePercent: '1.0', quoteVolume: '9' }, + ], + t7d: [{ symbol: 'TSLABUSDT', lastPrice: '326.11', priceChangePercent: '7.1', quoteVolume: '0' }], + }); + await getBstockPrices(); + + // Still well within the bound: kept. + dateSpy.mockReturnValue(1_000_000 + config.bstocksLastGoodMaxAgeMs - 1); + mockBinance({ + assets: [], trading: [], t24: [], t7d: [], + }); + expect(await getBstockPrices()).toHaveLength(1); + + // Past the bound: a total outage no longer serves the ancient price. + dateSpy.mockReturnValue(1_000_000 + config.bstocksLastGoodMaxAgeMs + 1); + mockBinance({ + assets: [], trading: [], t24: [], t7d: [], + }); + expect(await getBstockPrices()).toHaveLength(0); + } finally { + dateSpy.mockRestore(); + } + }); + + // Finding 7: the cross-repo id/provider contract (see the module doc) was + // only pinned against a single asset. Assert it holds as a property across + // a multi-asset fixture. + it('holds the coingecko-provider / bstock- id contract across a multi-asset fixture', async () => { + const codes = ['TSLAB', 'NVDAB', 'MSTRB', 'GOOGLB', 'AMZNB']; + const assets = codes.map((code) => ({ + assetCode: code, assetName: code, caList: [{ network: 'BSC', ca: '0xabc' }], + })); + const trading = [...codes.map((c) => `${c}USDT`), 'BTCUSDT']; + const t24 = [ + ...codes.map((code, i) => ({ + symbol: `${code}USDT`, lastPrice: `${100 + i}`, priceChangePercent: '1.0', quoteVolume: '1000', + })), + { symbol: 'BTCUSDT', lastPrice: '65222.00', priceChangePercent: '1.0', quoteVolume: '9' }, + ]; + const t7d = codes.map((code) => ({ + symbol: `${code}USDT`, lastPrice: '100', priceChangePercent: '2.0', quoteVolume: '1000', + })); + mockBinance({ + assets, trading, t24, t7d, + }); + + const prices = await getBstockPrices(); + expect(prices).toHaveLength(codes.length); + prices.forEach((p) => { + expect(p.provider).toBe('coingecko'); + expect(p.id).toMatch(/^bstock-[a-z0-9]+$/); + }); + }); }); diff --git a/tests/config.spec.ts b/tests/config.spec.ts new file mode 100644 index 0000000..8e340aa --- /dev/null +++ b/tests/config.spec.ts @@ -0,0 +1,39 @@ +/** + * Finding 5: bStocksEnabled must be overridable via env var (BSTOCKS_ENABLED) + * without a code change + redeploy. Each test re-imports the config module + * fresh (via jest.resetModules) so the env var is picked up at module-load + * time, mirroring how the real process reads it once on startup. + */ +describe('config.bStocksEnabled env override', () => { + const ORIGINAL_ENV = process.env.BSTOCKS_ENABLED; + + afterEach(() => { + if (ORIGINAL_ENV === undefined) { + delete process.env.BSTOCKS_ENABLED; + } else { + process.env.BSTOCKS_ENABLED = ORIGINAL_ENV; + } + jest.resetModules(); + }); + + it('defaults to enabled when BSTOCKS_ENABLED is unset', async () => { + delete process.env.BSTOCKS_ENABLED; + jest.resetModules(); + const config = (await import('../config')).default; + expect(config.bStocksEnabled).toBe(true); + }); + + it('disables via BSTOCKS_ENABLED=false without a code change', async () => { + process.env.BSTOCKS_ENABLED = 'false'; + jest.resetModules(); + const config = (await import('../config')).default; + expect(config.bStocksEnabled).toBe(false); + }); + + it('treats any other value (e.g. a typo) as enabled, matching the !== "false" contract', async () => { + process.env.BSTOCKS_ENABLED = 'no'; + jest.resetModules(); + const config = (await import('../config')).default; + expect(config.bStocksEnabled).toBe(true); + }); +}); diff --git a/tests/serviceRefresher.spec.ts b/tests/serviceRefresher.spec.ts new file mode 100644 index 0000000..9966855 --- /dev/null +++ b/tests/serviceRefresher.spec.ts @@ -0,0 +1,148 @@ +import type { Response, Request } from 'express'; +import type { CryptoPrice, PricesResponse } from '../src/types'; + +jest.mock('../src/services/zelcoreRates', () => ({ + __esModule: true, + default: { getAll: jest.fn() }, +})); +jest.mock('../src/services/zelcoreMarketsUSD', () => ({ + __esModule: true, + default: { getAll: jest.fn() }, +})); +jest.mock('../src/services/zelcoreRatesV2', () => ({ + __esModule: true, + default: { getAll: jest.fn() }, +})); + +// eslint-disable-next-line import/first +import zelcoreRates from '../src/services/zelcoreRates'; +// eslint-disable-next-line import/first +import zelcoreMarketsUSD from '../src/services/zelcoreMarketsUSD'; +// eslint-disable-next-line import/first +import zelcoreRatesV2 from '../src/services/zelcoreRatesV2'; +// eslint-disable-next-line import/first +import { serviceRefresher, getRatesV2 } from '../src/services/apiServices'; + +const mockedRates = zelcoreRates as jest.Mocked; +const mockedMarkets = zelcoreMarketsUSD as jest.Mocked; +const mockedRatesV2 = zelcoreRatesV2 as jest.Mocked; + +function makeCoins(provider: string, count: number): CryptoPrice[] { + return Array.from({ length: count }, (_, i) => ({ + id: `${provider}-coin-${i}`, + provider, + rates: { usd: 1, btc: 0.00001 }, + supply: 1, + volume: 1, + change24h: 1, + market: 1, + })); +} + +function makeFiat(count: number) { + return Array.from({ length: count }, (_, i) => ({ code: `C${i}`, name: `Currency ${i}`, rate: 1 })); +} + +/** Flushes a chain of already-resolved microtasks (the sequential `await`s + * inside serviceRefresher) without advancing the fake `delay(30s)` timer that + * would otherwise trigger serviceRefresher's own infinite recursion. */ +async function flush(steps = 25) { + for (let i = 0; i < steps; i += 1) { + // eslint-disable-next-line no-await-in-loop + await Promise.resolve(); + } +} + +function captureRatesV2(): PricesResponse { + const json = jest.fn(); + getRatesV2({} as Request, { json } as unknown as Response); + return json.mock.calls[0][0] as PricesResponse; +} + +// Finding 2 + finding 7 bullet 1: serviceRefresher had zero coverage, so the +// >300 floor's interaction with replaceCryptoByKey (a small-provider outage +// silently truncating /v2/rates) went unverified. +describe('serviceRefresher — small-provider-outage carry-forward (finding 2)', () => { + beforeEach(() => { + jest.useFakeTimers(); + mockedRates.getAll.mockResolvedValue([[], {}, { errors: {} }] as never); + mockedMarkets.getAll.mockResolvedValue([{}, { errors: {} }] as never); + }); + + afterEach(() => { + jest.useRealTimers(); + jest.clearAllMocks(); + }); + + it('carries forward only a failed provider\'s rows and lets fresh data win everywhere else', async () => { + // Cycle 1: every provider healthy. 320 + 39 + 2 = 361 rows, well clear of + // the >300 floor. + mockedRatesV2.getAll.mockResolvedValueOnce({ + crypto: [ + ...makeCoins('coingecko', 320), + ...makeCoins('cryptocompare', 39), + ...makeCoins('livecoinwatch', 2), + ], + fiat: makeFiat(25), + errors: {}, + }); + serviceRefresher(); + await flush(); + + expect(captureRatesV2().crypto).toHaveLength(361); + + // Cycle 2: CryptoCompare fails outright (0 rows), but 320 + 2 = 322 still + // clears the >300 floor -- exactly the scenario the finding describes. + // Under the old positional merge those 39 rows survived as a stale tail; + // under a naive key-rebuild-from-fresh-alone they vanish instead. + mockedRatesV2.getAll.mockResolvedValueOnce({ + crypto: [ + ...makeCoins('coingecko', 320), + ...makeCoins('livecoinwatch', 2), + ], + fiat: makeFiat(25), + errors: { cryptocompare: true }, + }); + serviceRefresher(); + await flush(); + + const payload = captureRatesV2(); + expect(payload.crypto).toHaveLength(361); // 322 fresh + 39 carried + const cryptocompareRows = payload.crypto.filter((c) => c.provider === 'cryptocompare'); + expect(cryptocompareRows).toHaveLength(39); // carried from cycle 1, not dropped + expect(payload.errors?.cryptocompare).toBe(true); // still surfaced as an error + }); + + it('does not carry forward rows from a provider that is healthy this cycle, even if it shrank', async () => { + // Cycle 1: coingecko returns 320 rows including one specific coin. + mockedRatesV2.getAll.mockResolvedValueOnce({ + crypto: [ + { ...makeCoins('coingecko', 1)[0], id: 'delisted-coin' }, + ...makeCoins('coingecko', 319), + ...makeCoins('cryptocompare', 39), + ], + fiat: makeFiat(25), + errors: {}, + }); + serviceRefresher(); + await flush(); + + // Cycle 2: coingecko succeeds again (no error) but legitimately no longer + // returns 'delisted-coin' -- that coin should NOT be carried forward, + // because coingecko did not error this cycle. + mockedRatesV2.getAll.mockResolvedValueOnce({ + crypto: [ + ...makeCoins('coingecko', 319), + ...makeCoins('cryptocompare', 39), + ], + fiat: makeFiat(25), + errors: {}, + }); + serviceRefresher(); + await flush(); + + const payload = captureRatesV2(); + expect(payload.crypto.find((c) => c.id === 'delisted-coin')).toBeUndefined(); + expect(payload.crypto).toHaveLength(358); // 319 + 39, no carry-forward + }); +}); diff --git a/tests/zelcoreRatesV2.spec.ts b/tests/zelcoreRatesV2.spec.ts new file mode 100644 index 0000000..b980a10 --- /dev/null +++ b/tests/zelcoreRatesV2.spec.ts @@ -0,0 +1,105 @@ +// Explicit factories (rather than bare `jest.mock(path)` automocks) so Jest +// never has to load the real provider modules -- each one wires up a real +// AxiosWrapper/axios instance at import time, which is unrelated overhead +// these tests don't need and which was observed to leave the test process +// hanging past its normal exit. +jest.mock('../src/services/providers', () => ({ + __esModule: true, + CoinGecko: { getInstance: jest.fn() }, + BitPay: { getInstance: jest.fn() }, + CryptoCompare: { getInstance: jest.fn() }, + LiveCoinWatch: { getInstance: jest.fn() }, +})); +jest.mock('../src/services/bstocks', () => ({ + __esModule: true, + getBstockPrices: jest.fn(), + isBstocksDegraded: jest.fn(), +})); + +// eslint-disable-next-line import/first +import { CoinGecko, BitPay, CryptoCompare, LiveCoinWatch } from '../src/services/providers'; +// eslint-disable-next-line import/first +import { getBstockPrices, isBstocksDegraded } from '../src/services/bstocks'; +// eslint-disable-next-line import/first +import { getAll } from '../src/services/zelcoreRatesV2'; +// eslint-disable-next-line import/first +import type { CryptoPrice } from '../src/types'; + +const MockedCoinGecko = CoinGecko as jest.Mocked; +const MockedBitPay = BitPay as jest.Mocked; +const MockedCryptoCompare = CryptoCompare as jest.Mocked; +const MockedLiveCoinWatch = LiveCoinWatch as jest.Mocked; +const mockedGetBstockPrices = getBstockPrices as jest.MockedFunction; +const mockedIsBstocksDegraded = isBstocksDegraded as jest.MockedFunction; + +beforeEach(() => { + // All non-bStock providers fail fast (rejected) so these tests exercise + // only the bStocks leg of getAll() without needing real market fixtures. + MockedCoinGecko.getInstance.mockReturnValue({ + getExchangeRates: jest.fn().mockRejectedValue(new Error('down')), + } as never); + MockedBitPay.getInstance.mockReturnValue({ + getFiatRates: jest.fn().mockRejectedValue(new Error('down')), + } as never); + MockedCryptoCompare.getInstance.mockReturnValue({ + getMarketData: jest.fn().mockRejectedValue(new Error('down')), + } as never); + MockedLiveCoinWatch.getInstance.mockReturnValue({ + getExchangeRates: jest.fn().mockRejectedValue(new Error('down')), + } as never); +}); + +afterEach(() => { + jest.clearAllTimers(); + jest.useRealTimers(); + jest.clearAllMocks(); +}); + +// Finding 4: a total Binance outage costs up to ~92s inside getBstockPrices() +// (AxiosWrapper's retry budget across two sequential calls plus a 3-chunk +// loop). getAll() awaited that serially, so it -- and therefore the refresh +// of all 364 non-bStock assets behind it -- would stall for just as long. +it('bounds the bStocks fetch to 10s so a hung/slow Binance outage cannot stall the whole refresh', async () => { + jest.useFakeTimers(); + mockedGetBstockPrices.mockImplementation(() => new Promise(() => {})); // never resolves + mockedIsBstocksDegraded.mockReturnValue(false); + + const resultPromise = getAll(); + let resolved: Awaited> | undefined; + resultPromise.then((r) => { resolved = r; }); + + await jest.advanceTimersByTimeAsync(10_000); + expect(resolved).toBeDefined(); + expect(resolved!.crypto.some((c) => c.id.startsWith('bstock-'))).toBe(false); +}); + +// Finding 3a: getBstockPrices() never rejects (every failure is caught +// internally), so a total outage must be surfaced via isBstocksDegraded() +// rather than the (unreachable) catch block. +it('sets errors.binance when bStocks degrades, even though getBstockPrices resolved without throwing', async () => { + const staleRow: CryptoPrice = { + id: 'bstock-tslab', + provider: 'coingecko', + rates: { usd: 300, btc: 0.005 }, + supply: 0, + volume: 0, + change24h: 0, + market: 0, + total_supply: 0, + change7d: 0, + }; + mockedGetBstockPrices.mockResolvedValue([staleRow]); + mockedIsBstocksDegraded.mockReturnValue(true); + + const result = await getAll(); + expect(result.errors?.binance).toBe(true); + expect(result.crypto.some((c) => c.id === 'bstock-tslab')).toBe(true); // stale rows still served +}); + +it('does not set errors.binance when bStocks is healthy', async () => { + mockedGetBstockPrices.mockResolvedValue([]); + mockedIsBstocksDegraded.mockReturnValue(false); + + const result = await getAll(); + expect(result.errors?.binance).toBeUndefined(); +}); From 96b92d49c1e90e4713da7698fcad0a30564f2453 Mon Sep 17 00:00:00 2001 From: Stultus Mundi Date: Wed, 5 Aug 2026 14:16:31 +0300 Subject: [PATCH 09/12] fix: serve last-good bStocks when the Binance race times out, not nothing The 10s bound added for the slow-refresh finding resolved to [] on timeout, which reintroduced the vanishing-rows failure it was meant to prevent, for bStocks specifically. The provider carry-forward in apiServices keys on the row's provider, but bStock rows carry provider "coingecko" while their failure is reported under errors.binance - so nothing could ever carry them. On a hung Binance the first cycle produced zero bStock rows AND an empty errors object: the wallet shows $0 with no banner. Since AxiosWrapper's worst case is ~23s against a 10s bound, the very first cycle of any outage hits it, and rows then blink out roughly every third cycle as the 60s negative cache expires. The timeout branch now serves the pruned last-known-good snapshot and flags the cycle degraded, since freshPricedLastRun still reflects an earlier run when the race is lost. isBstocksDegraded no longer requires lastGood to be non-empty: a cold start during an outage had nothing cached, so it reported a healthy service that was serving no bStocks at all. It now returns false when the feature is disabled, so "off" stays distinguishable from "broken". Also makes BSTOCKS_ENABLED case-insensitive - BSTOCKS_ENABLED=FALSE silently left the feature on - and documents it in .env.example, where an operator can actually find it. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01UnpQSycV6gfbF9JqRf4x1f --- .env.example | 5 ++++ config/index.ts | 2 +- src/services/bstocks.ts | 25 +++++++++++++++++++- src/services/zelcoreRatesV2.ts | 22 +++++++++++++----- tests/bstocks.spec.ts | 42 +++++++++++++++++++++++++++++++++- 5 files changed, 87 insertions(+), 9 deletions(-) diff --git a/.env.example b/.env.example index b16edf3..dc73935 100644 --- a/.env.example +++ b/.env.example @@ -3,3 +3,8 @@ CRYPTO_COMPARE_KEY='YOUR_CRYPTOCOMPARE_API_KEY' LIVE_COIN_WATCH_KEY='YOUR_LIVECOINWATCH_API_KEY' NODE_ENV=development BASE_URL=http://localhost:3333 + +# Kill switch for the Binance bStocks synthetic markets in /v2/rates. +# Set to "false" (case-insensitive) to stop serving bstock-* entries without +# a redeploy. Any other value, or unset, leaves them enabled. +BSTOCKS_ENABLED=true diff --git a/config/index.ts b/config/index.ts index 688f9d0..e93df57 100644 --- a/config/index.ts +++ b/config/index.ts @@ -15,7 +15,7 @@ export const config = { binanceAssetUrl: 'https://www.binance.com/', // Env kill switch: disabling in production should not require a code // change + redeploy. Defaults to enabled when unset. - bStocksEnabled: process.env.BSTOCKS_ENABLED !== 'false', + bStocksEnabled: (process.env.BSTOCKS_ENABLED ?? '').toLowerCase() !== 'false', // How long a failed Binance request (tokenised-asset list / trading-symbol // set) is negatively-cached before retrying, so an outage doesn't re-spend // the full AxiosWrapper retry budget on every 30s refresh cycle. diff --git a/src/services/bstocks.ts b/src/services/bstocks.ts index 20880c4..226e07d 100644 --- a/src/services/bstocks.ts +++ b/src/services/bstocks.ts @@ -35,7 +35,30 @@ export function _clearLastGoodForTests(): void { * @returns Whether the bStocks pipeline is currently degraded. */ export function isBstocksDegraded(): boolean { - return freshPricedLastRun === 0 && lastGood.size > 0; + if (!config.bStocksEnabled) return false; + // A cold start during a Binance outage has nothing in lastGood yet, so + // requiring lastGood.size > 0 would report a healthy service that is + // serving no bStocks at all. Any run that priced nothing fresh while the + // feature is enabled is degraded, whether or not we have stale data. + return freshPricedLastRun === 0; +} + +/** + * The current last-known-good rows, without touching Binance. + * + * Used when a caller has given up waiting on `getBstockPrices()`. Returning + * an empty array there would drop every bStock from the response while the + * provider-level carry-forward in apiServices cannot help: bStock rows carry + * `provider: 'coingecko'` but their failure is reported under + * `errors.binance`, so nothing would carry them. + * + * @returns The last-known-good rows, stale entries already pruned. + */ +export function getLastGoodBstockPrices(): CryptoPrice[] { + const cutoff = Date.now() - config.bstocksLastGoodMaxAgeMs; + return Array.from(lastGood.values()) + .filter((e) => e.at >= cutoff) + .map((e) => e.price); } /** diff --git a/src/services/zelcoreRatesV2.ts b/src/services/zelcoreRatesV2.ts index 39de534..1fd7f6b 100644 --- a/src/services/zelcoreRatesV2.ts +++ b/src/services/zelcoreRatesV2.ts @@ -1,7 +1,7 @@ import { coinAggregatorIDs } from './coinAggregatorIDs'; import * as log from '../lib/log'; import { CoinGecko, BitPay, CryptoCompare, LiveCoinWatch } from './providers'; -import { getBstockPrices, isBstocksDegraded } from './bstocks'; +import { getBstockPrices, isBstocksDegraded, getLastGoodBstockPrices } from './bstocks'; import { PricesResponse, CryptoPrice, ICurrencyRate } from '../types'; /** @@ -152,19 +152,29 @@ export async function getAll(): Promise { } // Fetch bStock prices from Binance. Bounded to 10s so a hung/slow Binance - // outage cannot stall the refresh of every other provider behind it -- the - // race losing simply means no bStock rows this cycle, same as any other - // degraded refresh (see isBstocksDegraded below). + // outage cannot stall the refresh of every other provider behind it. + // + // Losing the race must NOT resolve to []. bStock rows carry + // provider: 'coingecko' while their failure is reported under + // errors.binance, so the provider carry-forward in apiServices can never + // protect them -- an empty result would drop every bStock from /v2/rates + // and the wallet would show them at $0 with no error banner. Serve the + // last-known-good snapshot instead, and flag the cycle as degraded, since + // the timeout branch leaves freshPricedLastRun reflecting a previous run. + let raceLost = false; try { const bstocks = await Promise.race([ getBstockPrices(), - timeout(10_000).then((): CryptoPrice[] => []), + timeout(10_000).then((): CryptoPrice[] => { + raceLost = true; + return getLastGoodBstockPrices(); + }), ]); processed.push(...bstocks); // getBstockPrices() never rejects -- every failure inside it is caught // internally -- so a total Binance outage looks identical to a healthy // refresh unless we ask it directly whether it degraded this cycle. - if (isBstocksDegraded()) { + if (raceLost || isBstocksDegraded()) { errors.binance = true; } } catch (e) { diff --git a/tests/bstocks.spec.ts b/tests/bstocks.spec.ts index 4b23acb..a1f202b 100644 --- a/tests/bstocks.spec.ts +++ b/tests/bstocks.spec.ts @@ -1,6 +1,6 @@ import { Binance } from '../src/services/providers/binance'; import { - getBstockPrices, _clearLastGoodForTests, isBstocksDegraded, + getBstockPrices, getLastGoodBstockPrices, _clearLastGoodForTests, isBstocksDegraded, } from '../src/services/bstocks'; jest.mock('../src/services/providers/binance'); @@ -223,3 +223,43 @@ describe('bStocks assembler', () => { }); }); }); + +describe('bStocks degradation signalling and last-good snapshot', () => { + beforeEach(() => _clearLastGoodForTests()); + + // Regression guard: the 10s race in zelcoreRatesV2 used to resolve to [] on + // timeout. bStock rows carry provider "coingecko" but their failure is + // reported under errors.binance, so apiServices' provider carry-forward can + // never protect them -- every bStock would vanish from /v2/rates while the + // wallet showed $0 with no banner. The snapshot below is what the timeout + // branch serves instead. + it('exposes a last-good snapshot without touching Binance', async () => { + mockBinance({ + assets: [TSLAB], + trading: ['TSLABUSDT', 'BTCUSDT'], + t24: [ + { symbol: 'TSLABUSDT', lastPrice: '326.11', priceChangePercent: '2.5', quoteVolume: '1000000' }, + { symbol: 'BTCUSDT', lastPrice: '65222.00', priceChangePercent: '1.0', quoteVolume: '9' }, + ], + t7d: [{ symbol: 'TSLABUSDT', lastPrice: '326.11', priceChangePercent: '7.1', quoteVolume: '0' }], + }); + await getBstockPrices(); + + const snapshot = getLastGoodBstockPrices(); + expect(snapshot).toHaveLength(1); + expect(snapshot[0].id).toBe('bstock-tslab'); + expect(snapshot[0].provider).toBe('coingecko'); + expect(snapshot[0].rates.usd).toBeCloseTo(326.11); + }); + + it('reports degraded on a cold start with no last-good data at all', async () => { + // A fresh deploy while Binance is down has nothing cached. Requiring + // lastGood to be non-empty would report a healthy service serving zero + // bStocks -- silent, and exactly when someone needs to know. + mockBinance({ + assets: [], trading: [], t24: [], t7d: [], + }); + expect(await getBstockPrices()).toHaveLength(0); + expect(isBstocksDegraded()).toBe(true); + }); +}); From f771c1caf2d052399f9b90b7e4c1050a5c1218fb Mon Sep 17 00:00:00 2001 From: Stultus Mundi Date: Fri, 21 Aug 2026 14:06:14 +0300 Subject: [PATCH 10/12] fix: stop a carried Binance price from counting as a fresh quote mergeTickers backfills a symbol's last-known-good ticker whenever its fresh value is unusable, and returns a plain BinanceTicker either way, so getBstockPrices could not tell a live quote from a carried one: any positive price incremented freshCount and re-stamped lastGood with `at: now`. Both staleness defenses in the assembler read that timestamp, so both stayed disabled for as long as the carry lasted: - isBstocksDegraded() is freshPricedLastRun === 0, so a run that priced nothing live still reported a healthy service and errors.binance was never set -- no banner, no signal, indefinitely. - the bstocksLastGoodMaxAgeMs sweep compares now - at, and `at` moved forward on every ~30s refresh, so an entry could never reach the 7-day bound. A symbol that never prices again would be served at its frozen price forever, which is exactly what that bound exists to prevent. The reachable trigger is a ticker-endpoint failure while exchangeInfo still answers: Binance rate-limits the heavy /ticker routes (418/429) long before exchangeInfo stops responding, and getTradingSymbols' 1h cache keeps the symbol in `tradable` through an outright outage besides. A BREAK-status halt is NOT the trigger -- the symbol leaves the TRADING set, so its zeroed ticker is never consulted and the entry ages out correctly, bar the <=1h the cached set lags. Binance now exposes pricedFresh(symbol, window). The last-good store already records when each symbol last priced, and a batch answered from quoteCache legitimately carries a price up to one 60s TTL old, so that TTL is the bound between live and carried -- no new tunable, and it moves with the cache if the TTL ever changes. getBstockPrices counts only live quotes toward freshCount and stamps `at` at the time the price was actually live (now - lastGoodAgeMs) rather than the time it was re-served. tests/bstocksStaleness.spec.ts drives the real provider with only the HTTP layer mocked; tests/bstocks.spec.ts mocks the Binance class wholesale and so cannot see this seam at all. Its two tests were each verified to fail against a revert of the half they cover and to pass with both halves in place. The class stub in bstocks.spec.ts gained the two new methods, modelling its raw fixtures as what they are: batches priced live this round. Generated docs are left alone. The committed typedoc tree is already stale on this branch (no pages for getLastGoodBstockPrices or isBstocksDegraded, both added after cc6bd3d), and regenerating it from the main worktree rewrites all 58 pages with commit-pinned source links -- a separate call from this fix. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01AURdZitXfR5N9A28a1b2Hp --- src/services/bstocks.ts | 24 +++++-- src/services/providers/binance.ts | 28 +++++++- tests/bstocks.spec.ts | 9 +++ tests/bstocksStaleness.spec.ts | 109 ++++++++++++++++++++++++++++++ 4 files changed, 162 insertions(+), 8 deletions(-) create mode 100644 tests/bstocksStaleness.spec.ts diff --git a/src/services/bstocks.ts b/src/services/bstocks.ts index 226e07d..8fd1d6e 100644 --- a/src/services/bstocks.ts +++ b/src/services/bstocks.ts @@ -25,9 +25,9 @@ export function _clearLastGoodForTests(): void { } /** - * True when the most recent `getBstockPrices()` call priced nothing fresh - * (every underlying Binance call failed or returned unusable data) while - * there is still last-known-good data being served. Distinguishes "Binance is + * True when the most recent `getBstockPrices()` call priced nothing fresh — + * every underlying Binance call failed, returned unusable data, or served + * only prices carried over from an earlier refresh. Distinguishes "Binance is * down and we're serving frozen prices" from a normal, healthy refresh, which * `getBstockPrices()`'s return value alone cannot express since it never * rejects and unconditionally re-emits `lastGood` either way. @@ -117,14 +117,24 @@ export async function getBstockPrices(): Promise { let freshCount = 0; tradable.forEach((asset) => { const id = `bstock-${asset.assetCode.toLowerCase()}`; - const ticker = t24Map.get(`${asset.assetCode}USDT`); + const symbol = `${asset.assetCode}USDT`; + const ticker = t24Map.get(symbol); const px = Number(ticker?.lastPrice); if (!ticker || !Number.isFinite(px) || px <= 0 || !Number.isFinite(btcUsd) || btcUsd <= 0) { return; // keep lastGood entry as-is } - freshCount += 1; + // `ticker` is whatever the provider served, and for a symbol that did not + // price this batch that is its backfilled last-known-good value (see + // Binance.mergeTickers) — a positive number indistinguishable here from a + // live quote. Only a live quote may count as fresh or move the staleness + // clock: counting a carried price hides a ticker-endpoint outage from + // isBstocksDegraded(), and stamping `at: now` for one pushes the bound + // below out of reach on every refresh, so a symbol that never prices + // again would be served at its frozen price forever. + const ageMs = binance.lastGoodAgeMs(symbol, '24h') ?? 0; + if (binance.pricedFresh(symbol, '24h')) freshCount += 1; lastGood.set(id, { - at: now, + at: now - ageMs, price: { id, provider: 'coingecko', @@ -134,7 +144,7 @@ export async function getBstockPrices(): Promise { change24h: Number(ticker.priceChangePercent) || 0, market: 0, total_supply: 0, - change7d: Number(t7dMap.get(`${asset.assetCode}USDT`)?.priceChangePercent) || 0, + change7d: Number(t7dMap.get(symbol)?.priceChangePercent) || 0, }, }); }); diff --git a/src/services/providers/binance.ts b/src/services/providers/binance.ts index 4cff3cf..a433baa 100644 --- a/src/services/providers/binance.ts +++ b/src/services/providers/binance.ts @@ -8,6 +8,11 @@ import type { BinanceTicker, BinanceTokenisedAsset } from '../../types'; // 7d ticker window is fetched per-symbol; stay far under Binance's 200-weight/request cap. const TICKER_CHUNK = 20; +// Quote cache TTL, and with it the bound on how old a served price may be and +// still count as live: a batch answered from `quoteCache` legitimately carries +// a price up to one TTL old. See `pricedFresh`. +const QUOTE_CACHE_MS = 60 * 1000; + /** * Singleton class to interact with Binance's public (no-API-key) endpoints. * @@ -59,7 +64,7 @@ export class Binance { * Cache for ticker quotes, keyed by requested symbol set: 60 seconds. * @private */ - private quoteCache = new LRU({ max: 50, ttl: 60 * 1000 }); + private quoteCache = new LRU({ max: 50, ttl: QUOTE_CACHE_MS }); /** * Last-known-good ticker per `${window}:${symbol}`, independent of @@ -188,6 +193,27 @@ export class Binance { return ages.length ? Math.min(...ages) : null; } + /** + * Whether the price currently served for a symbol comes from a live quote + * rather than the last-known-good backfill. + * + * `mergeTickers` returns a plain `BinanceTicker` whether it was fetched or + * carried, so a caller cannot tell the two apart from the returned value — + * and a carried price is a valid, positive number, which makes the + * difference invisible to any price check. A batch answered from + * `quoteCache` legitimately carries a price up to one cache TTL old, so + * anything within that window is live; past it, nothing has priced the + * symbol since, so every batch in between was backfilled. + * + * @param symbol - The Binance symbol, e.g. `TSLABUSDT`. + * @param window - Which ticker window to check (`24h` or `7d`). + * @returns True when the symbol priced live within the quote-cache window. + */ + pricedFresh(symbol: string, window: '24h' | '7d'): boolean { + const age = this.lastGoodAgeMs(symbol, window); + return age !== null && age <= QUOTE_CACHE_MS; + } + /** * Retrieves the tokenised-asset universe (bStocks), filtered to those with a BSC contract. * diff --git a/tests/bstocks.spec.ts b/tests/bstocks.spec.ts index a1f202b..fc29f82 100644 --- a/tests/bstocks.spec.ts +++ b/tests/bstocks.spec.ts @@ -10,11 +10,20 @@ const MockedBinance = Binance as jest.Mocked; function mockBinance({ assets, trading, t24, t7d }: { assets: unknown[]; trading: string[]; t24: unknown[]; t7d: unknown[]; }) { + // Every fixture here is a batch the provider priced live this round: these + // tests hand back raw ticker arrays rather than exercising the provider's + // last-known-good backfill, so a symbol in `t24` is by definition fresh and + // anything else has never priced. The carried-price path -- where the + // provider serves an old ticker that looks identical to a live one -- is + // covered in bstocksStaleness.spec.ts against the real provider. + const live = new Set((t24 as { symbol: string }[]).map((t) => t.symbol)); MockedBinance.getInstance.mockReturnValue({ getTokenisedAssets: jest.fn().mockResolvedValue(assets), getTradingSymbols: jest.fn().mockResolvedValue(new Set(trading)), getTicker24h: jest.fn().mockResolvedValue(t24), getTicker7d: jest.fn().mockResolvedValue(t7d), + lastGoodAgeMs: jest.fn((symbol: string) => (live.has(symbol) ? 0 : null)), + pricedFresh: jest.fn((symbol: string) => live.has(symbol)), } as never); } diff --git a/tests/bstocksStaleness.spec.ts b/tests/bstocksStaleness.spec.ts new file mode 100644 index 0000000..e11a947 --- /dev/null +++ b/tests/bstocksStaleness.spec.ts @@ -0,0 +1,109 @@ +import type { AxiosResponse } from 'axios'; + +/** + * Staleness accounting across the provider/assembler seam. + * + * These tests drive the REAL Binance provider and mock only the HTTP layer. + * tests/bstocks.spec.ts mocks the `Binance` class wholesale, which hides the + * interaction pinned here: the provider backfills a symbol's last-known-good + * ticker into the batch it returns, and the assembler cannot tell that from a + * live quote unless it asks how old the price is. + * + * The scenario is a ticker-endpoint outage (Binance rate-limits the heavy + * `/ticker` routes with a 418/429 long before `exchangeInfo` stops answering), + * so the symbol keeps its TRADING status and stays in the assembler's + * `tradable` set while nothing prices live. + */ + +const DAY = 24 * 60 * 60 * 1000; + +const axiosResponse = (data: T): AxiosResponse => ({ + data, + status: 200, + statusText: 'OK', + headers: {}, + config: {} as AxiosResponse['config'], +}); + +const TSLAB = { assetCode: 'TSLAB', assetName: 'Tesla (bStocks)', caList: [{ network: 'BSC', ca: '0x5b19' }] }; + +let tickersDown = false; + +function route(url: string): Promise> { + if (url.includes('get-tokenised-asset')) return Promise.resolve(axiosResponse({ data: [TSLAB] })); + if (url.includes('exchangeInfo')) { + return Promise.resolve(axiosResponse({ + symbols: [ + { symbol: 'TSLABUSDT', status: 'TRADING' }, + { symbol: 'BTCUSDT', status: 'TRADING' }, + ], + })); + } + if (tickersDown) return Promise.reject(new Error('418 rate limited')); + if (url.includes('windowSize=7d')) { + return Promise.resolve(axiosResponse([ + { symbol: 'TSLABUSDT', lastPrice: '326.11', priceChangePercent: '7.1', quoteVolume: '0' }, + ])); + } + return Promise.resolve(axiosResponse([ + { symbol: 'TSLABUSDT', lastPrice: '326.11', priceChangePercent: '2.5', quoteVolume: '1000000' }, + { symbol: 'BTCUSDT', lastPrice: '65222.00', priceChangePercent: '1.0', quoteVolume: '9' }, + ])); +} + +/** + * Both the Binance singleton's caches and the assembler's last-known-good map + * are module-level state, so each test gets its own module registry. + */ +async function loadIsolated() { + jest.resetModules(); + const { AxiosWrapper } = await import('../src/lib/axios'); + jest.spyOn(AxiosWrapper.prototype, 'get').mockImplementation(route as never); + return import('../src/services/bstocks'); +} + +describe('bStocks staleness accounting', () => { + beforeEach(() => { + // `lru-cache` reads the clock from `performance.now`, which Jest's fake + // timers leave alone; bridge it to the fake `Date` so the provider's 60s + // quote cache and 1h universe cache expire as the test advances time. + jest.useFakeTimers({ doNotFake: ['performance'] }); + jest.spyOn(performance, 'now').mockImplementation(() => Date.now()); + tickersDown = false; + }); + + afterEach(() => { + jest.restoreAllMocks(); + jest.useRealTimers(); + }); + + it('reports degraded when every symbol is served from the provider carry-forward', async () => { + const { getBstockPrices, isBstocksDegraded } = await loadIsolated(); + + await getBstockPrices(); + expect(isBstocksDegraded()).toBe(false); + + tickersDown = true; + jest.advanceTimersByTime(10 * 60 * 1000); + const prices = await getBstockPrices(); + + // Still served -- that is what last-known-good is for ... + expect(prices.map((p) => p.id)).toEqual(['bstock-tslab']); + // ... but nothing priced live this run, so the outage has to be visible. + expect(isBstocksDegraded()).toBe(true); + }); + + it('expires a carried price once it passes the staleness bound', async () => { + const { getBstockPrices } = await loadIsolated(); + + await getBstockPrices(); // priced live at 326.11 + + tickersDown = true; + jest.advanceTimersByTime(4 * DAY); + await getBstockPrices(); // carried, four days stale -- must not reset the clock + jest.advanceTimersByTime(4 * DAY); + const prices = await getBstockPrices(); // eight days since the last live quote + + expect(prices).toEqual([]); + }); +}); From 5def8d06e95340e8311f45bc02f5ec565df0e274 Mon Sep 17 00:00:00 2001 From: Stultus Mundi Date: Fri, 21 Aug 2026 15:37:28 +0300 Subject: [PATCH 11/12] chore(lint): make ESLint actually lint TypeScript, and fix what it found `npm run lint` has not been checking any TypeScript in this repo. .eslintrc.js set `parserOptions.parser: 'babel-eslint'` -- a vue-cli idiom that plain ESLint ignores, pointing at a package that isn't installed -- so ESLint fell back to espree and failed on the first type annotation it met: src/lib/utils.ts 15:31 error Parsing error: Unexpected token : One parse error per file, no rules ever evaluated. `eslint ./` also only walks .js by default, so even a working parser would have skipped the whole src/ tree. - parser and type-aware config now live in a `**/*.ts` override, so plain JS (this config, config/*.js) is still linted without needing to be part of the tsconfig project - @typescript-eslint 7 + eslint-config-airbnb-typescript, matched to the repo's TypeScript 5.4; eslint-config-airbnb-base is now a direct dependency rather than something reached through the React config - dropped eslint-config-airbnb, eslint-plugin-react and eslint-plugin-jsx-a11y: React lint plugins in a Node rates API - lint script is `eslint . --ext .js,.ts --fix` - the repo's own rules (max-len 300 above all) are applied inside the TS override too, since an override's `extends` resolves after top-level `rules` and airbnb's 100-char default would otherwise win That surfaced 313 problems; 246 were formatting and are auto-fixed here. Four rules are configured rather than obeyed, each because it argues with a choice this codebase has already made -- for..of (airbnb's objection is regenerator-runtime, which an ES2020 Node service never pays), object-curly-newline's four-property trigger (this repo allows 300-character lines), prefer-destructuring on assignments (`rates[2] = fetched[2]` does not read better destructured), and the two import rules that only fire because config/index.ts and lib/axios.ts deliberately export the same value both named and default. The rest are fixed in the code. Two were real defects, not style: - tests/productionCompareRates.spec.ts declared `const diffs = []` inside the forEach, shadowing the accumulator it was meant to fill. Every mismatch was pushed into an array that was discarded, so `expect(diffs).toEqual([])` could not fail no matter how far local rates drifted from production. The second block in the same test does it correctly. Removing the inner declaration makes the assertion mean something -- that suite needs a local server on :3333 to run, so it is worth knowing it may now actually fail. - src/lib/axios.ts returned the setTimeout handle out of a promise executor, whose return value is unreachable by construction. Also renamed a `_contract` loop variable that is used (the underscore said otherwise) and a `cgCoins` local that shadowed the module import. Deliberate idioms kept their behaviour and gained a targeted disable with the reason: mergeDeep mutates its target by contract, the provider loops are sequential to stay inside upstream rate limits, cgTokens is a module-level cache the refresher reassigns. Verified: `npx eslint . --ext .js,.ts` clean, `npx tsc --noEmit` clean, and jest reports the same 41 passing as before this commit. The two productionCompare suites still fail for the reason they failed already -- nothing is listening on localhost:3333 here. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01AURdZitXfR5N9A28a1b2Hp --- .eslintignore | 3 +- .eslintrc.js | 165 ++++++++++++++++++++---- config/index.ts | 2 +- index.ts | 2 +- jest.config.ts | 18 ++- package.json | 10 +- src/lib/axios.ts | 116 +++++++++-------- src/lib/log.ts | 4 +- src/lib/objects.ts | 4 + src/lib/server.ts | 2 +- src/lib/utils.ts | 2 +- src/routes.ts | 2 +- src/services/apiServices.ts | 8 +- src/services/bstocks.ts | 3 + src/services/coinAggregatorIDs.ts | 20 +-- src/services/newContracts.ts | 4 +- src/services/providers/binance.ts | 2 + src/services/providers/bitpay.ts | 12 +- src/services/providers/coinGecko.ts | 25 ++-- src/services/providers/cryptoCompare.ts | 18 +-- src/services/providers/index.ts | 2 +- src/services/providers/liveCoinWatch.ts | 17 +-- src/services/zelcoreMarketsUSD.ts | 5 +- src/services/zelcoreRatesV2.ts | 6 +- src/types.ts | 2 +- tests/binanceProvider.spec.ts | 2 +- tests/bstocks.spec.ts | 25 +++- tests/mergeCrypto.spec.ts | 6 +- tests/productionCompareMarkets.spec.ts | 20 +-- tests/productionCompareRates.spec.ts | 7 +- 30 files changed, 316 insertions(+), 198 deletions(-) diff --git a/.eslintignore b/.eslintignore index 26f8ce4..ef333ca 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,2 @@ -config/coinsSimple.js \ No newline at end of file +config/coinsSimple.js +docs/ diff --git a/.eslintrc.js b/.eslintrc.js index e543be8..3c2cc8e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,42 +1,151 @@ +// Rules the repo has chosen for itself. They have to be applied inside the +// TypeScript override as well: an override's `extends` is resolved after the +// top-level `rules`, so airbnb's own defaults would otherwise win there (a +// 100-character max-len, for one). +const projectRules = { + 'max-len': [ + 'error', + { + code: 300, + ignoreUrls: true, + ignoreTrailingComments: true, + }, + ], + 'no-console': 'off', + 'linebreak-style': [ + 'error', + 'unix', + ], + + // A leading underscore marks an internal or test-only name here. TypeScript's + // `private` covers real privacy on the provider classes, and `__retryCount` + // is axios's own convention for the counter it hangs off a request config. + 'no-underscore-dangle': [ + 'error', + { + allow: ['__retryCount'], + allowAfterThis: true, + enforceInMethodNames: false, + }, + ], + + // airbnb bans for..of because transpiling it used to pull in + // regenerator-runtime. This service runs ES2020 on Node and pays no such + // cost; the rest of airbnb's restrictions are kept as-is. + 'no-restricted-syntax': [ + 'error', + { + selector: 'ForInStatement', + message: 'for..in iterates the prototype chain and needs a hasOwnProperty guard. Use Object.{keys,values,entries} instead.', + }, + { + selector: 'LabeledStatement', + message: 'Labels are a form of GOTO; use a function instead.', + }, + { + selector: 'WithStatement', + message: '`with` is disallowed in strict mode and makes scope ambiguous.', + }, + ], + + // Worth enforcing when introducing a binding, but rewriting an assignment + // such as `rates[2] = fetched[2]` as destructuring reads worse than the + // line it replaces. + 'prefer-destructuring': [ + 'error', + { + VariableDeclarator: { + array: true, + object: true, + }, + AssignmentExpression: { + array: false, + object: false, + }, + }, + ], + + // This repo allows 300-character lines; airbnb's rule additionally breaks + // any object literal with four or more properties, which contradicts that + // and turns compact fixtures into three-line blocks. Keep the consistency + // checks, drop the property-count trigger. + 'object-curly-newline': [ + 'error', + { + multiline: true, + consistent: true, + }, + ], + + // config/index.ts and lib/axios.ts deliberately export the same value both + // named and default, which is the entirety of what this rule sees. + 'import/no-named-as-default': 'off', + + // Named exports are the convention here. How many exports a module happens + // to have today is not a reason to change how callers import it. + 'import/prefer-default-export': 'off', +}; + module.exports = { root: true, env: { - commonjs: true, node: true, - mocha: true, + es2022: true, + jest: true, }, extends: [ 'airbnb-base', ], - rules: { - 'max-len': [ - 'error', - { - code: 300, - ignoreUrls: true, - ignoreTrailingComments: true, - }, - ], - 'no-console': 'off', - 'import/extensions': [ - 'error', - 'never', - ], - 'linebreak-style': [ - 'error', - 'unix', - ], - }, - parserOptions: { - parser: 'babel-eslint', - }, + rules: projectRules, overrides: [ + // TypeScript sources. The parser and the type-aware config live here + // rather than at the top level so plain JS (this file, config/*.js) is + // still linted without having to be part of the tsconfig project. { - files: [ - '**/__tests__/*.{j,t}s?(x)', + files: ['**/*.ts'], + parser: '@typescript-eslint/parser', + parserOptions: { + project: ['./tsconfig.json'], + tsconfigRootDir: __dirname, + }, + plugins: [ + '@typescript-eslint', + ], + extends: [ + 'airbnb-base', + 'airbnb-typescript/base', ], - env: { - mocha: true, + settings: { + 'import/resolver': { + typescript: { + project: './tsconfig.json', + }, + }, + }, + rules: { + ...projectRules, + // TypeScript resolves module specifiers without a file extension, and + // writing one would break `module: commonjs` resolution. + 'import/extensions': [ + 'error', + 'ignorePackages', + { + ts: 'never', + js: 'never', + }, + ], + }, + }, + // Tests import jest and the other devDependencies by design. + { + files: ['tests/**/*.ts'], + rules: { + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: true, + }, + ], }, }, ], diff --git a/config/index.ts b/config/index.ts index e93df57..2e08790 100644 --- a/config/index.ts +++ b/config/index.ts @@ -27,4 +27,4 @@ export const config = { bstocksLastGoodMaxAgeMs: 7 * 24 * 60 * 60 * 1000, }; -export default config; \ No newline at end of file +export default config; diff --git a/index.ts b/index.ts index 1070b3d..b908724 100644 --- a/index.ts +++ b/index.ts @@ -16,7 +16,7 @@ const { port } = config.server; */ function startService(): void { const data = apiServices.getData(); - console.log("startService -> data", !!data.rates[0][0], !!Object.keys(data.marketsUSD[0]).length); + console.log('startService -> data', !!data.rates[0][0], !!Object.keys(data.marketsUSD[0]).length); if (data.rates[0][0] && Object.keys(data.marketsUSD[0]).length) { setTimeout(() => { server.listen(port, () => { diff --git a/jest.config.ts b/jest.config.ts index 27f1d87..c805967 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -1,11 +1,9 @@ -import type {Config} from 'jest'; +import type { Config } from 'jest'; -export default async (): Promise => { - return { - testEnvironment: 'node', - transform: { - '^.+.tsx?$': ['ts-jest', {}], - }, - rootDir: './tests', - }; -} +export default async (): Promise => ({ + testEnvironment: 'node', + transform: { + '^.+.tsx?$': ['ts-jest', {}], + }, + rootDir: './tests', +}); diff --git a/package.json b/package.json index 4119757..b7e364e 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "start": "npx nodemon index.ts", "test": "jest", - "lint": "eslint ./ --fix" + "lint": "eslint . --ext .js,.ts --fix" }, "author": "Zelcore Technologies", "license": "MIT", @@ -38,11 +38,13 @@ "@types/morgan": "^1.9.9", "@types/swagger-ui-express": "^4.1.6", "@types/yamljs": "^0.2.34", + "@typescript-eslint/eslint-plugin": "~7.18.0", + "@typescript-eslint/parser": "~7.18.0", "eslint": "~8.57.0", - "eslint-config-airbnb": "~19.0.4", + "eslint-config-airbnb-base": "~15.0.0", + "eslint-config-airbnb-typescript": "~18.0.0", + "eslint-import-resolver-typescript": "~3.6.3", "eslint-plugin-import": "~2.30.0", - "eslint-plugin-jsx-a11y": "~6.10.0", - "eslint-plugin-react": "~7.35.2", "jest": "^29.7.0", "ts-jest": "^29.2.5", "typedoc": "^0.28.0", diff --git a/src/lib/axios.ts b/src/lib/axios.ts index f864c18..7cf4546 100644 --- a/src/lib/axios.ts +++ b/src/lib/axios.ts @@ -1,4 +1,4 @@ -import axios, { AxiosInstance, AxiosRequestConfig, AxiosError } from "axios"; +import axios, { AxiosInstance, AxiosRequestConfig, AxiosError } from 'axios'; /** * A wrapper around Axios to handle automatic retries and customizable configurations. @@ -24,11 +24,13 @@ import axios, { AxiosInstance, AxiosRequestConfig, AxiosError } from "axios"; * ``` */ export class AxiosWrapper { - private axiosInstance: AxiosInstance; - private maxRetries: number; - private timeout: number; + private axiosInstance: AxiosInstance; - /** + private maxRetries: number; + + private timeout: number; + + /** * Creates an instance of AxiosWrapper. * * @param baseURL - The base URL for all requests. @@ -40,19 +42,19 @@ export class AxiosWrapper { * const apiClient = new AxiosWrapper('https://api.example.com', 5, 10000); * ``` */ - constructor(baseURL: string, maxRetries: number = 3, timeout: number = 5000) { - this.maxRetries = maxRetries; - this.timeout = timeout; + constructor(baseURL: string, maxRetries: number = 3, timeout: number = 5000) { + this.maxRetries = maxRetries; + this.timeout = timeout; - this.axiosInstance = axios.create({ - baseURL, - timeout: this.timeout, - }); + this.axiosInstance = axios.create({ + baseURL, + timeout: this.timeout, + }); - this.initializeInterceptors(); - } + this.initializeInterceptors(); + } - /** + /** * Initializes response interceptors to handle retries for failed requests. * * This method sets up an interceptor that listens for response errors @@ -60,14 +62,14 @@ export class AxiosWrapper { * * @private */ - private initializeInterceptors() { - this.axiosInstance.interceptors.response.use( - response => response, - (error: AxiosError) => this.handleRetry(error) - ); - } + private initializeInterceptors() { + this.axiosInstance.interceptors.response.use( + (response) => response, + (error: AxiosError) => this.handleRetry(error), + ); + } - /** + /** * Handles retry logic for failed requests. * * If a request fails, this method checks if the maximum number of retries @@ -77,28 +79,28 @@ export class AxiosWrapper { * @param error - The error received from a failed request. * @returns A promise that resolves with the retried request or rejects with the error. */ - private async handleRetry(error: AxiosError): Promise { - const config = error.config as AxiosRequestConfig & { __retryCount?: number }; - - // Check if retry has been initialized - if (!config.__retryCount) { - config.__retryCount = 0; - } - - // If max retries have not been met, retry the request - if (config.__retryCount < this.maxRetries) { - config.__retryCount += 1; - // Delay before retrying - return new Promise((resolve) => - setTimeout(() => resolve(this.axiosInstance(config)), 1000) - ); - } - - // If max retries exceeded, reject the promise - return Promise.reject(error); + private async handleRetry(error: AxiosError): Promise { + const config = error.config as AxiosRequestConfig & { __retryCount?: number }; + + // Check if retry has been initialized + if (!config.__retryCount) { + config.__retryCount = 0; + } + + // If max retries have not been met, retry the request + if (config.__retryCount < this.maxRetries) { + config.__retryCount += 1; + // Delay before retrying + return new Promise((resolve) => { + setTimeout(() => resolve(this.axiosInstance(config)), 1000); + }); } - /** + // If max retries exceeded, reject the promise + return Promise.reject(error); + } + + /** * Performs a GET request. * * @param url - The URL to send the GET request to. @@ -112,11 +114,11 @@ export class AxiosWrapper { * .catch(error => console.error(error)); * ``` */ - public async get(url: string, config?: AxiosRequestConfig) { - return this.axiosInstance.get(url, config); - } + public async get(url: string, config?: AxiosRequestConfig) { + return this.axiosInstance.get(url, config); + } - /** + /** * Performs a POST request. * * @param url - The URL to send the POST request to. @@ -131,11 +133,11 @@ export class AxiosWrapper { * .catch(error => console.error(error)); * ``` */ - public async post(url: string, data?: any, config?: AxiosRequestConfig) { - return this.axiosInstance.post(url, data, config); - } + public async post(url: string, data?: any, config?: AxiosRequestConfig) { + return this.axiosInstance.post(url, data, config); + } - /** + /** * Performs a PUT request. * * @param url - The URL to send the PUT request to. @@ -150,11 +152,11 @@ export class AxiosWrapper { * .catch(error => console.error(error)); * ``` */ - public async put(url: string, data?: any, config?: AxiosRequestConfig) { - return this.axiosInstance.put(url, data, config); - } + public async put(url: string, data?: any, config?: AxiosRequestConfig) { + return this.axiosInstance.put(url, data, config); + } - /** + /** * Performs a DELETE request. * * @param url - The URL to send the DELETE request to. @@ -168,9 +170,9 @@ export class AxiosWrapper { * .catch(error => console.error(error)); * ``` */ - public async delete(url: string, config?: AxiosRequestConfig) { - return this.axiosInstance.delete(url, config); - } + public async delete(url: string, config?: AxiosRequestConfig) { + return this.axiosInstance.delete(url, config); + } } export default AxiosWrapper; diff --git a/src/lib/log.ts b/src/lib/log.ts index ca201d7..81062a2 100644 --- a/src/lib/log.ts +++ b/src/lib/log.ts @@ -69,8 +69,8 @@ function writeToFile(filepath: string, args: { message?: string; stack?: string const stream = fs.createWriteStream(filepath, { flags: flag }); stream.write( `${new Date().toISOString()} ${ensureString( - typeof args === 'object' && args.message ? args.message : args - )}\n` + typeof args === 'object' && args.message ? args.message : args, + )}\n`, ); if (typeof args === 'object' && args.stack && typeof args.stack === 'string') { stream.write(`${args.stack}\n`); diff --git a/src/lib/objects.ts b/src/lib/objects.ts index e87b4f5..7b6af97 100644 --- a/src/lib/objects.ts +++ b/src/lib/objects.ts @@ -17,6 +17,9 @@ * // result: { a: 1, b: { c: 2, d: 3 }, e: 4 } * ``` */ +// mergeDeep merges INTO `target` and returns it -- mutating the argument is +// the documented contract callers rely on, not an oversight. +/* eslint-disable no-param-reassign */ export function mergeDeep(target: any, source: any) { if (Array.isArray(source)) { if (!Array.isArray(target)) { @@ -45,6 +48,7 @@ export function mergeDeep(target: any, source: any) { } return target; } +/* eslint-enable no-param-reassign */ /** * Rebuilds the crypto array from `source` alone, de-duplicated by diff --git a/src/lib/server.ts b/src/lib/server.ts index bc0148d..6c29ca0 100644 --- a/src/lib/server.ts +++ b/src/lib/server.ts @@ -86,7 +86,7 @@ app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); * * @remarks * The `routes` function is responsible for setting up all the necessary routes in the Express application. - * + * * @param app - The Express application instance. * * @example diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 2f22fa6..04cfab5 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -53,4 +53,4 @@ export function makeRequestStrings(elements: string[], maxLength: number): strin } }); return result; -} \ No newline at end of file +} diff --git a/src/routes.ts b/src/routes.ts index cf6327d..d22015b 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -30,7 +30,7 @@ export default (app: Application): void => { metricsPath: '/metrics', collectDefaultMetrics: true, requestDurationBuckets: [0.1, 0.5, 1, 1.5], - }) + }), ); /** diff --git a/src/services/apiServices.ts b/src/services/apiServices.ts index 2d4c28a..21d2daf 100644 --- a/src/services/apiServices.ts +++ b/src/services/apiServices.ts @@ -155,7 +155,7 @@ export function getFoundContracts(): FoundContractStore { */ export async function checkContractsV2(req: Request, res: Response): Promise { try { - const contracts = req.body.contracts; + const { contracts } = req.body; const success = checkContracts(contracts); res.json({ success }); } catch (error) { @@ -201,7 +201,7 @@ export async function dataRefresher(): Promise { dataRefresher(); }, 60 * 60 * 1000); // 1 hour } catch (error) { - log.error("Error in dataRefresher"); + log.error('Error in dataRefresher'); log.error(error); setTimeout(() => { dataRefresher(); @@ -227,14 +227,14 @@ export async function serviceRefresher(): Promise { const ratesFetched = await zelcoreRates.getAll(); const marketsUSDFetched = await zelcoreMarketsUSD.getAll(); const ratesV2Fetched = await zelcoreRatesV2.getAll(); - + if (ratesFetched && ratesFetched[0]?.length > 20 && ratesFetched[1]) { if (Object.keys(ratesFetched[1]).length > 300) { rates = mergeDeep(rates, ratesFetched); rates[2] = ratesFetched[2]; // replace errors } } - + if (marketsUSDFetched && marketsUSDFetched[0]) { log.info(Object.keys(marketsUSDFetched[0])); log.info(Object.keys(marketsUSDFetched[0]).length); diff --git a/src/services/bstocks.ts b/src/services/bstocks.ts index 8fd1d6e..80c562e 100644 --- a/src/services/bstocks.ts +++ b/src/services/bstocks.ts @@ -19,6 +19,9 @@ let lastGood = new Map(); // exposes that distinction so a caller can surface it (e.g. errors.binance). let freshPricedLastRun = 0; +// The leading underscore marks this as a test-only escape hatch rather than +// part of the module's API; nothing in src/ calls it. +// eslint-disable-next-line no-underscore-dangle, @typescript-eslint/naming-convention export function _clearLastGoodForTests(): void { lastGood = new Map(); freshPricedLastRun = 0; diff --git a/src/services/coinAggregatorIDs.ts b/src/services/coinAggregatorIDs.ts index 00e00aa..9c3c997 100644 --- a/src/services/coinAggregatorIDs.ts +++ b/src/services/coinAggregatorIDs.ts @@ -18,9 +18,9 @@ export const coinAggregatorIDs = { * Add the CryptoCompare IDs at the end of this list. */ cryptoCompare: [ - 'CONI', 'PAX', 'SPHTX', 'GVT', 'INS', 'MDA', 'QSP', 'SNGLS', 'TNB', 'WABI', 'DGD', 'TENT', 'BBO', 'ICN', 'MCO', 'EDO', 'WINGS', 'DTA', 'ADT', 'ATL', + 'CONI', 'PAX', 'SPHTX', 'GVT', 'INS', 'MDA', 'QSP', 'SNGLS', 'TNB', 'WABI', 'DGD', 'TENT', 'BBO', 'ICN', 'MCO', 'EDO', 'WINGS', 'DTA', 'ADT', 'ATL', 'BCPT', 'BTH', 'USDS', 'VIDT', 'VBK', 'UST', 'GTO', 'ONGAS', 'MIOTA', 'TOK', - 'GNT', 'AGI', 'ETHOS', 'BSV', 'AMB', 'SIN', 'QTUM', 'XEM', 'XCASH', // These are not actually used in ZelCore or some tickers; just for testing until merge + 'GNT', 'AGI', 'ETHOS', 'BSV', 'AMB', 'SIN', 'QTUM', 'XEM', 'XCASH', // These are not actually used in ZelCore or some tickers; just for testing until merge ], /** * CoinGecko API IDs. @@ -67,6 +67,8 @@ export const zelData: { /** * Array of CoinGecko tokens. */ +// Reassigned wholesale by the refresher below once CoinGecko answers. +// eslint-disable-next-line import/no-mutable-exports export let cgTokens: CoinGeckoToken[] = cgCoins; /** @@ -99,13 +101,13 @@ export async function getLatestCoinInfo(): Promise { const uniqueCoinGeckoKeys = [...new Set(coinGeckoKeys)]; coinAggregatorIDs.coingecko = [...new Set([...coinAggregatorIDs.coingecko, ...uniqueCoinGeckoKeys])]; zelData.coinInfo = coinInfo; - const cgCoins = await CoinGecko.getInstance().getCoinsList(); - if (cgCoins) { - cgTokens = cgCoins as CoinGeckoToken[]; - cgCoins.forEach((coin: CoinGeckoToken) => { - for (const _contract of Object.values(coin.platforms)) { - if (_contract) { - cgContractMap[_contract] = coin; + const coinsList = await CoinGecko.getInstance().getCoinsList(); + if (coinsList) { + cgTokens = coinsList as CoinGeckoToken[]; + coinsList.forEach((coin: CoinGeckoToken) => { + for (const contract of Object.values(coin.platforms)) { + if (contract) { + cgContractMap[contract] = coin; } } }); diff --git a/src/services/newContracts.ts b/src/services/newContracts.ts index b0ae26c..866c970 100644 --- a/src/services/newContracts.ts +++ b/src/services/newContracts.ts @@ -35,7 +35,7 @@ export function checkContracts(contracts: ContractWithType[]): boolean { const cg = cgContractMap[contract.address]; if (cg) { if (foundContracts[contract.address]) { - foundContracts[contract.address].count++; + foundContracts[contract.address].count += 1; } else { foundContracts[contract.address] = { zel: contract, cg, count: 1 }; } @@ -46,4 +46,4 @@ export function checkContracts(contracts: ContractWithType[]): boolean { log.error(error); return false; } -} \ No newline at end of file +} diff --git a/src/services/providers/binance.ts b/src/services/providers/binance.ts index a433baa..d6f236c 100644 --- a/src/services/providers/binance.ts +++ b/src/services/providers/binance.ts @@ -120,6 +120,7 @@ export class Binance { * @param assets - The raw tokenised-asset list from Binance. * @returns Only the assets with at least one BSC entry in `caList`. */ + // eslint-disable-next-line class-methods-use-this -- pure helper, but part of the provider's instance API like the rest of the class. filterBscAssets(assets: BinanceTokenisedAsset[]): BinanceTokenisedAsset[] { return (assets || []).filter((a) => (a.caList || []) .some((c) => String(c.network).toUpperCase() === 'BSC' && !!c.ca)); @@ -132,6 +133,7 @@ export class Binance { * @param symbols - The full symbol list to split. * @returns An array of symbol chunks. */ + // eslint-disable-next-line class-methods-use-this -- pure helper, but part of the provider's instance API like the rest of the class. chunkSymbols(symbols: string[]): string[][] { return arraySplit(symbols, TICKER_CHUNK); } diff --git a/src/services/providers/bitpay.ts b/src/services/providers/bitpay.ts index 9b85e2d..6beea0a 100644 --- a/src/services/providers/bitpay.ts +++ b/src/services/providers/bitpay.ts @@ -1,6 +1,6 @@ -import AxiosWrapper from "../../lib/axios"; -import config from "../../../config"; import { LRUCache as LRU } from 'lru-cache'; +import AxiosWrapper from '../../lib/axios'; +import config from '../../../config'; /** * Singleton class to interact with the BitPay API. @@ -57,7 +57,7 @@ export class BitPay { */ constructor() { if (BitPay.instance) { - throw new Error("Use BitPay.getInstance()"); + throw new Error('Use BitPay.getInstance()'); } BitPay.instance = this; BitPay.axiosWrapper = new AxiosWrapper(config.bitPayUrl); @@ -115,17 +115,17 @@ export class BitPay { public async getFiatRates(): Promise { const cacheKey = 'fiatRates'; const cachedRates = this.cache.get(cacheKey); - + if (cachedRates) { return cachedRates; } try { const response = await this.get('rates/BTC'); - const data = response.data.data; + const { data } = response.data; this.cache.set(cacheKey, data); - + return data; } catch (error) { return null; diff --git a/src/services/providers/coinGecko.ts b/src/services/providers/coinGecko.ts index 519f71c..2831fd0 100644 --- a/src/services/providers/coinGecko.ts +++ b/src/services/providers/coinGecko.ts @@ -1,9 +1,9 @@ -import AxiosWrapper from "../../lib/axios"; -import config from "../../../config"; -import * as log from "../../lib/log"; -import { arraySplit } from "../../lib/utils"; import { LRUCache as LRU } from 'lru-cache'; -import { CoinGeckoPrice } from "../../types"; +import AxiosWrapper from '../../lib/axios'; +import config from '../../../config'; +import * as log from '../../lib/log'; +import { arraySplit } from '../../lib/utils'; +import { CoinGeckoPrice } from '../../types'; const MAX_IDS_PER_REQUEST = 250; @@ -42,7 +42,7 @@ export class CoinGecko { * The API key for authenticating with the CoinGecko API. * @private */ - private readonly apiKey: string = process.env['COIN_GECKO_KEY'] || config.coinGeckoApiKey; + private readonly apiKey: string = process.env.COIN_GECKO_KEY || config.coinGeckoApiKey; /** * The singleton instance of the CoinGecko class. @@ -80,7 +80,7 @@ export class CoinGecko { */ constructor() { if (CoinGecko.instance) { - throw new Error("Use CoinGecko.getInstance()"); + throw new Error('Use CoinGecko.getInstance()'); } CoinGecko.instance = this; CoinGecko.axiosWrapper = new AxiosWrapper(config.coinGeckoUrl); @@ -145,7 +145,7 @@ export class CoinGecko { try { const response = await this.get('key'); - const data = response.data; + const { data } = response; this.cache.set(cacheKey, data); @@ -180,7 +180,7 @@ export class CoinGecko { try { const response = await this.get('coins/list', { include_platform: includePlatform }); - const data = response.data; + const { data } = response; this.cache.set(cacheKey, data); @@ -214,7 +214,7 @@ export class CoinGecko { try { const response = await this.get('asset_platforms'); - const data = response.data; + const { data } = response; this.cache.set(cacheKey, data); @@ -244,7 +244,7 @@ export class CoinGecko { const response = await this.get('coins/markets', { vs_currency: vsCurrency, - ids: ids, + ids, order: 'market_cap_desc', per_page: 250, page: 1, @@ -252,7 +252,7 @@ export class CoinGecko { price_change_percentage: '7d', }); - const data = response.data; + const { data } = response; this.cache.set(cacheKey, data); @@ -280,6 +280,7 @@ export class CoinGecko { const allRates: CoinGeckoPrice[] = []; for (const id of newIds) { + // eslint-disable-next-line no-await-in-loop -- deliberately sequential: one id per request keeps us inside the upstream rate limit. const response = await this._getExchangeRates(id, vsCurrency); allRates.push(...response); } diff --git a/src/services/providers/cryptoCompare.ts b/src/services/providers/cryptoCompare.ts index 7996ee5..e23cedc 100644 --- a/src/services/providers/cryptoCompare.ts +++ b/src/services/providers/cryptoCompare.ts @@ -1,8 +1,8 @@ -import AxiosWrapper from "../../lib/axios"; -import config from "../../../config"; -import { makeRequestStrings } from "../../lib/utils"; import { LRUCache as LRU } from 'lru-cache'; -import { CryptoCompareMarkets, CryptoComparePrice } from "../../types"; +import AxiosWrapper from '../../lib/axios'; +import config from '../../../config'; +import { makeRequestStrings } from '../../lib/utils'; +import { CryptoCompareMarkets, CryptoComparePrice } from '../../types'; const MAX_LENGTH_PER_REQUEST = 300; @@ -30,7 +30,7 @@ export class CryptoCompare { * The API key for authenticating with the CryptoCompare API. * @private */ - private readonly apiKey: string = process.env['CRYPTO_COMPARE_KEY'] || config.cryptoCompareApiKey; + private readonly apiKey: string = process.env.CRYPTO_COMPARE_KEY || config.cryptoCompareApiKey; /** * The singleton instance of the CryptoCompare class. @@ -50,7 +50,7 @@ export class CryptoCompare { */ private readonly headers = { 'Content-Type': 'application/json', - 'authorization': `Apikey ${this.apiKey}`, + authorization: `Apikey ${this.apiKey}`, }; /** @@ -68,7 +68,7 @@ export class CryptoCompare { */ constructor() { if (CryptoCompare.instance) { - throw new Error("Use CryptoCompare.getInstance()"); + throw new Error('Use CryptoCompare.getInstance()'); } CryptoCompare.instance = this; CryptoCompare.axiosWrapper = new AxiosWrapper(config.cryptoCompareUrl); @@ -136,7 +136,7 @@ export class CryptoCompare { fsyms: ids, }); - const data: CryptoComparePrice = response.data; + const { data } = response; // Store in cache this.cache.set(cacheKey, data); @@ -165,6 +165,7 @@ export class CryptoCompare { let allRates: CryptoComparePrice = {}; for (const id of newIds) { + // eslint-disable-next-line no-await-in-loop -- deliberately sequential: one id per request keeps us inside the upstream rate limit. const response = await this._getExchangeRates(id, vsCurrency); allRates = { ...allRates, ...response }; } @@ -228,6 +229,7 @@ export class CryptoCompare { let allData: CryptoCompareMarkets = {}; for (const id of newIds) { + // eslint-disable-next-line no-await-in-loop -- deliberately sequential: one id per request keeps us inside the upstream rate limit. const response = await this._getMarketData(id, vsCurrency); allData = { ...allData, ...response }; } diff --git a/src/services/providers/index.ts b/src/services/providers/index.ts index 17f8911..2a60ef0 100644 --- a/src/services/providers/index.ts +++ b/src/services/providers/index.ts @@ -2,4 +2,4 @@ export { CoinGecko } from './coinGecko'; export { CryptoCompare } from './cryptoCompare'; export { BitPay } from './bitpay'; export { LiveCoinWatch } from './liveCoinWatch'; -export { Binance } from './binance'; \ No newline at end of file +export { Binance } from './binance'; diff --git a/src/services/providers/liveCoinWatch.ts b/src/services/providers/liveCoinWatch.ts index ed3f02b..6bd4349 100644 --- a/src/services/providers/liveCoinWatch.ts +++ b/src/services/providers/liveCoinWatch.ts @@ -1,8 +1,8 @@ -import AxiosWrapper from "../../lib/axios"; -import config from "../../../config"; -import { makeRequestStrings } from "../../lib/utils"; import { LRUCache as LRU } from 'lru-cache'; -import { LiveCoinWatchMarket } from "../../types"; +import AxiosWrapper from '../../lib/axios'; +import config from '../../../config'; +import { makeRequestStrings } from '../../lib/utils'; +import { LiveCoinWatchMarket } from '../../types'; const MAX_LENGTH_PER_REQUEST = 300; @@ -30,7 +30,7 @@ export class LiveCoinWatch { * The API key for authenticating with the LiveCoinWatch API. * @private */ - private readonly apiKey: string = process.env['LIVE_COIN_WATCH_KEY'] || config.liveCoinWatchApiKey; + private readonly apiKey: string = process.env.LIVE_COIN_WATCH_KEY || config.liveCoinWatchApiKey; /** * The singleton instance of the LiveCoinWatch class. @@ -68,7 +68,7 @@ export class LiveCoinWatch { */ constructor() { if (LiveCoinWatch.instance) { - throw new Error("Use LiveCoinWatch.getInstance()"); + throw new Error('Use LiveCoinWatch.getInstance()'); } LiveCoinWatch.instance = this; LiveCoinWatch.axiosWrapper = new AxiosWrapper(config.liveCoinWatchUrl); @@ -164,10 +164,11 @@ export class LiveCoinWatch { public async getExchangeRates(ids: string[], vsCurrency = 'BTC'): Promise { const newIds = makeRequestStrings(ids, MAX_LENGTH_PER_REQUEST); let allRates: LiveCoinWatchMarket[] = []; - + for (const id of newIds) { + // eslint-disable-next-line no-await-in-loop -- deliberately sequential: one id per request keeps us inside the upstream rate limit. const response = await this._getExchangeRates(id, vsCurrency); - allRates = [ ...allRates, ...response ]; + allRates = [...allRates, ...response]; } return allRates; diff --git a/src/services/zelcoreMarketsUSD.ts b/src/services/zelcoreMarketsUSD.ts index 255f232..0c41c73 100644 --- a/src/services/zelcoreMarketsUSD.ts +++ b/src/services/zelcoreMarketsUSD.ts @@ -19,7 +19,6 @@ import { MarketsData, IErrorObject, CoinGeckoPrice, LiveCoinWatchMarket, Currenc * ``` */ export async function getAll(): Promise { - const markets: MarketsData = [{}, { errors: {} }]; const cmk: CurrencyMap = {}; const errors: IErrorObject = { errors: {} }; @@ -64,7 +63,7 @@ export async function getAll(): Promise { log.error(e); errors.errors.coingecko = true; } - + // Fetch results from LiveCoinWatch try { const livecoinwatch = await LiveCoinWatch.getInstance().getExchangeRates(coinAggregatorIDs.livecoinwatch, 'USD'); @@ -138,4 +137,4 @@ export async function getAll(): Promise { export default { getAll, -}; \ No newline at end of file +}; diff --git a/src/services/zelcoreRatesV2.ts b/src/services/zelcoreRatesV2.ts index 1fd7f6b..8b49c7a 100644 --- a/src/services/zelcoreRatesV2.ts +++ b/src/services/zelcoreRatesV2.ts @@ -48,7 +48,7 @@ export async function getAll(): Promise { const processed: CryptoPrice[] = []; const fiat: ICurrencyRate[] = []; const errors: Record = {}; - + // Fetch fiat rates from BitPay try { const bitpayRates = await BitPay.getInstance().getFiatRates(); @@ -90,7 +90,7 @@ export async function getAll(): Promise { log.error(e); errors.coingecko = true; } - + // Fetch cryptocurrency prices from CryptoCompare try { const cryptocompare = await CryptoCompare.getInstance().getMarketData(coinAggregatorIDs.cryptoCompare); @@ -118,7 +118,7 @@ export async function getAll(): Promise { log.error(e); errors.cryptocompare = true; } - + // Fetch cryptocurrency prices from LiveCoinWatch try { const livecoinwatch = await LiveCoinWatch.getInstance().getExchangeRates(coinAggregatorIDs.livecoinwatch); diff --git a/src/types.ts b/src/types.ts index 225eeb6..3ef9a2f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -248,4 +248,4 @@ export type LiveCoinWatchMarket = { quarter: number | null; year: number | null; }; -}; \ No newline at end of file +}; diff --git a/tests/binanceProvider.spec.ts b/tests/binanceProvider.spec.ts index 59ca5be..8cdb9f8 100644 --- a/tests/binanceProvider.spec.ts +++ b/tests/binanceProvider.spec.ts @@ -92,7 +92,7 @@ describe('Binance provider', () => { const result = await binance.getTicker24h(['TSLABUSDT', 'NVDABUSDT']); expect(result).toEqual(raw); expect(getSpy).toHaveBeenCalledTimes(1); - const calledUrl = getSpy.mock.calls[0][0]; + const [[calledUrl]] = getSpy.mock.calls; expect(calledUrl).toBe(`api/v3/ticker/24hr?symbols=${encodeURIComponent(JSON.stringify(['TSLABUSDT', 'NVDABUSDT']))}`); await binance.getTicker24h(['TSLABUSDT', 'NVDABUSDT']); diff --git a/tests/bstocks.spec.ts b/tests/bstocks.spec.ts index fc29f82..e31c89a 100644 --- a/tests/bstocks.spec.ts +++ b/tests/bstocks.spec.ts @@ -57,15 +57,21 @@ describe('bStocks assembler', () => { }); it('skips assets without a TRADING symbol', async () => { - mockBinance({ assets: [TSLAB], trading: ['BTCUSDT'], t24: [ - { symbol: 'BTCUSDT', lastPrice: '65222.00', priceChangePercent: '1.0', quoteVolume: '9' }, - ], t7d: [] }); + mockBinance({ + assets: [TSLAB], + trading: ['BTCUSDT'], + t24: [ + { symbol: 'BTCUSDT', lastPrice: '65222.00', priceChangePercent: '1.0', quoteVolume: '9' }, + ], + t7d: [], + }); expect(await getBstockPrices()).toHaveLength(0); }); it('serves last-known-good when a symbol disappears (CEX halt)', async () => { mockBinance({ - assets: [TSLAB], trading: ['TSLABUSDT', 'BTCUSDT'], + assets: [TSLAB], + trading: ['TSLABUSDT', 'BTCUSDT'], t24: [ { symbol: 'TSLABUSDT', lastPrice: '326.11', priceChangePercent: '2.5', quoteVolume: '1000000' }, { symbol: 'BTCUSDT', lastPrice: '65222.00', priceChangePercent: '1.0', quoteVolume: '9' }, @@ -74,9 +80,14 @@ describe('bStocks assembler', () => { }); await getBstockPrices(); // Halt: ticker omits TSLABUSDT this round - mockBinance({ assets: [TSLAB], trading: ['TSLABUSDT', 'BTCUSDT'], t24: [ - { symbol: 'BTCUSDT', lastPrice: '65000.00', priceChangePercent: '0.5', quoteVolume: '9' }, - ], t7d: [] }); + mockBinance({ + assets: [TSLAB], + trading: ['TSLABUSDT', 'BTCUSDT'], + t24: [ + { symbol: 'BTCUSDT', lastPrice: '65000.00', priceChangePercent: '0.5', quoteVolume: '9' }, + ], + t7d: [], + }); const prices = await getBstockPrices(); expect(prices).toHaveLength(1); expect(prices[0].rates.usd).toBeCloseTo(326.11); diff --git a/tests/mergeCrypto.spec.ts b/tests/mergeCrypto.spec.ts index 994da42..ded9850 100644 --- a/tests/mergeCrypto.spec.ts +++ b/tests/mergeCrypto.spec.ts @@ -3,10 +3,6 @@ import { CryptoPrice } from '../src/types'; describe('replaceCryptoByKey', () => { it('replaces entries by provider-id key, not by index', () => { - const target = [ - { id: 'bitcoin', provider: 'coingecko', rates: { btc: 1 } }, - { id: 'stale', provider: 'livecoinwatch', rates: { btc: 9 } }, - ]; const source = [ { id: 'bstock-tslab', provider: 'coingecko', rates: { btc: 0.005 } }, { id: 'bitcoin', provider: 'coingecko', rates: { btc: 1.0001 } }, @@ -14,7 +10,7 @@ describe('replaceCryptoByKey', () => { const merged = replaceCryptoByKey(source); expect(merged).toHaveLength(2); expect(merged.find((e) => e.id === 'bitcoin')!.rates.btc).toBe(1.0001); - expect(merged.find((e) => e.id === 'stale')).toBeUndefined(); // stale tails dropped + expect(merged.find((e) => e.id === 'stale')).toBeUndefined(); // anything absent from the fresh array is dropped, not carried }); }); diff --git a/tests/productionCompareMarkets.spec.ts b/tests/productionCompareMarkets.spec.ts index 86d99ff..f01eefe 100644 --- a/tests/productionCompareMarkets.spec.ts +++ b/tests/productionCompareMarkets.spec.ts @@ -17,17 +17,6 @@ const isWithinRange = (prodMarket: number, localMarket: number): boolean => { return diff <= maxDiff; }; const AVOID = ['TOK', 'GUSD']; -/** - * Helper function to convert the API response to a dictionary with 'code' as key and 'rate' as value. - */ -const convertArrayToMap = (data: Array<{ code: string, name: string, rate: number }>): Record => { - const map: Record = {}; - data.forEach((entry) => { - map[entry.code] = entry.rate; - }); - return map; -}; - describe('Crypto rates comparison between production and localhost', () => { let prodMarkets: Record>; let localMarkets: Record>; @@ -35,12 +24,11 @@ describe('Crypto rates comparison between production and localhost', () => { beforeAll(async () => { // Fetch production rates const prodResponse = await axios.get(PRODUCTION_URL); - prodMarkets = prodResponse.data[0]; // Assuming the rates data is in the first element of the array - + prodMarkets = prodResponse.data[0]; // Assuming the rates data is in the first element of the array + // Fetch localhost rates const localResponse = await axios.get(LOCAL_URL); - localMarkets = localResponse.data[0]; // Assuming the rates data is in the first element of the array - + localMarkets = localResponse.data[0]; // Assuming the rates data is in the first element of the array }); test('All crypto codes should be present in both production and localhost', () => { @@ -57,7 +45,6 @@ describe('Crypto rates comparison between production and localhost', () => { expect(localMarkets[key]).toHaveProperty(prodKey); }); }); - }); test('All rates should be within a reasonable range', () => { @@ -80,7 +67,6 @@ describe('Crypto rates comparison between production and localhost', () => { }); } }); - }); // Ensure the rates are within the allowed range expect(diffs).toEqual([]); diff --git a/tests/productionCompareRates.spec.ts b/tests/productionCompareRates.spec.ts index 6073623..7939ea8 100644 --- a/tests/productionCompareRates.spec.ts +++ b/tests/productionCompareRates.spec.ts @@ -37,11 +37,11 @@ describe('Crypto rates comparison between production and localhost', () => { beforeAll(async () => { // Fetch production rates const prodResponse = await axios.get(PRODUCTION_URL); - const prodData = prodResponse.data[0]; // Assuming the rates data is in the first element of the array - + const [prodData] = prodResponse.data; // the rates data is the first element of the array + // Fetch localhost rates const localResponse = await axios.get(LOCAL_URL); - const localData = localResponse.data[0]; // Assuming the rates data is in the first element of the array + const [localData] = localResponse.data; // the rates data is the first element of the array // Convert the array of rates into a map with 'code' as key and 'rate' as value prodRates = convertArrayToMap(prodData); @@ -76,7 +76,6 @@ describe('Crypto rates comparison between production and localhost', () => { prodKeys.forEach((key) => { const prodRate = prodRates[key]; const localRate = localRates[key]; - const diffs = []; if (!isWithinRange(prodRate, localRate) && !AVOID.includes(key)) { diffs.push({ code: key, prodRate, localRate }); } From 746a6cdd29d775ad9ea5b906cde11c74481755ba Mon Sep 17 00:00:00 2001 From: Stultus Mundi Date: Fri, 21 Aug 2026 15:37:39 +0300 Subject: [PATCH 12/12] docs: regenerate typedoc, with source links pinned to a branch The committed docs tree had drifted: no pages for getLastGoodBstockPrices or isBstocksDegraded (both added after cc6bd3d regenerated it), nothing for Binance.pricedFresh, and every "Defined in" line number stale after the lint pass reflowed the sources. Regenerating from the main working tree rather than a linked worktree also changes the output: typedoc could not resolve the git remote from a worktree, where .git is a file rather than a directory, so it emitted bare paths. Here it resolves the remote and emits real links -- but by default it pins them to the current commit, which rewrites all 58 pages on every regeneration and leaves the links pointing at an ever-older commit. Setting `gitRevision: "master"` in typedoc.json makes them .../blob/master/src/... instead: stable across regenerations, so a docs refresh only shows real content changes from here on. The trade is that links to code added on a branch resolve once that branch merges. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01AURdZitXfR5N9A28a1b2Hp --- docs/src/lib/axios/classes/AxiosWrapper.md | 12 +-- docs/src/lib/objects/functions/mergeDeep.md | 2 +- .../objects/functions/replaceCryptoByKey.md | 2 +- docs/src/lib/server/variables/default.md | 2 +- docs/src/lib/utils/functions/arraySplit.md | 2 +- .../lib/utils/functions/makeRequestStrings.md | 2 +- docs/src/routes/variables/default.md | 2 +- .../apiServices/functions/checkContractsV2.md | 2 +- .../apiServices/functions/dataRefresher.md | 2 +- .../services/apiServices/functions/getData.md | 2 +- .../functions/getFoundContracts.md | 2 +- .../apiServices/functions/getMarketsUsd.md | 2 +- .../apiServices/functions/getRates.md | 2 +- .../apiServices/functions/getRatesV2.md | 2 +- .../functions/getRatesV2Compressed.md | 2 +- .../apiServices/functions/serviceRefresher.md | 2 +- .../services/apiServices/variables/default.md | 2 +- docs/src/services/bstocks/README.md | 2 + .../functions/clearLastGoodForTests.md | 2 +- .../bstocks/functions/getBstockPrices.md | 13 +++- .../functions/getLastGoodBstockPrices.md | 25 ++++++ .../bstocks/functions/isBstocksDegraded.md | 24 ++++++ .../functions/getLatestCoinInfo.md | 2 +- .../variables/cgContractMap.md | 2 +- .../coinAggregatorIDs/variables/cgTokens.md | 2 +- .../variables/coinAggregatorIDs.md | 2 +- .../coinAggregatorIDs/variables/zelData.md | 2 +- .../newContracts/functions/checkContracts.md | 2 +- .../newContracts/variables/foundContracts.md | 2 +- .../providers/binance/classes/Binance.md | 76 +++++++++++++++---- .../providers/bitpay/classes/BitPay.md | 8 +- .../providers/coinGecko/classes/CoinGecko.md | 14 ++-- .../cryptoCompare/classes/CryptoCompare.md | 10 +-- .../liveCoinWatch/classes/LiveCoinWatch.md | 8 +- .../zelcoreMarketsUSD/functions/getAll.md | 2 +- .../zelcoreMarketsUSD/variables/default.md | 2 +- .../services/zelcoreRates/functions/getAll.md | 2 +- .../zelcoreRates/variables/default.md | 2 +- .../zelcoreRatesV2/functions/getAll.md | 2 +- .../zelcoreRatesV2/variables/default.md | 2 +- docs/src/types/interfaces/ICurrencyData.md | 16 ++-- docs/src/types/interfaces/ICurrencyRate.md | 8 +- docs/src/types/interfaces/IErrorObject.md | 4 +- docs/src/types/type-aliases/BinanceTicker.md | 10 +-- .../type-aliases/BinanceTokenisedAsset.md | 12 +-- docs/src/types/type-aliases/CodeRates.md | 2 +- docs/src/types/type-aliases/CoinGeckoPrice.md | 56 +++++++------- docs/src/types/type-aliases/CoinGeckoToken.md | 10 +-- docs/src/types/type-aliases/CoinInfo.md | 48 ++++++------ .../types/type-aliases/ContractWithType.md | 6 +- .../type-aliases/CryptoCompareMarkets.md | 2 +- .../types/type-aliases/CryptoComparePrice.md | 2 +- docs/src/types/type-aliases/CryptoPrice.md | 22 +++--- docs/src/types/type-aliases/CurrencyMap.md | 2 +- docs/src/types/type-aliases/FiatPrice.md | 10 +-- .../types/type-aliases/FoundContractStore.md | 2 +- .../types/type-aliases/LiveCoinWatchMarket.md | 46 +++++------ docs/src/types/type-aliases/MarketsData.md | 2 +- docs/src/types/type-aliases/PricesResponse.md | 8 +- docs/src/types/type-aliases/RatesData.md | 2 +- typedoc.json | 1 + 61 files changed, 316 insertions(+), 207 deletions(-) create mode 100644 docs/src/services/bstocks/functions/getLastGoodBstockPrices.md create mode 100644 docs/src/services/bstocks/functions/isBstocksDegraded.md diff --git a/docs/src/lib/axios/classes/AxiosWrapper.md b/docs/src/lib/axios/classes/AxiosWrapper.md index 51ae47b..677411d 100644 --- a/docs/src/lib/axios/classes/AxiosWrapper.md +++ b/docs/src/lib/axios/classes/AxiosWrapper.md @@ -6,7 +6,7 @@ # Class: AxiosWrapper -Defined in: src/lib/axios.ts:26 +Defined in: [src/lib/axios.ts:26](https://github.com/ZelCore-io/rates-api/blob/master/src/lib/axios.ts#L26) A wrapper around Axios to handle automatic retries and customizable configurations. @@ -37,7 +37,7 @@ apiClient.post('/users', { name: 'John Doe' }) > **new AxiosWrapper**(`baseURL`, `maxRetries?`, `timeout?`): `AxiosWrapper` -Defined in: src/lib/axios.ts:43 +Defined in: [src/lib/axios.ts:45](https://github.com/ZelCore-io/rates-api/blob/master/src/lib/axios.ts#L45) Creates an instance of AxiosWrapper. @@ -77,7 +77,7 @@ const apiClient = new AxiosWrapper('https://api.example.com', 5, 10000); > **delete**(`url`, `config?`): `Promise`\<`AxiosResponse`\<`any`, `any`\>\> -Defined in: src/lib/axios.ts:171 +Defined in: [src/lib/axios.ts:173](https://github.com/ZelCore-io/rates-api/blob/master/src/lib/axios.ts#L173) Performs a DELETE request. @@ -115,7 +115,7 @@ apiClient.delete('/users/123') > **get**(`url`, `config?`): `Promise`\<`AxiosResponse`\<`any`, `any`\>\> -Defined in: src/lib/axios.ts:115 +Defined in: [src/lib/axios.ts:117](https://github.com/ZelCore-io/rates-api/blob/master/src/lib/axios.ts#L117) Performs a GET request. @@ -153,7 +153,7 @@ apiClient.get('/users') > **post**(`url`, `data?`, `config?`): `Promise`\<`AxiosResponse`\<`any`, `any`\>\> -Defined in: src/lib/axios.ts:134 +Defined in: [src/lib/axios.ts:136](https://github.com/ZelCore-io/rates-api/blob/master/src/lib/axios.ts#L136) Performs a POST request. @@ -197,7 +197,7 @@ apiClient.post('/users', { name: 'John Doe' }) > **put**(`url`, `data?`, `config?`): `Promise`\<`AxiosResponse`\<`any`, `any`\>\> -Defined in: src/lib/axios.ts:153 +Defined in: [src/lib/axios.ts:155](https://github.com/ZelCore-io/rates-api/blob/master/src/lib/axios.ts#L155) Performs a PUT request. diff --git a/docs/src/lib/objects/functions/mergeDeep.md b/docs/src/lib/objects/functions/mergeDeep.md index d3aef62..43adf12 100644 --- a/docs/src/lib/objects/functions/mergeDeep.md +++ b/docs/src/lib/objects/functions/mergeDeep.md @@ -8,7 +8,7 @@ > **mergeDeep**(`target`, `source`): `any` -Defined in: src/lib/objects.ts:20 +Defined in: [src/lib/objects.ts:23](https://github.com/ZelCore-io/rates-api/blob/master/src/lib/objects.ts#L23) Deeply merges two objects or arrays. diff --git a/docs/src/lib/objects/functions/replaceCryptoByKey.md b/docs/src/lib/objects/functions/replaceCryptoByKey.md index 35e9b93..aed45c5 100644 --- a/docs/src/lib/objects/functions/replaceCryptoByKey.md +++ b/docs/src/lib/objects/functions/replaceCryptoByKey.md @@ -8,7 +8,7 @@ > **replaceCryptoByKey**\<`T`\>(`source`): `T`[] -Defined in: src/lib/objects.ts:74 +Defined in: [src/lib/objects.ts:78](https://github.com/ZelCore-io/rates-api/blob/master/src/lib/objects.ts#L78) Rebuilds the crypto array from `source` alone, de-duplicated by `${provider}-${id}`, preserving source order with last-write-wins. diff --git a/docs/src/lib/server/variables/default.md b/docs/src/lib/server/variables/default.md index c5d997b..a631b95 100644 --- a/docs/src/lib/server/variables/default.md +++ b/docs/src/lib/server/variables/default.md @@ -8,7 +8,7 @@ > `const` **default**: `Express` -Defined in: src/lib/server.ts:33 +Defined in: [src/lib/server.ts:33](https://github.com/ZelCore-io/rates-api/blob/master/src/lib/server.ts#L33) The main Express application instance. diff --git a/docs/src/lib/utils/functions/arraySplit.md b/docs/src/lib/utils/functions/arraySplit.md index 9329cda..8cb711f 100644 --- a/docs/src/lib/utils/functions/arraySplit.md +++ b/docs/src/lib/utils/functions/arraySplit.md @@ -8,7 +8,7 @@ > **arraySplit**(`arr`, `size`): `string`[][] -Defined in: src/lib/utils.ts:15 +Defined in: [src/lib/utils.ts:15](https://github.com/ZelCore-io/rates-api/blob/master/src/lib/utils.ts#L15) Splits an array into chunks of a specified size. diff --git a/docs/src/lib/utils/functions/makeRequestStrings.md b/docs/src/lib/utils/functions/makeRequestStrings.md index f3195e0..3f140c5 100644 --- a/docs/src/lib/utils/functions/makeRequestStrings.md +++ b/docs/src/lib/utils/functions/makeRequestStrings.md @@ -8,7 +8,7 @@ > **makeRequestStrings**(`elements`, `maxLength`): `string`[] -Defined in: src/lib/utils.ts:41 +Defined in: [src/lib/utils.ts:41](https://github.com/ZelCore-io/rates-api/blob/master/src/lib/utils.ts#L41) Combines elements of a string array into comma-separated strings, ensuring that each combined string does not exceed a specified maximum length. diff --git a/docs/src/routes/variables/default.md b/docs/src/routes/variables/default.md index c999765..515e3a0 100644 --- a/docs/src/routes/variables/default.md +++ b/docs/src/routes/variables/default.md @@ -8,7 +8,7 @@ > **default**: (`app`) => `void` -Defined in: src/routes.ts:26 +Defined in: [src/routes.ts:26](https://github.com/ZelCore-io/rates-api/blob/master/src/routes.ts#L26) Configures the Express application by setting up routes, middleware, and caching. diff --git a/docs/src/services/apiServices/functions/checkContractsV2.md b/docs/src/services/apiServices/functions/checkContractsV2.md index f22f830..d833c1c 100644 --- a/docs/src/services/apiServices/functions/checkContractsV2.md +++ b/docs/src/services/apiServices/functions/checkContractsV2.md @@ -8,7 +8,7 @@ > **checkContractsV2**(`req`, `res`): `Promise`\<`void`\> -Defined in: src/services/apiServices.ts:156 +Defined in: [src/services/apiServices.ts:156](https://github.com/ZelCore-io/rates-api/blob/master/src/services/apiServices.ts#L156) Handles the request to check for new contracts. diff --git a/docs/src/services/apiServices/functions/dataRefresher.md b/docs/src/services/apiServices/functions/dataRefresher.md index 0c29bb7..00bd174 100644 --- a/docs/src/services/apiServices/functions/dataRefresher.md +++ b/docs/src/services/apiServices/functions/dataRefresher.md @@ -8,7 +8,7 @@ > **dataRefresher**(): `Promise`\<`void`\> -Defined in: src/services/apiServices.ts:195 +Defined in: [src/services/apiServices.ts:195](https://github.com/ZelCore-io/rates-api/blob/master/src/services/apiServices.ts#L195) Periodically refreshes coin information and aggregator IDs. diff --git a/docs/src/services/apiServices/functions/getData.md b/docs/src/services/apiServices/functions/getData.md index 5d83b7e..5de8925 100644 --- a/docs/src/services/apiServices/functions/getData.md +++ b/docs/src/services/apiServices/functions/getData.md @@ -8,7 +8,7 @@ > **getData**(): `object` -Defined in: src/services/apiServices.ts:105 +Defined in: [src/services/apiServices.ts:105](https://github.com/ZelCore-io/rates-api/blob/master/src/services/apiServices.ts#L105) Retrieves the current rates and market data. diff --git a/docs/src/services/apiServices/functions/getFoundContracts.md b/docs/src/services/apiServices/functions/getFoundContracts.md index 1e56dd7..aef8f88 100644 --- a/docs/src/services/apiServices/functions/getFoundContracts.md +++ b/docs/src/services/apiServices/functions/getFoundContracts.md @@ -8,7 +8,7 @@ > **getFoundContracts**(): [`FoundContractStore`](../../../types/type-aliases/FoundContractStore.md) -Defined in: src/services/apiServices.ts:141 +Defined in: [src/services/apiServices.ts:141](https://github.com/ZelCore-io/rates-api/blob/master/src/services/apiServices.ts#L141) Retrieves the found contracts. diff --git a/docs/src/services/apiServices/functions/getMarketsUsd.md b/docs/src/services/apiServices/functions/getMarketsUsd.md index db420f0..ecf9f39 100644 --- a/docs/src/services/apiServices/functions/getMarketsUsd.md +++ b/docs/src/services/apiServices/functions/getMarketsUsd.md @@ -8,7 +8,7 @@ > **getMarketsUsd**(`req`, `res`): `Promise`\<`void`\> -Defined in: src/services/apiServices.ts:123 +Defined in: [src/services/apiServices.ts:123](https://github.com/ZelCore-io/rates-api/blob/master/src/services/apiServices.ts#L123) Handles the GET request to retrieve market data in USD. diff --git a/docs/src/services/apiServices/functions/getRates.md b/docs/src/services/apiServices/functions/getRates.md index 0b1dddf..eecfe98 100644 --- a/docs/src/services/apiServices/functions/getRates.md +++ b/docs/src/services/apiServices/functions/getRates.md @@ -8,7 +8,7 @@ > **getRates**(`req`, `res`): `Promise`\<`void`\> -Defined in: src/services/apiServices.ts:47 +Defined in: [src/services/apiServices.ts:47](https://github.com/ZelCore-io/rates-api/blob/master/src/services/apiServices.ts#L47) Handles the GET request to retrieve exchange rates. diff --git a/docs/src/services/apiServices/functions/getRatesV2.md b/docs/src/services/apiServices/functions/getRatesV2.md index b8615eb..4faccee 100644 --- a/docs/src/services/apiServices/functions/getRatesV2.md +++ b/docs/src/services/apiServices/functions/getRatesV2.md @@ -8,7 +8,7 @@ > **getRatesV2**(`req`, `res`): `Promise`\<`void`\> -Defined in: src/services/apiServices.ts:66 +Defined in: [src/services/apiServices.ts:66](https://github.com/ZelCore-io/rates-api/blob/master/src/services/apiServices.ts#L66) Handles the GET request to retrieve version 2 of the exchange rates. diff --git a/docs/src/services/apiServices/functions/getRatesV2Compressed.md b/docs/src/services/apiServices/functions/getRatesV2Compressed.md index 66a9a05..770a9e2 100644 --- a/docs/src/services/apiServices/functions/getRatesV2Compressed.md +++ b/docs/src/services/apiServices/functions/getRatesV2Compressed.md @@ -8,7 +8,7 @@ > **getRatesV2Compressed**(`req`, `res`): `Promise`\<`void`\> -Defined in: src/services/apiServices.ts:85 +Defined in: [src/services/apiServices.ts:85](https://github.com/ZelCore-io/rates-api/blob/master/src/services/apiServices.ts#L85) Handles the GET request to retrieve compressed version of the exchange rates (version 2). diff --git a/docs/src/services/apiServices/functions/serviceRefresher.md b/docs/src/services/apiServices/functions/serviceRefresher.md index fa2fac8..b4a4392 100644 --- a/docs/src/services/apiServices/functions/serviceRefresher.md +++ b/docs/src/services/apiServices/functions/serviceRefresher.md @@ -8,7 +8,7 @@ > **serviceRefresher**(): `Promise`\<`void`\> -Defined in: src/services/apiServices.ts:224 +Defined in: [src/services/apiServices.ts:224](https://github.com/ZelCore-io/rates-api/blob/master/src/services/apiServices.ts#L224) Periodically refreshes market data and exchange rates. diff --git a/docs/src/services/apiServices/variables/default.md b/docs/src/services/apiServices/variables/default.md index e187661..fad3409 100644 --- a/docs/src/services/apiServices/variables/default.md +++ b/docs/src/services/apiServices/variables/default.md @@ -8,7 +8,7 @@ > **default**: `object` -Defined in: src/services/apiServices.ts:270 +Defined in: [src/services/apiServices.ts:280](https://github.com/ZelCore-io/rates-api/blob/master/src/services/apiServices.ts#L280) ## Type Declaration diff --git a/docs/src/services/bstocks/README.md b/docs/src/services/bstocks/README.md index c663b5a..885fcac 100644 --- a/docs/src/services/bstocks/README.md +++ b/docs/src/services/bstocks/README.md @@ -10,3 +10,5 @@ - [\_clearLastGoodForTests](functions/clearLastGoodForTests.md) - [getBstockPrices](functions/getBstockPrices.md) +- [getLastGoodBstockPrices](functions/getLastGoodBstockPrices.md) +- [isBstocksDegraded](functions/isBstocksDegraded.md) diff --git a/docs/src/services/bstocks/functions/clearLastGoodForTests.md b/docs/src/services/bstocks/functions/clearLastGoodForTests.md index c648803..4b9511b 100644 --- a/docs/src/services/bstocks/functions/clearLastGoodForTests.md +++ b/docs/src/services/bstocks/functions/clearLastGoodForTests.md @@ -8,7 +8,7 @@ > **\_clearLastGoodForTests**(): `void` -Defined in: src/services/bstocks.ts:13 +Defined in: [src/services/bstocks.ts:25](https://github.com/ZelCore-io/rates-api/blob/master/src/services/bstocks.ts#L25) ## Returns diff --git a/docs/src/services/bstocks/functions/getBstockPrices.md b/docs/src/services/bstocks/functions/getBstockPrices.md index 244ef95..559ffa1 100644 --- a/docs/src/services/bstocks/functions/getBstockPrices.md +++ b/docs/src/services/bstocks/functions/getBstockPrices.md @@ -8,7 +8,7 @@ > **getBstockPrices**(): `Promise`\<[`CryptoPrice`](../../../types/type-aliases/CryptoPrice.md)[]\> -Defined in: src/services/bstocks.ts:44 +Defined in: [src/services/bstocks.ts:101](https://github.com/ZelCore-io/rates-api/blob/master/src/services/bstocks.ts#L101) Assembles the bStocks synthetic market: the intersection of Binance's tokenised-asset universe (already filtered to BSC-listed assets by @@ -29,13 +29,20 @@ literals only meet if the provider here is exactly `"coingecko"`. Any other value makes the lookup miss silently — no error, just no price. This id/provider pairing is a cross-repo contract; do not change it in isolation. +`rank` is intentionally omitted (not zeroed) to match CryptoCompare's rows +elsewhere in this repo, which also carry no `rank`: a literal `rank: 0` +would sort every bStock ahead of Bitcoin in any ascending rank-ordered list. + A module-level last-known-good map means a symbol that drops out of a given refresh (CEX halt, e.g. around a stock split) keeps being served at its -previous price rather than disappearing from the response. +previous price rather than disappearing from the response, bounded by +`config.bstocksLastGoodMaxAgeMs` (see the halting comment on `lastGood` +above) so a permanently-delisted symbol doesn't get served forever. ## Returns `Promise`\<[`CryptoPrice`](../../../types/type-aliases/CryptoPrice.md)[]\> One `CryptoPrice` per tradable bStock (BSC contract + TRADING -`USDT` Spot symbol), including any carried over from a prior refresh. +`USDT` Spot symbol) still within the staleness bound, including any +carried over from a prior refresh. diff --git a/docs/src/services/bstocks/functions/getLastGoodBstockPrices.md b/docs/src/services/bstocks/functions/getLastGoodBstockPrices.md new file mode 100644 index 0000000..7f00ebc --- /dev/null +++ b/docs/src/services/bstocks/functions/getLastGoodBstockPrices.md @@ -0,0 +1,25 @@ +[**rates-api v3.0.0**](../../../../README.md) + +*** + +[rates-api](../../../../modules.md) / [src/services/bstocks](../README.md) / getLastGoodBstockPrices + +# Function: getLastGoodBstockPrices() + +> **getLastGoodBstockPrices**(): [`CryptoPrice`](../../../types/type-aliases/CryptoPrice.md)[] + +Defined in: [src/services/bstocks.ts:60](https://github.com/ZelCore-io/rates-api/blob/master/src/services/bstocks.ts#L60) + +The current last-known-good rows, without touching Binance. + +Used when a caller has given up waiting on `getBstockPrices()`. Returning +an empty array there would drop every bStock from the response while the +provider-level carry-forward in apiServices cannot help: bStock rows carry +`provider: 'coingecko'` but their failure is reported under +`errors.binance`, so nothing would carry them. + +## Returns + +[`CryptoPrice`](../../../types/type-aliases/CryptoPrice.md)[] + +The last-known-good rows, stale entries already pruned. diff --git a/docs/src/services/bstocks/functions/isBstocksDegraded.md b/docs/src/services/bstocks/functions/isBstocksDegraded.md new file mode 100644 index 0000000..e7f8f42 --- /dev/null +++ b/docs/src/services/bstocks/functions/isBstocksDegraded.md @@ -0,0 +1,24 @@ +[**rates-api v3.0.0**](../../../../README.md) + +*** + +[rates-api](../../../../modules.md) / [src/services/bstocks](../README.md) / isBstocksDegraded + +# Function: isBstocksDegraded() + +> **isBstocksDegraded**(): `boolean` + +Defined in: [src/services/bstocks.ts:40](https://github.com/ZelCore-io/rates-api/blob/master/src/services/bstocks.ts#L40) + +True when the most recent `getBstockPrices()` call priced nothing fresh — +every underlying Binance call failed, returned unusable data, or served +only prices carried over from an earlier refresh. Distinguishes "Binance is +down and we're serving frozen prices" from a normal, healthy refresh, which +`getBstockPrices()`'s return value alone cannot express since it never +rejects and unconditionally re-emits `lastGood` either way. + +## Returns + +`boolean` + +Whether the bStocks pipeline is currently degraded. diff --git a/docs/src/services/coinAggregatorIDs/functions/getLatestCoinInfo.md b/docs/src/services/coinAggregatorIDs/functions/getLatestCoinInfo.md index e1deffd..d71ec2e 100644 --- a/docs/src/services/coinAggregatorIDs/functions/getLatestCoinInfo.md +++ b/docs/src/services/coinAggregatorIDs/functions/getLatestCoinInfo.md @@ -8,7 +8,7 @@ > **getLatestCoinInfo**(): `Promise`\<`void`\> -Defined in: src/services/coinAggregatorIDs.ts:92 +Defined in: [src/services/coinAggregatorIDs.ts:94](https://github.com/ZelCore-io/rates-api/blob/master/src/services/coinAggregatorIDs.ts#L94) Fetches the latest coin information and updates the global data. diff --git a/docs/src/services/coinAggregatorIDs/variables/cgContractMap.md b/docs/src/services/coinAggregatorIDs/variables/cgContractMap.md index 1cd2d0e..46e67ae 100644 --- a/docs/src/services/coinAggregatorIDs/variables/cgContractMap.md +++ b/docs/src/services/coinAggregatorIDs/variables/cgContractMap.md @@ -8,6 +8,6 @@ > `const` **cgContractMap**: `Record`\<`string`, [`CoinGeckoToken`](../../../types/type-aliases/CoinGeckoToken.md)\> = `{}` -Defined in: src/services/coinAggregatorIDs.ts:75 +Defined in: [src/services/coinAggregatorIDs.ts:77](https://github.com/ZelCore-io/rates-api/blob/master/src/services/coinAggregatorIDs.ts#L77) Map of contract addresses to CoinGecko tokens. diff --git a/docs/src/services/coinAggregatorIDs/variables/cgTokens.md b/docs/src/services/coinAggregatorIDs/variables/cgTokens.md index 07a73b9..8c255be 100644 --- a/docs/src/services/coinAggregatorIDs/variables/cgTokens.md +++ b/docs/src/services/coinAggregatorIDs/variables/cgTokens.md @@ -8,6 +8,6 @@ > **cgTokens**: [`CoinGeckoToken`](../../../types/type-aliases/CoinGeckoToken.md)[] = `cgCoins` -Defined in: src/services/coinAggregatorIDs.ts:70 +Defined in: [src/services/coinAggregatorIDs.ts:72](https://github.com/ZelCore-io/rates-api/blob/master/src/services/coinAggregatorIDs.ts#L72) Array of CoinGecko tokens. diff --git a/docs/src/services/coinAggregatorIDs/variables/coinAggregatorIDs.md b/docs/src/services/coinAggregatorIDs/variables/coinAggregatorIDs.md index 4dfd07e..ff14829 100644 --- a/docs/src/services/coinAggregatorIDs/variables/coinAggregatorIDs.md +++ b/docs/src/services/coinAggregatorIDs/variables/coinAggregatorIDs.md @@ -8,7 +8,7 @@ > `const` **coinAggregatorIDs**: `object` -Defined in: src/services/coinAggregatorIDs.ts:14 +Defined in: [src/services/coinAggregatorIDs.ts:14](https://github.com/ZelCore-io/rates-api/blob/master/src/services/coinAggregatorIDs.ts#L14) An object containing arrays of cryptocurrency IDs used by different data aggregators. diff --git a/docs/src/services/coinAggregatorIDs/variables/zelData.md b/docs/src/services/coinAggregatorIDs/variables/zelData.md index f0e3a29..3cc90ca 100644 --- a/docs/src/services/coinAggregatorIDs/variables/zelData.md +++ b/docs/src/services/coinAggregatorIDs/variables/zelData.md @@ -8,7 +8,7 @@ > `const` **zelData**: `object` -Defined in: src/services/coinAggregatorIDs.ts:61 +Defined in: [src/services/coinAggregatorIDs.ts:61](https://github.com/ZelCore-io/rates-api/blob/master/src/services/coinAggregatorIDs.ts#L61) Global object to store coin information. diff --git a/docs/src/services/newContracts/functions/checkContracts.md b/docs/src/services/newContracts/functions/checkContracts.md index ae2ee9d..9c007f0 100644 --- a/docs/src/services/newContracts/functions/checkContracts.md +++ b/docs/src/services/newContracts/functions/checkContracts.md @@ -8,7 +8,7 @@ > **checkContracts**(`contracts`): `boolean` -Defined in: src/services/newContracts.ts:32 +Defined in: [src/services/newContracts.ts:32](https://github.com/ZelCore-io/rates-api/blob/master/src/services/newContracts.ts#L32) Checks the provided contracts against the CoinGecko contract map and updates the `foundContracts` store. diff --git a/docs/src/services/newContracts/variables/foundContracts.md b/docs/src/services/newContracts/variables/foundContracts.md index acbd6a8..51d9d26 100644 --- a/docs/src/services/newContracts/variables/foundContracts.md +++ b/docs/src/services/newContracts/variables/foundContracts.md @@ -8,6 +8,6 @@ > `const` **foundContracts**: [`FoundContractStore`](../../../types/type-aliases/FoundContractStore.md) = `{}` -Defined in: src/services/newContracts.ts:8 +Defined in: [src/services/newContracts.ts:8](https://github.com/ZelCore-io/rates-api/blob/master/src/services/newContracts.ts#L8) Stores the found contracts with their occurrence count. diff --git a/docs/src/services/providers/binance/classes/Binance.md b/docs/src/services/providers/binance/classes/Binance.md index 46aa0ab..06b115d 100644 --- a/docs/src/services/providers/binance/classes/Binance.md +++ b/docs/src/services/providers/binance/classes/Binance.md @@ -6,7 +6,7 @@ # Class: Binance -Defined in: src/services/providers/binance.ts:33 +Defined in: [src/services/providers/binance.ts:38](https://github.com/ZelCore-io/rates-api/blob/master/src/services/providers/binance.ts#L38) Singleton class to interact with Binance's public (no-API-key) endpoints. @@ -46,7 +46,7 @@ async function fetchBStocks() { > **chunkSymbols**(`symbols`): `string`[][] -Defined in: src/services/providers/binance.ts:122 +Defined in: [src/services/providers/binance.ts:137](https://github.com/ZelCore-io/rates-api/blob/master/src/services/providers/binance.ts#L137) Splits a symbol list into chunks of at most `TICKER_CHUNK` symbols, to stay under Binance's per-request weight cap on the 7d rolling-window ticker. @@ -71,7 +71,7 @@ An array of symbol chunks. > **filterBscAssets**(`assets`): [`BinanceTokenisedAsset`](../../../../types/type-aliases/BinanceTokenisedAsset.md)[] -Defined in: src/services/providers/binance.ts:110 +Defined in: [src/services/providers/binance.ts:124](https://github.com/ZelCore-io/rates-api/blob/master/src/services/providers/binance.ts#L124) Filters tokenised assets down to those with a BSC (BNB Smart Chain) contract listed. @@ -95,7 +95,7 @@ Only the assets with at least one BSC entry in `caList`. > **getTicker24h**(`symbols`): `Promise`\<[`BinanceTicker`](../../../../types/type-aliases/BinanceTicker.md)[]\> -Defined in: src/services/providers/binance.ts:231 +Defined in: [src/services/providers/binance.ts:291](https://github.com/ZelCore-io/rates-api/blob/master/src/services/providers/binance.ts#L291) Retrieves 24h tickers for the given symbols in a single request. @@ -123,7 +123,7 @@ One ticker per requested symbol that has ever been seen. > **getTicker7d**(`symbols`): `Promise`\<[`BinanceTicker`](../../../../types/type-aliases/BinanceTicker.md)[]\> -Defined in: src/services/providers/binance.ts:261 +Defined in: [src/services/providers/binance.ts:321](https://github.com/ZelCore-io/rates-api/blob/master/src/services/providers/binance.ts#L321) Retrieves 7d rolling-window tickers for the given symbols, chunked to stay under Binance's per-request weight cap. @@ -153,7 +153,7 @@ One ticker per requested symbol that has ever been seen. > **getTokenisedAssets**(): `Promise`\<[`BinanceTokenisedAsset`](../../../../types/type-aliases/BinanceTokenisedAsset.md)[]\> -Defined in: src/services/providers/binance.ts:173 +Defined in: [src/services/providers/binance.ts:226](https://github.com/ZelCore-io/rates-api/blob/master/src/services/providers/binance.ts#L226) Retrieves the tokenised-asset universe (bStocks), filtered to those with a BSC contract. @@ -171,7 +171,7 @@ The BSC-listed tokenised assets. > **getTradingSymbols**(): `Promise`\<`Set`\<`string`\>\> -Defined in: src/services/providers/binance.ts:201 +Defined in: [src/services/providers/binance.ts:258](https://github.com/ZelCore-io/rates-api/blob/master/src/services/providers/binance.ts#L258) Retrieves the set of Spot symbols currently in `TRADING` status. @@ -192,13 +192,14 @@ The set of currently-trading symbols. ### lastGoodAgeMs() -> **lastGoodAgeMs**(`symbol`): `number` \| `null` +> **lastGoodAgeMs**(`symbol`, `window?`): `number` \| `null` -Defined in: src/services/providers/binance.ts:161 +Defined in: [src/services/providers/binance.ts:186](https://github.com/ZelCore-io/rates-api/blob/master/src/services/providers/binance.ts#L186) Age in milliseconds of the last-known-good price for a symbol, or null if -none has ever been recorded. Lets a caller distinguish a live price from -one carried through a long halt, which the ticker itself cannot express. +none has ever been recorded for the requested window(s). Lets a caller +distinguish a live price from one carried through a long halt, which the +ticker itself cannot express. #### Parameters @@ -208,11 +209,60 @@ one carried through a long halt, which the ticker itself cannot express. The Binance symbol, e.g. `TSLABUSDT`. +##### window? + +`"7d"` \| `"24h"` + +Which window's last-known-good entry to check (`24h` or +`7d`). Omit to get the freshest of the two — the age of whichever window +priced most recently — which is what a caller asking "how stale is this +symbol overall" generally wants. + #### Returns `number` \| `null` -Age in ms, or null when the symbol has never priced successfully. +Age in ms, or null when the symbol has never priced successfully +for the requested window (or for either window, when unspecified). + +*** + +### pricedFresh() + +> **pricedFresh**(`symbol`, `window`): `boolean` + +Defined in: [src/services/providers/binance.ts:214](https://github.com/ZelCore-io/rates-api/blob/master/src/services/providers/binance.ts#L214) + +Whether the price currently served for a symbol comes from a live quote +rather than the last-known-good backfill. + +`mergeTickers` returns a plain `BinanceTicker` whether it was fetched or +carried, so a caller cannot tell the two apart from the returned value — +and a carried price is a valid, positive number, which makes the +difference invisible to any price check. A batch answered from +`quoteCache` legitimately carries a price up to one cache TTL old, so +anything within that window is live; past it, nothing has priced the +symbol since, so every batch in between was backfilled. + +#### Parameters + +##### symbol + +`string` + +The Binance symbol, e.g. `TSLABUSDT`. + +##### window + +`"7d"` \| `"24h"` + +Which ticker window to check (`24h` or `7d`). + +#### Returns + +`boolean` + +True when the symbol priced live within the quote-cache window. *** @@ -220,7 +270,7 @@ Age in ms, or null when the symbol has never priced successfully. > `static` **getInstance**(): `Binance` -Defined in: src/services/providers/binance.ts:99 +Defined in: [src/services/providers/binance.ts:112](https://github.com/ZelCore-io/rates-api/blob/master/src/services/providers/binance.ts#L112) Returns the singleton instance of the Binance class. diff --git a/docs/src/services/providers/bitpay/classes/BitPay.md b/docs/src/services/providers/bitpay/classes/BitPay.md index 7ea766a..c678d94 100644 --- a/docs/src/services/providers/bitpay/classes/BitPay.md +++ b/docs/src/services/providers/bitpay/classes/BitPay.md @@ -6,7 +6,7 @@ # Class: BitPay -Defined in: src/services/providers/bitpay.ts:24 +Defined in: [src/services/providers/bitpay.ts:24](https://github.com/ZelCore-io/rates-api/blob/master/src/services/providers/bitpay.ts#L24) Singleton class to interact with the BitPay API. @@ -33,7 +33,7 @@ fetchRates(); > **new BitPay**(): `BitPay` -Defined in: src/services/providers/bitpay.ts:58 +Defined in: [src/services/providers/bitpay.ts:58](https://github.com/ZelCore-io/rates-api/blob/master/src/services/providers/bitpay.ts#L58) Private constructor to enforce the singleton pattern. @@ -53,7 +53,7 @@ If an instance already exists. > **getFiatRates**(): `Promise`\<`any`\> -Defined in: src/services/providers/bitpay.ts:115 +Defined in: [src/services/providers/bitpay.ts:115](https://github.com/ZelCore-io/rates-api/blob/master/src/services/providers/bitpay.ts#L115) Retrieves fiat currency exchange rates from the BitPay API. @@ -80,7 +80,7 @@ console.log(rates); > `static` **getInstance**(): `BitPay` -Defined in: src/services/providers/bitpay.ts:81 +Defined in: [src/services/providers/bitpay.ts:81](https://github.com/ZelCore-io/rates-api/blob/master/src/services/providers/bitpay.ts#L81) Returns the singleton instance of the BitPay class. diff --git a/docs/src/services/providers/coinGecko/classes/CoinGecko.md b/docs/src/services/providers/coinGecko/classes/CoinGecko.md index b7efa8f..9382ae9 100644 --- a/docs/src/services/providers/coinGecko/classes/CoinGecko.md +++ b/docs/src/services/providers/coinGecko/classes/CoinGecko.md @@ -6,7 +6,7 @@ # Class: CoinGecko -Defined in: src/services/providers/coinGecko.ts:40 +Defined in: [src/services/providers/coinGecko.ts:40](https://github.com/ZelCore-io/rates-api/blob/master/src/services/providers/coinGecko.ts#L40) Singleton class to interact with the CoinGecko API. @@ -33,7 +33,7 @@ fetchRates(); > **new CoinGecko**(): `CoinGecko` -Defined in: src/services/providers/coinGecko.ts:81 +Defined in: [src/services/providers/coinGecko.ts:81](https://github.com/ZelCore-io/rates-api/blob/master/src/services/providers/coinGecko.ts#L81) Private constructor to enforce the singleton pattern. @@ -53,7 +53,7 @@ If an instance already exists. > **getAssetPlatformData**(): `Promise`\<`any`\> -Defined in: src/services/providers/coinGecko.ts:207 +Defined in: [src/services/providers/coinGecko.ts:207](https://github.com/ZelCore-io/rates-api/blob/master/src/services/providers/coinGecko.ts#L207) Retrieves asset platform data from CoinGecko. @@ -77,7 +77,7 @@ console.log('Asset Platforms:', assetPlatforms); > **getCoinsList**(`includePlatform?`): `Promise`\<`any`\> -Defined in: src/services/providers/coinGecko.ts:173 +Defined in: [src/services/providers/coinGecko.ts:173](https://github.com/ZelCore-io/rates-api/blob/master/src/services/providers/coinGecko.ts#L173) Retrieves a list of all coins supported by CoinGecko. @@ -109,7 +109,7 @@ console.log('Coins List:', coinsList); > **getExchangeRates**(`ids`, `vsCurrency?`): `Promise`\<[`CoinGeckoPrice`](../../../../types/type-aliases/CoinGeckoPrice.md)[]\> -Defined in: src/services/providers/coinGecko.ts:278 +Defined in: [src/services/providers/coinGecko.ts:278](https://github.com/ZelCore-io/rates-api/blob/master/src/services/providers/coinGecko.ts#L278) Retrieves exchange rates for an array of coin IDs. @@ -149,7 +149,7 @@ console.log('Exchange Rates:', rates); > **getKeyUsage**(): `Promise`\<`KeyUsage` \| `null`\> -Defined in: src/services/providers/coinGecko.ts:138 +Defined in: [src/services/providers/coinGecko.ts:138](https://github.com/ZelCore-io/rates-api/blob/master/src/services/providers/coinGecko.ts#L138) Retrieves the usage statistics of the CoinGecko API key. @@ -176,7 +176,7 @@ console.log('API Key Usage:', usage); > `static` **getInstance**(): `CoinGecko` -Defined in: src/services/providers/coinGecko.ts:104 +Defined in: [src/services/providers/coinGecko.ts:104](https://github.com/ZelCore-io/rates-api/blob/master/src/services/providers/coinGecko.ts#L104) Returns the singleton instance of the CoinGecko class. diff --git a/docs/src/services/providers/cryptoCompare/classes/CryptoCompare.md b/docs/src/services/providers/cryptoCompare/classes/CryptoCompare.md index 94cd451..2b89b98 100644 --- a/docs/src/services/providers/cryptoCompare/classes/CryptoCompare.md +++ b/docs/src/services/providers/cryptoCompare/classes/CryptoCompare.md @@ -6,7 +6,7 @@ # Class: CryptoCompare -Defined in: src/services/providers/cryptoCompare.ts:28 +Defined in: [src/services/providers/cryptoCompare.ts:28](https://github.com/ZelCore-io/rates-api/blob/master/src/services/providers/cryptoCompare.ts#L28) Singleton class to interact with the CryptoCompare API. @@ -33,7 +33,7 @@ fetchExchangeRates(); > **new CryptoCompare**(): `CryptoCompare` -Defined in: src/services/providers/cryptoCompare.ts:69 +Defined in: [src/services/providers/cryptoCompare.ts:69](https://github.com/ZelCore-io/rates-api/blob/master/src/services/providers/cryptoCompare.ts#L69) Private constructor to enforce the singleton pattern. @@ -53,7 +53,7 @@ If an instance already exists. > **getExchangeRates**(`ids`, `vsCurrency?`): `Promise`\<[`CryptoComparePrice`](../../../../types/type-aliases/CryptoComparePrice.md)\> -Defined in: src/services/providers/cryptoCompare.ts:163 +Defined in: [src/services/providers/cryptoCompare.ts:163](https://github.com/ZelCore-io/rates-api/blob/master/src/services/providers/cryptoCompare.ts#L163) Retrieves exchange rates for an array of cryptocurrency symbols. @@ -93,7 +93,7 @@ console.log('Exchange Rates:', rates); > **getMarketData**(`ids`, `vsCurrency?`): `Promise`\<[`CryptoCompareMarkets`](../../../../types/type-aliases/CryptoCompareMarkets.md)\> -Defined in: src/services/providers/cryptoCompare.ts:226 +Defined in: [src/services/providers/cryptoCompare.ts:227](https://github.com/ZelCore-io/rates-api/blob/master/src/services/providers/cryptoCompare.ts#L227) Retrieves market data for an array of cryptocurrency symbols. @@ -133,7 +133,7 @@ console.log('Market Data:', marketData); > `static` **getInstance**(): `CryptoCompare` -Defined in: src/services/providers/cryptoCompare.ts:92 +Defined in: [src/services/providers/cryptoCompare.ts:92](https://github.com/ZelCore-io/rates-api/blob/master/src/services/providers/cryptoCompare.ts#L92) Returns the singleton instance of the CryptoCompare class. diff --git a/docs/src/services/providers/liveCoinWatch/classes/LiveCoinWatch.md b/docs/src/services/providers/liveCoinWatch/classes/LiveCoinWatch.md index c789730..a9f33cf 100644 --- a/docs/src/services/providers/liveCoinWatch/classes/LiveCoinWatch.md +++ b/docs/src/services/providers/liveCoinWatch/classes/LiveCoinWatch.md @@ -6,7 +6,7 @@ # Class: LiveCoinWatch -Defined in: src/services/providers/liveCoinWatch.ts:28 +Defined in: [src/services/providers/liveCoinWatch.ts:28](https://github.com/ZelCore-io/rates-api/blob/master/src/services/providers/liveCoinWatch.ts#L28) Singleton class to interact with the LiveCoinWatch API. @@ -33,7 +33,7 @@ fetchExchangeRates(); > **new LiveCoinWatch**(): `LiveCoinWatch` -Defined in: src/services/providers/liveCoinWatch.ts:69 +Defined in: [src/services/providers/liveCoinWatch.ts:69](https://github.com/ZelCore-io/rates-api/blob/master/src/services/providers/liveCoinWatch.ts#L69) Private constructor to enforce the singleton pattern. @@ -53,7 +53,7 @@ If an instance already exists. > **getExchangeRates**(`ids`, `vsCurrency?`): `Promise`\<[`LiveCoinWatchMarket`](../../../../types/type-aliases/LiveCoinWatchMarket.md)[]\> -Defined in: src/services/providers/liveCoinWatch.ts:164 +Defined in: [src/services/providers/liveCoinWatch.ts:164](https://github.com/ZelCore-io/rates-api/blob/master/src/services/providers/liveCoinWatch.ts#L164) Retrieves exchange rates for an array of cryptocurrency symbols. @@ -93,7 +93,7 @@ console.log('Exchange Rates:', rates); > `static` **getInstance**(): `LiveCoinWatch` -Defined in: src/services/providers/liveCoinWatch.ts:92 +Defined in: [src/services/providers/liveCoinWatch.ts:92](https://github.com/ZelCore-io/rates-api/blob/master/src/services/providers/liveCoinWatch.ts#L92) Returns the singleton instance of the LiveCoinWatch class. diff --git a/docs/src/services/zelcoreMarketsUSD/functions/getAll.md b/docs/src/services/zelcoreMarketsUSD/functions/getAll.md index e0de134..30e9a89 100644 --- a/docs/src/services/zelcoreMarketsUSD/functions/getAll.md +++ b/docs/src/services/zelcoreMarketsUSD/functions/getAll.md @@ -8,7 +8,7 @@ > **getAll**(): `Promise`\<[`MarketsData`](../../../types/type-aliases/MarketsData.md)\> -Defined in: src/services/zelcoreMarketsUSD.ts:21 +Defined in: [src/services/zelcoreMarketsUSD.ts:21](https://github.com/ZelCore-io/rates-api/blob/master/src/services/zelcoreMarketsUSD.ts#L21) Fetches market data from multiple providers and aggregates it. diff --git a/docs/src/services/zelcoreMarketsUSD/variables/default.md b/docs/src/services/zelcoreMarketsUSD/variables/default.md index 2da8843..b5f5740 100644 --- a/docs/src/services/zelcoreMarketsUSD/variables/default.md +++ b/docs/src/services/zelcoreMarketsUSD/variables/default.md @@ -8,7 +8,7 @@ > **default**: `object` -Defined in: src/services/zelcoreMarketsUSD.ts:139 +Defined in: [src/services/zelcoreMarketsUSD.ts:138](https://github.com/ZelCore-io/rates-api/blob/master/src/services/zelcoreMarketsUSD.ts#L138) ## Type Declaration diff --git a/docs/src/services/zelcoreRates/functions/getAll.md b/docs/src/services/zelcoreRates/functions/getAll.md index 62a8134..73f61fd 100644 --- a/docs/src/services/zelcoreRates/functions/getAll.md +++ b/docs/src/services/zelcoreRates/functions/getAll.md @@ -8,7 +8,7 @@ > **getAll**(): `Promise`\<[`RatesData`](../../../types/type-aliases/RatesData.md)\> -Defined in: src/services/zelcoreRates.ts:34 +Defined in: [src/services/zelcoreRates.ts:34](https://github.com/ZelCore-io/rates-api/blob/master/src/services/zelcoreRates.ts#L34) Fetches exchange rates and price data from various providers and aggregates them. diff --git a/docs/src/services/zelcoreRates/variables/default.md b/docs/src/services/zelcoreRates/variables/default.md index 6bc0e12..ec7a6e2 100644 --- a/docs/src/services/zelcoreRates/variables/default.md +++ b/docs/src/services/zelcoreRates/variables/default.md @@ -8,7 +8,7 @@ > **default**: `object` -Defined in: src/services/zelcoreRates.ts:153 +Defined in: [src/services/zelcoreRates.ts:153](https://github.com/ZelCore-io/rates-api/blob/master/src/services/zelcoreRates.ts#L153) ## Type Declaration diff --git a/docs/src/services/zelcoreRatesV2/functions/getAll.md b/docs/src/services/zelcoreRatesV2/functions/getAll.md index 1db60b4..099f0ad 100644 --- a/docs/src/services/zelcoreRatesV2/functions/getAll.md +++ b/docs/src/services/zelcoreRatesV2/functions/getAll.md @@ -8,7 +8,7 @@ > **getAll**(): `Promise`\<[`PricesResponse`](../../../types/type-aliases/PricesResponse.md)\> -Defined in: src/services/zelcoreRatesV2.ts:29 +Defined in: [src/services/zelcoreRatesV2.ts:47](https://github.com/ZelCore-io/rates-api/blob/master/src/services/zelcoreRatesV2.ts#L47) Fetches and aggregates cryptocurrency prices and fiat rates from multiple providers. diff --git a/docs/src/services/zelcoreRatesV2/variables/default.md b/docs/src/services/zelcoreRatesV2/variables/default.md index d1d9804..49753de 100644 --- a/docs/src/services/zelcoreRatesV2/variables/default.md +++ b/docs/src/services/zelcoreRatesV2/variables/default.md @@ -8,7 +8,7 @@ > **default**: `object` -Defined in: src/services/zelcoreRatesV2.ts:153 +Defined in: [src/services/zelcoreRatesV2.ts:193](https://github.com/ZelCore-io/rates-api/blob/master/src/services/zelcoreRatesV2.ts#L193) ## Type Declaration diff --git a/docs/src/types/interfaces/ICurrencyData.md b/docs/src/types/interfaces/ICurrencyData.md index 2bd14b4..5e9a2e7 100644 --- a/docs/src/types/interfaces/ICurrencyData.md +++ b/docs/src/types/interfaces/ICurrencyData.md @@ -6,7 +6,7 @@ # Interface: ICurrencyData -Defined in: src/types.ts:81 +Defined in: [src/types.ts:81](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L81) ## Properties @@ -14,7 +14,7 @@ Defined in: src/types.ts:81 > **change**: `number` -Defined in: src/types.ts:84 +Defined in: [src/types.ts:84](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L84) *** @@ -22,7 +22,7 @@ Defined in: src/types.ts:84 > `optional` **change7d?**: `number` -Defined in: src/types.ts:88 +Defined in: [src/types.ts:88](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L88) *** @@ -30,7 +30,7 @@ Defined in: src/types.ts:88 > **market**: `number` -Defined in: src/types.ts:85 +Defined in: [src/types.ts:85](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L85) *** @@ -38,7 +38,7 @@ Defined in: src/types.ts:85 > `optional` **rank?**: `number` -Defined in: src/types.ts:86 +Defined in: [src/types.ts:86](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L86) *** @@ -46,7 +46,7 @@ Defined in: src/types.ts:86 > **supply**: `number` -Defined in: src/types.ts:82 +Defined in: [src/types.ts:82](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L82) *** @@ -54,7 +54,7 @@ Defined in: src/types.ts:82 > `optional` **total\_supply?**: `number` -Defined in: src/types.ts:87 +Defined in: [src/types.ts:87](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L87) *** @@ -62,4 +62,4 @@ Defined in: src/types.ts:87 > **volume**: `number` -Defined in: src/types.ts:83 +Defined in: [src/types.ts:83](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L83) diff --git a/docs/src/types/interfaces/ICurrencyRate.md b/docs/src/types/interfaces/ICurrencyRate.md index 7be564b..4521bec 100644 --- a/docs/src/types/interfaces/ICurrencyRate.md +++ b/docs/src/types/interfaces/ICurrencyRate.md @@ -6,7 +6,7 @@ # Interface: ICurrencyRate -Defined in: src/types.ts:67 +Defined in: [src/types.ts:67](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L67) ## Properties @@ -14,7 +14,7 @@ Defined in: src/types.ts:67 > **code**: `string` -Defined in: src/types.ts:68 +Defined in: [src/types.ts:68](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L68) *** @@ -22,7 +22,7 @@ Defined in: src/types.ts:68 > **name**: `string` -Defined in: src/types.ts:69 +Defined in: [src/types.ts:69](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L69) *** @@ -30,4 +30,4 @@ Defined in: src/types.ts:69 > **rate**: `number` -Defined in: src/types.ts:70 +Defined in: [src/types.ts:70](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L70) diff --git a/docs/src/types/interfaces/IErrorObject.md b/docs/src/types/interfaces/IErrorObject.md index 93c6346..cc19e37 100644 --- a/docs/src/types/interfaces/IErrorObject.md +++ b/docs/src/types/interfaces/IErrorObject.md @@ -6,7 +6,7 @@ # Interface: IErrorObject -Defined in: src/types.ts:75 +Defined in: [src/types.ts:75](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L75) ## Properties @@ -14,7 +14,7 @@ Defined in: src/types.ts:75 > **errors**: `object` -Defined in: src/types.ts:76 +Defined in: [src/types.ts:76](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L76) #### Index Signature diff --git a/docs/src/types/type-aliases/BinanceTicker.md b/docs/src/types/type-aliases/BinanceTicker.md index 0d0dca1..ad54096 100644 --- a/docs/src/types/type-aliases/BinanceTicker.md +++ b/docs/src/types/type-aliases/BinanceTicker.md @@ -8,7 +8,7 @@ > **BinanceTicker** = `object` -Defined in: src/types.ts:137 +Defined in: [src/types.ts:137](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L137) ## Properties @@ -16,7 +16,7 @@ Defined in: src/types.ts:137 > **lastPrice**: `string` -Defined in: src/types.ts:139 +Defined in: [src/types.ts:139](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L139) *** @@ -24,7 +24,7 @@ Defined in: src/types.ts:139 > **priceChangePercent**: `string` -Defined in: src/types.ts:140 +Defined in: [src/types.ts:140](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L140) *** @@ -32,7 +32,7 @@ Defined in: src/types.ts:140 > **quoteVolume**: `string` -Defined in: src/types.ts:141 +Defined in: [src/types.ts:141](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L141) *** @@ -40,4 +40,4 @@ Defined in: src/types.ts:141 > **symbol**: `string` -Defined in: src/types.ts:138 +Defined in: [src/types.ts:138](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L138) diff --git a/docs/src/types/type-aliases/BinanceTokenisedAsset.md b/docs/src/types/type-aliases/BinanceTokenisedAsset.md index 7938f80..fedca1c 100644 --- a/docs/src/types/type-aliases/BinanceTokenisedAsset.md +++ b/docs/src/types/type-aliases/BinanceTokenisedAsset.md @@ -8,7 +8,7 @@ > **BinanceTokenisedAsset** = `object` -Defined in: src/types.ts:129 +Defined in: [src/types.ts:129](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L129) ## Properties @@ -16,7 +16,7 @@ Defined in: src/types.ts:129 > **assetCode**: `string` -Defined in: src/types.ts:130 +Defined in: [src/types.ts:130](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L130) *** @@ -24,7 +24,7 @@ Defined in: src/types.ts:130 > **assetName**: `string` -Defined in: src/types.ts:131 +Defined in: [src/types.ts:131](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L131) *** @@ -32,7 +32,7 @@ Defined in: src/types.ts:131 > `optional` **caList?**: `object`[] -Defined in: src/types.ts:134 +Defined in: [src/types.ts:134](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L134) #### ca @@ -48,7 +48,7 @@ Defined in: src/types.ts:134 > `optional` **logo?**: `string` -Defined in: src/types.ts:133 +Defined in: [src/types.ts:133](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L133) *** @@ -56,4 +56,4 @@ Defined in: src/types.ts:133 > `optional` **uq?**: `string` -Defined in: src/types.ts:132 +Defined in: [src/types.ts:132](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L132) diff --git a/docs/src/types/type-aliases/CodeRates.md b/docs/src/types/type-aliases/CodeRates.md index bf9c8fc..b6e4bf4 100644 --- a/docs/src/types/type-aliases/CodeRates.md +++ b/docs/src/types/type-aliases/CodeRates.md @@ -8,7 +8,7 @@ > **CodeRates** = `object` -Defined in: src/types.ts:73 +Defined in: [src/types.ts:73](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L73) ## Index Signature diff --git a/docs/src/types/type-aliases/CoinGeckoPrice.md b/docs/src/types/type-aliases/CoinGeckoPrice.md index 9ff603e..b9616f9 100644 --- a/docs/src/types/type-aliases/CoinGeckoPrice.md +++ b/docs/src/types/type-aliases/CoinGeckoPrice.md @@ -8,7 +8,7 @@ > **CoinGeckoPrice** = `object` -Defined in: src/types.ts:95 +Defined in: [src/types.ts:95](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L95) ## Properties @@ -16,7 +16,7 @@ Defined in: src/types.ts:95 > **ath**: `number` -Defined in: src/types.ts:114 +Defined in: [src/types.ts:114](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L114) *** @@ -24,7 +24,7 @@ Defined in: src/types.ts:114 > **ath\_change\_percentage**: `number` -Defined in: src/types.ts:115 +Defined in: [src/types.ts:115](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L115) *** @@ -32,7 +32,7 @@ Defined in: src/types.ts:115 > **ath\_date**: `string` -Defined in: src/types.ts:116 +Defined in: [src/types.ts:116](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L116) *** @@ -40,7 +40,7 @@ Defined in: src/types.ts:116 > **atl**: `number` -Defined in: src/types.ts:117 +Defined in: [src/types.ts:117](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L117) *** @@ -48,7 +48,7 @@ Defined in: src/types.ts:117 > **atl\_change\_percentage**: `number` -Defined in: src/types.ts:118 +Defined in: [src/types.ts:118](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L118) *** @@ -56,7 +56,7 @@ Defined in: src/types.ts:118 > **atl\_date**: `string` -Defined in: src/types.ts:119 +Defined in: [src/types.ts:119](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L119) *** @@ -64,7 +64,7 @@ Defined in: src/types.ts:119 > **circulating\_supply**: `number` -Defined in: src/types.ts:111 +Defined in: [src/types.ts:111](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L111) *** @@ -72,7 +72,7 @@ Defined in: src/types.ts:111 > **current\_price**: `number` -Defined in: src/types.ts:100 +Defined in: [src/types.ts:100](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L100) *** @@ -80,7 +80,7 @@ Defined in: src/types.ts:100 > **fully\_diluted\_valuation**: `number` -Defined in: src/types.ts:103 +Defined in: [src/types.ts:103](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L103) *** @@ -88,7 +88,7 @@ Defined in: src/types.ts:103 > **high\_24h**: `number` -Defined in: src/types.ts:105 +Defined in: [src/types.ts:105](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L105) *** @@ -96,7 +96,7 @@ Defined in: src/types.ts:105 > **id**: `string` -Defined in: src/types.ts:96 +Defined in: [src/types.ts:96](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L96) *** @@ -104,7 +104,7 @@ Defined in: src/types.ts:96 > **image**: `string` -Defined in: src/types.ts:99 +Defined in: [src/types.ts:99](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L99) *** @@ -112,7 +112,7 @@ Defined in: src/types.ts:99 > **last\_updated**: `string` -Defined in: src/types.ts:125 +Defined in: [src/types.ts:125](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L125) *** @@ -120,7 +120,7 @@ Defined in: src/types.ts:125 > **low\_24h**: `number` -Defined in: src/types.ts:106 +Defined in: [src/types.ts:106](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L106) *** @@ -128,7 +128,7 @@ Defined in: src/types.ts:106 > **market\_cap**: `number` -Defined in: src/types.ts:101 +Defined in: [src/types.ts:101](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L101) *** @@ -136,7 +136,7 @@ Defined in: src/types.ts:101 > **market\_cap\_change\_24h**: `number` -Defined in: src/types.ts:109 +Defined in: [src/types.ts:109](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L109) *** @@ -144,7 +144,7 @@ Defined in: src/types.ts:109 > **market\_cap\_change\_percentage\_24h**: `number` -Defined in: src/types.ts:110 +Defined in: [src/types.ts:110](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L110) *** @@ -152,7 +152,7 @@ Defined in: src/types.ts:110 > **market\_cap\_rank**: `number` -Defined in: src/types.ts:102 +Defined in: [src/types.ts:102](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L102) *** @@ -160,7 +160,7 @@ Defined in: src/types.ts:102 > **max\_supply**: `number` -Defined in: src/types.ts:113 +Defined in: [src/types.ts:113](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L113) *** @@ -168,7 +168,7 @@ Defined in: src/types.ts:113 > **name**: `string` -Defined in: src/types.ts:98 +Defined in: [src/types.ts:98](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L98) *** @@ -176,7 +176,7 @@ Defined in: src/types.ts:98 > **price\_change\_24h**: `number` -Defined in: src/types.ts:107 +Defined in: [src/types.ts:107](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L107) *** @@ -184,7 +184,7 @@ Defined in: src/types.ts:107 > **price\_change\_percentage\_24h**: `number` -Defined in: src/types.ts:108 +Defined in: [src/types.ts:108](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L108) *** @@ -192,7 +192,7 @@ Defined in: src/types.ts:108 > **price\_change\_percentage\_7d\_in\_currency**: `number` -Defined in: src/types.ts:126 +Defined in: [src/types.ts:126](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L126) *** @@ -200,7 +200,7 @@ Defined in: src/types.ts:126 > **roi**: `null` \| \{ `currency`: `string`; `percentage`: `number`; `times`: `number`; \} -Defined in: src/types.ts:120 +Defined in: [src/types.ts:120](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L120) *** @@ -208,7 +208,7 @@ Defined in: src/types.ts:120 > **symbol**: `string` -Defined in: src/types.ts:97 +Defined in: [src/types.ts:97](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L97) *** @@ -216,7 +216,7 @@ Defined in: src/types.ts:97 > **total\_supply**: `number` -Defined in: src/types.ts:112 +Defined in: [src/types.ts:112](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L112) *** @@ -224,4 +224,4 @@ Defined in: src/types.ts:112 > **total\_volume**: `number` -Defined in: src/types.ts:104 +Defined in: [src/types.ts:104](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L104) diff --git a/docs/src/types/type-aliases/CoinGeckoToken.md b/docs/src/types/type-aliases/CoinGeckoToken.md index 29dcd3b..5d959c6 100644 --- a/docs/src/types/type-aliases/CoinGeckoToken.md +++ b/docs/src/types/type-aliases/CoinGeckoToken.md @@ -8,7 +8,7 @@ > **CoinGeckoToken** = `object` -Defined in: src/types.ts:58 +Defined in: [src/types.ts:58](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L58) ## Properties @@ -16,7 +16,7 @@ Defined in: src/types.ts:58 > **id**: `string` -Defined in: src/types.ts:59 +Defined in: [src/types.ts:59](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L59) *** @@ -24,7 +24,7 @@ Defined in: src/types.ts:59 > **name**: `string` -Defined in: src/types.ts:61 +Defined in: [src/types.ts:61](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L61) *** @@ -32,7 +32,7 @@ Defined in: src/types.ts:61 > **platforms**: `Record`\<`string`, `string`\> -Defined in: src/types.ts:62 +Defined in: [src/types.ts:62](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L62) *** @@ -40,4 +40,4 @@ Defined in: src/types.ts:62 > **symbol**: `string` -Defined in: src/types.ts:60 +Defined in: [src/types.ts:60](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L60) diff --git a/docs/src/types/type-aliases/CoinInfo.md b/docs/src/types/type-aliases/CoinInfo.md index 0cfdcee..0e663d2 100644 --- a/docs/src/types/type-aliases/CoinInfo.md +++ b/docs/src/types/type-aliases/CoinInfo.md @@ -8,7 +8,7 @@ > **CoinInfo** = `object` -Defined in: src/types.ts:32 +Defined in: [src/types.ts:32](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L32) ## Properties @@ -16,7 +16,7 @@ Defined in: src/types.ts:32 > **auditInfos**: `string`[] -Defined in: src/types.ts:54 +Defined in: [src/types.ts:54](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L54) *** @@ -24,7 +24,7 @@ Defined in: src/types.ts:54 > **bitcointalk**: `string` -Defined in: src/types.ts:41 +Defined in: [src/types.ts:41](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L41) *** @@ -32,7 +32,7 @@ Defined in: src/types.ts:41 > **circulating\_supply**: `number` \| `null` -Defined in: src/types.ts:35 +Defined in: [src/types.ts:35](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L35) *** @@ -40,7 +40,7 @@ Defined in: src/types.ts:35 > **coingeckoID**: `string` -Defined in: src/types.ts:53 +Defined in: [src/types.ts:53](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L53) *** @@ -48,7 +48,7 @@ Defined in: src/types.ts:53 > **coinMarketCapID**: `string` -Defined in: src/types.ts:52 +Defined in: [src/types.ts:52](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L52) *** @@ -56,7 +56,7 @@ Defined in: src/types.ts:52 > **cryptoCompareID**: `string` -Defined in: src/types.ts:51 +Defined in: [src/types.ts:51](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L51) *** @@ -64,7 +64,7 @@ Defined in: src/types.ts:51 > **description**: `string` -Defined in: src/types.ts:33 +Defined in: [src/types.ts:33](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L33) *** @@ -72,7 +72,7 @@ Defined in: src/types.ts:33 > **discord**: `string` -Defined in: src/types.ts:39 +Defined in: [src/types.ts:39](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L39) *** @@ -80,7 +80,7 @@ Defined in: src/types.ts:39 > **explorers**: `string`[] -Defined in: src/types.ts:37 +Defined in: [src/types.ts:37](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L37) *** @@ -88,7 +88,7 @@ Defined in: src/types.ts:37 > **facebook**: `string` -Defined in: src/types.ts:42 +Defined in: [src/types.ts:42](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L42) *** @@ -96,7 +96,7 @@ Defined in: src/types.ts:42 > **instagram**: `string` -Defined in: src/types.ts:47 +Defined in: [src/types.ts:47](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L47) *** @@ -104,7 +104,7 @@ Defined in: src/types.ts:47 > **linkedin**: `string` -Defined in: src/types.ts:50 +Defined in: [src/types.ts:50](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L50) *** @@ -112,7 +112,7 @@ Defined in: src/types.ts:50 > **medium**: `string` -Defined in: src/types.ts:38 +Defined in: [src/types.ts:38](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L38) *** @@ -120,7 +120,7 @@ Defined in: src/types.ts:38 > **reddit**: `string` -Defined in: src/types.ts:44 +Defined in: [src/types.ts:44](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L44) *** @@ -128,7 +128,7 @@ Defined in: src/types.ts:44 > **repository**: `string` -Defined in: src/types.ts:45 +Defined in: [src/types.ts:45](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L45) *** @@ -136,7 +136,7 @@ Defined in: src/types.ts:45 > **telegram**: `string` -Defined in: src/types.ts:40 +Defined in: [src/types.ts:40](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L40) *** @@ -144,7 +144,7 @@ Defined in: src/types.ts:40 > **tiktok**: `string` -Defined in: src/types.ts:48 +Defined in: [src/types.ts:48](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L48) *** @@ -152,7 +152,7 @@ Defined in: src/types.ts:48 > **total\_supply**: `number` \| `null` -Defined in: src/types.ts:34 +Defined in: [src/types.ts:34](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L34) *** @@ -160,7 +160,7 @@ Defined in: src/types.ts:34 > **twitch**: `string` -Defined in: src/types.ts:49 +Defined in: [src/types.ts:49](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L49) *** @@ -168,7 +168,7 @@ Defined in: src/types.ts:49 > **twitter**: `string` -Defined in: src/types.ts:43 +Defined in: [src/types.ts:43](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L43) *** @@ -176,7 +176,7 @@ Defined in: src/types.ts:43 > **websites**: `string`[] -Defined in: src/types.ts:36 +Defined in: [src/types.ts:36](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L36) *** @@ -184,7 +184,7 @@ Defined in: src/types.ts:36 > **whitepaper**: `string`[] -Defined in: src/types.ts:55 +Defined in: [src/types.ts:55](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L55) *** @@ -192,4 +192,4 @@ Defined in: src/types.ts:55 > **youtube**: `string` -Defined in: src/types.ts:46 +Defined in: [src/types.ts:46](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L46) diff --git a/docs/src/types/type-aliases/ContractWithType.md b/docs/src/types/type-aliases/ContractWithType.md index dc0faa1..6df2b62 100644 --- a/docs/src/types/type-aliases/ContractWithType.md +++ b/docs/src/types/type-aliases/ContractWithType.md @@ -8,7 +8,7 @@ > **ContractWithType** = `object` -Defined in: src/types.ts:27 +Defined in: [src/types.ts:27](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L27) ## Properties @@ -16,7 +16,7 @@ Defined in: src/types.ts:27 > **address**: `string` -Defined in: src/types.ts:28 +Defined in: [src/types.ts:28](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L28) *** @@ -24,4 +24,4 @@ Defined in: src/types.ts:28 > **type**: `string` -Defined in: src/types.ts:29 +Defined in: [src/types.ts:29](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L29) diff --git a/docs/src/types/type-aliases/CryptoCompareMarkets.md b/docs/src/types/type-aliases/CryptoCompareMarkets.md index 5f16e63..b821206 100644 --- a/docs/src/types/type-aliases/CryptoCompareMarkets.md +++ b/docs/src/types/type-aliases/CryptoCompareMarkets.md @@ -8,7 +8,7 @@ > **CryptoCompareMarkets** = `object` -Defined in: src/types.ts:149 +Defined in: [src/types.ts:149](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L149) ## Index Signature diff --git a/docs/src/types/type-aliases/CryptoComparePrice.md b/docs/src/types/type-aliases/CryptoComparePrice.md index 8df7423..02cda1c 100644 --- a/docs/src/types/type-aliases/CryptoComparePrice.md +++ b/docs/src/types/type-aliases/CryptoComparePrice.md @@ -8,7 +8,7 @@ > **CryptoComparePrice** = `object` -Defined in: src/types.ts:144 +Defined in: [src/types.ts:144](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L144) ## Index Signature diff --git a/docs/src/types/type-aliases/CryptoPrice.md b/docs/src/types/type-aliases/CryptoPrice.md index 69c611e..4e19ef0 100644 --- a/docs/src/types/type-aliases/CryptoPrice.md +++ b/docs/src/types/type-aliases/CryptoPrice.md @@ -8,7 +8,7 @@ > **CryptoPrice** = `object` -Defined in: src/types.ts:1 +Defined in: [src/types.ts:1](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L1) ## Properties @@ -16,7 +16,7 @@ Defined in: src/types.ts:1 > **change24h**: `number` -Defined in: src/types.ts:7 +Defined in: [src/types.ts:7](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L7) *** @@ -24,7 +24,7 @@ Defined in: src/types.ts:7 > `optional` **change7d?**: `number` -Defined in: src/types.ts:11 +Defined in: [src/types.ts:11](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L11) *** @@ -32,7 +32,7 @@ Defined in: src/types.ts:11 > **id**: `string` -Defined in: src/types.ts:2 +Defined in: [src/types.ts:2](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L2) *** @@ -40,7 +40,7 @@ Defined in: src/types.ts:2 > **market**: `number` -Defined in: src/types.ts:8 +Defined in: [src/types.ts:8](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L8) *** @@ -48,7 +48,7 @@ Defined in: src/types.ts:8 > **provider**: `string` -Defined in: src/types.ts:3 +Defined in: [src/types.ts:3](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L3) *** @@ -56,7 +56,7 @@ Defined in: src/types.ts:3 > `optional` **rank?**: `number` -Defined in: src/types.ts:9 +Defined in: [src/types.ts:9](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L9) *** @@ -64,7 +64,7 @@ Defined in: src/types.ts:9 > **rates**: `Record`\<`string`, `number`\> -Defined in: src/types.ts:4 +Defined in: [src/types.ts:4](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L4) *** @@ -72,7 +72,7 @@ Defined in: src/types.ts:4 > **supply**: `number` -Defined in: src/types.ts:5 +Defined in: [src/types.ts:5](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L5) *** @@ -80,7 +80,7 @@ Defined in: src/types.ts:5 > `optional` **total\_supply?**: `number` -Defined in: src/types.ts:10 +Defined in: [src/types.ts:10](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L10) *** @@ -88,4 +88,4 @@ Defined in: src/types.ts:10 > **volume**: `number` -Defined in: src/types.ts:6 +Defined in: [src/types.ts:6](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L6) diff --git a/docs/src/types/type-aliases/CurrencyMap.md b/docs/src/types/type-aliases/CurrencyMap.md index 3af8338..64a431a 100644 --- a/docs/src/types/type-aliases/CurrencyMap.md +++ b/docs/src/types/type-aliases/CurrencyMap.md @@ -8,7 +8,7 @@ > **CurrencyMap** = `object` -Defined in: src/types.ts:91 +Defined in: [src/types.ts:91](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L91) ## Index Signature diff --git a/docs/src/types/type-aliases/FiatPrice.md b/docs/src/types/type-aliases/FiatPrice.md index 1b719b8..4d406c6 100644 --- a/docs/src/types/type-aliases/FiatPrice.md +++ b/docs/src/types/type-aliases/FiatPrice.md @@ -8,7 +8,7 @@ > **FiatPrice** = `object` -Defined in: src/types.ts:14 +Defined in: [src/types.ts:14](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L14) ## Properties @@ -16,7 +16,7 @@ Defined in: src/types.ts:14 > **code**: `string` -Defined in: src/types.ts:15 +Defined in: [src/types.ts:15](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L15) *** @@ -24,7 +24,7 @@ Defined in: src/types.ts:15 > **name**: `string` -Defined in: src/types.ts:16 +Defined in: [src/types.ts:16](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L16) *** @@ -32,7 +32,7 @@ Defined in: src/types.ts:16 > `optional` **provider?**: `string` -Defined in: src/types.ts:18 +Defined in: [src/types.ts:18](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L18) *** @@ -40,4 +40,4 @@ Defined in: src/types.ts:18 > **rate**: `number` -Defined in: src/types.ts:17 +Defined in: [src/types.ts:17](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L17) diff --git a/docs/src/types/type-aliases/FoundContractStore.md b/docs/src/types/type-aliases/FoundContractStore.md index 095cd45..439a7bb 100644 --- a/docs/src/types/type-aliases/FoundContractStore.md +++ b/docs/src/types/type-aliases/FoundContractStore.md @@ -8,4 +8,4 @@ > **FoundContractStore** = `Record`\<`string`, \{ `cg`: [`CoinGeckoToken`](CoinGeckoToken.md); `count`: `number`; `zel`: [`ContractWithType`](ContractWithType.md); \}\> -Defined in: src/types.ts:65 +Defined in: [src/types.ts:65](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L65) diff --git a/docs/src/types/type-aliases/LiveCoinWatchMarket.md b/docs/src/types/type-aliases/LiveCoinWatchMarket.md index c916a15..6aa34b5 100644 --- a/docs/src/types/type-aliases/LiveCoinWatchMarket.md +++ b/docs/src/types/type-aliases/LiveCoinWatchMarket.md @@ -8,7 +8,7 @@ > **LiveCoinWatchMarket** = `object` -Defined in: src/types.ts:204 +Defined in: [src/types.ts:204](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L204) ## Properties @@ -16,7 +16,7 @@ Defined in: src/types.ts:204 > **age**: `number` -Defined in: src/types.ts:207 +Defined in: [src/types.ts:207](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L207) *** @@ -24,7 +24,7 @@ Defined in: src/types.ts:207 > **allTimeHighUSD**: `number` -Defined in: src/types.ts:217 +Defined in: [src/types.ts:217](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L217) *** @@ -32,7 +32,7 @@ Defined in: src/types.ts:217 > **cap**: `number` \| `null` -Defined in: src/types.ts:242 +Defined in: [src/types.ts:242](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L242) *** @@ -40,7 +40,7 @@ Defined in: src/types.ts:242 > **categories**: `string`[] -Defined in: src/types.ts:216 +Defined in: [src/types.ts:216](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L216) *** @@ -48,7 +48,7 @@ Defined in: src/types.ts:216 > **circulatingSupply**: `number` \| `null` -Defined in: src/types.ts:218 +Defined in: [src/types.ts:218](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L218) *** @@ -56,7 +56,7 @@ Defined in: src/types.ts:218 > **code**: `string` -Defined in: src/types.ts:239 +Defined in: [src/types.ts:239](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L239) *** @@ -64,7 +64,7 @@ Defined in: src/types.ts:239 > **color**: `string` -Defined in: src/types.ts:208 +Defined in: [src/types.ts:208](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L208) *** @@ -72,7 +72,7 @@ Defined in: src/types.ts:208 > **delta**: `object` -Defined in: src/types.ts:243 +Defined in: [src/types.ts:243](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L243) #### day @@ -104,7 +104,7 @@ Defined in: src/types.ts:243 > **exchanges**: `number` -Defined in: src/types.ts:213 +Defined in: [src/types.ts:213](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L213) *** @@ -112,7 +112,7 @@ Defined in: src/types.ts:213 > **links**: `object` -Defined in: src/types.ts:221 +Defined in: [src/types.ts:221](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L221) #### discord @@ -184,7 +184,7 @@ Defined in: src/types.ts:221 > **markets**: `number` -Defined in: src/types.ts:214 +Defined in: [src/types.ts:214](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L214) *** @@ -192,7 +192,7 @@ Defined in: src/types.ts:214 > **maxSupply**: `number` \| `null` -Defined in: src/types.ts:220 +Defined in: [src/types.ts:220](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L220) *** @@ -200,7 +200,7 @@ Defined in: src/types.ts:220 > **name**: `string` -Defined in: src/types.ts:205 +Defined in: [src/types.ts:205](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L205) *** @@ -208,7 +208,7 @@ Defined in: src/types.ts:205 > **pairs**: `number` -Defined in: src/types.ts:215 +Defined in: [src/types.ts:215](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L215) *** @@ -216,7 +216,7 @@ Defined in: src/types.ts:215 > **png32**: `string` -Defined in: src/types.ts:209 +Defined in: [src/types.ts:209](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L209) *** @@ -224,7 +224,7 @@ Defined in: src/types.ts:209 > **png64**: `string` -Defined in: src/types.ts:210 +Defined in: [src/types.ts:210](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L210) *** @@ -232,7 +232,7 @@ Defined in: src/types.ts:210 > **rank**: `number` -Defined in: src/types.ts:206 +Defined in: [src/types.ts:206](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L206) *** @@ -240,7 +240,7 @@ Defined in: src/types.ts:206 > **rate**: `number` \| `null` -Defined in: src/types.ts:240 +Defined in: [src/types.ts:240](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L240) *** @@ -248,7 +248,7 @@ Defined in: src/types.ts:240 > **totalSupply**: `number` -Defined in: src/types.ts:219 +Defined in: [src/types.ts:219](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L219) *** @@ -256,7 +256,7 @@ Defined in: src/types.ts:219 > **volume**: `number` \| `null` -Defined in: src/types.ts:241 +Defined in: [src/types.ts:241](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L241) *** @@ -264,7 +264,7 @@ Defined in: src/types.ts:241 > **webp32**: `string` -Defined in: src/types.ts:211 +Defined in: [src/types.ts:211](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L211) *** @@ -272,4 +272,4 @@ Defined in: src/types.ts:211 > **webp64**: `string` -Defined in: src/types.ts:212 +Defined in: [src/types.ts:212](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L212) diff --git a/docs/src/types/type-aliases/MarketsData.md b/docs/src/types/type-aliases/MarketsData.md index fefa979..bbe302b 100644 --- a/docs/src/types/type-aliases/MarketsData.md +++ b/docs/src/types/type-aliases/MarketsData.md @@ -8,4 +8,4 @@ > **MarketsData** = \[[`CurrencyMap`](CurrencyMap.md), [`IErrorObject`](../interfaces/IErrorObject.md)\] -Defined in: src/types.ts:93 +Defined in: [src/types.ts:93](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L93) diff --git a/docs/src/types/type-aliases/PricesResponse.md b/docs/src/types/type-aliases/PricesResponse.md index 692b204..95bed5a 100644 --- a/docs/src/types/type-aliases/PricesResponse.md +++ b/docs/src/types/type-aliases/PricesResponse.md @@ -8,7 +8,7 @@ > **PricesResponse** = `object` -Defined in: src/types.ts:21 +Defined in: [src/types.ts:21](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L21) ## Properties @@ -16,7 +16,7 @@ Defined in: src/types.ts:21 > **crypto**: [`CryptoPrice`](CryptoPrice.md)[] -Defined in: src/types.ts:22 +Defined in: [src/types.ts:22](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L22) *** @@ -24,7 +24,7 @@ Defined in: src/types.ts:22 > `optional` **errors?**: `Record`\<`string`, `any`\> -Defined in: src/types.ts:24 +Defined in: [src/types.ts:24](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L24) *** @@ -32,4 +32,4 @@ Defined in: src/types.ts:24 > **fiat**: [`FiatPrice`](FiatPrice.md)[] -Defined in: src/types.ts:23 +Defined in: [src/types.ts:23](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L23) diff --git a/docs/src/types/type-aliases/RatesData.md b/docs/src/types/type-aliases/RatesData.md index 4b45a40..b170dff 100644 --- a/docs/src/types/type-aliases/RatesData.md +++ b/docs/src/types/type-aliases/RatesData.md @@ -8,4 +8,4 @@ > **RatesData** = \[[`ICurrencyRate`](../interfaces/ICurrencyRate.md)[], [`CodeRates`](CodeRates.md), [`IErrorObject`](../interfaces/IErrorObject.md)\] -Defined in: src/types.ts:79 +Defined in: [src/types.ts:79](https://github.com/ZelCore-io/rates-api/blob/master/src/types.ts#L79) diff --git a/typedoc.json b/typedoc.json index 86b52a6..cee1a4e 100644 --- a/typedoc.json +++ b/typedoc.json @@ -10,6 +10,7 @@ "excludePrivate": true, "excludeProtected": true, "includeVersion": true, + "gitRevision": "master", "compilerOptions": { "target": "es2020", "module": "commonjs",