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