@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.
- package/lib/index.d.ts +177 -17
- package/lib/index.js +654 -294
- 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
|
|
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
|
-
var NOW_NODES_API = process.env['NOW_NODES_API'];
|
|
82
|
+
// Configure automatic retries for transient failures
|
|
57
83
|
axiosRetry(axios, {
|
|
58
|
-
retries: 3,
|
|
84
|
+
retries: 3,
|
|
59
85
|
retryDelay: function (retryCount) {
|
|
60
|
-
log.error(TAG, "
|
|
61
|
-
return retryCount * 2000; //
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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:
|
|
75
|
-
|
|
76
|
-
},
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
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,
|
|
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 =
|
|
148
|
+
tag = TAG + " | init_network | ";
|
|
121
149
|
_a.label = 1;
|
|
122
150
|
case 1:
|
|
123
|
-
_a.trys.push([1,
|
|
124
|
-
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];
|
|
125
155
|
return [4 /*yield*/, nodes.getBlockbooks()];
|
|
126
156
|
case 2:
|
|
127
157
|
SEED_NODES = _a.sent();
|
|
128
|
-
|
|
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)) {
|
|
131
|
-
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"));
|
|
132
180
|
}
|
|
133
181
|
else {
|
|
134
|
-
|
|
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, "
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
152
|
-
log.error(tag, "
|
|
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.
|
|
157
|
-
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"));
|
|
158
217
|
return [2 /*return*/, true];
|
|
159
|
-
case
|
|
218
|
+
case 7:
|
|
160
219
|
e_1 = _a.sent();
|
|
161
|
-
|
|
220
|
+
log.error(tag, "Failed to initialize network:", e_1);
|
|
162
221
|
throw e_1;
|
|
163
|
-
case
|
|
222
|
+
case 8: return [2 /*return*/];
|
|
164
223
|
}
|
|
165
224
|
});
|
|
166
225
|
});
|
|
167
|
-
}
|
|
168
|
-
|
|
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,
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
return [2 /*return*/,
|
|
264
|
+
feeData = resp.data;
|
|
265
|
+
setCache(cacheKey, feeData);
|
|
266
|
+
return [2 /*return*/, feeData];
|
|
197
267
|
case 3:
|
|
198
268
|
e_2 = _a.sent();
|
|
199
|
-
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
295
|
+
_e.label = 1;
|
|
214
296
|
case 1:
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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*/,
|
|
330
|
+
return [4 /*yield*/, fetchPage(params)];
|
|
229
331
|
case 2:
|
|
230
|
-
|
|
231
|
-
log.debug(tag, "
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
236
|
-
|
|
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
|
|
407
|
+
case 12: return [2 /*return*/];
|
|
239
408
|
}
|
|
240
409
|
});
|
|
241
410
|
});
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
-
|
|
271
|
-
|
|
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
|
-
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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 + " |
|
|
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
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
356
|
-
|
|
357
|
-
|
|
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
|
-
if (!
|
|
365
|
-
throw new Error("
|
|
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 =
|
|
368
|
-
log.debug(tag, "Broadcasting to
|
|
369
|
-
|
|
370
|
-
|
|
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
|
-
|
|
654
|
+
_c.label = 2;
|
|
381
655
|
case 2:
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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 (
|
|
392
|
-
|
|
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
|
-
|
|
703
|
+
errorMessage = e_7.message || 'Request setup error';
|
|
396
704
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
}
|
|
427
|
-
|
|
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
|
-
|
|
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
|
-
|
|
433
|
-
|
|
434
|
-
throw
|
|
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
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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 + " |
|
|
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
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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
|
-
|
|
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
|
-
|
|
468
|
-
|
|
469
|
-
throw
|
|
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
|
-
|
|
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,
|
|
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:
|
|
486
|
-
log.debug(tag, "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
|
-
|
|
490
|
-
log.debug(tag, "
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
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
|
-
|
|
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
|
-
|
|
500
|
-
|
|
501
|
-
throw
|
|
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
|
-
|
|
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*/,
|
|
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
|
-
|
|
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
|
+
}
|