@pioneer-platform/blockbook 8.3.17 → 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/lib/index.js CHANGED
@@ -1,23 +1,8 @@
1
1
  "use strict";
2
- /**
3
- * Blockbook Client Integration Module
4
- *
5
- * Provides a unified interface for interacting with Blockbook blockchain explorers
6
- * across multiple cryptocurrency networks. Handles pagination, retries, and fallbacks.
7
- *
8
- * @module blockbook-client
2
+ /*
3
+
4
+
9
5
  */
10
- var __assign = (this && this.__assign) || function () {
11
- __assign = Object.assign || function(t) {
12
- for (var s, i = 1, n = arguments.length; i < n; i++) {
13
- s = arguments[i];
14
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
15
- t[p] = s[p];
16
- }
17
- return t;
18
- };
19
- return __assign.apply(this, arguments);
20
- };
21
6
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
22
7
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
23
8
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -54,184 +39,277 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
54
39
  if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
55
40
  }
56
41
  };
57
- var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
58
- if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
59
- if (ar || !(i in from)) {
60
- if (!ar) ar = Array.prototype.slice.call(from, 0, i);
61
- ar[i] = from[i];
62
- }
63
- }
64
- return to.concat(ar || Array.prototype.slice.call(from));
65
- };
42
+ var TAG = " | blockbook-client | ";
66
43
  var Blockbook = require('blockbook-client').Blockbook;
67
44
  var log = require('@pioneer-platform/loggerdog')();
68
45
  var fakeUa = require('fake-useragent');
69
- var Axios = require('axios');
46
+ var axiosLib = require('axios');
47
+ var Axios = axiosLib.default || axiosLib;
70
48
  var https = require('https');
71
- var nodes = require('@pioneer-platform/nodes');
72
- var axiosRetry = require('axios-retry');
73
- var TAG = " | blockbook-client | ";
74
- var NOW_NODES_API = process.env['NOW_NODES_API'];
75
- // Configure axios with retry logic and custom settings
49
+ var nodes = require("@pioneer-platform/nodes");
76
50
  var axios = Axios.create({
77
51
  httpsAgent: new https.Agent({
78
52
  rejectUnauthorized: false
79
53
  }),
80
- timeout: 30000 // 30 seconds timeout
54
+ timeout: 30000 // 10 seconds
81
55
  });
