@pioneer-platform/blockbook 8.3.16 → 8.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +11 -0
- package/CHANGELOG.md +14 -0
- package/lib/index.d.ts +16 -179
- package/lib/index.js +374 -629
- package/package.json +12 -12
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,163 @@ 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
46
|
var Axios = require('axios');
|
|
70
47
|
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
|
|
48
|
+
var nodes = require("@pioneer-platform/nodes");
|
|
76
49
|
var axios = Axios.create({
|
|
77
50
|
httpsAgent: new https.Agent({
|
|
78
51
|
rejectUnauthorized: false
|
|
79
52
|
}),
|
|
80
|
-
timeout: 30000 //
|
|
81
|
-
});
|
|
82
|
-
// Configure automatic retries for transient failures
|
|
83
|
-
axiosRetry(axios, {
|
|
84
|
-
retries: 3,
|
|
85
|
-
retryDelay: function (retryCount) {
|
|
86
|
-
log.error(TAG, "Retry attempt: ".concat(retryCount));
|
|
87
|
-
return retryCount * 2000; // Exponential backoff
|
|
88
|
-
},
|
|
89
|
-
retryCondition: function (error) {
|
|
90
|
-
var _a;
|
|
91
|
-
// Retry on network errors or 5xx status codes
|
|
92
|
-
return axiosRetry.isNetworkOrIdempotentRequestError(error) ||
|
|
93
|
-
(((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) ? error.response.status >= 500 : false);
|
|
94
|
-
},
|
|
53
|
+
timeout: 30000 // 10 seconds
|
|
95
54
|
});
|
|
96
|
-
//
|
|
55
|
+
// const axiosRetry = require('axios-retry');
|
|
56
|
+
var NOW_NODES_API = process.env['NOW_NODES_API'];
|
|
57
|
+
// axiosRetry(axios, {
|
|
58
|
+
// retries: 3, // number of retries
|
|
59
|
+
// retryDelay: (retryCount: number) => {
|
|
60
|
+
// log.error(TAG,`retry attempt: ${retryCount}`);
|
|
61
|
+
// return retryCount * 2000; // time interval between retries
|
|
62
|
+
// },
|
|
63
|
+
// retryCondition: (error: { response: { status: number; }; }) => {
|
|
64
|
+
// log.error(TAG,error)
|
|
65
|
+
// //@TODO mark node offline, and punish
|
|
66
|
+
// // if retry condition is not specified, by default idempotent requests are retried
|
|
67
|
+
// return error?.response?.status === 503;
|
|
68
|
+
// },
|
|
69
|
+
// });
|
|
97
70
|
var BLOCKBOOK_URLS = {};
|
|
98
71
|
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
|
|
120
72
|
module.exports = {
|
|
121
|
-
init:
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
73
|
+
init: function (servers) {
|
|
74
|
+
return init_network(servers);
|
|
75
|
+
},
|
|
76
|
+
addNode: function (coin, url) {
|
|
77
|
+
return add_custom_node(coin, url);
|
|
78
|
+
},
|
|
79
|
+
getInfo: function () {
|
|
80
|
+
return get_node_info();
|
|
81
|
+
},
|
|
82
|
+
getBlockbooks: function () {
|
|
83
|
+
return BLOCKBOOK_URLS;
|
|
84
|
+
},
|
|
85
|
+
getBlockbookSockets: function () {
|
|
86
|
+
return BLOCKBOOK_SOCKETS;
|
|
87
|
+
},
|
|
88
|
+
getFees: function (coin) {
|
|
89
|
+
return get_fees(coin);
|
|
90
|
+
},
|
|
91
|
+
getTransaction: function (coin, txid) {
|
|
92
|
+
return get_transaction(coin, txid);
|
|
93
|
+
},
|
|
94
|
+
getAddressInfo: function (coin, address, filter) {
|
|
95
|
+
return get_info_by_address(coin, address, filter);
|
|
96
|
+
},
|
|
97
|
+
getPubkeyInfo: function (coin, pubkey, filter) {
|
|
98
|
+
return get_info_by_pubkey(coin, pubkey, filter);
|
|
99
|
+
},
|
|
100
|
+
txidsByAddress: function (coin, address, page) {
|
|
101
|
+
return get_txids_by_address(coin, address, page);
|
|
102
|
+
},
|
|
103
|
+
txsByXpub: function (coin, addresses) {
|
|
104
|
+
return get_txs_by_xpub(coin, addresses);
|
|
105
|
+
},
|
|
106
|
+
utxosByXpub: function (coin, xpub) {
|
|
107
|
+
return get_utxos_by_xpub(coin, xpub);
|
|
108
|
+
},
|
|
109
|
+
getBalanceByXpub: function (coin, xpub) {
|
|
110
|
+
return get_balance_by_xpub(coin, xpub);
|
|
111
|
+
},
|
|
112
|
+
broadcast: function (coin, hex) {
|
|
113
|
+
return broadcast_transaction(coin, hex);
|
|
114
|
+
},
|
|
136
115
|
};
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
116
|
+
// Add a custom node at runtime
|
|
117
|
+
var add_custom_node = function (coin, url) {
|
|
118
|
+
var symbol = coin.toUpperCase();
|
|
119
|
+
var cleanUrl = url.replace(/\/$/, ''); // Remove trailing slash
|
|
120
|
+
// Store URLs as arrays if not already
|
|
121
|
+
if (typeof BLOCKBOOK_URLS[symbol] === 'string') {
|
|
122
|
+
BLOCKBOOK_URLS[symbol] = [BLOCKBOOK_URLS[symbol]];
|
|
123
|
+
}
|
|
124
|
+
if (!BLOCKBOOK_URLS[symbol]) {
|
|
125
|
+
BLOCKBOOK_URLS[symbol] = [];
|
|
126
|
+
}
|
|
127
|
+
// Check for duplicates
|
|
128
|
+
if (Array.isArray(BLOCKBOOK_URLS[symbol]) && BLOCKBOOK_URLS[symbol].includes(cleanUrl)) {
|
|
129
|
+
log.warn("Node ".concat(cleanUrl, " already exists for ").concat(symbol));
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
if (Array.isArray(BLOCKBOOK_URLS[symbol])) {
|
|
133
|
+
BLOCKBOOK_URLS[symbol].push(cleanUrl);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
BLOCKBOOK_URLS[symbol] = [BLOCKBOOK_URLS[symbol], cleanUrl];
|
|
137
|
+
}
|
|
138
|
+
log.info("Added custom node for ".concat(symbol, ": ").concat(cleanUrl));
|
|
139
|
+
log.info("".concat(symbol, " now has ").concat(Array.isArray(BLOCKBOOK_URLS[symbol]) ? BLOCKBOOK_URLS[symbol].length : 1, " node(s)"));
|
|
140
|
+
return true;
|
|
141
|
+
};
|
|
142
|
+
var init_network = function (servers) {
|
|
143
143
|
return __awaiter(this, void 0, void 0, function () {
|
|
144
|
-
var tag, SEED_NODES,
|
|
144
|
+
var tag, SEED_NODES, blockbooks, i, blockbook, url, e_1;
|
|
145
145
|
return __generator(this, function (_a) {
|
|
146
146
|
switch (_a.label) {
|
|
147
147
|
case 0:
|
|
148
|
-
tag =
|
|
148
|
+
tag = ' | get_txs_by_address | ';
|
|
149
149
|
_a.label = 1;
|
|
150
150
|
case 1:
|
|
151
|
-
_a.trys.push([1,
|
|
152
|
-
log.debug(tag, "
|
|
153
|
-
SEED_NODES = [];
|
|
154
|
-
if (!(typeof nodes.getBlockbooks === 'function')) return [3 /*break*/, 3];
|
|
151
|
+
_a.trys.push([1, 3, , 4]);
|
|
152
|
+
log.debug(tag, "checkpoint: ");
|
|
155
153
|
return [4 /*yield*/, nodes.getBlockbooks()];
|
|
156
154
|
case 2:
|
|
157
155
|
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"));
|
|
156
|
+
log.info(tag, "SEED_NODES: ", SEED_NODES);
|
|
176
157
|
blockbooks = [];
|
|
177
|
-
if (servers && Array.isArray(servers)) {
|
|
178
|
-
blockbooks = servers.concat(SEED_NODES);
|
|
179
|
-
log.info(tag, "Added ".concat(servers.length, " custom servers"));
|
|
158
|
+
if (servers && Array.isArray(servers)) { // Type checking for array
|
|
159
|
+
blockbooks = servers.concat(SEED_NODES); // Combine arrays
|
|
180
160
|
}
|
|
181
161
|
else {
|
|
182
|
-
|
|
183
|
-
log.warn(tag, "Invalid 'servers' parameter. Expected an array.");
|
|
184
|
-
}
|
|
162
|
+
console.error("Invalid 'servers' parameter. Expected an array.");
|
|
185
163
|
blockbooks = SEED_NODES;
|
|
186
164
|
}
|
|
187
|
-
log.debug(tag, "
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
}
|
|
165
|
+
log.debug(tag, "blockbooks: ", blockbooks.length);
|
|
166
|
+
for (i = 0; i < blockbooks.length; i++) {
|
|
167
|
+
blockbook = blockbooks[i];
|
|
168
|
+
//get swagger
|
|
169
|
+
if (blockbook && blockbook.service)
|
|
170
|
+
BLOCKBOOK_URLS[blockbook.symbol.toUpperCase()] = blockbook.service;
|
|
171
|
+
if (blockbook && blockbook.websocket) {
|
|
172
|
+
url = blockbook.websocket.replace("/websocket", "");
|
|
173
|
+
url = blockbook.websocket.replace("wss://", "https://");
|
|
174
|
+
BLOCKBOOK_SOCKETS[blockbook.symbol.toUpperCase()] = new Blockbook({
|
|
175
|
+
nodes: [url],
|
|
176
|
+
disableTypeValidation: true,
|
|
177
|
+
});
|
|
210
178
|
}
|
|
211
|
-
|
|
212
|
-
log.error(tag, "
|
|
179
|
+
else {
|
|
180
|
+
log.error(tag, "invalid unchained service: ", blockbook);
|
|
181
|
+
// throw Error("invalid unchained service!")
|
|
213
182
|
}
|
|
214
183
|
}
|
|
215
|
-
log.
|
|
216
|
-
log.
|
|
184
|
+
log.debug(tag, "BLOCKBOOK_URLS: ", BLOCKBOOK_URLS);
|
|
185
|
+
log.debug(tag, "BLOCKBOOK_SOCKETS: ", BLOCKBOOK_SOCKETS);
|
|
217
186
|
return [2 /*return*/, true];
|
|
218
|
-
case
|
|
187
|
+
case 3:
|
|
219
188
|
e_1 = _a.sent();
|
|
220
|
-
|
|
189
|
+
// console.error(tag, 'Error: ', e)
|
|
221
190
|
throw e_1;
|
|
222
|
-
case
|
|
191
|
+
case 4: return [2 /*return*/];
|
|
223
192
|
}
|
|
224
193
|
});
|
|
225
194
|
});
|
|
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) {
|
|
195
|
+
};
|
|
196
|
+
var get_fees = function (coin) {
|
|
233
197
|
return __awaiter(this, void 0, void 0, function () {
|
|
234
|
-
var tag,
|
|
198
|
+
var tag, url, body, resp, e_2;
|
|
235
199
|
return __generator(this, function (_a) {
|
|
236
200
|
switch (_a.label) {
|
|
237
201
|
case 0:
|
|
@@ -239,254 +203,74 @@ function get_fees(coin) {
|
|
|
239
203
|
_a.label = 1;
|
|
240
204
|
case 1:
|
|
241
205
|
_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
|
-
})];
|
|
206
|
+
url = BLOCKBOOK_URLS[coin.toUpperCase()] + "/api/v2/fees";
|
|
207
|
+
log.debug(tag, "url: ", url);
|
|
208
|
+
body = {
|
|
209
|
+
method: 'GET',
|
|
210
|
+
url: url,
|
|
211
|
+
headers: {
|
|
212
|
+
'content-type': 'application/json',
|
|
213
|
+
'User-Agent': fakeUa()
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
return [4 /*yield*/, axios(body)
|
|
217
|
+
//log.debug(tag,"resp: ",resp)
|
|
218
|
+
//TODO paginate?
|
|
219
|
+
];
|
|
262
220
|
case 2:
|
|
263
221
|
resp = _a.sent();
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
return [2 /*return*/,
|
|
222
|
+
//log.debug(tag,"resp: ",resp)
|
|
223
|
+
//TODO paginate?
|
|
224
|
+
return [2 /*return*/, resp.data];
|
|
267
225
|
case 3:
|
|
268
226
|
e_2 = _a.sent();
|
|
269
|
-
|
|
227
|
+
console.error(tag, e_2);
|
|
270
228
|
throw e_2;
|
|
271
229
|
case 4: return [2 /*return*/];
|
|
272
230
|
}
|
|
273
231
|
});
|
|
274
232
|
});
|
|
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) {
|
|
233
|
+
};
|
|
234
|
+
var get_info_by_pubkey = function (coin, pubkey, page) {
|
|
235
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
236
|
+
var tag, url, body, resp, e_3;
|
|
237
|
+
return __generator(this, function (_a) {
|
|
238
|
+
switch (_a.label) {
|
|
293
239
|
case 0:
|
|
294
240
|
tag = TAG + " | get_info_by_pubkey | ";
|
|
295
|
-
|
|
241
|
+
_a.label = 1;
|
|
296
242
|
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",
|
|
243
|
+
_a.trys.push([1, 3, , 4]);
|
|
244
|
+
if (!page)
|
|
245
|
+
page = "1";
|
|
246
|
+
url = BLOCKBOOK_URLS[coin.toUpperCase()] + "/api/v2/xpub/" + pubkey;
|
|
247
|
+
log.debug(tag, "url: ", url);
|
|
248
|
+
body = {
|
|
249
|
+
method: 'GET',
|
|
250
|
+
url: url,
|
|
251
|
+
headers: {
|
|
252
|
+
'content-type': 'application/json',
|
|
253
|
+
'User-Agent': fakeUa()
|
|
254
|
+
},
|
|
329
255
|
};
|
|
330
|
-
return [4 /*yield*/,
|
|
256
|
+
return [4 /*yield*/, axios(body)];
|
|
331
257
|
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)];
|
|
258
|
+
resp = _a.sent();
|
|
259
|
+
log.debug(tag, "resp: ", resp);
|
|
260
|
+
//TODO paginate?
|
|
261
|
+
return [2 /*return*/, resp.data];
|
|
343
262
|
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);
|
|
263
|
+
e_3 = _a.sent();
|
|
264
|
+
console.error(tag, e_3);
|
|
406
265
|
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 : [])];
|
|
266
|
+
case 4: return [2 /*return*/];
|
|
475
267
|
}
|
|
476
268
|
});
|
|
477
269
|
});
|
|
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; }
|
|
270
|
+
};
|
|
271
|
+
var get_txids_by_address = function (coin, address, page) {
|
|
272
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
273
|
+
var tag, url, body, resp, e_4;
|
|
490
274
|
return __generator(this, function (_a) {
|
|
491
275
|
switch (_a.label) {
|
|
492
276
|
case 0:
|
|
@@ -494,53 +278,37 @@ function get_txids_by_address(coin_1, address_1) {
|
|
|
494
278
|
_a.label = 1;
|
|
495
279
|
case 1:
|
|
496
280
|
_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
|
-
})];
|
|
281
|
+
if (!page)
|
|
282
|
+
page = 1;
|
|
283
|
+
url = BLOCKBOOK_URLS[coin.toUpperCase()] + "/api/v2/address/" + address + "?page=" + page + "&details=all";
|
|
284
|
+
log.debug(tag, "url: ", url);
|
|
285
|
+
body = {
|
|
286
|
+
method: 'GET',
|
|
287
|
+
url: url,
|
|
288
|
+
headers: {
|
|
289
|
+
'content-type': 'application/json',
|
|
290
|
+
'User-Agent': fakeUa()
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
return [4 /*yield*/, axios(body)
|
|
294
|
+
//TODO paginate?
|
|
295
|
+
];
|
|
516
296
|
case 2:
|
|
517
297
|
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];
|
|
298
|
+
//TODO paginate?
|
|
299
|
+
return [2 /*return*/, resp.data];
|
|
524
300
|
case 3:
|
|
525
301
|
e_4 = _a.sent();
|
|
526
|
-
|
|
302
|
+
console.error(tag, e_4);
|
|
527
303
|
throw e_4;
|
|
528
304
|
case 4: return [2 /*return*/];
|
|
529
305
|
}
|
|
530
306
|
});
|
|
531
307
|
});
|
|
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'; }
|
|
308
|
+
};
|
|
309
|
+
var get_info_by_address = function (coin, address, filter) {
|
|
310
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
311
|
+
var tag, url, body, resp, e_5;
|
|
544
312
|
return __generator(this, function (_a) {
|
|
545
313
|
switch (_a.label) {
|
|
546
314
|
case 0:
|
|
@@ -548,141 +316,118 @@ function get_info_by_address(coin_1, address_1) {
|
|
|
548
316
|
_a.label = 1;
|
|
549
317
|
case 1:
|
|
550
318
|
_a.trys.push([1, 3, , 4]);
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
})];
|
|
319
|
+
if (!filter)
|
|
320
|
+
filter = "all";
|
|
321
|
+
//let url = ETH_BLOCKBOOK_URL+"/api/v2/address/"+address+"?="+filter
|
|
322
|
+
if (!BLOCKBOOK_URLS[coin.toUpperCase()])
|
|
323
|
+
throw Error("invalid coin: " + coin);
|
|
324
|
+
url = BLOCKBOOK_URLS[coin.toUpperCase()] + "/api/v2/address/" + address + "?details=all";
|
|
325
|
+
body = {
|
|
326
|
+
method: 'GET',
|
|
327
|
+
url: url,
|
|
328
|
+
headers: {
|
|
329
|
+
'content-type': 'application/json',
|
|
330
|
+
'User-Agent': fakeUa()
|
|
331
|
+
},
|
|
332
|
+
};
|
|
333
|
+
return [4 /*yield*/, axios(body)
|
|
334
|
+
//TODO paginate?
|
|
335
|
+
];
|
|
569
336
|
case 2:
|
|
570
337
|
resp = _a.sent();
|
|
338
|
+
//TODO paginate?
|
|
571
339
|
return [2 /*return*/, resp.data];
|
|
572
340
|
case 3:
|
|
573
341
|
e_5 = _a.sent();
|
|
574
|
-
|
|
342
|
+
console.error(tag, e_5);
|
|
575
343
|
throw e_5;
|
|
576
344
|
case 4: return [2 /*return*/];
|
|
577
345
|
}
|
|
578
346
|
});
|
|
579
347
|
});
|
|
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) {
|
|
348
|
+
};
|
|
349
|
+
var get_txs_by_xpub = function (coin, xpub) {
|
|
588
350
|
return __awaiter(this, void 0, void 0, function () {
|
|
589
|
-
var tag,
|
|
351
|
+
var tag, url, body, resp, e_6;
|
|
590
352
|
return __generator(this, function (_a) {
|
|
591
353
|
switch (_a.label) {
|
|
592
354
|
case 0:
|
|
593
|
-
tag = TAG + " | get_txs_by_xpub | ";
|
|
355
|
+
tag = TAG + " | FA get_txs_by_xpub | ";
|
|
594
356
|
_a.label = 1;
|
|
595
357
|
case 1:
|
|
596
358
|
_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
|
-
})];
|
|
359
|
+
url = BLOCKBOOK_URLS[coin.toUpperCase()] + "/api/v2/xpub/" + xpub + "?details=all";
|
|
360
|
+
body = {
|
|
361
|
+
method: 'GET',
|
|
362
|
+
url: url,
|
|
363
|
+
headers: {
|
|
364
|
+
'content-type': 'application/json',
|
|
365
|
+
'User-Agent': fakeUa()
|
|
366
|
+
},
|
|
367
|
+
};
|
|
368
|
+
return [4 /*yield*/, axios(body)];
|
|
616
369
|
case 2:
|
|
617
370
|
resp = _a.sent();
|
|
618
371
|
return [2 /*return*/, resp.data];
|
|
619
372
|
case 3:
|
|
620
373
|
e_6 = _a.sent();
|
|
621
|
-
|
|
374
|
+
console.error(tag, e_6);
|
|
622
375
|
throw e_6;
|
|
623
376
|
case 4: return [2 /*return*/];
|
|
624
377
|
}
|
|
625
378
|
});
|
|
626
379
|
});
|
|
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) {
|
|
380
|
+
};
|
|
381
|
+
var broadcast_transaction = function (coin, hex) {
|
|
635
382
|
return __awaiter(this, void 0, void 0, function () {
|
|
636
|
-
var tag,
|
|
637
|
-
var _a
|
|
638
|
-
return __generator(this, function (
|
|
639
|
-
switch (
|
|
383
|
+
var tag, url, data, body, output, resp, e_7, errorMessage, statusCode, e_8;
|
|
384
|
+
var _a;
|
|
385
|
+
return __generator(this, function (_b) {
|
|
386
|
+
switch (_b.label) {
|
|
640
387
|
case 0:
|
|
641
388
|
tag = TAG + " | broadcast_transaction | ";
|
|
642
|
-
|
|
389
|
+
_b.label = 1;
|
|
643
390
|
case 1:
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
391
|
+
_b.trys.push([1, 6, , 7]);
|
|
392
|
+
url = BLOCKBOOK_URLS[coin.toUpperCase()] + "/api/v2/sendtx/";
|
|
393
|
+
data = hex;
|
|
394
|
+
body = {
|
|
395
|
+
url: url,
|
|
396
|
+
headers: {
|
|
397
|
+
'content-type': 'application/json',
|
|
398
|
+
'User-Agent': fakeUa()
|
|
399
|
+
},
|
|
400
|
+
method: 'POST',
|
|
401
|
+
json: false,
|
|
402
|
+
data: data,
|
|
403
|
+
};
|
|
651
404
|
output = {
|
|
652
405
|
success: false
|
|
653
406
|
};
|
|
654
|
-
|
|
407
|
+
resp = void 0;
|
|
408
|
+
_b.label = 2;
|
|
655
409
|
case 2:
|
|
656
|
-
|
|
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
|
-
})];
|
|
410
|
+
_b.trys.push([2, 4, , 5]);
|
|
411
|
+
return [4 /*yield*/, axios(body)];
|
|
666
412
|
case 3:
|
|
667
|
-
resp =
|
|
413
|
+
resp = _b.sent();
|
|
414
|
+
output.resp = resp;
|
|
668
415
|
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
416
|
return [3 /*break*/, 5];
|
|
673
417
|
case 4:
|
|
674
|
-
e_7 =
|
|
675
|
-
log.error(tag, "Broadcast error:", e_7.message || e_7);
|
|
418
|
+
e_7 = _b.sent();
|
|
419
|
+
log.error(tag, "Broadcast error: ", e_7.message || e_7);
|
|
676
420
|
errorMessage = 'Unknown error occurred';
|
|
677
421
|
statusCode = null;
|
|
678
422
|
if (e_7.response) {
|
|
679
423
|
statusCode = e_7.response.status;
|
|
680
|
-
log.debug(tag, "HTTP Status:", statusCode);
|
|
681
|
-
log.debug(tag, "Response headers:", e_7.response.headers);
|
|
424
|
+
log.debug(tag, "HTTP Status: ", statusCode);
|
|
425
|
+
log.debug(tag, "Response headers: ", e_7.response.headers);
|
|
682
426
|
if (e_7.response.data) {
|
|
683
|
-
log.debug(tag, "Response data:", e_7.response.data);
|
|
427
|
+
log.debug(tag, "Response data: ", e_7.response.data);
|
|
684
428
|
if (e_7.response.data.error) {
|
|
685
429
|
errorMessage = e_7.response.data.error;
|
|
430
|
+
log.error(tag, "API Error: ", errorMessage);
|
|
686
431
|
}
|
|
687
432
|
else if (typeof e_7.response.data === 'string') {
|
|
688
433
|
errorMessage = e_7.response.data;
|
|
@@ -696,10 +441,12 @@ function broadcast_transaction(coin, hex) {
|
|
|
696
441
|
}
|
|
697
442
|
}
|
|
698
443
|
else if (e_7.request) {
|
|
444
|
+
// Request was made but no response received
|
|
699
445
|
errorMessage = 'Network error: No response received';
|
|
700
|
-
log.debug(tag, "Request config:", (
|
|
446
|
+
log.debug(tag, "Request config: ", (_a = e_7.config) === null || _a === void 0 ? void 0 : _a.url);
|
|
701
447
|
}
|
|
702
448
|
else {
|
|
449
|
+
// Something else happened
|
|
703
450
|
errorMessage = e_7.message || 'Request setup error';
|
|
704
451
|
}
|
|
705
452
|
output.error = errorMessage;
|
|
@@ -707,23 +454,17 @@ function broadcast_transaction(coin, hex) {
|
|
|
707
454
|
return [3 /*break*/, 5];
|
|
708
455
|
case 5: return [2 /*return*/, output];
|
|
709
456
|
case 6:
|
|
710
|
-
e_8 =
|
|
711
|
-
|
|
457
|
+
e_8 = _b.sent();
|
|
458
|
+
console.error(tag, 'error: ', e_8);
|
|
712
459
|
throw e_8;
|
|
713
460
|
case 7: return [2 /*return*/];
|
|
714
461
|
}
|
|
715
462
|
});
|
|
716
463
|
});
|
|
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) {
|
|
464
|
+
};
|
|
465
|
+
var get_transaction = function (coin, txid) {
|
|
725
466
|
return __awaiter(this, void 0, void 0, function () {
|
|
726
|
-
var tag,
|
|
467
|
+
var tag, url, body, resp, e_9;
|
|
727
468
|
return __generator(this, function (_a) {
|
|
728
469
|
switch (_a.label) {
|
|
729
470
|
case 0:
|
|
@@ -731,98 +472,114 @@ function get_transaction(coin, txid) {
|
|
|
731
472
|
_a.label = 1;
|
|
732
473
|
case 1:
|
|
733
474
|
_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
|
-
})];
|
|
475
|
+
url = BLOCKBOOK_URLS[coin.toUpperCase()] + "/api/v2/tx/" + txid;
|
|
476
|
+
console.log("url: ", url);
|
|
477
|
+
body = {
|
|
478
|
+
method: 'GET',
|
|
479
|
+
url: url,
|
|
480
|
+
headers: {
|
|
481
|
+
'content-type': 'application/json',
|
|
482
|
+
'User-Agent': fakeUa()
|
|
483
|
+
},
|
|
484
|
+
};
|
|
485
|
+
return [4 /*yield*/, axios(body)];
|
|
754
486
|
case 2:
|
|
755
487
|
resp = _a.sent();
|
|
756
|
-
|
|
757
|
-
// Cache confirmed transactions
|
|
758
|
-
if (txData.confirmations > 0) {
|
|
759
|
-
setCache(cacheKey, txData);
|
|
760
|
-
}
|
|
761
|
-
return [2 /*return*/, txData];
|
|
488
|
+
return [2 /*return*/, resp.data];
|
|
762
489
|
case 3:
|
|
763
490
|
e_9 = _a.sent();
|
|
764
|
-
|
|
491
|
+
console.error(tag, e_9);
|
|
765
492
|
throw e_9;
|
|
766
493
|
case 4: return [2 /*return*/];
|
|
767
494
|
}
|
|
768
495
|
});
|
|
769
496
|
});
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
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; }
|
|
782
|
-
return __generator(this, function (_a) {
|
|
783
|
-
switch (_a.label) {
|
|
497
|
+
};
|
|
498
|
+
var get_utxos_by_xpub = function (coin, xpub) {
|
|
499
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
500
|
+
var tag, baseUrls, results, allUtxos, i, result, utxos, seen, uniqueUtxos, _i, allUtxos_1, utxo, key, url, body, resp, e_10;
|
|
501
|
+
var _this = this;
|
|
502
|
+
var _a;
|
|
503
|
+
return __generator(this, function (_b) {
|
|
504
|
+
switch (_b.label) {
|
|
784
505
|
case 0:
|
|
785
|
-
tag = TAG + " | get_utxos_by_xpub | ";
|
|
786
|
-
|
|
506
|
+
tag = TAG + " | FA get_utxos_by_xpub | ";
|
|
507
|
+
_b.label = 1;
|
|
787
508
|
case 1:
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
509
|
+
_b.trys.push([1, 6, , 7]);
|
|
510
|
+
log.info(tag, "get_utxos_by_xpub: ", BLOCKBOOK_URLS);
|
|
511
|
+
baseUrls = BLOCKBOOK_URLS[coin.toUpperCase()];
|
|
512
|
+
if (!Array.isArray(baseUrls)) return [3 /*break*/, 3];
|
|
513
|
+
// Multiple nodes - query each and merge results
|
|
514
|
+
log.info(tag, "Querying ".concat(baseUrls.length, " nodes for ").concat(coin, "..."));
|
|
515
|
+
return [4 /*yield*/, Promise.allSettled(baseUrls.map(function (baseUrl) { return __awaiter(_this, void 0, void 0, function () {
|
|
516
|
+
var url, body, resp;
|
|
517
|
+
return __generator(this, function (_a) {
|
|
518
|
+
switch (_a.label) {
|
|
519
|
+
case 0:
|
|
520
|
+
url = baseUrl + "/api/v2/utxo/" + xpub + "?confirmed=false";
|
|
521
|
+
console.log("Querying node:", url);
|
|
522
|
+
body = {
|
|
523
|
+
method: 'GET',
|
|
524
|
+
url: url,
|
|
525
|
+
};
|
|
526
|
+
return [4 /*yield*/, axios(body)];
|
|
527
|
+
case 1:
|
|
528
|
+
resp = _a.sent();
|
|
529
|
+
return [2 /*return*/, resp.data];
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
}); }))];
|
|
803
533
|
case 2:
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
534
|
+
results = _b.sent();
|
|
535
|
+
allUtxos = [];
|
|
536
|
+
for (i = 0; i < results.length; i++) {
|
|
537
|
+
result = results[i];
|
|
538
|
+
if (result.status === 'fulfilled') {
|
|
539
|
+
utxos = result.value;
|
|
540
|
+
log.info(tag, "Node ".concat(i + 1, " returned ").concat(utxos.length, " UTXOs"));
|
|
541
|
+
allUtxos.push.apply(allUtxos, utxos);
|
|
542
|
+
}
|
|
543
|
+
else {
|
|
544
|
+
log.warn(tag, "Node ".concat(i + 1, " failed:"), (_a = result.reason) === null || _a === void 0 ? void 0 : _a.message);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
seen = new Set();
|
|
548
|
+
uniqueUtxos = [];
|
|
549
|
+
for (_i = 0, allUtxos_1 = allUtxos; _i < allUtxos_1.length; _i++) {
|
|
550
|
+
utxo = allUtxos_1[_i];
|
|
551
|
+
key = "".concat(utxo.txid, ":").concat(utxo.vout);
|
|
552
|
+
if (!seen.has(key)) {
|
|
553
|
+
seen.add(key);
|
|
554
|
+
uniqueUtxos.push(utxo);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
log.info(tag, "Returning ".concat(uniqueUtxos.length, " unique UTXOs from ").concat(allUtxos.length, " total"));
|
|
558
|
+
return [2 /*return*/, uniqueUtxos];
|
|
808
559
|
case 3:
|
|
809
|
-
|
|
810
|
-
log
|
|
560
|
+
url = baseUrls + "/api/v2/utxo/" + xpub + "?confirmed=false";
|
|
561
|
+
console.log("url:", url);
|
|
562
|
+
body = {
|
|
563
|
+
method: 'GET',
|
|
564
|
+
url: url,
|
|
565
|
+
};
|
|
566
|
+
return [4 /*yield*/, axios(body)];
|
|
567
|
+
case 4:
|
|
568
|
+
resp = _b.sent();
|
|
569
|
+
return [2 /*return*/, resp.data];
|
|
570
|
+
case 5: return [3 /*break*/, 7];
|
|
571
|
+
case 6:
|
|
572
|
+
e_10 = _b.sent();
|
|
573
|
+
console.error(tag, e_10);
|
|
811
574
|
throw e_10;
|
|
812
|
-
case
|
|
575
|
+
case 7: return [2 /*return*/];
|
|
813
576
|
}
|
|
814
577
|
});
|
|
815
578
|
});
|
|
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) {
|
|
579
|
+
};
|
|
580
|
+
var get_balance_by_xpub = function (coin, xpub) {
|
|
824
581
|
return __awaiter(this, void 0, void 0, function () {
|
|
825
|
-
var tag,
|
|
582
|
+
var tag, output, balance, i, uxto, e_11;
|
|
826
583
|
return __generator(this, function (_a) {
|
|
827
584
|
switch (_a.label) {
|
|
828
585
|
case 0:
|
|
@@ -830,53 +587,41 @@ function get_balance_by_xpub(coin, xpub) {
|
|
|
830
587
|
_a.label = 1;
|
|
831
588
|
case 1:
|
|
832
589
|
_a.trys.push([1, 3, , 4]);
|
|
833
|
-
log.debug(tag, "
|
|
834
|
-
log.debug(tag, "xpub:", xpub);
|
|
835
|
-
return [4 /*yield*/, get_utxos_by_xpub(coin, xpub
|
|
590
|
+
log.debug(tag, "coin: ", coin);
|
|
591
|
+
log.debug(tag, "xpub: ", xpub);
|
|
592
|
+
return [4 /*yield*/, get_utxos_by_xpub(coin, xpub)];
|
|
836
593
|
case 2:
|
|
837
|
-
|
|
838
|
-
log.debug(tag, "
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
balanceSatoshis += value;
|
|
845
|
-
}
|
|
594
|
+
output = _a.sent();
|
|
595
|
+
log.debug(tag, "output: ", output);
|
|
596
|
+
balance = 0;
|
|
597
|
+
//tally
|
|
598
|
+
for (i = 0; i < output.length; i++) {
|
|
599
|
+
uxto = output[i];
|
|
600
|
+
balance = balance + parseInt(uxto.value);
|
|
846
601
|
}
|
|
847
|
-
|
|
848
|
-
log.info(tag, "Total balance: ".concat(balanceBTC, " BTC (").concat(balanceSatoshis, " satoshis)"));
|
|
849
|
-
return [2 /*return*/, balanceBTC];
|
|
602
|
+
return [2 /*return*/, balance / 100000000];
|
|
850
603
|
case 3:
|
|
851
604
|
e_11 = _a.sent();
|
|
852
|
-
|
|
605
|
+
console.error(tag, e_11);
|
|
853
606
|
throw e_11;
|
|
854
607
|
case 4: return [2 /*return*/];
|
|
855
608
|
}
|
|
856
609
|
});
|
|
857
610
|
});
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
* Get information about the Blockbook nodes
|
|
861
|
-
* @returns Promise with node status information
|
|
862
|
-
*/
|
|
863
|
-
function get_node_info() {
|
|
611
|
+
};
|
|
612
|
+
var get_node_info = function () {
|
|
864
613
|
return __awaiter(this, void 0, void 0, function () {
|
|
865
614
|
var tag;
|
|
866
615
|
return __generator(this, function (_a) {
|
|
867
616
|
tag = TAG + " | get_node_info | ";
|
|
868
617
|
try {
|
|
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
|
-
}];
|
|
618
|
+
return [2 /*return*/, true];
|
|
874
619
|
}
|
|
875
620
|
catch (e) {
|
|
876
|
-
|
|
621
|
+
console.error(tag, e);
|
|
877
622
|
throw e;
|
|
878
623
|
}
|
|
879
624
|
return [2 /*return*/];
|
|
880
625
|
});
|
|
881
626
|
});
|
|
882
|
-
}
|
|
627
|
+
};
|