@pioneer-platform/blockbook 8.3.13 → 8.3.16

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.
Files changed (3) hide show
  1. package/lib/index.d.ts +177 -17
  2. package/lib/index.js +654 -294
  3. package/package.json +2 -1
package/lib/index.js CHANGED
@@ -1,8 +1,23 @@
1
1
  "use strict";
2
- /*
3
-
4
-
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
5
9
  */
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
+ };
6
21
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
7
22
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
8
23
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -39,135 +54,184 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
39
54
  if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
40
55
  }
41
56
  };
42
- var TAG = " | blockbook-client | ";
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
+ };
43
66
  var Blockbook = require('blockbook-client').Blockbook;
44
67
  var log = require('@pioneer-platform/loggerdog')();
45
68
  var fakeUa = require('fake-useragent');
46
69
  var Axios = require('axios');
47
70
  var https = require('https');
48
- var nodes = require("@pioneer-platform/nodes");
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
76
  var axios = Axios.create({
50
77
  httpsAgent: new https.Agent({
51
78
  rejectUnauthorized: false
52
79
  }),
53
- timeout: 30000 // 10 seconds
80
+ timeout: 30000 // 30 seconds timeout
54
81
  });
55
- var axiosRetry = require('axios-retry');
56
- var NOW_NODES_API = process.env['NOW_NODES_API'];
82
+ // Configure automatic retries for transient failures
57
83
  axiosRetry(axios, {
58
- retries: 3, // number of retries
84
+ retries: 3,
59
85
  retryDelay: function (retryCount) {
60
- log.error(TAG, "retry attempt: ".concat(retryCount));
61
- return retryCount * 2000; // time interval between retries
86
+ log.error(TAG, "Retry attempt: ".concat(retryCount));
87
+ return retryCount * 2000; // Exponential backoff
62
88
  },
63
89
  retryCondition: function (error) {
64
90
  var _a;
65
- log.error(TAG, error);
66
- //@TODO mark node offline, and punish
67
- // if retry condition is not specified, by default idempotent requests are retried
68
- return ((_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.status) === 503;
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);
69
94
  },
70
95
  });
96
+ // Global storage for Blockbook URLs and WebSocket connections
71
97
  var BLOCKBOOK_URLS = {};
72
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;
109
+ }
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
73
120
  module.exports = {
74
- init: function (servers) {
75
- return init_network(servers);
76
- },
77
- getInfo: function () {
78
- return get_node_info();
79
- },
80
- getBlockbooks: function () {
81
- return BLOCKBOOK_URLS;
82
- },
83
- getBlockbookSockets: function () {
84
- return BLOCKBOOK_SOCKETS;
85
- },
86
- getFees: function (coin) {
87
- return get_fees(coin);
88
- },
89
- getTransaction: function (coin, txid) {
90
- return get_transaction(coin, txid);
91
- },
92
- getAddressInfo: function (coin, address, filter) {
93
- return get_info_by_address(coin, address, filter);
94
- },
95
- getPubkeyInfo: function (coin, pubkey, filter) {
96
- return get_info_by_pubkey(coin, pubkey, filter);
97
- },
98
- txidsByAddress: function (coin, address, page) {
99
- return get_txids_by_address(coin, address, page);
100
- },
101
- txsByXpub: function (coin, addresses) {
102
- return get_txs_by_xpub(coin, addresses);
103
- },
104
- utxosByXpub: function (coin, xpub) {
105
- return get_utxos_by_xpub(coin, xpub);
106
- },
107
- getBalanceByXpub: function (coin, xpub) {
108
- return get_balance_by_xpub(coin, xpub);
109
- },
110
- broadcast: function (coin, hex) {
111
- return broadcast_transaction(coin, hex);
112
- },
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,
113
136
  };