82
- // Configure automatic retries for transient failures
83
- axiosRetry(axios, {
84
- retries: 3,
85
- retryDelay: function (retryCount) {
86
- log.error(TAG, "Retry attempt: ".concat(retryCount));
87
- return retryCount * 2000; // Exponential backoff
56
+ // const axiosRetry = require('axios-retry');
57
+ var NOW_NODES_API = process.env['NOW_NODES_API'];
58
+ // Enhanced node management
59
+ var BLOCKBOOK_NODES = {};
60
+ var BLOCKBOOK_SOCKETS = {};
61
+ // Legacy compatibility - will be populated from BLOCKBOOK_NODES
62
+ var BLOCKBOOK_URLS = {};
63
+ module.exports = {
64
+ init: function (servers) {
65
+ return init_network(servers);
88
66
  },
89
- retryCondition: function (error) {
90
- var _a;
91
- // Retry on network errors or 5xx status codes
92
- return axiosRetry.isNetworkOrIdempotentRequestError(error) ||
93
- (((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) ? error.response.status >= 500 : false);
67
+ addNode: function (coin, url, priority) {
68
+ return add_custom_node(coin, url, priority);
94
69
  },
95
- });
96
- // Global storage for Blockbook URLs and WebSocket connections
97
- var BLOCKBOOK_URLS = {};
98
- var BLOCKBOOK_SOCKETS = {};
99
- // Cache for frequently accessed data
100
- var cache = new Map();
101
- var CACHE_TTL = 60000; // 1 minute cache TTL
102
- /**
103
- * Get cached data if available and not expired
104
- */
105
- function getCached(key) {
106
- var cached = cache.get(key);
107
- if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
108
- return cached.data;
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);
81
+ },
82
+ getInfo: function () {
83
+ return get_node_info();
84
+ },
85
+ getBlockbooks: function () {
86
+ return BLOCKBOOK_URLS;
87
+ },
88
+ getBlockbookSockets: function () {
89
+ return BLOCKBOOK_SOCKETS;
90
+ },
91
+ getFees: function (coin) {
92
+ return get_fees(coin);
93
+ },
94
+ getTransaction: function (coin, txid) {
95
+ return get_transaction(coin, txid);
96
+ },
97
+ getAddressInfo: function (coin, address, filter) {
98
+ return get_info_by_address(coin, address, filter);
99
+ },
100
+ getPubkeyInfo: function (coin, pubkey, filter) {
101
+ return get_info_by_pubkey(coin, pubkey, filter);
102
+ },
103
+ txidsByAddress: function (coin, address, page) {
104
+ return get_txids_by_address(coin, address, page);
105
+ },
106
+ txsByXpub: function (coin, addresses) {
107
+ return get_txs_by_xpub(coin, addresses);
108
+ },
109
+ utxosByXpub: function (coin, xpub) {
110
+ return get_utxos_by_xpub(coin, xpub);
111
+ },
112
+ getBalanceByXpub: function (coin, xpub) {
113
+ return get_balance_by_xpub(coin, xpub);
114
+ },
115
+ broadcast: function (coin, hex) {
116
+ return broadcast_transaction(coin, hex);
117
+ },
118
+ };
119
+ // Enhanced node management functions
120
+ var add_custom_node = function (coin, url, priority) {
121
+ var symbol = coin.toUpperCase();
122
+ var cleanUrl = url.replace(/\/$/, ''); // Remove trailing slash
123
+ // Initialize if not exists
124
+ if (!BLOCKBOOK_NODES[symbol]) {
125
+ BLOCKBOOK_NODES[symbol] = [];
109
126
  }
110
- cache.delete(key);
111
- return null;
112
- }
113
- /**
114
- * Store data in cache
115
- */
116
- function setCache(key, data) {
117
- cache.set(key, { data: data, timestamp: Date.now() });
118
- }
119
- // Main module exports
120
- module.exports = {
121
- init: init_network,
122
- getInfo: get_node_info,
123
- getBlockbooks: function () { return BLOCKBOOK_URLS; },
124
- getBlockbookSockets: function () { return BLOCKBOOK_SOCKETS; },
125
- getFees: get_fees,
126
- getTransaction: get_transaction,
127
- getAddressInfo: get_info_by_address,
128
- getPubkeyInfo: get_info_by_pubkey,
129
- getNextIndexes: get_next_indexes_by_pubkey,
130
- computeNextIndexes: computeNextIndexes,
131
- txidsByAddress: get_txids_by_address,
132
- txsByXpub: get_txs_by_xpub,
133
- utxosByXpub: get_utxos_by_xpub,
134
- getBalanceByXpub: get_balance_by_xpub,
135
- broadcast: broadcast_transaction,
127
+ // Check for duplicates
128
+ var existingNode = BLOCKBOOK_NODES[symbol].find(function (node) { return node.url === cleanUrl; });
129
+ if (existingNode) {
130
+ log.warn("Node ".concat(cleanUrl, " already exists for ").concat(symbol));
131
+ return false;
132
+ }
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;
136
155
  };
137
- /**
138
- * Initialize the Blockbook network with seed nodes and custom servers
139
- * @param servers Optional array of custom Blockbook server configurations
140
- * @returns Promise<boolean> indicating successful initialization
141
- */
142
- function init_network(servers) {
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;
162
+ }
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;
168
+ }
169
+ update_legacy_urls(symbol);
170
+ log.info("Removed node for ".concat(symbol, ": ").concat(cleanUrl));
171
+ return true;
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
+ };
234
+ var init_network = function (servers) {
143
235
  return __awaiter(this, void 0, void 0, function () {
144
- var tag, SEED_NODES, allNodes, blockbooks, _i, blockbooks_1, blockbook, symbol, httpUrl, 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:
148
- tag = TAG + " | init_network | ";
240
+ tag = ' | get_txs_by_address | ';
149
241
  _a.label = 1;
150
242
  case 1:
151
- _a.trys.push([1, 7, , 8]);
152
- log.debug(tag, "Initializing Blockbook network...");
153
- SEED_NODES = [];
154
- if (!(typeof nodes.getBlockbooks === 'function')) return [3 /*break*/, 3];
243
+ _a.trys.push([1, 3, , 4]);
244
+ log.debug(tag, "checkpoint: ");
155
245
  return [4 /*yield*/, nodes.getBlockbooks()];
156
246
  case 2:
157
247
  SEED_NODES = _a.sent();
158
- return [3 /*break*/, 6];
159
- case 3:
160
- if (!(typeof nodes.getNodes === 'function')) return [3 /*break*/, 5];
161
- return [4 /*yield*/, nodes.getNodes()];
162
- case 4:
163
- allNodes = _a.sent();
164
- SEED_NODES = allNodes.filter(function (n) { return n.type === 'blockbook' || n.service; });
165
- return [3 /*break*/, 6];
166
- case 5:
167
- if (nodes.blockbooks) {
168
- SEED_NODES = nodes.blockbooks;
169
- }
170
- else {
171
- log.warn(tag, "Unable to load seed nodes from @pioneer-platform/nodes");
172
- }
173
- _a.label = 6;
174
- case 6:
175
- log.info(tag, "Loaded ".concat(SEED_NODES.length, " seed nodes"));
248
+ log.info(tag, "SEED_NODES: ", SEED_NODES);
176
249
  blockbooks = [];
177
- if (servers && Array.isArray(servers)) {
178
- blockbooks = servers.concat(SEED_NODES);
179
- log.info(tag, "Added ".concat(servers.length, " custom servers"));
250
+ if (servers && Array.isArray(servers)) { // Type checking for array
251
+ blockbooks = servers.concat(SEED_NODES); // Combine arrays
180
252
  }
181
253
  else {
182
- if (servers) {
183
- log.warn(tag, "Invalid 'servers' parameter. Expected an array.");
184
- }
254
+ console.error("Invalid 'servers' parameter. Expected an array.");
185
255
  blockbooks = SEED_NODES;
186
256
  }
187
- log.debug(tag, "Total blockbook servers: ".concat(blockbooks.length));
188
- // Initialize each Blockbook server
189
- for (_i = 0, blockbooks_1 = blockbooks; _i < blockbooks_1.length; _i++) {
190
- blockbook = blockbooks_1[_i];
191
- try {
192
- if (blockbook === null || blockbook === void 0 ? void 0 : blockbook.service) {
193
- symbol = blockbook.symbol.toUpperCase();
194
- BLOCKBOOK_URLS[symbol] = blockbook.service;
195
- if (blockbook.websocket) {
196
- httpUrl = blockbook.websocket
197
- .replace("/websocket", "")
198
- .replace("wss://", "https://")
199
- .replace("ws://", "http://");
200
- BLOCKBOOK_SOCKETS[symbol] = new Blockbook({
201
- nodes: [httpUrl],
202
- disableTypeValidation: true,
203
- });
204
- log.debug(tag, "Initialized ".concat(symbol, " with URL: ").concat(blockbook.service));
205
- }
257
+ log.debug(tag, "blockbooks: ", blockbooks.length);
258
+ // Clear existing nodes
259
+ BLOCKBOOK_NODES = {};
260
+ // Process nodes with priority assignment
261
+ for (i = 0; i < blockbooks.length; i++) {
262
+ blockbook = blockbooks[i];
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)
206
268
  }
207
- else {
208
- log.warn(tag, "Invalid blockbook configuration:", blockbook);
269
+ else if (blockbook.service.includes('zelcore.io')) {
270
+ priority = 10; // Lower priority for Zelcore (slower)
209
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);
210
277
  }
211
- catch (error) {
212
- log.error(tag, "Failed to initialize blockbook ".concat(blockbook === null || blockbook === void 0 ? void 0 : blockbook.symbol, ":"), error);
278
+ if (blockbook && blockbook.websocket) {
279
+ url = blockbook.websocket.replace("/websocket", "");
280
+ url = blockbook.websocket.replace("wss://", "https://");
281
+ BLOCKBOOK_SOCKETS[blockbook.symbol.toUpperCase()] = new Blockbook({
282
+ nodes: [url],
283
+ disableTypeValidation: true,
284
+ });
213
285
  }
286
+ else {
287
+ log.info(tag, "no websocket for: ", blockbook.symbol);
288
+ }
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
+ });
214
297
  }
215
- log.info(tag, "Initialized ".concat(Object.keys(BLOCKBOOK_URLS).length, " Blockbook URLs"));
216
- log.info(tag, "Initialized ".concat(Object.keys(BLOCKBOOK_SOCKETS).length, " WebSocket connections"));
298
+ log.debug(tag, "BLOCKBOOK_URLS: ", BLOCKBOOK_URLS);
299
+ log.debug(tag, "BLOCKBOOK_SOCKETS: ", BLOCKBOOK_SOCKETS);
217
300
  return [2 /*return*/, true];
218
- case 7:
301
+ case 3:
219
302
  e_1 = _a.sent();
220
- log.error(tag, "Failed to initialize network:", e_1);
303
+ // console.error(tag, 'Error: ', e)
221
304
  throw e_1;
222
- case 8: return [2 /*return*/];
305
+ case 4: return [2 /*return*/];
223
306
  }
224
307
  });
225
308
  });
