@pioneer-platform/markets 8.12.0 → 8.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,421 @@
1
+ "use strict";
2
+ /*
3
+ CCXT Pricing Integration
4
+
5
+ Provides access to 100+ cryptocurrency exchanges for price data
6
+ Used as fallback when CoinGecko, CoinMarketCap, and CoinCap fail
7
+
8
+ Strategy:
9
+ 1. Use major exchanges (Binance, Kraken, Coinbase) for reliability
10
+ 2. Fetch USDT pairs (most liquid)
11
+ 3. Fallback to BTC pairs with conversion
12
+ 4. Cache results to avoid rate limits
13
+ */
14
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ var desc = Object.getOwnPropertyDescriptor(m, k);
17
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
18
+ desc = { enumerable: true, get: function() { return m[k]; } };
19
+ }
20
+ Object.defineProperty(o, k2, desc);
21
+ }) : (function(o, m, k, k2) {
22
+ if (k2 === undefined) k2 = k;
23
+ o[k2] = m[k];
24
+ }));
25
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
26
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
27
+ }) : function(o, v) {
28
+ o["default"] = v;
29
+ });
30
+ var __importStar = (this && this.__importStar) || (function () {
31
+ var ownKeys = function(o) {
32
+ ownKeys = Object.getOwnPropertyNames || function (o) {
33
+ var ar = [];
34
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
35
+ return ar;
36
+ };
37
+ return ownKeys(o);
38
+ };
39
+ return function (mod) {
40
+ if (mod && mod.__esModule) return mod;
41
+ var result = {};
42
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
43
+ __setModuleDefault(result, mod);
44
+ return result;
45
+ };
46
+ })();
47
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
48
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
49
+ return new (P || (P = Promise))(function (resolve, reject) {
50
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
51
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
52
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
53
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
54
+ });
55
+ };
56
+ var __generator = (this && this.__generator) || function (thisArg, body) {
57
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
58
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
59
+ function verb(n) { return function (v) { return step([n, v]); }; }
60
+ function step(op) {
61
+ if (f) throw new TypeError("Generator is already executing.");
62
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
63
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
64
+ if (y = 0, t) op = [op[0] & 2, t.value];
65
+ switch (op[0]) {
66
+ case 0: case 1: t = op; break;
67
+ case 4: _.label++; return { value: op[1], done: false };
68
+ case 5: _.label++; y = op[1]; op = [0]; continue;
69
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
70
+ default:
71
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
72
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
73
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
74
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
75
+ if (t[2]) _.ops.pop();
76
+ _.trys.pop(); continue;
77
+ }
78
+ op = body.call(thisArg, _);
79
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
80
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
81
+ }
82
+ };
83
+ Object.defineProperty(exports, "__esModule", { value: true });
84
+ exports.getPriceFromCCXT = getPriceFromCCXT;
85
+ exports.getBatchPricesFromCCXT = getBatchPricesFromCCXT;
86
+ exports.clearPriceCache = clearPriceCache;
87
+ exports.getCacheStats = getCacheStats;
88
+ exports.healthCheck = healthCheck;
89
+ var ccxt = __importStar(require("ccxt"));
90
+ var log = require('@pioneer-platform/loggerdog')();
91
+ var TAG = ' | ccxt-pricing | ';
92
+ // Exchange instances (lazy loaded)
93
+ var exchanges = new Map();
94
+ var priceCache = new Map();
95
+ var CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
96
+ // Exchange configuration (in order of preference)
97
+ var EXCHANGE_CONFIG = [
98
+ { id: 'binance', name: 'Binance', rateLimit: 1200 },
99
+ { id: 'kraken', name: 'Kraken', rateLimit: 1000 },
100
+ { id: 'coinbase', name: 'Coinbase', rateLimit: 1000 },
101
+ { id: 'kucoin', name: 'KuCoin', rateLimit: 1000 },
102
+ { id: 'bybit', name: 'Bybit', rateLimit: 1000 },
103
+ { id: 'okx', name: 'OKX', rateLimit: 1000 },
104
+ ];
105
+ // Symbol mapping for major cryptocurrencies
106
+ // Special cases handled here to avoid confusion
107
+ var SYMBOL_MAP = {
108
+ 'BTC': 'BTC',
109
+ 'ETH': 'ETH',
110
+ 'ETHEREUM': 'ETH',
111
+ 'BNB': 'BNB',
112
+ 'MATIC': 'MATIC',
113
+ 'POL': 'POL',
114
+ 'ATOM': 'ATOM',
115
+ 'RUNE': 'RUNE',
116
+ 'XRP': 'XRP',
117
+ 'LTC': 'LTC',
118
+ 'DOGE': 'DOGE',
119
+ 'ADA': 'ADA',
120
+ 'DOT': 'DOT',
121
+ 'SOL': 'SOL',
122
+ 'AVAX': 'AVAX',
123
+ 'LINK': 'LINK',
124
+ 'UNI': 'UNI',
125
+ 'AAVE': 'AAVE',
126
+ 'ALGO': 'ALGO',
127
+ 'BCH': 'BCH',
128
+ // Special case: Base network uses ETH as native asset
129
+ // "BASE" symbol on exchanges refers to Base Protocol (different token)
130
+ 'BASE': 'ETH',
131
+ };
132
+ /**
133
+ * Initialize exchange instances
134
+ */
135
+ function initializeExchange(exchangeId) {
136
+ var tag = TAG + 'initializeExchange | ';
137
+ try {
138
+ if (exchanges.has(exchangeId)) {
139
+ return exchanges.get(exchangeId);
140
+ }
141
+ var ExchangeClass = ccxt[exchangeId];
142
+ if (!ExchangeClass) {
143
+ log.error(tag, "Exchange ".concat(exchangeId, " not found in CCXT"));
144
+ return null;
145
+ }
146
+ var exchange = new ExchangeClass({
147
+ enableRateLimit: true,
148
+ timeout: 5000,
149
+ });
150
+ exchanges.set(exchangeId, exchange);
151
+ log.info(tag, "Initialized ".concat(exchangeId, " exchange"));
152
+ return exchange;
153
+ }
154
+ catch (error) {
155
+ log.error(tag, "Failed to initialize ".concat(exchangeId, ":"), error);
156
+ return null;
157
+ }
158
+ }
159
+ /**
160
+ * Get price from cache if valid
161
+ */
162
+ function getCachedPrice(symbol) {
163
+ var cached = priceCache.get(symbol);
164
+ if (cached && (Date.now() - cached.timestamp) < CACHE_TTL_MS) {
165
+ return cached.price;
166
+ }
167
+ return null;
168
+ }
169
+ /**
170
+ * Set price in cache
171
+ */
172
+ function setCachedPrice(symbol, price, source) {
173
+ priceCache.set(symbol, {
174
+ price: price,
175
+ timestamp: Date.now(),
176
+ source: source
177
+ });
178
+ }
179
+ /**
180
+ * Fetch price from a specific exchange
181
+ */
182
+ function fetchPriceFromExchange(exchangeId, symbol) {
183
+ return __awaiter(this, void 0, void 0, function () {
184
+ var tag, exchange, pairs, _i, pairs_1, pair, ticker, pairError_1, error_1;
185
+ return __generator(this, function (_a) {
186
+ switch (_a.label) {
187
+ case 0:
188
+ tag = TAG + 'fetchPriceFromExchange | ';
189
+ _a.label = 1;
190
+ case 1:
191
+ _a.trys.push([1, 9, , 10]);
192
+ exchange = initializeExchange(exchangeId);
193
+ if (!exchange) {
194
+ return [2 /*return*/, 0];
195
+ }
196
+ pairs = [
197
+ "".concat(symbol, "/USDT"),
198
+ "".concat(symbol, "/USD"),
199
+ "".concat(symbol, "/USDC"),
200
+ ];
201
+ _i = 0, pairs_1 = pairs;
202
+ _a.label = 2;
203
+ case 2:
204
+ if (!(_i < pairs_1.length)) return [3 /*break*/, 8];
205
+ pair = pairs_1[_i];
206
+ _a.label = 3;
207
+ case 3:
208
+ _a.trys.push([3, 6, , 7]);
209
+ return [4 /*yield*/, exchange.loadMarkets()];
210
+ case 4:
211
+ _a.sent();
212
+ if (!exchange.has['fetchTicker']) {
213
+ log.debug(tag, "".concat(exchangeId, " doesn't support fetchTicker"));
214
+ return [3 /*break*/, 8];
215
+ }
216
+ if (!exchange.markets[pair]) {
217
+ return [3 /*break*/, 7]; // Pair not available on this exchange
218
+ }
219
+ return [4 /*yield*/, exchange.fetchTicker(pair)];
220
+ case 5:
221
+ ticker = _a.sent();
222
+ if (ticker && ticker.last && ticker.last > 0) {
223
+ log.info(tag, "Got price from ".concat(exchangeId, " for ").concat(pair, ": $").concat(ticker.last));
224
+ return [2 /*return*/, ticker.last];
225
+ }
226
+ return [3 /*break*/, 7];
227
+ case 6:
228
+ pairError_1 = _a.sent();
229
+ // Pair not found, try next
230
+ log.debug(tag, "".concat(pair, " not available on ").concat(exchangeId));
231
+ return [3 /*break*/, 7];
232
+ case 7:
233
+ _i++;
234
+ return [3 /*break*/, 2];
235
+ case 8: return [3 /*break*/, 10];
236
+ case 9:
237
+ error_1 = _a.sent();
238
+ log.debug(tag, "Error fetching from ".concat(exchangeId, ":"), error_1.message);
239
+ return [3 /*break*/, 10];
240
+ case 10: return [2 /*return*/, 0];
241
+ }
242
+ });
243
+ });
244
+ }
245
+ /**
246
+ * Get price for a symbol from any available exchange
247
+ * Tries exchanges in order of preference until successful
248
+ */
249
+ function getPriceFromCCXT(symbol) {
250
+ return __awaiter(this, void 0, void 0, function () {
251
+ var tag, normalizedSymbol, cached, _i, EXCHANGE_CONFIG_1, config, price, error_2;
252
+ return __generator(this, function (_a) {
253
+ switch (_a.label) {
254
+ case 0:
255
+ tag = TAG + 'getPriceFromCCXT | ';
256
+ _a.label = 1;
257
+ case 1:
258
+ _a.trys.push([1, 6, , 7]);
259
+ normalizedSymbol = SYMBOL_MAP[symbol.toUpperCase()] || symbol.toUpperCase();
260
+ cached = getCachedPrice(normalizedSymbol);
261
+ if (cached !== null) {
262
+ log.debug(tag, "Cache hit for ".concat(normalizedSymbol, ": $").concat(cached));
263
+ return [2 /*return*/, cached];
264
+ }
265
+ log.info(tag, "Fetching price for ".concat(normalizedSymbol, " from CCXT exchanges..."));
266
+ _i = 0, EXCHANGE_CONFIG_1 = EXCHANGE_CONFIG;
267
+ _a.label = 2;
268
+ case 2:
269
+ if (!(_i < EXCHANGE_CONFIG_1.length)) return [3 /*break*/, 5];
270
+ config = EXCHANGE_CONFIG_1[_i];
271
+ return [4 /*yield*/, fetchPriceFromExchange(config.id, normalizedSymbol)];
272
+ case 3:
273
+ price = _a.sent();
274
+ if (price > 0) {
275
+ // Cache the result
276
+ setCachedPrice(normalizedSymbol, price, config.id);
277
+ log.info(tag, "\u2705 Got price for ".concat(normalizedSymbol, " from ").concat(config.name, ": $").concat(price));
278
+ return [2 /*return*/, price];
279
+ }
280
+ _a.label = 4;
281
+ case 4:
282
+ _i++;
283
+ return [3 /*break*/, 2];
284
+ case 5:
285
+ log.warn(tag, "No price found for ".concat(normalizedSymbol, " on any exchange"));
286
+ return [2 /*return*/, 0];
287
+ case 6:
288
+ error_2 = _a.sent();
289
+ log.error(tag, "Error fetching price for ".concat(symbol, ":"), error_2);
290
+ return [2 /*return*/, 0];
291
+ case 7: return [2 /*return*/];
292
+ }
293
+ });
294
+ });
295
+ }
296
+ /**
297
+ * Get prices for multiple symbols in parallel
298
+ * More efficient than calling getPriceFromCCXT multiple times
299
+ */
300
+ function getBatchPricesFromCCXT(symbols) {
301
+ return __awaiter(this, void 0, void 0, function () {
302
+ var tag, results, pricePromises, prices, _i, prices_1, _a, symbol, price, successCount, error_3;
303
+ var _this = this;
304
+ return __generator(this, function (_b) {
305
+ switch (_b.label) {
306
+ case 0:
307
+ tag = TAG + 'getBatchPricesFromCCXT | ';
308
+ _b.label = 1;
309
+ case 1:
310
+ _b.trys.push([1, 3, , 4]);
311
+ log.info(tag, "Fetching batch prices for ".concat(symbols.length, " symbols"));
312
+ results = {};
313
+ pricePromises = symbols.map(function (symbol) { return __awaiter(_this, void 0, void 0, function () {
314
+ var price, error_4;
315
+ return __generator(this, function (_a) {
316
+ switch (_a.label) {
317
+ case 0:
318
+ _a.trys.push([0, 2, , 3]);
319
+ return [4 /*yield*/, getPriceFromCCXT(symbol)];
320
+ case 1:
321
+ price = _a.sent();
322
+ return [2 /*return*/, { symbol: symbol, price: price }];
323
+ case 2:
324
+ error_4 = _a.sent();
325
+ log.error(tag, "Error fetching ".concat(symbol, ":"), error_4);
326
+ return [2 /*return*/, { symbol: symbol, price: 0 }];
327
+ case 3: return [2 /*return*/];
328
+ }
329
+ });
330
+ }); });
331
+ return [4 /*yield*/, Promise.all(pricePromises)];
332
+ case 2:
333
+ prices = _b.sent();
334
+ // Build results object
335
+ for (_i = 0, prices_1 = prices; _i < prices_1.length; _i++) {
336
+ _a = prices_1[_i], symbol = _a.symbol, price = _a.price;
337
+ results[symbol] = price;
338
+ }
339
+ successCount = Object.values(results).filter(function (p) { return p > 0; }).length;
340
+ log.info(tag, "Batch complete: ".concat(successCount, "/").concat(symbols.length, " prices fetched"));
341
+ return [2 /*return*/, results];
342
+ case 3:
343
+ error_3 = _b.sent();
344
+ log.error(tag, "Error in batch fetch:", error_3);
345
+ return [2 /*return*/, {}];
346
+ case 4: return [2 /*return*/];
347
+ }
348
+ });
349
+ });
350
+ }
351
+ /**
352
+ * Clear the price cache
353
+ * Useful for testing or when fresh prices are needed
354
+ */
355
+ function clearPriceCache() {
356
+ priceCache.clear();
357
+ log.info(TAG, 'Price cache cleared');
358
+ }
359
+ /**
360
+ * Get cache statistics
361
+ */
362
+ function getCacheStats() {
363
+ var entries = Array.from(priceCache.entries()).map(function (_a) {
364
+ var symbol = _a[0], cached = _a[1];
365
+ return ({
366
+ symbol: symbol,
367
+ price: cached.price,
368
+ age: Date.now() - cached.timestamp,
369
+ source: cached.source
370
+ });
371
+ });
372
+ return {
373
+ size: priceCache.size,
374
+ entries: entries
375
+ };
376
+ }
377
+ /**
378
+ * Health check - verify we can connect to exchanges
379
+ */
380
+ function healthCheck() {
381
+ return __awaiter(this, void 0, void 0, function () {
382
+ var tag, status, _i, EXCHANGE_CONFIG_2, config, exchange, error_5, healthy;
383
+ return __generator(this, function (_a) {
384
+ switch (_a.label) {
385
+ case 0:
386
+ tag = TAG + 'healthCheck | ';
387
+ status = {};
388
+ _i = 0, EXCHANGE_CONFIG_2 = EXCHANGE_CONFIG;
389
+ _a.label = 1;
390
+ case 1:
391
+ if (!(_i < EXCHANGE_CONFIG_2.length)) return [3 /*break*/, 8];
392
+ config = EXCHANGE_CONFIG_2[_i];
393
+ _a.label = 2;
394
+ case 2:
395
+ _a.trys.push([2, 6, , 7]);
396
+ exchange = initializeExchange(config.id);
397
+ if (!exchange) return [3 /*break*/, 4];
398
+ return [4 /*yield*/, exchange.loadMarkets()];
399
+ case 3:
400
+ _a.sent();
401
+ status[config.id] = true;
402
+ return [3 /*break*/, 5];
403
+ case 4:
404
+ status[config.id] = false;
405
+ _a.label = 5;
406
+ case 5: return [3 /*break*/, 7];
407
+ case 6:
408
+ error_5 = _a.sent();
409
+ log.debug(tag, "".concat(config.id, " health check failed:"), error_5);
410
+ status[config.id] = false;
411
+ return [3 /*break*/, 7];
412
+ case 7:
413
+ _i++;
414
+ return [3 /*break*/, 1];
415
+ case 8:
416
+ healthy = Object.values(status).some(function (s) { return s === true; });
417
+ return [2 /*return*/, { healthy: healthy, exchanges: status }];
418
+ }
419
+ });
420
+ });
421
+ }