@pioneer-platform/blockbook 8.4.0 → 8.4.1

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @pioneer-platform/blockbook
2
2
 
3
+ ## 8.4.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Automated patch version bump for all packages
8
+ - Updated dependencies
9
+ - @pioneer-platform/loggerdog@8.4.1
10
+ - @pioneer-platform/nodes@8.4.1
11
+ - @pioneer-platform/pioneer-caip@9.3.1
12
+
3
13
  ## 8.4.0
4
14
 
5
15
  ### Minor Changes
package/lib/index.d.ts CHANGED
@@ -2,14 +2,36 @@ declare const TAG = " | blockbook-client | ";
2
2
  declare const Blockbook: any;
3
3
  declare const log: any;
4
4
  declare const fakeUa: any;
5
+ declare const axiosLib: any;
5
6
  declare const Axios: any;
6
7
  declare const https: any;
7
8
  declare const nodes: any;
8
9
  declare const axios: any;
9
10
  declare let NOW_NODES_API: string | undefined;
10
- declare let BLOCKBOOK_URLS: any;
11
+ interface NodeConfig {
12
+ url: string;
13
+ priority: number;
14
+ avgResponseTime: number;
15
+ successRate: number;
16
+ lastChecked: number;
17
+ isActive: boolean;
18
+ consecutiveFailures: number;
19
+ totalRequests: number;
20
+ successfulRequests: number;
21
+ }
22
+ interface CoinNodes {
23
+ [symbol: string]: NodeConfig[];
24
+ }
25
+ declare let BLOCKBOOK_NODES: CoinNodes;
11
26
  declare let BLOCKBOOK_SOCKETS: any;
12
- declare let add_custom_node: (coin: string, url: string) => boolean;
27
+ declare let BLOCKBOOK_URLS: any;
28
+ declare let add_custom_node: (coin: string, url: string, priority?: number) => boolean;
29
+ declare let remove_node: (coin: string, url: string) => boolean;
30
+ declare let set_node_priority: (coin: string, url: string, priority: number) => boolean;
31
+ declare let get_node_stats: (coin?: string) => any;
32
+ declare let reorder_nodes_by_performance: (coin: string) => void;
33
+ declare let reorder_nodes_by_priority: (symbol: string) => void;
34
+ declare let update_legacy_urls: (symbol: string) => void;
13
35
  declare let init_network: (servers?: any[]) => Promise<boolean>;
14
36
  declare let get_fees: (coin: string) => Promise<any>;
15
37
  declare let get_info_by_pubkey: (coin: string, pubkey: string, page?: string | undefined) => Promise<any>;
@@ -19,5 +41,6 @@ declare let get_txs_by_xpub: (coin: string, xpub: string) => Promise<any>;
19
41
  declare let broadcast_transaction: (coin: string, hex: string) => Promise<any>;
20
42
  declare let get_transaction: (coin: string, txid: string) => Promise<any>;
21
43
  declare let get_utxos_by_xpub: (coin: string, xpub: string) => Promise<any>;
44
+ declare let update_node_performance: (coin: string, url: string, success: boolean, responseTime: number) => void;
22
45
  declare let get_balance_by_xpub: (coin: string, xpub: any) => Promise<number>;
23
46
  declare let get_node_info: () => Promise<boolean>;
package/lib/index.js CHANGED
@@ -43,7 +43,8 @@ var TAG = " | blockbook-client | ";
43
43
  var Blockbook = require('blockbook-client').Blockbook;
44
44
  var log = require('@pioneer-platform/loggerdog')();
45
45
  var fakeUa = require('fake-useragent');
46
- var Axios = require('axios');
46
+ var axiosLib = require('axios');
47
+ var Axios = axiosLib.default || axiosLib;
47
48
  var https = require('https');
48
49
  var nodes = require("@pioneer-platform/nodes");