114
- var init_network = function (servers) {
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) {
115
143
  return __awaiter(this, void 0, void 0, function () {
116
- var tag, SEED_NODES, blockbooks, i, blockbook, url, e_1;
144
+ var tag, SEED_NODES, allNodes, blockbooks, _i, blockbooks_1, blockbook, symbol, httpUrl, e_1;
117
145
  return __generator(this, function (_a) {
118
146
  switch (_a.label) {
119
147
  case 0:
120
- tag = ' | get_txs_by_address | ';
148
+ tag = TAG + " | init_network | ";
121
149
  _a.label = 1;
122
150
  case 1:
123
- _a.trys.push([1, 3, , 4]);
124
- log.debug(tag, "checkpoint: ");
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];
125
155
  return [4 /*yield*/, nodes.getBlockbooks()];
126
156
  case 2:
127
157
  SEED_NODES = _a.sent();
128
- log.info(tag, "SEED_NODES: ", SEED_NODES);
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"));
129
176
  blockbooks = [];
130
- if (servers && Array.isArray(servers)) { // Type checking for array
131
- blockbooks = servers.concat(SEED_NODES); // Combine arrays
177
+ if (servers && Array.isArray(servers)) {
178
+ blockbooks = servers.concat(SEED_NODES);
179
+ log.info(tag, "Added ".concat(servers.length, " custom servers"));
132
180
  }
133
181
  else {
134
- console.error("Invalid 'servers' parameter. Expected an array.");
182
+ if (servers) {
183
+ log.warn(tag, "Invalid 'servers' parameter. Expected an array.");
184
+ }
135
185
  blockbooks = SEED_NODES;
136
186
  }
137
- log.debug(tag, "blockbooks: ", blockbooks.length);
138
- for (i = 0; i < blockbooks.length; i++) {
139
- blockbook = blockbooks[i];
140
- //get swagger
141
- if (blockbook && blockbook.service)
142
- BLOCKBOOK_URLS[blockbook.symbol.toUpperCase()] = blockbook.service;
143
- if (blockbook && blockbook.websocket) {
144
- url = blockbook.websocket.replace("/websocket", "");
145
- url = blockbook.websocket.replace("wss://", "https://");
146
- BLOCKBOOK_SOCKETS[blockbook.symbol.toUpperCase()] = new Blockbook({
147
- nodes: [url],
148
- disableTypeValidation: true,
149
- });
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
+ }
206
+ }
207
+ else {
208
+ log.warn(tag, "Invalid blockbook configuration:", blockbook);
209
+ }
150
210
  }
151
- else {
152
- log.error(tag, "invalid unchained service: ", blockbook);
153
- // throw Error("invalid unchained service!")
211
+ catch (error) {
212
+ log.error(tag, "Failed to initialize blockbook ".concat(blockbook === null || blockbook === void 0 ? void 0 : blockbook.symbol, ":"), error);
154
213
  }
155
214
  }
156
- log.debug(tag, "BLOCKBOOK_URLS: ", BLOCKBOOK_URLS);
157
- log.debug(tag, "BLOCKBOOK_SOCKETS: ", BLOCKBOOK_SOCKETS);
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"));
158
217
  return [2 /*return*/, true];
159
- case 3:
218
+ case 7:
160
219
  e_1 = _a.sent();
161
- // console.error(tag, 'Error: ', e)
220
+ log.error(tag, "Failed to initialize network:", e_1);
162
221
  throw e_1;
163
- case 4: return [2 /*return*/];
222
+ case 8: return [2 /*return*/];
164
223
  }
165
224
  });
166
225
  });
