@pioneer-platform/blockbook 8.3.15 → 8.3.17
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/.claude/settings.local.json +11 -0
- package/lib/index.d.ts +179 -15
- package/lib/index.js +656 -291
- package/package.json +1 -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,134 +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
|
|
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(
|
|
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 //
|
|
80
|
+
timeout: 30000 // 30 seconds timeout
|
|
54
81
|
});
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
//
|
|
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
|
|
88
|
+
},
|
|
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);
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
// Global storage for Blockbook URLs and WebSocket connections
|
|
70
97
|
var BLOCKBOOK_URLS = {};
|
|
71
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
|
|
72
120
|
module.exports = {
|
|
73
|
-
init:
|
|
74
|
-
|
|
75
|
-
},
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
getTransaction: function (coin, txid) {
|
|
89
|
-
return get_transaction(coin, txid);
|
|
90
|
-
},
|
|
91
|
-
getAddressInfo: function (coin, address, filter) {
|
|
92
|
-
return get_info_by_address(coin, address, filter);
|
|
93
|
-
},
|
|
94
|
-
getPubkeyInfo: function (coin, pubkey, filter) {
|
|
95
|
-
return get_info_by_pubkey(coin, pubkey, filter);
|
|
96
|
-
},
|
|
97
|
-
txidsByAddress: function (coin, address, page) {
|
|
98
|
-
return get_txids_by_address(coin, address, page);
|
|
99
|
-
},
|
|
100
|
-
txsByXpub: function (coin, addresses) {
|
|
101
|
-
return get_txs_by_xpub(coin, addresses);
|
|
102
|
-
},
|
|
103
|
-
utxosByXpub: function (coin, xpub) {
|
|
104
|
-
return get_utxos_by_xpub(coin, xpub);
|
|
105
|
-
},
|
|
106
|
-
getBalanceByXpub: function (coin, xpub) {
|
|
107
|
-
return get_balance_by_xpub(coin, xpub);
|
|
108
|
-
},
|
|
109
|
-
broadcast: function (coin, hex) {
|
|
110
|
-
return broadcast_transaction(coin, hex);
|
|
111
|
-
},
|
|
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,
|
|
112
136
|
};
|
|
113
|
-
|
|
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) {
|
|
114
143
|
return __awaiter(this, void 0, void 0, function () {
|
|
115
|
-
var tag, SEED_NODES, blockbooks,
|
|
144
|
+
var tag, SEED_NODES, allNodes, blockbooks, _i, blockbooks_1, blockbook, symbol, httpUrl, e_1;
|
|
116
145
|
return __generator(this, function (_a) {
|
|
117
146
|
switch (_a.label) {
|
|
118
147
|
case 0:
|
|
119
|
-
tag =
|
|
148
|
+
tag = TAG + " | init_network | ";
|
|
120
149
|
_a.label = 1;
|
|
121
150
|
case 1:
|
|
122
|
-
_a.trys.push([1,
|
|
123
|
-
log.debug(tag, "
|
|
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];
|
|
124
155
|
return [4 /*yield*/, nodes.getBlockbooks()];
|
|
125
156
|
case 2:
|
|
126
157
|
SEED_NODES = _a.sent();
|
|
127
|
-
|
|
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"));
|
|
128
176
|
blockbooks = [];
|
|
129
|
-
if (servers && Array.isArray(servers)) {
|
|
130
|
-
blockbooks = servers.concat(SEED_NODES);
|
|
177
|
+
if (servers && Array.isArray(servers)) {
|
|
178
|
+
blockbooks = servers.concat(SEED_NODES);
|
|
179
|
+
log.info(tag, "Added ".concat(servers.length, " custom servers"));
|
|
131
180
|
}
|
|
132
181
|
else {
|
|
133
|
-
|
|
182
|
+
if (servers) {
|
|
183
|
+
log.warn(tag, "Invalid 'servers' parameter. Expected an array.");
|
|
184
|
+
}
|
|
134
185
|
blockbooks = SEED_NODES;
|
|
135
186
|
}
|
|
136
|
-
log.debug(tag, "
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
+
}
|
|
149
210
|
}
|
|
150
|
-
|
|
151
|
-
log.error(tag, "
|
|
152
|
-
// 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);
|
|
153
213
|
}
|
|
154
214
|
}
|
|
155
|
-
log.
|
|
156
|
-
log.
|
|
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"));
|
|
157
217
|
return [2 /*return*/, true];
|
|
158
|
-
case
|
|
218
|
+
case 7:
|
|
159
219
|
e_1 = _a.sent();
|
|
160
|
-
|
|
220
|
+
log.error(tag, "Failed to initialize network:", e_1);
|
|
161
221
|
throw e_1;
|
|
162
|
-
case
|
|
222
|
+
case 8: return [2 /*return*/];
|
|
163
223
|
}
|
|
164
224
|
});
|
|
165
225
|
});
|
|
166
|
-
}
|
|
167
|
-
|
|
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) {
|
|
168
233
|
return __awaiter(this, void 0, void 0, function () {
|
|
169
|
-
var tag,
|
|
234
|
+
var tag, cacheKey, cached, baseUrl, url, resp, feeData, e_2;
|
|
170
235
|
return __generator(this, function (_a) {
|
|
171
236
|
switch (_a.label) {
|
|
172
237
|
case 0:
|
|
@@ -174,74 +239,254 @@ var get_fees = function (coin) {
|
|
|
174
239
|
_a.label = 1;
|
|
175
240
|
case 1:
|
|
176
241
|
_a.trys.push([1, 3, , 4]);
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
+
})];
|
|
191
262
|
case 2:
|
|
192
263
|
resp = _a.sent();
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
return [2 /*return*/,
|
|
264
|
+
feeData = resp.data;
|
|
265
|
+
setCache(cacheKey, feeData);
|
|
266
|
+
return [2 /*return*/, feeData];
|
|
196
267
|
case 3:
|
|
197
268
|
e_2 = _a.sent();
|
|
198
|
-
|
|
269
|
+
log.error(tag, "Failed to get fees for ".concat(coin, ":"), e_2);
|
|
199
270
|
throw e_2;
|
|
200
271
|
case 4: return [2 /*return*/];
|
|
201
272
|
}
|
|
202
273
|
});
|
|
203
274
|
});
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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) {
|
|
210
293
|
case 0:
|
|
211
294
|
tag = TAG + " | get_info_by_pubkey | ";
|
|
212
|
-
|
|
295
|
+
_e.label = 1;
|
|
213
296
|
case 1:
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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",
|
|
226
329
|
};
|
|
227
|
-
return [4 /*yield*/,
|
|
330
|
+
return [4 /*yield*/, fetchPage(params)];
|
|
228
331
|
case 2:
|
|
229
|
-
|
|
230
|
-
log.debug(tag, "
|
|
231
|
-
|
|
232
|
-
|
|
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)];
|
|
233
343
|
case 3:
|
|
234
|
-
|
|
235
|
-
|
|
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);
|
|
236
406
|
throw e_3;
|
|
237
|
-
case
|
|
407
|
+
case 12: return [2 /*return*/];
|
|
238
408
|
}
|
|
239
409
|
});
|
|
240
410
|
});
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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; }
|
|
245
490
|
return __generator(this, function (_a) {
|
|
246
491
|
switch (_a.label) {
|
|
247
492
|
case 0:
|
|
@@ -249,37 +494,53 @@ var get_txids_by_address = function (coin, address, page) {
|
|
|
249
494
|
_a.label = 1;
|
|
250
495
|
case 1:
|
|
251
496
|
_a.trys.push([1, 3, , 4]);
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
+
})];
|
|
267
516
|
case 2:
|
|
268
517
|
resp = _a.sent();
|
|
269
|
-
|
|
270
|
-
|
|
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];
|
|
271
524
|
case 3:
|
|
272
525
|
e_4 = _a.sent();
|
|
273
|
-
|
|
526
|
+
log.error(tag, "Failed to get txids for address ".concat(address, ":"), e_4);
|
|
274
527
|
throw e_4;
|
|
275
528
|
case 4: return [2 /*return*/];
|
|
276
529
|
}
|
|
277
530
|
});
|
|
278
531
|
});
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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'; }
|
|
283
544
|
return __generator(this, function (_a) {
|
|
284
545
|
switch (_a.label) {
|
|
285
546
|
case 0:
|
|
@@ -287,118 +548,141 @@ var get_info_by_address = function (coin, address, filter) {
|
|
|
287
548
|
_a.label = 1;
|
|
288
549
|
case 1:
|
|
289
550
|
_a.trys.push([1, 3, , 4]);
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
+
})];
|
|
307
569
|
case 2:
|
|
308
570
|
resp = _a.sent();
|
|
309
|
-
//TODO paginate?
|
|
310
571
|
return [2 /*return*/, resp.data];
|
|
311
572
|
case 3:
|
|
312
573
|
e_5 = _a.sent();
|
|
313
|
-
|
|
574
|
+
log.error(tag, "Failed to get info for address ".concat(address, ":"), e_5);
|
|
314
575
|
throw e_5;
|
|
315
576
|
case 4: return [2 /*return*/];
|
|
316
577
|
}
|
|
317
578
|
});
|
|
318
579
|
});
|
|
319
|
-
}
|
|
320
|
-
|
|
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) {
|
|
321
588
|
return __awaiter(this, void 0, void 0, function () {
|
|
322
|
-
var tag,
|
|
589
|
+
var tag, baseUrl, url, resp, e_6;
|
|
323
590
|
return __generator(this, function (_a) {
|
|
324
591
|
switch (_a.label) {
|
|
325
592
|
case 0:
|
|
326
|
-
tag = TAG + " |
|
|
593
|
+
tag = TAG + " | get_txs_by_xpub | ";
|
|
327
594
|
_a.label = 1;
|
|
328
595
|
case 1:
|
|
329
596
|
_a.trys.push([1, 3, , 4]);
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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
|
+
})];
|
|
340
616
|
case 2:
|
|
341
617
|
resp = _a.sent();
|
|
342
618
|
return [2 /*return*/, resp.data];
|
|
343
619
|
case 3:
|
|
344
620
|
e_6 = _a.sent();
|
|
345
|
-
|
|
621
|
+
log.error(tag, "Failed to get transactions for xpub:", e_6);
|
|
346
622
|
throw e_6;
|
|
347
623
|
case 4: return [2 /*return*/];
|
|
348
624
|
}
|
|
349
625
|
});
|
|
350
626
|
});
|
|
351
|
-
}
|
|
352
|
-
|
|
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) {
|
|
353
635
|
return __awaiter(this, void 0, void 0, function () {
|
|
354
|
-
var tag,
|
|
355
|
-
var _a;
|
|
356
|
-
return __generator(this, function (
|
|
357
|
-
switch (
|
|
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
|
-
|
|
642
|
+
_c.label = 1;
|
|
361
643
|
case 1:
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
'User-Agent': fakeUa()
|
|
370
|
-
},
|
|
371
|
-
method: 'POST',
|
|
372
|
-
json: false,
|
|
373
|
-
data: data,
|
|
374
|
-
};
|
|
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"));
|
|
648
|
+
}
|
|
649
|
+
url = "".concat(baseUrl, "/api/v2/sendtx/");
|
|
650
|
+
log.debug(tag, "Broadcasting to:", url);
|
|
375
651
|
output = {
|
|
376
652
|
success: false
|
|
377
653
|
};
|
|
378
|
-
|
|
379
|
-
_b.label = 2;
|
|
654
|
+
_c.label = 2;
|
|
380
655
|
case 2:
|
|
381
|
-
|
|
382
|
-
return [4 /*yield*/, axios(
|
|
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
|
+
})];
|
|
383
666
|
case 3:
|
|
384
|
-
resp =
|
|
385
|
-
output.resp = resp;
|
|
667
|
+
resp = _c.sent();
|
|
386
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));
|
|
387
672
|
return [3 /*break*/, 5];
|
|
388
673
|
case 4:
|
|
389
|
-
e_7 =
|
|
390
|
-
log.error(tag, "Broadcast error:
|
|
674
|
+
e_7 = _c.sent();
|
|
675
|
+
log.error(tag, "Broadcast error:", e_7.message || e_7);
|
|
391
676
|
errorMessage = 'Unknown error occurred';
|
|
392
677
|
statusCode = null;
|
|
393
678
|
if (e_7.response) {
|
|
394
679
|
statusCode = e_7.response.status;
|
|
395
|
-
log.debug(tag, "HTTP Status:
|
|
396
|
-
log.debug(tag, "Response headers:
|
|
680
|
+
log.debug(tag, "HTTP Status:", statusCode);
|
|
681
|
+
log.debug(tag, "Response headers:", e_7.response.headers);
|
|
397
682
|
if (e_7.response.data) {
|
|
398
|
-
log.debug(tag, "Response data:
|
|
683
|
+
log.debug(tag, "Response data:", e_7.response.data);
|
|
399
684
|
if (e_7.response.data.error) {
|
|
400
685
|
errorMessage = e_7.response.data.error;
|
|
401
|
-
log.error(tag, "API Error: ", errorMessage);
|
|
402
686
|
}
|
|
403
687
|
else if (typeof e_7.response.data === 'string') {
|
|
404
688
|
errorMessage = e_7.response.data;
|
|
@@ -412,12 +696,10 @@ var broadcast_transaction = function (coin, hex) {
|
|
|
412
696
|
}
|
|
413
697
|
}
|
|
414
698
|
else if (e_7.request) {
|
|
415
|
-
// Request was made but no response received
|
|
416
699
|
errorMessage = 'Network error: No response received';
|
|
417
|
-
log.debug(tag, "Request config:
|
|
700
|
+
log.debug(tag, "Request config:", (_b = e_7.config) === null || _b === void 0 ? void 0 : _b.url);
|
|
418
701
|
}
|
|
419
702
|
else {
|
|
420
|
-
// Something else happened
|
|
421
703
|
errorMessage = e_7.message || 'Request setup error';
|
|
422
704
|
}
|
|
423
705
|
output.error = errorMessage;
|
|
@@ -425,17 +707,23 @@ var broadcast_transaction = function (coin, hex) {
|
|
|
425
707
|
return [3 /*break*/, 5];
|
|
426
708
|
case 5: return [2 /*return*/, output];
|
|
427
709
|
case 6:
|
|
428
|
-
e_8 =
|
|
429
|
-
|
|
710
|
+
e_8 = _c.sent();
|
|
711
|
+
log.error(tag, 'Unexpected error:', e_8);
|
|
430
712
|
throw e_8;
|
|
431
713
|
case 7: return [2 /*return*/];
|
|
432
714
|
}
|
|
433
715
|
});
|
|
434
716
|
});
|
|
435
|
-
}
|
|
436
|
-
|
|
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) {
|
|
437
725
|
return __awaiter(this, void 0, void 0, function () {
|
|
438
|
-
var tag,
|
|
726
|
+
var tag, cacheKey, cached, baseUrl, url, resp, txData, e_9;
|
|
439
727
|
return __generator(this, function (_a) {
|
|
440
728
|
switch (_a.label) {
|
|
441
729
|
case 0:
|
|
@@ -443,67 +731,112 @@ var get_transaction = function (coin, txid) {
|
|
|
443
731
|
_a.label = 1;
|
|
444
732
|
case 1:
|
|
445
733
|
_a.trys.push([1, 3, , 4]);
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
}
|
|
456
|
-
|
|
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
|
+
})];
|
|
457
754
|
case 2:
|
|
458
755
|
resp = _a.sent();
|
|
459
|
-
|
|
756
|
+
txData = resp.data;
|
|
757
|
+
// Cache confirmed transactions
|
|
758
|
+
if (txData.confirmations > 0) {
|
|
759
|
+
setCache(cacheKey, txData);
|
|
760
|
+
}
|
|
761
|
+
return [2 /*return*/, txData];
|
|
460
762
|
case 3:
|
|
461
763
|
e_9 = _a.sent();
|
|
462
|
-
|
|
764
|
+
log.error(tag, "Failed to get transaction ".concat(txid, ":"), e_9);
|
|
463
765
|
throw e_9;
|
|
464
766
|
case 4: return [2 /*return*/];
|
|
465
767
|
}
|
|
466
768
|
});
|
|
467
769
|
});
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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; }
|
|
472
782
|
return __generator(this, function (_a) {
|
|
473
783
|
switch (_a.label) {
|
|
474
784
|
case 0:
|
|
475
|
-
tag = TAG + " |
|
|
785
|
+
tag = TAG + " | get_utxos_by_xpub | ";
|
|
476
786
|
_a.label = 1;
|
|
477
787
|
case 1:
|
|
478
788
|
_a.trys.push([1, 3, , 4]);
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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(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
|
+
})];
|
|
492
806
|
case 2:
|
|
493
807
|
resp = _a.sent();
|
|
494
|
-
|
|
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));
|
|
819
|
+
}
|
|
820
|
+
log.info(tag, "Found ".concat(utxos.length, " UTXOs for xpub"));
|
|
821
|
+
return [2 /*return*/, utxos];
|
|
495
822
|
case 3:
|
|
496
823
|
e_10 = _a.sent();
|
|
497
|
-
|
|
824
|
+
log.error(tag, "Failed to get UTXOs for xpub:", e_10);
|
|
498
825
|
throw e_10;
|
|
499
826
|
case 4: return [2 /*return*/];
|
|
500
827
|
}
|
|
501
828
|
});
|
|
502
829
|
});
|
|
503
|
-
}
|
|
504
|
-
|
|
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) {
|
|
505
838
|
return __awaiter(this, void 0, void 0, function () {
|
|
506
|
-
var tag,
|
|
839
|
+
var tag, utxos, balanceSatoshis, _i, utxos_1, utxo, value, balanceBTC, e_11;
|
|
507
840
|
return __generator(this, function (_a) {
|
|
508
841
|
switch (_a.label) {
|
|
509
842
|
case 0:
|
|
@@ -511,41 +844,73 @@ var get_balance_by_xpub = function (coin, xpub) {
|
|
|
511
844
|
_a.label = 1;
|
|
512
845
|
case 1:
|
|
513
846
|
_a.trys.push([1, 3, , 4]);
|
|
514
|
-
log.debug(tag, "coin:
|
|
515
|
-
log.debug(tag, "xpub:
|
|
516
|
-
|
|
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)];
|
|
517
854
|
case 2:
|
|
518
|
-
|
|
519
|
-
log.debug(tag, "
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
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");
|
|
861
|
+
}
|
|
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
|
+
}
|
|
525
876
|
}
|
|
526
|
-
|
|
877
|
+
balanceBTC = balanceSatoshis / 100000000;
|
|
878
|
+
log.info(tag, "Total balance: ".concat(balanceBTC, " BTC (").concat(balanceSatoshis, " satoshis)"));
|
|
879
|
+
return [2 /*return*/, balanceBTC];
|
|
527
880
|
case 3:
|
|
528
881
|
e_11 = _a.sent();
|
|
529
|
-
|
|
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
|
+
}
|
|
530
887
|
throw e_11;
|
|
531
888
|
case 4: return [2 /*return*/];
|
|
532
889
|
}
|
|
533
890
|
});
|
|
534
891
|
});
|
|
535
|
-
}
|
|
536
|
-
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* Get information about the Blockbook nodes
|
|
895
|
+
* @returns Promise with node status information
|
|
896
|
+
*/
|
|
897
|
+
function get_node_info() {
|
|
537
898
|
return __awaiter(this, void 0, void 0, function () {
|
|
538
899
|
var tag;
|
|
539
900
|
return __generator(this, function (_a) {
|
|
540
901
|
tag = TAG + " | get_node_info | ";
|
|
541
902
|
try {
|
|
542
|
-
return [2 /*return*/,
|
|
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
|
+
}];
|
|
543
908
|
}
|
|
544
909
|
catch (e) {
|
|
545
|
-
|
|
910
|
+
log.error(tag, "Failed to get node info:", e);
|
|
546
911
|
throw e;
|
|
547
912
|
}
|
|
548
913
|
return [2 /*return*/];
|
|
549
914
|
});
|
|
550
915
|
});
|
|
551
|
-
}
|
|
916
|
+
}
|