226
- }
227
- /**
228
- * Get fee estimates for a specific coin
229
- * @param coin Coin symbol (e.g., 'BTC', 'ETH')
230
- * @returns Promise<FeeEstimate> containing fee recommendations
231
- */
232
- function get_fees(coin) {
309
+ };
310
+ var get_fees = function (coin) {
233
311
  return __awaiter(this, void 0, void 0, function () {
234
- var tag, cacheKey, cached, baseUrl, url, resp, feeData, e_2;
312
+ var tag, url, body, resp, e_2;
235
313
  return __generator(this, function (_a) {
236
314
  switch (_a.label) {
237
315
  case 0:
@@ -239,254 +317,74 @@ function get_fees(coin) {
239
317
  _a.label = 1;
240
318
  case 1:
241
319
  _a.trys.push([1, 3, , 4]);
242
- cacheKey = "fees:".concat(coin.toUpperCase());
243
- cached = getCached(cacheKey);
244
- if (cached) {
245
- log.debug(tag, "Returning cached fees for ".concat(coin));
246
- return [2 /*return*/, cached];
247
- }
248
- baseUrl = BLOCKBOOK_URLS[coin.toUpperCase()];
249
- if (!baseUrl) {
250
- throw new Error("Unknown coin '".concat(coin, "' or missing BLOCKBOOK_URLS entry"));
251
- }
252
- url = "".concat(baseUrl, "/api/v2/fees");
253
- log.debug(tag, "Fetching fees from:", url);
254
- return [4 /*yield*/, axios({
255
- method: 'GET',
256
- url: url,
257
- headers: {
258
- 'content-type': 'application/json',
259
- 'User-Agent': typeof fakeUa === 'function' ? fakeUa() : 'blockbook-client/1.0'
260
- },
261
- })];
320
+ url = BLOCKBOOK_URLS[coin.toUpperCase()] + "/api/v2/fees";
321
+ log.debug(tag, "url: ", url);
322
+ body = {
323
+ method: 'GET',
324
+ url: url,
325
+ headers: {
326
+ 'content-type': 'application/json',
327
+ 'User-Agent': fakeUa()
328
+ },
329
+ };
330
+ return [4 /*yield*/, axios(body)
331
+ //log.debug(tag,"resp: ",resp)
332
+ //TODO paginate?
333
+ ];
262
334
  case 2:
263
335
  resp = _a.sent();
264
- feeData = resp.data;
265
- setCache(cacheKey, feeData);
266
- return [2 /*return*/, feeData];
336
+ //log.debug(tag,"resp: ",resp)
337
+ //TODO paginate?
338
+ return [2 /*return*/, resp.data];
267
339
  case 3:
268
340
  e_2 = _a.sent();
269
- log.error(tag, "Failed to get fees for ".concat(coin, ":"), e_2);
341
+ console.error(tag, e_2);
270
342
  throw e_2;
271
343
  case 4: return [2 /*return*/];
272
344
  }
273
345
  });
274
346
  });
275
- }
276
- /**
277
- * Fetch xpub info from Blockbook with tokens, handling pagination and fallbacks.
278
- * @param coin Uppercase/anycase coin key resolvable in BLOCKBOOK_URLS (e.g., 'BTC')
279
- * @param pubkey xpub/ypub/zpub/tpub/vpub
280
- * @param page Starting page number (default 1)
281
- * @param pageSize Items per page (default 1000)
282
- * @returns Promise<XpubResponse> with complete token information including changeIndex and receiveIndex
283
- */
284
- function get_info_by_pubkey(coin_1, pubkey_1) {
285
- return __awaiter(this, arguments, void 0, function (coin, pubkey, page, pageSize) {
286
- var tag, baseUrl, url_1, fetchPage, params, data, totalPages, allTokens, startPage, pagePromises, p, pageResults, _i, pageResults_1, pageData, txidsParams, txidsData, err_1, nextIndexes, enhancedData, e_3;
287
- var _this = this;
288
- var _a, _b, _c, _d;
289
- if (page === void 0) { page = 1; }
290
- if (pageSize === void 0) { pageSize = 1000; }
291
- return __generator(this, function (_e) {
292
- switch (_e.label) {
347
+ };
348
+ var get_info_by_pubkey = function (coin, pubkey, page) {
349
+ return __awaiter(this, void 0, void 0, function () {
350
+ var tag, url, body, resp, e_3;
351
+ return __generator(this, function (_a) {
352
+ switch (_a.label) {
293
353
  case 0:
294
354
  tag = TAG + " | get_info_by_pubkey | ";
295
- _e.label = 1;
355
+ _a.label = 1;
296
356
  case 1:
297
- _e.trys.push([1, 11, , 12]);
298
- baseUrl = BLOCKBOOK_URLS[coin.toUpperCase()];
299
- if (!baseUrl) {
300
- throw new Error("Unknown coin '".concat(coin, "' or missing BLOCKBOOK_URLS entry"));
301
- }
302
- url_1 = "".concat(baseUrl, "/api/v2/xpub/").concat(encodeURIComponent(pubkey));
303
- fetchPage = function (params) { return __awaiter(_this, void 0, void 0, function () {
304
- var resp;
305
- return __generator(this, function (_a) {
306
- switch (_a.label) {
307
- case 0:
308
- log.debug(tag, "Fetching page with params:", params);
309
- return [4 /*yield*/, axios({
310
- method: "GET",
311
- url: url_1,
312
- headers: {
313
- "content-type": "application/json",
314
- "User-Agent": typeof fakeUa === "function" ? fakeUa() : "blockbook-client/1.0",
315
- },
316
- params: params,
317
- })];
318
- case 1:
319
- resp = _a.sent();
320
- return [2 /*return*/, resp.data];
321
- }
322
- });
323
- }); };
324
- params = {
325
- page: page,
326
- pageSize: pageSize,
327
- details: "tokenBalances",
328
- tokens: "derived",
357
+ _a.trys.push([1, 3, , 4]);
358
+ if (!page)
359
+ page = "1";
360
+ url = BLOCKBOOK_URLS[coin.toUpperCase()] + "/api/v2/xpub/" + pubkey;
361
+ log.debug(tag, "url: ", url);
362
+ body = {
363
+ method: 'GET',
364
+ url: url,
365
+ headers: {
366
+ 'content-type': 'application/json',
367
+ 'User-Agent': fakeUa()
368
+ },
329
369
  };
330
- return [4 /*yield*/, fetchPage(params)];
370
+ return [4 /*yield*/, axios(body)];
331
371
  case 2:
332
- data = _e.sent();
333
- log.debug(tag, "Initial fetch returned ".concat(((_a = data.tokens) === null || _a === void 0 ? void 0 : _a.length) || 0, " tokens"));
334
- if (!(!data.tokens || data.tokens.length === 0)) return [3 /*break*/, 4];
335
- log.debug(tag, "No derived tokens found, trying 'used' fallback");
336
- params = {
337
- page: page,
338
- pageSize: pageSize,
339
- details: "tokens",
340
- tokens: "used",
341
- };
342
- return [4 /*yield*/, fetchPage(params)];
372
+ resp = _a.sent();
373
+ log.debug(tag, "resp: ", resp);
374
+ //TODO paginate?
375
+ return [2 /*return*/, resp.data];
343
376
  case 3:
344
- data = _e.sent();
345
- _e.label = 4;
346
- case 4:
347
- // Ensure tokens array exists
348
- if (!data.tokens) {
349
- data.tokens = [];
350
- }
351
- totalPages = Number((_b = data.totalPages) !== null && _b !== void 0 ? _b : 1);
352
- if (!(totalPages > 1)) return [3 /*break*/, 6];
353
- log.info(tag, "Fetching ".concat(totalPages, " pages of token data"));
354
- allTokens = __spreadArray([], data.tokens, true);
355
- startPage = Number((_c = data.page) !== null && _c !== void 0 ? _c : page);
356
- pagePromises = [];
357
- for (p = startPage + 1; p <= totalPages; p++) {
358
- pagePromises.push(fetchPage(__assign(__assign({}, params), { page: p })));
359
- }
360
- return [4 /*yield*/, Promise.all(pagePromises)];
361
- case 5:
362
- pageResults = _e.sent();
363
- for (_i = 0, pageResults_1 = pageResults; _i < pageResults_1.length; _i++) {
364
- pageData = pageResults_1[_i];
365
- if ((_d = pageData === null || pageData === void 0 ? void 0 : pageData.tokens) === null || _d === void 0 ? void 0 : _d.length) {
366
- allTokens.push.apply(allTokens, pageData.tokens);
367
- }
368
- }
369
- // Update metadata to reflect merged data
370
- data.tokens = allTokens;
371
- data.page = 1;
372
- data.totalPages = 1;
373
- data.itemsOnPage = allTokens.length;
374
- log.info(tag, "Merged ".concat(allTokens.length, " total tokens from ").concat(totalPages, " pages"));
375
- _e.label = 6;
376
- case 6:
377
- if (!(data.txs > 0 && (!data.txids || data.txids.length === 0))) return [3 /*break*/, 10];
378
- log.debug(tag, "Fetching txids for ".concat(data.txs, " transactions"));
379
- txidsParams = {
380
- page: 1,
381
- pageSize: Math.min(1000, data.txs * 2), // Get more to ensure we get all
382
- details: "txids",
383
- };
384
- _e.label = 7;
385
- case 7:
386
- _e.trys.push([7, 9, , 10]);
387
- return [4 /*yield*/, fetchPage(txidsParams)];
388
- case 8:
389
- txidsData = _e.sent();
390
- if (txidsData.txids && txidsData.txids.length > 0) {
391
- data.txids = txidsData.txids;
392
- log.info(tag, "Fetched ".concat(txidsData.txids.length, " txids"));
393
- }
394
- return [3 /*break*/, 10];
395
- case 9:
396
- err_1 = _e.sent();
397
- log.warn(tag, "Failed to fetch txids separately:", err_1);
398
- return [3 /*break*/, 10];
399
- case 10:
400
- nextIndexes = computeNextIndexes(data.tokens || []);
401
- enhancedData = __assign(__assign({}, data), { changeIndex: nextIndexes.nextChangeAddressIndex, receiveIndex: nextIndexes.nextReceiveAddressIndex });
402
- return [2 /*return*/, enhancedData];
403
- case 11:
404
- e_3 = _e.sent();
405
- log.error(tag, "Failed to get pubkey info for ".concat(coin, ":"), e_3);
377
+ e_3 = _a.sent();
378
+ console.error(tag, e_3);
406
379
  throw e_3;
407
- case 12: return [2 /*return*/];
408
- }
409
- });
410
- });
411
- }
412
- /**
413
- * Compute next indexes from Blockbook tokens array.
414
- * Returns the next external (receive) and internal (change) indexes.
415
- *
416
- * Logic:
417
- * - Consider an address "used" if transfers > 0 OR balance !== '0'
418
- * - Next index = (max used index on that branch) + 1
419
- *
420
- * @param tokens Array of BlockbookToken objects with path information
421
- * @returns Object with nextReceiveAddressIndex and nextChangeAddressIndex
422
- */
423
- function computeNextIndexes(tokens) {
424
- var _a, _b, _c, _d;
425
- var next = []; // next[0] = next receive, next[1] = next change
426
- for (var _i = 0, _e = tokens !== null && tokens !== void 0 ? tokens : []; _i < _e.length; _i++) {
427
- var token = _e[_i];
428
- if (!token.path)
429
- continue;
430
- // Parse BIP32 path: m/84'/0'/0'/1/5
431
- var parts = token.path.split("/");
432
- if (parts.length < 6)
433
- continue;
434
- var change = Number(parts[4]);
435
- var index = Number(parts[5]);
436
- if (!Number.isInteger(change) || !Number.isInteger(index))
437
- continue;
438
- if (change !== 0 && change !== 1)
439
- continue; // Only handle standard change values
440
- // Check if address is used
441
- var used = ((_a = token.transfers) !== null && _a !== void 0 ? _a : 0) > 0 || ((_b = token.balance) !== null && _b !== void 0 ? _b : "0") !== "0";
442
- if (!used)
443
- continue;
444
- // Update next index if this is higher than current
445
- var candidate = index + 1;
446
- if (next[change] == null || candidate > next[change]) {
447
- next[change] = candidate;
448
- }
449
- }
450
- return {
451
- nextReceiveAddressIndex: (_c = next[0]) !== null && _c !== void 0 ? _c : 0,
452
- nextChangeAddressIndex: (_d = next[1]) !== null && _d !== void 0 ? _d : 0,
453
- };
454
- }
455
- /**
456
- * Convenience function: fetch tokens and return the next indexes directly.
457
- * @param coin Coin symbol
458
- * @param pubkey Extended public key
459
- * @param page Starting page
460
- * @param pageSize Items per page
461
- * @returns Promise<NextIndexes> with next receive and change indexes
462
- */
463
- function get_next_indexes_by_pubkey(coin_1, pubkey_1) {
464
- return __awaiter(this, arguments, void 0, function (coin, pubkey, page, pageSize) {
465
- var info;
466
- var _a;
467
- if (page === void 0) { page = 1; }
468
- if (pageSize === void 0) { pageSize = 1000; }
469
- return __generator(this, function (_b) {
470
- switch (_b.label) {
471
- case 0: return [4 /*yield*/, get_info_by_pubkey(coin, pubkey, page, pageSize)];
472
- case 1:
473
- info = _b.sent();
474
- return [2 /*return*/, computeNextIndexes((_a = info.tokens) !== null && _a !== void 0 ? _a : [])];
380
+ case 4: return [2 /*return*/];
475
381
  }
476
382
  });
477
383
  });
478
- }
479
- /**
480
- * Get transaction IDs for a specific address with pagination support
481
- * @param coin Coin symbol
482
- * @param address Blockchain address
483
- * @param page Page number (default 1)
484
- * @returns Promise with address info including transaction IDs
485
- */
486
- function get_txids_by_address(coin_1, address_1) {
487
- return __awaiter(this, arguments, void 0, function (coin, address, page) {
488
- var tag, baseUrl, url, resp, data, e_4;
489
- if (page === void 0) { page = 1; }
384
+ };
385
+ var get_txids_by_address = function (coin, address, page) {
386
+ return __awaiter(this, void 0, void 0, function () {
387
+ var tag, url, body, resp, e_4;
490
388
  return __generator(this, function (_a) {
491
389
  switch (_a.label) {
492
390
  case 0:
@@ -494,53 +392,37 @@ function get_txids_by_address(coin_1, address_1) {
494
392
  _a.label = 1;
495
393
  case 1:
496
394
  _a.trys.push([1, 3, , 4]);
497
- baseUrl = BLOCKBOOK_URLS[coin.toUpperCase()];
498
- if (!baseUrl) {
499
- throw new Error("Unknown coin '".concat(coin, "' or missing BLOCKBOOK_URLS entry"));
500
- }
501
- url = "".concat(baseUrl, "/api/v2/address/").concat(address);
502
- log.debug(tag, "Fetching txids from:", url);
503
- return [4 /*yield*/, axios({
504
- method: 'GET',
505
- url: url,
506
- headers: {
507
- 'content-type': 'application/json',
508
- 'User-Agent': typeof fakeUa === 'function' ? fakeUa() : 'blockbook-client/1.0'
509
- },
510
- params: {
511
- page: page,
512
- details: 'txids',
513
- pageSize: 1000
514
- }
515
- })];
395
+ if (!page)
396
+ page = 1;
397
+ url = BLOCKBOOK_URLS[coin.toUpperCase()] + "/api/v2/address/" + address + "?page=" + page + "&details=all";
398
+ log.debug(tag, "url: ", url);
399
+ body = {
400
+ method: 'GET',
401
+ url: url,
402
+ headers: {
403
+ 'content-type': 'application/json',
404
+ 'User-Agent': fakeUa()
405
+ },
406
+ };
407
+ return [4 /*yield*/, axios(body)
408
+ //TODO paginate?
409
+ ];
516
410
  case 2:
517
411
  resp = _a.sent();
518
- data = resp.data;
519
- if (data.totalPages > 1 && page === 1) {
520
- log.info(tag, "Address has ".concat(data.totalPages, " pages of transactions"));
521
- // Consider implementing full pagination here if needed
522
- }
523
- return [2 /*return*/, data];
412
+ //TODO paginate?
413
+ return [2 /*return*/, resp.data];
524
414
  case 3:
525
415
  e_4 = _a.sent();
526
- log.error(tag, "Failed to get txids for address ".concat(address, ":"), e_4);
416
+ console.error(tag, e_4);
527
417
  throw e_4;
528
418
  case 4: return [2 /*return*/];
529
419
  }
530
420
  });
531
421
  });
532
- }
533
- /**
534
- * Get detailed information for a specific address
535
- * @param coin Coin symbol
536
- * @param address Blockchain address
537
- * @param filter Detail level filter (default 'all')
538
- * @returns Promise with complete address information
539
- */
540
- function get_info_by_address(coin_1, address_1) {
541
- return __awaiter(this, arguments, void 0, function (coin, address, filter) {
542
- var tag, baseUrl, url, resp, e_5;
543
- if (filter === void 0) { filter = 'all'; }
422
+ };
423
+ var get_info_by_address = function (coin, address, filter) {
424
+ return __awaiter(this, void 0, void 0, function () {
425
+ var tag, url, body, resp, e_5;
544
426
  return __generator(this, function (_a) {
545
427
  switch (_a.label) {
546
428
  case 0:
@@ -548,92 +430,71 @@ function get_info_by_address(coin_1, address_1) {
548
430
  _a.label = 1;
549
431
  case 1:
550
432
  _a.trys.push([1, 3, , 4]);
551
- baseUrl = BLOCKBOOK_URLS[coin.toUpperCase()];
552
- if (!baseUrl) {
553
- throw new Error("Unknown coin '".concat(coin, "' or missing BLOCKBOOK_URLS entry"));
554
- }
555
- url = "".concat(baseUrl, "/api/v2/address/").concat(address);
556
- log.debug(tag, "Fetching address info from:", url);
557
- return [4 /*yield*/, axios({
558
- method: 'GET',
559
- url: url,
560
- headers: {
561
- 'content-type': 'application/json',
562
- 'User-Agent': typeof fakeUa === 'function' ? fakeUa() : 'blockbook-client/1.0'
563
- },
564
- params: {
565
- details: filter,
566
- pageSize: 1000
567
- }
568
- })];
433
+ if (!filter)
434
+ filter = "all";
435
+ //let url = ETH_BLOCKBOOK_URL+"/api/v2/address/"+address+"?="+filter
436
+ if (!BLOCKBOOK_URLS[coin.toUpperCase()])
437
+ throw Error("invalid coin: " + coin);
438
+ url = BLOCKBOOK_URLS[coin.toUpperCase()] + "/api/v2/address/" + address + "?details=all";
439
+ body = {
440
+ method: 'GET',
441
+ url: url,
442
+ headers: {
443
+ 'content-type': 'application/json',
444
+ 'User-Agent': fakeUa()
445
+ },
446
+ };
447
+ return [4 /*yield*/, axios(body)
448
+ //TODO paginate?
449
+ ];
569
450
  case 2:
570
451
  resp = _a.sent();
452
+ //TODO paginate?
571
453
  return [2 /*return*/, resp.data];
572
454
  case 3:
573
455
  e_5 = _a.sent();
574
- log.error(tag, "Failed to get info for address ".concat(address, ":"), e_5);
456
+ console.error(tag, e_5);
575
457
  throw e_5;
576
458
  case 4: return [2 /*return*/];
577
459
  }
578
460
  });
579
461
  });
580
- }
581
- /**
582
- * Get all transactions for an extended public key
583
- * @param coin Coin symbol
584
- * @param xpub Extended public key
585
- * @returns Promise with xpub info including all transactions
586
- */
587
- function get_txs_by_xpub(coin, xpub) {
462
+ };
463
+ var get_txs_by_xpub = function (coin, xpub) {
588
464
  return __awaiter(this, void 0, void 0, function () {
589
- var tag, baseUrl, url, resp, e_6;
465
+ var tag, url, body, resp, e_6;
590
466
  return __generator(this, function (_a) {
591
467
  switch (_a.label) {
592
468
  case 0:
593
- tag = TAG + " | get_txs_by_xpub | ";
469
+ tag = TAG + " | FA get_txs_by_xpub | ";
594
470
  _a.label = 1;
595
471
  case 1:
596
472
  _a.trys.push([1, 3, , 4]);
597
- baseUrl = BLOCKBOOK_URLS[coin.toUpperCase()];
598
- if (!baseUrl) {
599
- throw new Error("Unknown coin '".concat(coin, "' or missing BLOCKBOOK_URLS entry"));
600
- }
601
- url = "".concat(baseUrl, "/api/v2/xpub/").concat(xpub);
602
- log.debug(tag, "Fetching xpub transactions from:", url);
603
- return [4 /*yield*/, axios({
604
- method: 'GET',
605
- url: url,
606
- headers: {
607
- 'content-type': 'application/json',
608
- 'User-Agent': typeof fakeUa === 'function' ? fakeUa() : 'blockbook-client/1.0'
609
- },
610
- params: {
611
- details: 'txs',
612
- tokens: 'derived',
613
- pageSize: 1000
614
- }
615
- })];
473
+ url = BLOCKBOOK_URLS[coin.toUpperCase()] + "/api/v2/xpub/" + xpub + "?details=all";
474
+ body = {
475
+ method: 'GET',
476
+ url: url,
477
+ headers: {
478
+ 'content-type': 'application/json',
479
+ 'User-Agent': fakeUa()
480
+ },
481
+ };
482
+ return [4 /*yield*/, axios(body)];
616
483
  case 2:
617
484
  resp = _a.sent();
618
485
  return [2 /*return*/, resp.data];
619
486
  case 3:
620
487
  e_6 = _a.sent();
621
- log.error(tag, "Failed to get transactions for xpub:", e_6);
488
+ console.error(tag, e_6);
622
489
  throw e_6;
623
490
  case 4: return [2 /*return*/];
624
491
  }
625
492
  });
626
493
  });
627
- }
628
- /**
629
- * Broadcast a raw transaction to the network
630
- * @param coin Coin symbol
631
- * @param hex Raw transaction hex
632
- * @returns Promise with broadcast result including success status and txid or error
633
- */
634
- function broadcast_transaction(coin, hex) {
494
+ };
495
+ var broadcast_transaction = function (coin, hex) {
635
496
  return __awaiter(this, void 0, void 0, function () {
636
- var tag, baseUrl, url, output, resp, e_7, errorMessage, statusCode, e_8;
497
+ var tag, url, data, body, output, resp, e_7, errorMessage, statusCode, e_8;
637
498
  var _a, _b;
638
499
  return __generator(this, function (_c) {
639
500
  switch (_c.label) {
@@ -642,47 +503,47 @@ function broadcast_transaction(coin, hex) {
642
503
  _c.label = 1;
643
504
  case 1:
644
505
  _c.trys.push([1, 6, , 7]);
645
- baseUrl = BLOCKBOOK_URLS[coin.toUpperCase()];
646
- if (!baseUrl) {
647
- throw new Error("Unknown coin '".concat(coin, "' or missing BLOCKBOOK_URLS entry"));
648
- }
649
- url = "".concat(baseUrl, "/api/v2/sendtx/");
650
- log.debug(tag, "Broadcasting to:", url);
506
+ url = BLOCKBOOK_URLS[coin.toUpperCase()] + "/api/v2/sendtx/";
507
+ data = hex;
508
+ body = {
509
+ url: url,
510
+ headers: {
511
+ 'content-type': 'text/plain',
512
+ 'User-Agent': fakeUa()
513
+ },
514
+ method: 'POST',
515
+ data: data,
516
+ };
651
517
  output = {
652
518
  success: false
653
519
  };
520
+ resp = void 0;
654
521
  _c.label = 2;
655
522
  case 2:
656
523
  _c.trys.push([2, 4, , 5]);
657
- return [4 /*yield*/, axios({
658
- url: url,
659
- method: 'POST',
660
- headers: {
661
- 'content-type': 'text/plain',
662
- 'User-Agent': typeof fakeUa === 'function' ? fakeUa() : 'blockbook-client/1.0'
663
- },
664
- data: hex,
665
- })];
524
+ return [4 /*yield*/, axios(body)
525
+ // Only store serializable data, not the full axios response
526
+ ];
666
527
  case 3:
667
528
  resp = _c.sent();
668
- output.success = true;
529
+ // Only store serializable data, not the full axios response
669
530
  output.txid = ((_a = resp.data) === null || _a === void 0 ? void 0 : _a.result) || resp.data;
670
- output.resp = resp.data;
671
- log.info(tag, "Transaction broadcast successful: ".concat(output.txid));
531
+ output.success = true;
672
532
  return [3 /*break*/, 5];
673
533
  case 4:
674
534
  e_7 = _c.sent();
675
- log.error(tag, "Broadcast error:", e_7.message || e_7);
535
+ log.error(tag, "Broadcast error: ", e_7.message || e_7);
676
536
  errorMessage = 'Unknown error occurred';
677
537
  statusCode = null;
678
538
  if (e_7.response) {
679
539
  statusCode = e_7.response.status;
680
- log.debug(tag, "HTTP Status:", statusCode);
681
- log.debug(tag, "Response headers:", e_7.response.headers);
540
+ log.debug(tag, "HTTP Status: ", statusCode);
541
+ log.debug(tag, "Response headers: ", e_7.response.headers);
682
542
  if (e_7.response.data) {
683
- log.debug(tag, "Response data:", e_7.response.data);
543
+ log.debug(tag, "Response data: ", e_7.response.data);
684
544
  if (e_7.response.data.error) {
685
545
  errorMessage = e_7.response.data.error;
546
+ log.error(tag, "API Error: ", errorMessage);
686
547
  }
687
548
  else if (typeof e_7.response.data === 'string') {
688
549
  errorMessage = e_7.response.data;
@@ -696,10 +557,12 @@ function broadcast_transaction(coin, hex) {
696
557
  }
697
558
  }
698
559
  else if (e_7.request) {
560
+ // Request was made but no response received
699
561
  errorMessage = 'Network error: No response received';
700
- log.debug(tag, "Request config:", (_b = e_7.config) === null || _b === void 0 ? void 0 : _b.url);
562
+ log.debug(tag, "Request config: ", (_b = e_7.config) === null || _b === void 0 ? void 0 : _b.url);
701
563
  }
702
564
  else {
565
+ // Something else happened
703
566
  errorMessage = e_7.message || 'Request setup error';
704
567
  }
705
568
  output.error = errorMessage;
@@ -708,22 +571,16 @@ function broadcast_transaction(coin, hex) {
708
571
  case 5: return [2 /*return*/, output];
709
572
  case 6:
710
573
  e_8 = _c.sent();
711
- log.error(tag, 'Unexpected error:', e_8);
574
+ console.error(tag, 'error: ', e_8);
712
575
  throw e_8;
713
576
  case 7: return [2 /*return*/];
714
577
  }
715
578
  });
716
579
  });
717
- }
718
- /**
719
- * Get detailed information about a specific transaction
720
- * @param coin Coin symbol
721
- * @param txid Transaction ID
722
- * @returns Promise with transaction details
723
- */
724
- function get_transaction(coin, txid) {
580
+ };
581
+ var get_transaction = function (coin, txid) {
725
582
  return __awaiter(this, void 0, void 0, function () {
726
- var tag, cacheKey, cached, baseUrl, url, resp, txData, e_9;
583
+ var tag, url, body, resp, e_9;
727
584
  return __generator(this, function (_a) {
728
585
  switch (_a.label) {
729
586
  case 0:
@@ -731,112 +588,149 @@ function get_transaction(coin, txid) {
731
588
  _a.label = 1;
732
589
  case 1:
733
590
  _a.trys.push([1, 3, , 4]);
734
- cacheKey = "tx:".concat(coin, ":").concat(txid);
735
- cached = getCached(cacheKey);
736
- if (cached) {
737
- log.debug(tag, "Returning cached transaction ".concat(txid));
738
- return [2 /*return*/, cached];
739
- }
740
- baseUrl = BLOCKBOOK_URLS[coin.toUpperCase()];
741
- if (!baseUrl) {
742
- throw new Error("Unknown coin '".concat(coin, "' or missing BLOCKBOOK_URLS entry"));
743
- }
744
- url = "".concat(baseUrl, "/api/v2/tx/").concat(txid);
745
- log.debug(tag, "Fetching transaction from:", url);
746
- return [4 /*yield*/, axios({
747
- method: 'GET',
748
- url: url,
749
- headers: {
750
- 'content-type': 'application/json',
751
- 'User-Agent': typeof fakeUa === 'function' ? fakeUa() : 'blockbook-client/1.0'
752
- },
753
- })];
591
+ url = BLOCKBOOK_URLS[coin.toUpperCase()] + "/api/v2/tx/" + txid;
592
+ console.log("url: ", url);
593
+ body = {
594
+ method: 'GET',
595
+ url: url,
596
+ headers: {
597
+ 'content-type': 'application/json',
598
+ 'User-Agent': fakeUa()
599
+ },
600
+ };
601
+ return [4 /*yield*/, axios(body)];
754
602
  case 2:
755
603
  resp = _a.sent();
756
- txData = resp.data;
757
- // Cache confirmed transactions
758
- if (txData.confirmations > 0) {
759
- setCache(cacheKey, txData);
760
- }
761
- return [2 /*return*/, txData];
604
+ return [2 /*return*/, resp.data];
762
605
  case 3:
763
606
  e_9 = _a.sent();
764
- log.error(tag, "Failed to get transaction ".concat(txid, ":"), e_9);
607
+ console.error(tag, e_9);
765
608
  throw e_9;
766
609
  case 4: return [2 /*return*/];
767
610
  }
768
611
  });
769
612
  });
770
- }
771
- /**
772
- * Get UTXOs (Unspent Transaction Outputs) for an extended public key
773
- * @param coin Coin symbol
774
- * @param xpub Extended public key
775
- * @param confirmedOnly Whether to include only confirmed UTXOs (default false)
776
- * @returns Promise with array of UTXOs
777
- */
778
- function get_utxos_by_xpub(coin_1, xpub_1) {
779
- return __awaiter(this, arguments, void 0, function (coin, xpub, confirmedOnly) {
780
- var tag, baseUrl, url, hasApiKeyInUrl, resp, utxos, e_10;
781
- if (confirmedOnly === void 0) { confirmedOnly = false; }
613
+ };
614
+ // Enhanced UTXO function with priority-based sequential failover
615
+ var get_utxos_by_xpub = function (coin, xpub) {
616
+ return __awaiter(this, void 0, void 0, function () {
617
+ var tag, symbol, nodes_2, activeNodes, i, node, startTime, url, body, resp, responseTime, error_1, responseTime, errorMessage, e_10;
782
618
  return __generator(this, function (_a) {
783
619
  switch (_a.label) {
784
620
  case 0:
785
621
  tag = TAG + " | get_utxos_by_xpub | ";
786
622
  _a.label = 1;
787
623
  case 1:
788
- _a.trys.push([1, 3, , 4]);
789
- baseUrl = BLOCKBOOK_URLS[coin.toUpperCase()];
790
- if (!baseUrl) {
791
- throw new Error("Unknown coin '".concat(coin, "' or missing BLOCKBOOK_URLS entry"));
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));
792
629
  }
793
- url = "".concat(baseUrl, "/api/v2/utxo/").concat(encodeURIComponent(xpub));
794
- log.debug(tag, "Fetching UTXOs from:", url);
795
- log.debug(tag, "Base URL:", baseUrl);
796
- log.debug(tag, "Full xpub:", xpub);
797
- hasApiKeyInUrl = baseUrl.includes(NOW_NODES_API || '');
798
- return [4 /*yield*/, axios({
799
- method: 'GET',
800
- url: url,
801
- headers: __assign({ 'content-type': 'application/json', 'User-Agent': typeof fakeUa === 'function' ? fakeUa() : 'blockbook-client/1.0' }, (NOW_NODES_API && !hasApiKeyInUrl && { 'api-key': NOW_NODES_API })),
802
- params: {
803
- confirmed: confirmedOnly
804
- }
805
- })];
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));
633
+ }
634
+ log.info(tag, "Attempting UTXO query for ".concat(symbol, " with ").concat(activeNodes.length, " nodes available"));
635
+ i = 0;
636
+ _a.label = 2;
806
637
  case 2:
638
+ if (!(i < activeNodes.length)) return [3 /*break*/, 7];
639
+ node = activeNodes[i];
640
+ startTime = Date.now();
641
+ _a.label = 3;
642
+ case 3:
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";
646
+ body = {
647
+ method: 'GET',
648
+ url: url,
649
+ timeout: 15000 // 15s timeout per node
650
+ };
651
+ return [4 /*yield*/, axios(body)];
652
+ case 4:
807
653
  resp = _a.sent();
808
- // Check if we got HTML instead of JSON (error page)
809
- if (typeof resp.data === 'string' && resp.data.includes('<!doctype html>')) {
810
- log.error(tag, "Received HTML error page instead of JSON data");
811
- log.error(tag, "Response content:", resp.data.substring(0, 500) + "...");
812
- throw new Error("Blockbook server returned HTML error page for ".concat(coin, ". Check server status and xpub format."));
813
- }
814
- utxos = resp.data;
815
- if (!Array.isArray(utxos)) {
816
- log.error(tag, "Invalid response format - expected array but got:", typeof utxos);
817
- log.error(tag, "Response data:", utxos);
818
- throw new Error("Invalid UTXO response format from Blockbook server for ".concat(coin));
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"));
658
+ return [2 /*return*/, resp.data];
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));
819
669
  }
820
- log.info(tag, "Found ".concat(utxos.length, " UTXOs for xpub"));
821
- return [2 /*return*/, utxos];
822
- case 3:
670
+ // Continue to next node
671
+ return [3 /*break*/, 6];
672
+ case 6:
673
+ i++;
674
+ return [3 /*break*/, 2];
675
+ case 7: return [3 /*break*/, 9];
676
+ case 8:
823
677
  e_10 = _a.sent();
824
- log.error(tag, "Failed to get UTXOs for xpub:", e_10);
678
+ console.error(tag, e_10);
825
679
  throw e_10;
826
- case 4: return [2 /*return*/];
680
+ case 9: return [2 /*return*/];
827
681
  }
828
682
  });
829
683
  });
830
- }
831
- /**
832
- * Get total balance for an extended public key in BTC (not satoshis)
833
- * @param coin Coin symbol
834
- * @param xpub Extended public key
835
- * @returns Promise with balance in BTC (not satoshis)
836
- */
837
- function get_balance_by_xpub(coin, xpub) {
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
+ };
731
+ var get_balance_by_xpub = function (coin, xpub) {
838
732
  return __awaiter(this, void 0, void 0, function () {
839
- var tag, utxos, balanceSatoshis, _i, utxos_1, utxo, value, balanceBTC, e_11;
733
+ var tag, output, balance, i, uxto, e_11;
840
734
  return __generator(this, function (_a) {
841
735
  switch (_a.label) {
842
736
  case 0:
@@ -844,73 +738,41 @@ function get_balance_by_xpub(coin, xpub) {
844
738
  _a.label = 1;
845
739
  case 1:
846
740
  _a.trys.push([1, 3, , 4]);
847
- log.debug(tag, "Getting balance for coin:", coin);
848
- log.debug(tag, "xpub:", xpub);
849
- // Validate xpub format before making API call
850
- if (!xpub || typeof xpub !== 'string' || xpub.length < 50) {
851
- throw new Error("Invalid xpub format: ".concat(xpub));
852
- }
853
- return [4 /*yield*/, get_utxos_by_xpub(coin, xpub, false)];
741
+ log.debug(tag, "coin: ", coin);
742
+ log.debug(tag, "xpub: ", xpub);
743
+ return [4 /*yield*/, get_utxos_by_xpub(coin, xpub)];
854
744
  case 2:
855
- utxos = _a.sent();
856
- log.debug(tag, "Processing ".concat(utxos.length, " UTXOs"));
857
- // Validate UTXOs array
858
- if (!Array.isArray(utxos)) {
859
- log.error(tag, "UTXOs response is not an array:", typeof utxos);
860
- throw new Error("Invalid UTXOs response format");
745
+ output = _a.sent();
746
+ log.debug(tag, "output: ", output);
747
+ balance = 0;
748
+ //tally
749
+ for (i = 0; i < output.length; i++) {
750
+ uxto = output[i];
751
+ balance = balance + parseInt(uxto.value);
861
752
  }
862
- balanceSatoshis = 0;
863
- for (_i = 0, utxos_1 = utxos; _i < utxos_1.length; _i++) {
864
- utxo = utxos_1[_i];
865
- if (!utxo || typeof utxo !== 'object') {
866
- log.warn(tag, "Invalid UTXO object:", utxo);
867
- continue;
868
- }
869
- value = parseInt(utxo.value, 10);
870
- if (!isNaN(value) && value > 0) {
871
- balanceSatoshis += value;
872
- }
873
- else {
874
- log.warn(tag, "Invalid UTXO value:", utxo.value);
875
- }
876
- }
877
- balanceBTC = balanceSatoshis / 100000000;
878
- log.info(tag, "Total balance: ".concat(balanceBTC, " BTC (").concat(balanceSatoshis, " satoshis)"));
879
- return [2 /*return*/, balanceBTC];
753
+ return [2 /*return*/, balance / 100000000];
880
754
  case 3:
881
755
  e_11 = _a.sent();
882
- log.error(tag, "Failed to get balance for xpub:", e_11);
883
- // Re-throw with more context if it's a generic error
884
- if (e_11 instanceof Error && e_11.message.includes('<!doctype html>')) {
885
- throw new Error("Blockbook server error: Received HTML response instead of JSON data for ".concat(coin, ". Server may be down or xpub format invalid."));
886
- }
756
+ console.error(tag, e_11);
887
757
  throw e_11;
888
758
  case 4: return [2 /*return*/];
889
759
  }
890
760
  });
891
761
  });
892
- }
893
- /**
894
- * Get information about the Blockbook nodes
895
- * @returns Promise with node status information
896
- */
897
- function get_node_info() {
762
+ };
763
+ var get_node_info = function () {
898
764
  return __awaiter(this, void 0, void 0, function () {
899
765
  var tag;
900
766
  return __generator(this, function (_a) {
901
767
  tag = TAG + " | get_node_info | ";
902
768
  try {
903
- return [2 /*return*/, {
904
- initialized: Object.keys(BLOCKBOOK_URLS).length > 0,
905
- availableNodes: Object.keys(BLOCKBOOK_URLS),
906
- activeWebsockets: Object.keys(BLOCKBOOK_SOCKETS),
907
- }];
769
+ return [2 /*return*/, true];
908
770
  }
909
771
  catch (e) {
910
- log.error(tag, "Failed to get node info:", e);
772
+ console.error(tag, e);
911
773
  throw e;
912
774
  }
913
775
  return [2 /*return*/];
914
776
  });
915
777
  });
916
- }
778
+ };