167
- };
168
- var get_fees = function (coin) {
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) {
169
233
  return __awaiter(this, void 0, void 0, function () {
170
- var tag, url, body, resp, e_2;
234
+ var tag, cacheKey, cached, baseUrl, url, resp, feeData, e_2;
171
235
  return __generator(this, function (_a) {
172
236
  switch (_a.label) {
173
237
  case 0:
@@ -175,74 +239,254 @@ var get_fees = function (coin) {
175
239
  _a.label = 1;
176
240
  case 1:
177
241
  _a.trys.push([1, 3, , 4]);
178
- url = BLOCKBOOK_URLS[coin.toUpperCase()] + "/api/v2/fees";
179
- log.debug(tag, "url: ", url);
180
- body = {
181
- method: 'GET',
182
- url: url,
183
- headers: {
184
- 'content-type': 'application/json',
185
- 'User-Agent': fakeUa()
186
- },
187
- };
188
- return [4 /*yield*/, axios(body)
189
- //log.debug(tag,"resp: ",resp)
190
- //TODO paginate?
191
- ];
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
+ })];
192
262
  case 2:
193
263
  resp = _a.sent();
194
- //log.debug(tag,"resp: ",resp)
195
- //TODO paginate?
196
- return [2 /*return*/, resp.data];
264
+ feeData = resp.data;
265
+ setCache(cacheKey, feeData);
266
+ return [2 /*return*/, feeData];
197
267
  case 3:
198
268
  e_2 = _a.sent();
199
- console.error(tag, e_2);
269
+ log.error(tag, "Failed to get fees for ".concat(coin, ":"), e_2);
200
270
  throw e_2;
201
271
  case 4: return [2 /*return*/];
202
272
  }
203
273
  });
204
274
  });
205
- };
206
- var get_info_by_pubkey = function (coin, pubkey, page) {
207
- return __awaiter(this, void 0, void 0, function () {
208
- var tag, url, body, resp, e_3;
209
- return __generator(this, function (_a) {
210
- switch (_a.label) {
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) {
211
293
  case 0:
212
294
  tag = TAG + " | get_info_by_pubkey | ";
213
- _a.label = 1;
295
+ _e.label = 1;
214
296
  case 1:
215
- _a.trys.push([1, 3, , 4]);
216
- if (!page)
217
- page = "1";
218
- url = BLOCKBOOK_URLS[coin.toUpperCase()] + "/api/v2/xpub/" + pubkey;
219
- log.debug(tag, "url: ", url);
220
- body = {
221
- method: 'GET',
222
- url: url,
223
- headers: {
224
- 'content-type': 'application/json',
225
- 'User-Agent': fakeUa()
226
- },
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",
227
329
  };
228
- return [4 /*yield*/, axios(body)];
330
+ return [4 /*yield*/, fetchPage(params)];
229
331
  case 2:
230
- resp = _a.sent();
231
- log.debug(tag, "resp: ", resp);
232
- //TODO paginate?
233
- return [2 /*return*/, resp.data];
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)];
234
343
  case 3:
235
- e_3 = _a.sent();
236
- console.error(tag, e_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);
237
406
  throw e_3;
238
- case 4: return [2 /*return*/];
407
+ case 12: return [2 /*return*/];
239
408
  }
240
409
  });
241
410
  });
242
- };
243
- var get_txids_by_address = function (coin, address, page) {
244
- return __awaiter(this, void 0, void 0, function () {
245
- var tag, url, body, resp, e_4;
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 : [])];
475
+ }
476
+ });
477
+ });
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; }
246
490
  return __generator(this, function (_a) {
247
491
  switch (_a.label) {
248
492
  case 0:
@@ -250,37 +494,53 @@ var get_txids_by_address = function (coin, address, page) {
250
494
  _a.label = 1;
251
495
  case 1:
252
496
  _a.trys.push([1, 3, , 4]);
253
- if (!page)
254
- page = 1;
255
- url = BLOCKBOOK_URLS[coin.toUpperCase()] + "/api/v2/address/" + address + "?page=" + page + "&details=all";
256
- log.debug(tag, "url: ", url);
257
- body = {
258
- method: 'GET',
259
- url: url,
260
- headers: {
261
- 'content-type': 'application/json',
262
- 'User-Agent': fakeUa()
263
- },
264
- };
265
- return [4 /*yield*/, axios(body)
266
- //TODO paginate?
267
- ];
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
+ })];
268
516
  case 2:
269
517
  resp = _a.sent();
270
- //TODO paginate?
271
- return [2 /*return*/, resp.data];
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];
272
524
  case 3:
273
525
  e_4 = _a.sent();
274
- console.error(tag, e_4);
526
+ log.error(tag, "Failed to get txids for address ".concat(address, ":"), e_4);
275
527
  throw e_4;
276
528
  case 4: return [2 /*return*/];
277
529
  }
278
530
  });
279
531
  });