49
50
  var axios = Axios.create({
@@ -54,27 +55,29 @@ var axios = Axios.create({
54
55
  });
55
56
  // const axiosRetry = require('axios-retry');
56
57
  var NOW_NODES_API = process.env['NOW_NODES_API'];
57
- // axiosRetry(axios, {
58
- // retries: 3, // number of retries
59
- // retryDelay: (retryCount: number) => {
60
- // log.error(TAG,`retry attempt: ${retryCount}`);
61
- // return retryCount * 2000; // time interval between retries
62
- // },
63
- // retryCondition: (error: { response: { status: number; }; }) => {
64
- // log.error(TAG,error)
65
- // //@TODO mark node offline, and punish
66
- // // if retry condition is not specified, by default idempotent requests are retried
67
- // return error?.response?.status === 503;
68
- // },
69
- // });
70
- var BLOCKBOOK_URLS = {};
58
+ // Enhanced node management
59
+ var BLOCKBOOK_NODES = {};
71
60
  var BLOCKBOOK_SOCKETS = {};
61
+ // Legacy compatibility - will be populated from BLOCKBOOK_NODES
62
+ var BLOCKBOOK_URLS = {};
72
63
  module.exports = {
73
64
  init: function (servers) {
74
65
  return init_network(servers);
75
66
  },
76
- addNode: function (coin, url) {
77
- return add_custom_node(coin, url);
67
+ addNode: function (coin, url, priority) {
68
+ return add_custom_node(coin, url, priority);
69
+ },
70
+ removeNode: function (coin, url) {
71
+ return remove_node(coin, url);
72
+ },
73
+ setPriority: function (coin, url, priority) {
74
+ return set_node_priority(coin, url, priority);
75
+ },
76
+ getNodeStats: function (coin) {
77
+ return get_node_stats(coin);
78
+ },
79
+ reorderNodes: function (coin) {
80
+ return reorder_nodes_by_performance(coin);
78
81
  },
79
82
  getInfo: function () {
80
83
  return get_node_info();
@@ -113,35 +116,124 @@ module.exports = {
113
116
  return broadcast_transaction(coin, hex);
114
117
  },
115
118
  };
116
- // Add a custom node at runtime
117
- var add_custom_node = function (coin, url) {
119
+ // Enhanced node management functions
120
+ var add_custom_node = function (coin, url, priority) {
118
121
  var symbol = coin.toUpperCase();
119
122
  var cleanUrl = url.replace(/\/$/, ''); // Remove trailing slash
120
- // Store URLs as arrays if not already
121
- if (typeof BLOCKBOOK_URLS[symbol] === 'string') {
122
- BLOCKBOOK_URLS[symbol] = [BLOCKBOOK_URLS[symbol]];
123
- }
124
- if (!BLOCKBOOK_URLS[symbol]) {
125
- BLOCKBOOK_URLS[symbol] = [];
123
+ // Initialize if not exists
124
+ if (!BLOCKBOOK_NODES[symbol]) {
125
+ BLOCKBOOK_NODES[symbol] = [];
126
126
  }
127
127
  // Check for duplicates
128
- if (Array.isArray(BLOCKBOOK_URLS[symbol]) && BLOCKBOOK_URLS[symbol].includes(cleanUrl)) {
128
+ var existingNode = BLOCKBOOK_NODES[symbol].find(function (node) { return node.url === cleanUrl; });
129
+ if (existingNode) {
129
130
  log.warn("Node ".concat(cleanUrl, " already exists for ").concat(symbol));
130
131
  return false;
131
132
  }
132
- if (Array.isArray(BLOCKBOOK_URLS[symbol])) {
133
- BLOCKBOOK_URLS[symbol].push(cleanUrl);
133
+ // Determine priority
134
+ var defaultPriority = priority !== undefined ? priority :
135
+ (BLOCKBOOK_NODES[symbol].length === 0 ? 0 : Math.max.apply(Math, BLOCKBOOK_NODES[symbol].map(function (n) { return n.priority; })) + 10);
136
+ // Create new node config
137
+ var newNode = {
138
+ url: cleanUrl,
139
+ priority: defaultPriority,
140
+ avgResponseTime: 1000, // Default 1s
141
+ successRate: 100, // Optimistic start
142
+ lastChecked: Date.now(),
143
+ isActive: true,
144
+ consecutiveFailures: 0,
145
+ totalRequests: 0,
146
+ successfulRequests: 0
147
+ };
148
+ BLOCKBOOK_NODES[symbol].push(newNode);
149
+ // Sort by priority and update legacy compatibility
150
+ reorder_nodes_by_priority(symbol);
151
+ update_legacy_urls(symbol);
152
+ log.info("Added node for ".concat(symbol, ": ").concat(cleanUrl, " (priority: ").concat(defaultPriority, ")"));
153
+ log.info("".concat(symbol, " now has ").concat(BLOCKBOOK_NODES[symbol].length, " node(s)"));
154
+ return true;
155
+ };
156
+ var remove_node = function (coin, url) {
157
+ var symbol = coin.toUpperCase();
158
+ var cleanUrl = url.replace(/\/$/, '');
159
+ if (!BLOCKBOOK_NODES[symbol]) {
160
+ log.warn("No nodes configured for ".concat(symbol));
161
+ return false;
134
162
  }
135
- else {
136
- BLOCKBOOK_URLS[symbol] = [BLOCKBOOK_URLS[symbol], cleanUrl];
163
+ var initialLength = BLOCKBOOK_NODES[symbol].length;
164
+ BLOCKBOOK_NODES[symbol] = BLOCKBOOK_NODES[symbol].filter(function (node) { return node.url !== cleanUrl; });
165
+ if (BLOCKBOOK_NODES[symbol].length === initialLength) {
166
+ log.warn("Node ".concat(cleanUrl, " not found for ").concat(symbol));
167
+ return false;
137
168
  }
138
- log.info("Added custom node for ".concat(symbol, ": ").concat(cleanUrl));
139
- log.info("".concat(symbol, " now has ").concat(Array.isArray(BLOCKBOOK_URLS[symbol]) ? BLOCKBOOK_URLS[symbol].length : 1, " node(s)"));
169
+ update_legacy_urls(symbol);
170
+ log.info("Removed node for ".concat(symbol, ": ").concat(cleanUrl));
140
171
  return true;
141
172
  };
173
+ var set_node_priority = function (coin, url, priority) {
174
+ var symbol = coin.toUpperCase();
175
+ var cleanUrl = url.replace(/\/$/, '');
176
+ if (!BLOCKBOOK_NODES[symbol]) {
177
+ log.warn("No nodes configured for ".concat(symbol));
178
+ return false;
179
+ }
180
+ var node = BLOCKBOOK_NODES[symbol].find(function (n) { return n.url === cleanUrl; });
181
+ if (!node) {
182
+ log.warn("Node ".concat(cleanUrl, " not found for ").concat(symbol));
183
+ return false;
184
+ }
185
+ node.priority = priority;
186
+ reorder_nodes_by_priority(symbol);
187
+ update_legacy_urls(symbol);
188
+ log.info("Updated priority for ".concat(symbol, " node ").concat(cleanUrl, ": ").concat(priority));
189
+ return true;
190
+ };
191
+ var get_node_stats = function (coin) {
192
+ if (coin) {
193
+ var symbol = coin.toUpperCase();
194
+ return BLOCKBOOK_NODES[symbol] || [];
195
+ }
196
+ return BLOCKBOOK_NODES;
197
+ };
198
+ var reorder_nodes_by_performance = function (coin) {
199
+ var symbol = coin.toUpperCase();
200
+ if (!BLOCKBOOK_NODES[symbol])
201
+ return;
202
+ // Sort by: active status, success rate, response time, then priority
203
+ BLOCKBOOK_NODES[symbol].sort(function (a, b) {
204
+ if (a.isActive !== b.isActive)
205
+ return b.isActive ? 1 : -1;
206
+ if (Math.abs(a.successRate - b.successRate) > 5)
207
+ return b.successRate - a.successRate;
208
+ if (Math.abs(a.avgResponseTime - b.avgResponseTime) > 500)
209
+ return a.avgResponseTime - b.avgResponseTime;
210
+ return a.priority - b.priority;
211
+ });
212
+ update_legacy_urls(symbol);
213
+ };
214
+ var reorder_nodes_by_priority = function (symbol) {
215
+ if (!BLOCKBOOK_NODES[symbol])
216
+ return;
217
+ BLOCKBOOK_NODES[symbol].sort(function (a, b) { return a.priority - b.priority; });
218
+ };
219
+ var update_legacy_urls = function (symbol) {
220
+ if (!BLOCKBOOK_NODES[symbol])
221
+ return;
222
+ var activeNodes = BLOCKBOOK_NODES[symbol].filter(function (n) { return n.isActive; });
223
+ if (activeNodes.length === 0) {
224
+ delete BLOCKBOOK_URLS[symbol];
225
+ return;
226
+ }
227
+ if (activeNodes.length === 1) {
228
+ BLOCKBOOK_URLS[symbol] = activeNodes[0].url;
229
+ }
230
+ else {
231
+ BLOCKBOOK_URLS[symbol] = activeNodes.map(function (n) { return n.url; });
232
+ }
233
+ };
142
234
  var init_network = function (servers) {
143
235
  return __awaiter(this, void 0, void 0, function () {
144
- var tag, SEED_NODES, blockbooks, i, blockbook, url, e_1;
236
+ var tag, SEED_NODES, blockbooks, i, blockbook, symbol, priority, url, symbol, nodes_1, e_1;
145
237
  return __generator(this, function (_a) {
146
238
  switch (_a.label) {
147
239
  case 0:
@@ -163,11 +255,26 @@ var init_network = function (servers) {
163
255
  blockbooks = SEED_NODES;
164
256
  }
165
257
  log.debug(tag, "blockbooks: ", blockbooks.length);
258
+ // Clear existing nodes
259
+ BLOCKBOOK_NODES = {};
260
+ // Process nodes with priority assignment
166
261
  for (i = 0; i < blockbooks.length; i++) {
167
262
  blockbook = blockbooks[i];
168
- //get swagger
169
- if (blockbook && blockbook.service)
170
- BLOCKBOOK_URLS[blockbook.symbol.toUpperCase()] = blockbook.service;
263
+ if (blockbook && blockbook.service) {
264
+ symbol = blockbook.symbol.toUpperCase();
265
+ priority = 50;
266
+ if (blockbook.service.includes('nownodes.io')) {
267
+ priority = 0; // Highest priority for NowNodes (fastest)
268
+ }
269
+ else if (blockbook.service.includes('zelcore.io')) {
270
+ priority = 10; // Lower priority for Zelcore (slower)
271
+ }
272
+ else if (blockbook.service.includes('shapeshift.com')) {
273
+ priority = 20; // Even lower for ShapeShift
274
+ }
275
+ // Add node with proper priority
276
+ add_custom_node(symbol, blockbook.service, priority);
277
+ }
171
278
  if (blockbook && blockbook.websocket) {
172
279
  url = blockbook.websocket.replace("/websocket", "");
173
280
  url = blockbook.websocket.replace("wss://", "https://");
@@ -177,10 +284,17 @@ var init_network = function (servers) {
177
284
  });
178
285
  }
179
286
  else {
180
- log.error(tag, "invalid unchained service: ", blockbook);
181
- // throw Error("invalid unchained service!")
287
+ log.info(tag, "no websocket for: ", blockbook.symbol);
182
288
  }
183
289
  }
290
+ // Log final configuration
291
+ for (symbol in BLOCKBOOK_NODES) {
292
+ nodes_1 = BLOCKBOOK_NODES[symbol];
293
+ log.info(tag, "".concat(symbol, ": ").concat(nodes_1.length, " nodes configured"));
294
+ nodes_1.forEach(function (node, idx) {
295
+ log.info(tag, " ".concat(idx + 1, ". ").concat(node.url, " (priority: ").concat(node.priority, ")"));
296
+ });
297
+ }
184
298
  log.debug(tag, "BLOCKBOOK_URLS: ", BLOCKBOOK_URLS);
185
299
  log.debug(tag, "BLOCKBOOK_SOCKETS: ", BLOCKBOOK_SOCKETS);
186
300
  return [2 /*return*/, true];
@@ -381,41 +495,43 @@ var get_txs_by_xpub = function (coin, xpub) {
381
495
  var broadcast_transaction = function (coin, hex) {
382
496
  return __awaiter(this, void 0, void 0, function () {
383
497
  var tag, url, data, body, output, resp, e_7, errorMessage, statusCode, e_8;
384
- var _a;
385
- return __generator(this, function (_b) {
386
- switch (_b.label) {
498
+ var _a, _b;
499
+ return __generator(this, function (_c) {
500
+ switch (_c.label) {
387
501
  case 0:
388
502
  tag = TAG + " | broadcast_transaction | ";
389
- _b.label = 1;
503
+ _c.label = 1;
390
504
  case 1:
391
- _b.trys.push([1, 6, , 7]);
505
+ _c.trys.push([1, 6, , 7]);
392
506
  url = BLOCKBOOK_URLS[coin.toUpperCase()] + "/api/v2/sendtx/";
393
507
  data = hex;
394
508
  body = {
395
509
  url: url,
396
510
  headers: {
397
- 'content-type': 'application/json',
511
+ 'content-type': 'text/plain',
398
512
  'User-Agent': fakeUa()
399
513
  },
400
514
  method: 'POST',
401
- json: false,
402
515
  data: data,
403
516
  };
404
517
  output = {
405
518
  success: false
406
519
  };
407
520
  resp = void 0;
408
- _b.label = 2;
521
+ _c.label = 2;
409
522
  case 2:
410
- _b.trys.push([2, 4, , 5]);
411
- return [4 /*yield*/, axios(body)];
523
+ _c.trys.push([2, 4, , 5]);
524
+ return [4 /*yield*/, axios(body)
525
+ // Only store serializable data, not the full axios response
526
+ ];
412
527
  case 3:
413
- resp = _b.sent();
414
- output.resp = resp;
528
+ resp = _c.sent();
529
+ // Only store serializable data, not the full axios response
530
+ output.txid = ((_a = resp.data) === null || _a === void 0 ? void 0 : _a.result) || resp.data;
415
531
  output.success = true;
416
532
  return [3 /*break*/, 5];
417
533
  case 4:
418
- e_7 = _b.sent();
534
+ e_7 = _c.sent();
419
535
  log.error(tag, "Broadcast error: ", e_7.message || e_7);
420
536
  errorMessage = 'Unknown error occurred';
421
537
  statusCode = null;
@@ -443,7 +559,7 @@ var broadcast_transaction = function (coin, hex) {
443
559
  else if (e_7.request) {
444
560
  // Request was made but no response received
445
561
  errorMessage = 'Network error: No response received';
446
- log.debug(tag, "Request config: ", (_a = e_7.config) === null || _a === void 0 ? void 0 : _a.url);
562
+ log.debug(tag, "Request config: ", (_b = e_7.config) === null || _b === void 0 ? void 0 : _b.url);
447
563
  }
448
564
  else {
449
565
  // Something else happened
@@ -454,7 +570,7 @@ var broadcast_transaction = function (coin, hex) {
454
570
  return [3 /*break*/, 5];
455
571
  case 5: return [2 /*return*/, output];
456
572
  case 6:
457
- e_8 = _b.sent();
573
+ e_8 = _c.sent();
458
574
  console.error(tag, 'error: ', e_8);
459
575
  throw e_8;
460
576
  case 7: return [2 /*return*/];
@@ -495,88 +611,123 @@ var get_transaction = function (coin, txid) {
495
611
  });
496
612
  });
497
613
  };
614
+ // Enhanced UTXO function with priority-based sequential failover
498
615
  var get_utxos_by_xpub = function (coin, xpub) {
499
616
  return __awaiter(this, void 0, void 0, function () {
500
- var tag, baseUrls, results, allUtxos, i, result, utxos, seen, uniqueUtxos, _i, allUtxos_1, utxo, key, url, body, resp, e_10;
501
- var _this = this;
502
- var _a;
503
- return __generator(this, function (_b) {
504
- switch (_b.label) {
617
+ var tag, symbol, nodes_2, activeNodes, i, node, startTime, url, body, resp, responseTime, error_1, responseTime, errorMessage, e_10;
618
+ return __generator(this, function (_a) {
619
+ switch (_a.label) {
505
620
  case 0:
506
- tag = TAG + " | FA get_utxos_by_xpub | ";
507
- _b.label = 1;
621
+ tag = TAG + " | get_utxos_by_xpub | ";
622
+ _a.label = 1;
508
623
  case 1:
509
- _b.trys.push([1, 6, , 7]);
510
- log.info(tag, "get_utxos_by_xpub: ", BLOCKBOOK_URLS);
511
- baseUrls = BLOCKBOOK_URLS[coin.toUpperCase()];
512
- if (!Array.isArray(baseUrls)) return [3 /*break*/, 3];
513
- // Multiple nodes - query each and merge results
514
- log.info(tag, "Querying ".concat(baseUrls.length, " nodes for ").concat(coin, "..."));
515
- return [4 /*yield*/, Promise.allSettled(baseUrls.map(function (baseUrl) { return __awaiter(_this, void 0, void 0, function () {
516
- var url, body, resp;
517
- return __generator(this, function (_a) {
518
- switch (_a.label) {
519
- case 0:
520
- url = baseUrl + "/api/v2/utxo/" + xpub + "?confirmed=false";
521
- console.log("Querying node:", url);
522
- body = {
523
- method: 'GET',
524
- url: url,
525
- };
526
- return [4 /*yield*/, axios(body)];
527
- case 1:
528
- resp = _a.sent();
529
- return [2 /*return*/, resp.data];
530
- }
531
- });
532
- }); }))];
533
- case 2:
534
- results = _b.sent();
535
- allUtxos = [];
536
- for (i = 0; i < results.length; i++) {
537
- result = results[i];
538
- if (result.status === 'fulfilled') {
539
- utxos = result.value;
540
- log.info(tag, "Node ".concat(i + 1, " returned ").concat(utxos.length, " UTXOs"));
541
- allUtxos.push.apply(allUtxos, utxos);
542
- }
543
- else {
544
- log.warn(tag, "Node ".concat(i + 1, " failed:"), (_a = result.reason) === null || _a === void 0 ? void 0 : _a.message);
545
- }
624
+ _a.trys.push([1, 8, , 9]);
625
+ symbol = coin.toUpperCase();
626
+ nodes_2 = BLOCKBOOK_NODES[symbol];
627
+ if (!nodes_2 || nodes_2.length === 0) {
628
+ throw new Error("No nodes configured for ".concat(symbol));
546
629
  }
547
- seen = new Set();
548
- uniqueUtxos = [];
549
- for (_i = 0, allUtxos_1 = allUtxos; _i < allUtxos_1.length; _i++) {
550
- utxo = allUtxos_1[_i];
551
- key = "".concat(utxo.txid, ":").concat(utxo.vout);
552
- if (!seen.has(key)) {
553
- seen.add(key);
554
- uniqueUtxos.push(utxo);
555
- }
630
+ activeNodes = nodes_2.filter(function (n) { return n.isActive; }).sort(function (a, b) { return a.priority - b.priority; });
631
+ if (activeNodes.length === 0) {
632
+ throw new Error("No active nodes available for ".concat(symbol));
556
633
  }
557
- log.info(tag, "Returning ".concat(uniqueUtxos.length, " unique UTXOs from ").concat(allUtxos.length, " total"));
558
- return [2 /*return*/, uniqueUtxos];
634
+ log.info(tag, "Attempting UTXO query for ".concat(symbol, " with ").concat(activeNodes.length, " nodes available"));
635
+ i = 0;
636
+ _a.label = 2;
637
+ case 2:
638
+ if (!(i < activeNodes.length)) return [3 /*break*/, 7];
639
+ node = activeNodes[i];
640
+ startTime = Date.now();
641
+ _a.label = 3;
559
642
  case 3:
560
- url = baseUrls + "/api/v2/utxo/" + xpub + "?confirmed=false";
561
- console.log("url:", url);
643
+ _a.trys.push([3, 5, , 6]);
644
+ log.info(tag, "Trying node ".concat(i + 1, "/").concat(activeNodes.length, ": ").concat(node.url, " (priority: ").concat(node.priority, ")"));
645
+ url = node.url + "/api/v2/utxo/" + xpub + "?confirmed=false";
562
646
  body = {
563
647
  method: 'GET',
564
648
  url: url,
649
+ timeout: 15000 // 15s timeout per node
565
650
  };
566
651
  return [4 /*yield*/, axios(body)];
567
652
  case 4:
568
- resp = _b.sent();
653
+ resp = _a.sent();
654
+ responseTime = Date.now() - startTime;
655
+ // Update node performance metrics
656
+ update_node_performance(symbol, node.url, true, responseTime);
657
+ log.info(tag, "\u2705 Node ".concat(i + 1, " succeeded in ").concat(responseTime, "ms, returned ").concat(resp.data.length, " UTXOs"));
569
658
  return [2 /*return*/, resp.data];
570
- case 5: return [3 /*break*/, 7];
659
+ case 5:
660
+ error_1 = _a.sent();
661
+ responseTime = Date.now() - startTime;
662
+ // Update node performance metrics
663
+ update_node_performance(symbol, node.url, false, responseTime);
664
+ errorMessage = error_1 instanceof Error ? error_1.message : String(error_1);
665
+ log.warn(tag, "\u274C Node ".concat(i + 1, " failed after ").concat(responseTime, "ms:"), errorMessage);
666
+ // If this is the last node, throw the error
667
+ if (i === activeNodes.length - 1) {
668
+ throw new Error("All ".concat(activeNodes.length, " nodes failed for ").concat(symbol, ". Last error: ").concat(errorMessage));
669
+ }
670
+ // Continue to next node
671
+ return [3 /*break*/, 6];
571
672
  case 6:
572
- e_10 = _b.sent();
673
+ i++;
674
+ return [3 /*break*/, 2];
675
+ case 7: return [3 /*break*/, 9];
676
+ case 8:
677
+ e_10 = _a.sent();
573
678
  console.error(tag, e_10);
574
679
  throw e_10;
575
- case 7: return [2 /*return*/];
680
+ case 9: return [2 /*return*/];
576
681
  }
577
682
  });
578
683
  });
579
684
  };
685
+ // Performance tracking function
686
+ var update_node_performance = function (coin, url, success, responseTime) {
687
+ var symbol = coin.toUpperCase();
688
+ var nodes = BLOCKBOOK_NODES[symbol];
689
+ if (!nodes)
690
+ return;
691
+ var node = nodes.find(function (n) { return n.url === url; });
692
+ if (!node)
693
+ return;
694
+ // Update counters
695
+ node.totalRequests++;
696
+ if (success) {
697
+ node.successfulRequests++;
698
+ node.consecutiveFailures = 0;
699
+ }
700
+ else {
701
+ node.consecutiveFailures++;
702
+ }
703
+ // Update success rate
704
+ node.successRate = (node.successfulRequests / node.totalRequests) * 100;
705
+ // Update average response time (exponential moving average)
706
+ var alpha = 0.3; // Weight for new sample
707
+ node.avgResponseTime = (alpha * responseTime) + ((1 - alpha) * node.avgResponseTime);
708
+ // Update health status
709
+ var wasActive = node.isActive;
710
+ // Deactivate if too many consecutive failures or very low success rate
711
+ if (node.consecutiveFailures >= 3 || (node.totalRequests >= 10 && node.successRate < 50)) {
712
+ node.isActive = false;
713
+ if (wasActive) {
714
+ log.warn("Deactivated node ".concat(url, " for ").concat(symbol, " (failures: ").concat(node.consecutiveFailures, ", success rate: ").concat(node.successRate.toFixed(1), "%)"));
715
+ }
716
+ }
717
+ // Reactivate if it was down but now working
718
+ if (!node.isActive && success && node.consecutiveFailures === 0) {
719
+ node.isActive = true;
720
+ log.info("Reactivated node ".concat(url, " for ").concat(symbol));
721
+ }
722
+ node.lastChecked = Date.now();
723
+ // Update legacy compatibility
724
+ update_legacy_urls(symbol);
725
+ // Auto-reorder nodes based on performance every 10 requests
726
+ if (node.totalRequests % 10 === 0) {
727
+ reorder_nodes_by_performance(symbol);
728
+ log.info("Auto-reordered ".concat(symbol, " nodes based on performance"));
729
+ }
730
+ };
580
731
  var get_balance_by_xpub = function (coin, xpub) {
581
732
  return __awaiter(this, void 0, void 0, function () {
582
733
  var tag, output, balance, i, uxto, e_11;
package/package.json CHANGED
@@ -1,12 +1,19 @@
1
1
  {
2
2
  "name": "@pioneer-platform/blockbook",
3
- "version": "8.4.0",
3
+ "version": "8.4.1",
4
4
  "main": "./lib/index.js",
5
5
  "types": "./lib/index.d.ts",
6
+ "scripts": {
7
+ "test": "pnpm run build && node __tests__/test-module.js",
8
+ "start": "pnpm run build:live",
9
+ "build": "tsc -p .",
10
+ "prepublish": "tsc -p .",
11
+ "build:live": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/index.ts"
12
+ },
6
13
  "dependencies": {
7
- "@pioneer-platform/loggerdog": "^8.4.0",
8
- "@pioneer-platform/nodes": "^8.4.0",
9
- "@pioneer-platform/pioneer-caip": "^9.3.0",
14
+ "@pioneer-platform/loggerdog": "^8.4.1",
15
+ "@pioneer-platform/nodes": "^8.4.1",
16
+ "@pioneer-platform/pioneer-caip": "^9.3.1",
10
17
  "@types/request-promise-native": "^1.0.17",
11
18
  "axiom": "^0.1.6",
12
19
  "axios": "^1.6.0",
@@ -24,12 +31,5 @@
24
31
  "keepkey"
25
32
  ],
26
33
  "author": "highlander",
27
- "license": "MIT",
28
- "scripts": {
29
- "test": "pnpm run build && node __tests__/test-module.js",
30
- "start": "pnpm run build:live",
31
- "build": "tsc -p .",
32
- "prepublish": "tsc -p .",
33
- "build:live": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/index.ts"
34
- }
34
+ "license": "MIT"
35
35
  }