280
- };
281
- var get_info_by_address = function (coin, address, filter) {
282
- return __awaiter(this, void 0, void 0, function () {
283
- var tag, url, body, resp, e_5;
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'; }
284
544
  return __generator(this, function (_a) {
285
545
  switch (_a.label) {
286
546
  case 0:
@@ -288,125 +548,182 @@ var get_info_by_address = function (coin, address, filter) {
288
548
  _a.label = 1;
289
549
  case 1:
290
550
  _a.trys.push([1, 3, , 4]);
291
- if (!filter)
292
- filter = "all";
293
- //let url = ETH_BLOCKBOOK_URL+"/api/v2/address/"+address+"?="+filter
294
- if (!BLOCKBOOK_URLS[coin.toUpperCase()])
295
- throw Error("invalid coin: " + coin);
296
- url = BLOCKBOOK_URLS[coin.toUpperCase()] + "/api/v2/address/" + address + "?details=all";
297
- body = {
298
- method: 'GET',
299
- url: url,
300
- headers: {
301
- 'content-type': 'application/json',
302
- 'User-Agent': fakeUa()
303
- },
304
- };
305
- return [4 /*yield*/, axios(body)
306
- //TODO paginate?
307
- ];
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
+ })];
308
569
  case 2:
309
570
  resp = _a.sent();
310
- //TODO paginate?
311
571
  return [2 /*return*/, resp.data];
312
572
  case 3:
313
573
  e_5 = _a.sent();
314
- console.error(tag, e_5);
574
+ log.error(tag, "Failed to get info for address ".concat(address, ":"), e_5);
315
575
  throw e_5;
316
576
  case 4: return [2 /*return*/];
317
577
  }
318
578
  });
319
579
  });
320
- };
321
- var get_txs_by_xpub = function (coin, xpub) {
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) {
322
588
  return __awaiter(this, void 0, void 0, function () {
323
- var tag, url, body, resp, e_6;
589
+ var tag, baseUrl, url, resp, e_6;
324
590
  return __generator(this, function (_a) {
325
591
  switch (_a.label) {
326
592
  case 0:
327
- tag = TAG + " | FA get_txs_by_xpub | ";
593
+ tag = TAG + " | get_txs_by_xpub | ";
328
594
  _a.label = 1;
329
595
  case 1:
330
596
  _a.trys.push([1, 3, , 4]);
331
- url = BLOCKBOOK_URLS[coin.toUpperCase()] + "/api/v2/xpub/" + xpub + "?details=all";
332
- body = {
333
- method: 'GET',
334
- url: url,
335
- headers: {
336
- 'content-type': 'application/json',
337
- 'User-Agent': fakeUa()
338
- },
339
- };
340
- return [4 /*yield*/, axios(body)];
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
+ })];
341
616
  case 2:
342
617
  resp = _a.sent();
343
618
  return [2 /*return*/, resp.data];
344
619
  case 3:
345
620
  e_6 = _a.sent();
346
- console.error(tag, e_6);
621
+ log.error(tag, "Failed to get transactions for xpub:", e_6);
347
622
  throw e_6;
348
623
  case 4: return [2 /*return*/];
349
624
  }
350
625
  });
351
626
  });
352
- };
353
- var broadcast_transaction = function (coin, hex) {
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) {
354
635
  return __awaiter(this, void 0, void 0, function () {
355
- var tag, url, body, resp, e_7;
356
- return __generator(this, function (_a) {
357
- switch (_a.label) {
636
+ var tag, baseUrl, url, output, resp, e_7, errorMessage, statusCode, e_8;
637
+ var _a, _b;
638
+ return __generator(this, function (_c) {
639
+ switch (_c.label) {
358
640
  case 0:
359
641
  tag = TAG + " | broadcast_transaction | ";
360
- _a.label = 1;
642
+ _c.label = 1;
361
643
  case 1:
362
- _a.trys.push([1, 3, , 4]);
363
- log.info(tag, "Broadcasting transaction:", hex.substring(0, 20) + "..." + " for coin " + coin);
364
- if (!BLOCKBOOK_URLS[coin.toUpperCase()]) {
365
- throw new Error("No blockbook URL configured for coin: ".concat(coin));
644
+ _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"));
366
648
  }
367
- url = BLOCKBOOK_URLS[coin.toUpperCase()] + "/api/v2/sendtx";
368
- log.debug(tag, "Broadcasting to URL:", url);
369
- body = {
370
- method: 'POST',
371
- url: url,
372
- headers: {
373
- 'Content-Type': 'application/json',
374
- 'User-Agent': fakeUa()
375
- },
376
- data: {
377
- hex: hex
378
- }
649
+ url = "".concat(baseUrl, "/api/v2/sendtx/");
650
+ log.debug(tag, "Broadcasting to:", url);
651
+ output = {
652
+ success: false
379
653
  };
380
- return [4 /*yield*/, axios(body)];
654
+ _c.label = 2;
381
655
  case 2:
382
- resp = _a.sent();
383
- log.debug(tag, "Broadcast response:", resp.data);
384
- // Blockbook returns { result: "txid" } or { error: "message" }
385
- if (resp.data && resp.data.result) {
386
- return [2 /*return*/, {
387
- txid: resp.data.result,
388
- success: true
389
- }];
656
+ _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
+ })];
666
+ case 3:
667
+ resp = _c.sent();
668
+ output.success = true;
669
+ 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));
672
+ return [3 /*break*/, 5];
673
+ case 4:
674
+ e_7 = _c.sent();
675
+ log.error(tag, "Broadcast error:", e_7.message || e_7);
676
+ errorMessage = 'Unknown error occurred';
677
+ statusCode = null;
678
+ if (e_7.response) {
679
+ statusCode = e_7.response.status;
680
+ log.debug(tag, "HTTP Status:", statusCode);
681
+ log.debug(tag, "Response headers:", e_7.response.headers);
682
+ if (e_7.response.data) {
683
+ log.debug(tag, "Response data:", e_7.response.data);
684
+ if (e_7.response.data.error) {
685
+ errorMessage = e_7.response.data.error;
686
+ }
687
+ else if (typeof e_7.response.data === 'string') {
688
+ errorMessage = e_7.response.data;
689
+ }
690
+ else {
691
+ errorMessage = "HTTP ".concat(statusCode, ": ").concat(e_7.response.statusText || 'Request failed');
692
+ }
693
+ }
694
+ else {
695
+ errorMessage = "HTTP ".concat(statusCode, ": ").concat(e_7.response.statusText || 'Request failed');
696
+ }
390
697
  }
391
- else if (resp.data && resp.data.error) {
392
- throw new Error("Blockchain broadcast failed: ".concat(resp.data.error));
698
+ else if (e_7.request) {
699
+ errorMessage = 'Network error: No response received';
700
+ log.debug(tag, "Request config:", (_b = e_7.config) === null || _b === void 0 ? void 0 : _b.url);
393
701
  }
394
702
  else {
395
- throw new Error("Unexpected response format: ".concat(JSON.stringify(resp.data)));
703
+ errorMessage = e_7.message || 'Request setup error';
396
704
  }
397
- return [3 /*break*/, 4];
398
- case 3:
399
- e_7 = _a.sent();
400
- console.error(tag, "Broadcast error:", e_7);
401
- throw e_7;
402
- case 4: return [2 /*return*/];
705
+ output.error = errorMessage;
706
+ output.statusCode = statusCode;
707
+ return [3 /*break*/, 5];
708
+ case 5: return [2 /*return*/, output];
709
+ case 6:
710
+ e_8 = _c.sent();
711
+ log.error(tag, 'Unexpected error:', e_8);
712
+ throw e_8;
713
+ case 7: return [2 /*return*/];
403
714
  }
404
715
  });
405
716
  });
406
- };
407
- var get_transaction = function (coin, txid) {
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) {
408
725
  return __awaiter(this, void 0, void 0, function () {
409
- var tag, url, body, resp, e_8;
726
+ var tag, cacheKey, cached, baseUrl, url, resp, txData, e_9;
410
727
  return __generator(this, function (_a) {
411
728
  switch (_a.label) {
412
729
  case 0:
@@ -414,67 +731,98 @@ var get_transaction = function (coin, txid) {
414
731
  _a.label = 1;
415
732
  case 1:
416
733
  _a.trys.push([1, 3, , 4]);
417
- url = BLOCKBOOK_URLS[coin.toUpperCase()] + "/api/v2/tx/" + txid;
418
- console.log("url: ", url);
419
- body = {
420
- method: 'GET',
421
- url: url,
422
- headers: {
423
- 'content-type': 'application/json',
424
- 'User-Agent': fakeUa()
425
- },
426
- };
427
- return [4 /*yield*/, axios(body)];
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
+ })];
428
754
  case 2:
429
755
  resp = _a.sent();
430
- return [2 /*return*/, resp.data];
756
+ txData = resp.data;
757
+ // Cache confirmed transactions
758
+ if (txData.confirmations > 0) {
759
+ setCache(cacheKey, txData);
760
+ }
761
+ return [2 /*return*/, txData];
431
762
  case 3:
432
- e_8 = _a.sent();
433
- console.error(tag, e_8);
434
- throw e_8;
763
+ e_9 = _a.sent();
764
+ log.error(tag, "Failed to get transaction ".concat(txid, ":"), e_9);
765
+ throw e_9;
435
766
  case 4: return [2 /*return*/];
436
767
  }
437
768
  });
438
769
  });
439
- };
440
- var get_utxos_by_xpub = function (coin, xpub) {
441
- return __awaiter(this, void 0, void 0, function () {
442
- var tag, url, body, resp, e_9;
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, resp, utxos, e_10;
781
+ if (confirmedOnly === void 0) { confirmedOnly = false; }
443
782
  return __generator(this, function (_a) {
444
783
  switch (_a.label) {
445
784
  case 0:
446
- tag = TAG + " | FA get_utxos_by_xpub | ";
785
+ tag = TAG + " | get_utxos_by_xpub | ";
447
786
  _a.label = 1;
448
787
  case 1:
449
788
  _a.trys.push([1, 3, , 4]);
450
- log.info(tag, "get_utxos_by_xpub: ", BLOCKBOOK_URLS);
451
- url = BLOCKBOOK_URLS[coin.toUpperCase()] + "/api/v2/utxo/" + xpub + "?confirmed=false";
452
- console.log("url: ", url);
453
- body = {
454
- method: 'GET',
455
- url: url,
456
- // headers: {
457
- // 'api-key': NOW_NODES_API,
458
- // 'content-type': 'application/json',
459
- // 'User-Agent': fakeUa()
460
- // },
461
- };
462
- return [4 /*yield*/, axios(body)];
789
+ baseUrl = BLOCKBOOK_URLS[coin.toUpperCase()];
790
+ if (!baseUrl) {
791
+ throw new Error("Unknown coin '".concat(coin, "' or missing BLOCKBOOK_URLS entry"));
792
+ }
793
+ url = "".concat(baseUrl, "/api/v2/utxo/").concat(xpub);
794
+ log.debug(tag, "Fetching UTXOs from:", url);
795
+ return [4 /*yield*/, axios({
796
+ method: 'GET',
797
+ url: url,
798
+ headers: __assign({ 'content-type': 'application/json', 'User-Agent': typeof fakeUa === 'function' ? fakeUa() : 'blockbook-client/1.0' }, (NOW_NODES_API && { 'api-key': NOW_NODES_API })),
799
+ params: {
800
+ confirmed: confirmedOnly
801
+ }
802
+ })];
463
803
  case 2:
464
804
  resp = _a.sent();
465
- return [2 /*return*/, resp.data];
805
+ utxos = resp.data;
806
+ log.info(tag, "Found ".concat(utxos.length, " UTXOs for xpub"));
807
+ return [2 /*return*/, utxos];
466
808
  case 3:
467
- e_9 = _a.sent();
468
- console.error(tag, e_9);
469
- throw e_9;
809
+ e_10 = _a.sent();
810
+ log.error(tag, "Failed to get UTXOs for xpub:", e_10);
811
+ throw e_10;
470
812
  case 4: return [2 /*return*/];
471
813
  }
472
814
  });
473
815
  });
474
- };
475
- var get_balance_by_xpub = function (coin, xpub) {
816
+ }
817
+ /**
818
+ * Get total balance for an extended public key in BTC (not satoshis)
819
+ * @param coin Coin symbol
820
+ * @param xpub Extended public key
821
+ * @returns Promise with balance in BTC (not satoshis)
822
+ */
823
+ function get_balance_by_xpub(coin, xpub) {
476
824
  return __awaiter(this, void 0, void 0, function () {
477
- var tag, output, balance, i, uxto, e_10;
825
+ var tag, utxos, balanceSatoshis, _i, utxos_1, utxo, value, balanceBTC, e_11;
478
826
  return __generator(this, function (_a) {
479
827
  switch (_a.label) {
480
828
  case 0:
@@ -482,41 +830,53 @@ var get_balance_by_xpub = function (coin, xpub) {
482
830
  _a.label = 1;
483
831
  case 1:
484
832
  _a.trys.push([1, 3, , 4]);
485
- log.debug(tag, "coin: ", coin);
486
- log.debug(tag, "xpub: ", xpub);
487
- return [4 /*yield*/, get_utxos_by_xpub(coin, xpub)];
833
+ log.debug(tag, "Getting balance for coin:", coin);
834
+ log.debug(tag, "xpub:", xpub);
835
+ return [4 /*yield*/, get_utxos_by_xpub(coin, xpub, false)];
488
836
  case 2:
489
- output = _a.sent();
490
- log.debug(tag, "output: ", output);
491
- balance = 0;
492
- //tally
493
- for (i = 0; i < output.length; i++) {
494
- uxto = output[i];
495
- balance = balance + parseInt(uxto.value);
837
+ utxos = _a.sent();
838
+ log.debug(tag, "Processing ".concat(utxos.length, " UTXOs"));
839
+ balanceSatoshis = 0;
840
+ for (_i = 0, utxos_1 = utxos; _i < utxos_1.length; _i++) {
841
+ utxo = utxos_1[_i];
842
+ value = parseInt(utxo.value, 10);
843
+ if (!isNaN(value)) {
844
+ balanceSatoshis += value;
845
+ }
496
846
  }
497
- return [2 /*return*/, balance / 100000000];
847
+ balanceBTC = balanceSatoshis / 100000000;
848
+ log.info(tag, "Total balance: ".concat(balanceBTC, " BTC (").concat(balanceSatoshis, " satoshis)"));
849
+ return [2 /*return*/, balanceBTC];
498
850
  case 3:
499
- e_10 = _a.sent();
500
- console.error(tag, e_10);
501
- throw e_10;
851
+ e_11 = _a.sent();
852
+ log.error(tag, "Failed to get balance for xpub:", e_11);
853
+ throw e_11;
502
854
  case 4: return [2 /*return*/];
503
855
  }
504
856
  });
505
857
  });
506
- };
507
- var get_node_info = function () {
858
+ }
859
+ /**
860
+ * Get information about the Blockbook nodes
861
+ * @returns Promise with node status information
862
+ */
863
+ function get_node_info() {
508
864
  return __awaiter(this, void 0, void 0, function () {
509
865
  var tag;
510
866
  return __generator(this, function (_a) {
511
867
  tag = TAG + " | get_node_info | ";
512
868
  try {
513
- return [2 /*return*/, true];
869
+ return [2 /*return*/, {
870
+ initialized: Object.keys(BLOCKBOOK_URLS).length > 0,
871
+ availableNodes: Object.keys(BLOCKBOOK_URLS),
872
+ activeWebsockets: Object.keys(BLOCKBOOK_SOCKETS),
873
+ }];
514
874
  }
515
875
  catch (e) {
516
- console.error(tag, e);
876
+ log.error(tag, "Failed to get node info:", e);
517
877
  throw e;
518
878
  }
519
879
  return [2 /*return*/];
520
880
  });
521
881
  });
522
- };
882
+ }