@pioneer-platform/blockbook 8.18.0 → 8.20.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.
@@ -0,0 +1,43 @@
1
+ import EventEmitter from 'events';
2
+ export interface BlockbookMessage {
3
+ id: string;
4
+ method?: string;
5
+ params?: any;
6
+ data?: any;
7
+ }
8
+ export interface SubscribeAddressesResult {
9
+ subscribed: boolean;
10
+ }
11
+ export interface AddressNotification {
12
+ address: string;
13
+ tx?: any;
14
+ }
15
+ export declare class BlockbookWebSocket extends EventEmitter {
16
+ private ws?;
17
+ private url;
18
+ private connected;
19
+ private reconnecting;
20
+ private reconnectAttempts;
21
+ private maxReconnectAttempts;
22
+ private reconnectDelay;
23
+ private requestCounter;
24
+ private requestTimeout;
25
+ private pingInterval?;
26
+ private pendingRequests;
27
+ private subscriptions;
28
+ private methodToId;
29
+ constructor(url: string);
30
+ connect(): Promise<void>;
31
+ private handleMessage;
32
+ private request;
33
+ subscribeAddresses(addresses: string[], callback: (data: AddressNotification) => void | Promise<void>): Promise<SubscribeAddressesResult>;
34
+ unsubscribeAddresses(): Promise<SubscribeAddressesResult>;
35
+ ping(): Promise<any>;
36
+ getInfo(): Promise<any>;
37
+ disconnect(): Promise<void>;
38
+ private startPing;
39
+ private stopPing;
40
+ private rejectAllPending;
41
+ private scheduleReconnect;
42
+ isConnected(): boolean;
43
+ }
@@ -0,0 +1,407 @@
1
+ "use strict";
2
+ var __extends = (this && this.__extends) || (function () {
3
+ var extendStatics = function (d, b) {
4
+ extendStatics = Object.setPrototypeOf ||
5
+ ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
6
+ function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
7
+ return extendStatics(d, b);
8
+ };
9
+ return function (d, b) {
10
+ if (typeof b !== "function" && b !== null)
11
+ throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
12
+ extendStatics(d, b);
13
+ function __() { this.constructor = d; }
14
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
15
+ };
16
+ })();
17
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
18
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
19
+ return new (P || (P = Promise))(function (resolve, reject) {
20
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
21
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
22
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
23
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
24
+ });
25
+ };
26
+ var __generator = (this && this.__generator) || function (thisArg, body) {
27
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
28
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
29
+ function verb(n) { return function (v) { return step([n, v]); }; }
30
+ function step(op) {
31
+ if (f) throw new TypeError("Generator is already executing.");
32
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
33
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
34
+ if (y = 0, t) op = [op[0] & 2, t.value];
35
+ switch (op[0]) {
36
+ case 0: case 1: t = op; break;
37
+ case 4: _.label++; return { value: op[1], done: false };
38
+ case 5: _.label++; y = op[1]; op = [0]; continue;
39
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
40
+ default:
41
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
42
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
43
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
44
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
45
+ if (t[2]) _.ops.pop();
46
+ _.trys.pop(); continue;
47
+ }
48
+ op = body.call(thisArg, _);
49
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
50
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
51
+ }
52
+ };
53
+ var __importDefault = (this && this.__importDefault) || function (mod) {
54
+ return (mod && mod.__esModule) ? mod : { "default": mod };
55
+ };
56
+ Object.defineProperty(exports, "__esModule", { value: true });
57
+ exports.BlockbookWebSocket = void 0;
58
+ var ws_1 = __importDefault(require("ws"));
59
+ var events_1 = __importDefault(require("events"));
60
+ var BlockbookWebSocket = /** @class */ (function (_super) {
61
+ __extends(BlockbookWebSocket, _super);
62
+ function BlockbookWebSocket(url) {
63
+ var _this = _super.call(this) || this;
64
+ _this.connected = false;
65
+ _this.reconnecting = false;
66
+ _this.reconnectAttempts = 0;
67
+ _this.maxReconnectAttempts = 10;
68
+ _this.reconnectDelay = 2000;
69
+ _this.requestCounter = 0;
70
+ _this.requestTimeout = 5000;
71
+ // Track pending requests by ID
72
+ _this.pendingRequests = new Map();
73
+ // Track active subscriptions by ID
74
+ _this.subscriptions = new Map();
75
+ // Track subscription method to ID mapping (only one subscription per method)
76
+ _this.methodToId = new Map();
77
+ _this.url = url;
78
+ return _this;
79
+ }
80
+ BlockbookWebSocket.prototype.connect = function () {
81
+ return __awaiter(this, void 0, void 0, function () {
82
+ var _this = this;
83
+ return __generator(this, function (_a) {
84
+ if (this.connected) {
85
+ return [2 /*return*/];
86
+ }
87
+ return [2 /*return*/, new Promise(function (resolve, reject) {
88
+ try {
89
+ _this.ws = new ws_1.default(_this.url);
90
+ _this.ws.once('open', function () {
91
+ _this.connected = true;
92
+ _this.reconnectAttempts = 0;
93
+ _this.emit('connected', _this.url);
94
+ _this.startPing();
95
+ resolve();
96
+ });
97
+ _this.ws.once('error', function (error) {
98
+ console.error("WebSocket connection error for ".concat(_this.url, ":"), error);
99
+ reject(error);
100
+ });
101
+ // Handle incoming messages - PROPERLY HANDLE BUFFERS
102
+ _this.ws.on('message', function (data) {
103
+ try {
104
+ // Convert Buffer to string in Bun
105
+ var messageStr = void 0;
106
+ if (Buffer.isBuffer(data)) {
107
+ messageStr = data.toString('utf8');
108
+ }
109
+ else if (typeof data === 'string') {
110
+ messageStr = data;
111
+ }
112
+ else if (Array.isArray(data)) {
113
+ messageStr = Buffer.concat(data).toString('utf8');
114
+ }
115
+ else {
116
+ _this.emit('error', new Error("Unexpected WebSocket data type: ".concat(typeof data)));
117
+ return;
118
+ }
119
+ var message = JSON.parse(messageStr);
120
+ _this.handleMessage(message);
121
+ }
122
+ catch (error) {
123
+ _this.emit('error', new Error("Failed to parse WebSocket message: ".concat(error)));
124
+ }
125
+ });
126
+ _this.ws.on('close', function (code) {
127
+ _this.connected = false;
128
+ _this.stopPing();
129
+ _this.rejectAllPending('WebSocket closed');
130
+ _this.emit('disconnected', code);
131
+ // Auto-reconnect if not a normal closure
132
+ if (code !== 1000 && code !== 1005 && !_this.reconnecting) {
133
+ _this.scheduleReconnect();
134
+ }
135
+ });
136
+ _this.ws.on('error', function (error) {
137
+ // Don't emit error events that are already handled in other listeners
138
+ // Just log them to avoid unhandled error crashes
139
+ console.error("WebSocket error for ".concat(_this.url, ":"), error);
140
+ });
141
+ }
142
+ catch (error) {
143
+ reject(error);
144
+ }
145
+ })];
146
+ });
147
+ });
148
+ };
149
+ BlockbookWebSocket.prototype.handleMessage = function (message) {
150
+ var _this = this;
151
+ var id = message.id, data = message.data;
152
+ if (!id) {
153
+ this.emit('error', new Error('Received message without ID'));
154
+ return;
155
+ }
156
+ // Check if this is a response to a pending request
157
+ var pending = this.pendingRequests.get(id);
158
+ if (pending) {
159
+ this.pendingRequests.delete(id);
160
+ if (data === null || data === void 0 ? void 0 : data.error) {
161
+ pending.reject(new Error(data.error.message || 'Request failed'));
162
+ }
163
+ else {
164
+ pending.resolve(data);
165
+ }
166
+ return;
167
+ }
168
+ // Check if this is a subscription notification
169
+ var subscription = this.subscriptions.get(id);
170
+ if (subscription) {
171
+ if (data === null || data === void 0 ? void 0 : data.error) {
172
+ this.emit('error', new Error("Subscription error for ".concat(subscription.method, ": ").concat(data.error.message)));
173
+ }
174
+ else {
175
+ try {
176
+ var result = subscription.callback(data);
177
+ if (result instanceof Promise) {
178
+ result.catch(function (error) {
179
+ _this.emit('error', new Error("Subscription callback error: ".concat(error)));
180
+ });
181
+ }
182
+ }
183
+ catch (error) {
184
+ this.emit('error', new Error("Subscription callback error: ".concat(error)));
185
+ }
186
+ }
187
+ return;
188
+ }
189
+ // Unknown message
190
+ this.emit('unknown-message', message);
191
+ };
192
+ BlockbookWebSocket.prototype.request = function (method, params) {
193
+ var _this = this;
194
+ if (!this.connected || !this.ws) {
195
+ return Promise.reject(new Error('WebSocket not connected'));
196
+ }
197
+ var id = (this.requestCounter++).toString();
198
+ var message = {
199
+ id: id,
200
+ method: method,
201
+ params: params,
202
+ };
203
+ return new Promise(function (resolve, reject) {
204
+ // Set timeout
205
+ var timeout = setTimeout(function () {
206
+ if (_this.pendingRequests.has(id)) {
207
+ _this.pendingRequests.delete(id);
208
+ reject(new Error("Timeout waiting for ".concat(method, " response (id: ").concat(id, ")")));
209
+ }
210
+ }, _this.requestTimeout);
211
+ // Store pending request
212
+ _this.pendingRequests.set(id, {
213
+ resolve: function (value) {
214
+ clearTimeout(timeout);
215
+ resolve(value);
216
+ },
217
+ reject: function (reason) {
218
+ clearTimeout(timeout);
219
+ reject(reason);
220
+ },
221
+ method: method,
222
+ });
223
+ // Send request
224
+ _this.ws.send(JSON.stringify(message));
225
+ });
226
+ };
227
+ BlockbookWebSocket.prototype.subscribeAddresses = function (addresses, callback) {
228
+ return __awaiter(this, void 0, void 0, function () {
229
+ var id, method, params, oldId, result, error_1;
230
+ return __generator(this, function (_a) {
231
+ switch (_a.label) {
232
+ case 0:
233
+ id = (this.requestCounter++).toString();
234
+ method = 'subscribeAddresses';
235
+ params = { addresses: addresses };
236
+ // Store subscription BEFORE sending request
237
+ this.subscriptions.set(id, { method: method, params: params, callback: callback });
238
+ oldId = this.methodToId.get(method);
239
+ if (oldId) {
240
+ this.subscriptions.delete(oldId);
241
+ }
242
+ this.methodToId.set(method, id);
243
+ _a.label = 1;
244
+ case 1:
245
+ _a.trys.push([1, 3, , 4]);
246
+ return [4 /*yield*/, this.request(method, params)];
247
+ case 2:
248
+ result = _a.sent();
249
+ return [2 /*return*/, result];
250
+ case 3:
251
+ error_1 = _a.sent();
252
+ // If request fails, clean up subscription
253
+ this.subscriptions.delete(id);
254
+ if (this.methodToId.get(method) === id) {
255
+ this.methodToId.delete(method);
256
+ }
257
+ throw error_1;
258
+ case 4: return [2 /*return*/];
259
+ }
260
+ });
261
+ });
262
+ };
263
+ BlockbookWebSocket.prototype.unsubscribeAddresses = function () {
264
+ return __awaiter(this, void 0, void 0, function () {
265
+ var method, id;
266
+ return __generator(this, function (_a) {
267
+ method = 'subscribeAddresses';
268
+ id = this.methodToId.get(method);
269
+ if (!id) {
270
+ return [2 /*return*/, { subscribed: false }];
271
+ }
272
+ this.subscriptions.delete(id);
273
+ this.methodToId.delete(method);
274
+ return [2 /*return*/, this.request('unsubscribeAddresses', {})];
275
+ });
276
+ });
277
+ };
278
+ BlockbookWebSocket.prototype.ping = function () {
279
+ return __awaiter(this, void 0, void 0, function () {
280
+ return __generator(this, function (_a) {
281
+ return [2 /*return*/, this.request('ping', {})];
282
+ });
283
+ });
284
+ };
285
+ BlockbookWebSocket.prototype.getInfo = function () {
286
+ return __awaiter(this, void 0, void 0, function () {
287
+ return __generator(this, function (_a) {
288
+ return [2 /*return*/, this.request('getInfo', {})];
289
+ });
290
+ });
291
+ };
292
+ BlockbookWebSocket.prototype.disconnect = function () {
293
+ return __awaiter(this, void 0, void 0, function () {
294
+ var _this = this;
295
+ return __generator(this, function (_a) {
296
+ if (!this.ws || !this.connected) {
297
+ return [2 /*return*/];
298
+ }
299
+ this.reconnecting = false; // Prevent auto-reconnect
300
+ return [2 /*return*/, new Promise(function (resolve) {
301
+ _this.ws.once('close', function () { return resolve(); });
302
+ _this.ws.close(1000, 'Normal closure');
303
+ })];
304
+ });
305
+ });
306
+ };
307
+ BlockbookWebSocket.prototype.startPing = function () {
308
+ var _this = this;
309
+ this.stopPing();
310
+ // Ping every 25 seconds to keep connection alive
311
+ this.pingInterval = setInterval(function () { return __awaiter(_this, void 0, void 0, function () {
312
+ var error_2;
313
+ var _a;
314
+ return __generator(this, function (_b) {
315
+ switch (_b.label) {
316
+ case 0:
317
+ _b.trys.push([0, 2, , 3]);
318
+ return [4 /*yield*/, this.ping()];
319
+ case 1:
320
+ _b.sent();
321
+ return [3 /*break*/, 3];
322
+ case 2:
323
+ error_2 = _b.sent();
324
+ this.emit('error', new Error("Ping failed: ".concat(error_2)));
325
+ (_a = this.ws) === null || _a === void 0 ? void 0 : _a.terminate(); // Force close to trigger reconnect
326
+ return [3 /*break*/, 3];
327
+ case 3: return [2 /*return*/];
328
+ }
329
+ });
330
+ }); }, 25000);
331
+ };
332
+ BlockbookWebSocket.prototype.stopPing = function () {
333
+ if (this.pingInterval) {
334
+ clearInterval(this.pingInterval);
335
+ this.pingInterval = undefined;
336
+ }
337
+ };
338
+ BlockbookWebSocket.prototype.rejectAllPending = function (reason) {
339
+ this.pendingRequests.forEach(function (pending, id) {
340
+ pending.reject(new Error(reason));
341
+ });
342
+ this.pendingRequests.clear();
343
+ };
344
+ BlockbookWebSocket.prototype.scheduleReconnect = function () {
345
+ return __awaiter(this, void 0, void 0, function () {
346
+ var delay, jitter;
347
+ var _this = this;
348
+ return __generator(this, function (_a) {
349
+ if (this.reconnecting) {
350
+ return [2 /*return*/];
351
+ }
352
+ this.reconnectAttempts++;
353
+ if (this.reconnectAttempts > this.maxReconnectAttempts) {
354
+ this.emit('error', new Error('Max reconnect attempts reached'));
355
+ return [2 /*return*/];
356
+ }
357
+ this.reconnecting = true;
358
+ delay = Math.min(this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1), 60000);
359
+ jitter = Math.random() * 1000;
360
+ this.emit('reconnecting', { attempt: this.reconnectAttempts, delay: delay });
361
+ setTimeout(function () { return __awaiter(_this, void 0, void 0, function () {
362
+ var subsToRestore, _i, subsToRestore_1, sub, error_3;
363
+ return __generator(this, function (_a) {
364
+ switch (_a.label) {
365
+ case 0:
366
+ _a.trys.push([0, 6, , 7]);
367
+ return [4 /*yield*/, this.connect()];
368
+ case 1:
369
+ _a.sent();
370
+ subsToRestore = Array.from(this.subscriptions.values());
371
+ this.subscriptions.clear();
372
+ this.methodToId.clear();
373
+ _i = 0, subsToRestore_1 = subsToRestore;
374
+ _a.label = 2;
375
+ case 2:
376
+ if (!(_i < subsToRestore_1.length)) return [3 /*break*/, 5];
377
+ sub = subsToRestore_1[_i];
378
+ if (!(sub.method === 'subscribeAddresses')) return [3 /*break*/, 4];
379
+ return [4 /*yield*/, this.subscribeAddresses(sub.params.addresses, sub.callback)];
380
+ case 3:
381
+ _a.sent();
382
+ _a.label = 4;
383
+ case 4:
384
+ _i++;
385
+ return [3 /*break*/, 2];
386
+ case 5:
387
+ this.reconnecting = false;
388
+ return [3 /*break*/, 7];
389
+ case 6:
390
+ error_3 = _a.sent();
391
+ this.reconnecting = false;
392
+ this.scheduleReconnect();
393
+ return [3 /*break*/, 7];
394
+ case 7: return [2 /*return*/];
395
+ }
396
+ });
397
+ }); }, delay + jitter);
398
+ return [2 /*return*/];
399
+ });
400
+ });
401
+ };
402
+ BlockbookWebSocket.prototype.isConnected = function () {
403
+ return this.connected;
404
+ };
405
+ return BlockbookWebSocket;
406
+ }(events_1.default));
407
+ exports.BlockbookWebSocket = BlockbookWebSocket;
package/lib/index.js CHANGED
@@ -54,7 +54,7 @@ var axios = Axios.create({
54
54
  timeout: 30000 // 10 seconds
55
55
  });
56
56
  // const axiosRetry = require('axios-retry');
57
- var NOW_NODES_API = process.env['NOW_NODES_API'];
57
+ var NOW_NODES_API = process.env['NOW_NODES_API'] || process.env['NOWNODES_API_KEY'];
58
58
  // Enhanced node management
59
59
  var BLOCKBOOK_NODES = {};
60
60
  var BLOCKBOOK_SOCKETS = {};
@@ -233,7 +233,7 @@ var update_legacy_urls = function (symbol) {
233
233
  };
234
234
  var init_network = function (servers) {
235
235
  return __awaiter(this, void 0, void 0, function () {
236
- var tag, SEED_NODES, blockbooks, i, blockbook, symbol, priority, url, symbol, nodes_1, e_1;
236
+ var tag, SEED_NODES, blockbooks, i, blockbook, symbol, priority, wsUrl, symbol, nodes_1, e_1;
237
237
  return __generator(this, function (_a) {
238
238
  switch (_a.label) {
239
239
  case 0:
@@ -276,10 +276,27 @@ var init_network = function (servers) {
276
276
  add_custom_node(symbol, blockbook.service, priority);
277
277
  }
278
278
  if (blockbook && blockbook.websocket) {
279
- url = blockbook.websocket.replace("/websocket", "");
280
- url = blockbook.websocket.replace("wss://", "https://");
279
+ wsUrl = blockbook.websocket;
280
+ // Ensure proper wss:// protocol
281
+ if (!wsUrl.startsWith('wss://') && !wsUrl.startsWith('ws://')) {
282
+ wsUrl = 'wss://' + wsUrl;
283
+ }
284
+ // Append NowNodes API key if needed
285
+ if (wsUrl.includes('nownodes.io') && NOW_NODES_API && !wsUrl.includes(NOW_NODES_API)) {
286
+ // NowNodes format: wss://btc.nownodes.io/wss/API_KEY/websocket
287
+ // Remove any existing /wss/ suffix, then add /wss/API_KEY
288
+ wsUrl = wsUrl.replace(/\/wss\/?$/, ''); // Remove trailing /wss or /wss/
289
+ wsUrl = wsUrl + '/wss/' + NOW_NODES_API;
290
+ log.info(tag, "Configured NowNodes websocket for ".concat(blockbook.symbol, " with API key"));
291
+ }
292
+ // Ensure /websocket suffix (only if not already there)
293
+ if (!wsUrl.endsWith('/websocket')) {
294
+ wsUrl = wsUrl + '/websocket';
295
+ }
296
+ log.info(tag, "WebSocket URL for ".concat(blockbook.symbol, ": ").concat(wsUrl));
297
+ // Use blockbook-client package
281
298
  BLOCKBOOK_SOCKETS[blockbook.symbol.toUpperCase()] = new Blockbook({
282
- nodes: [url],
299
+ nodes: [wsUrl],
283
300
  disableTypeValidation: true,
284
301
  });
285
302
  }
@@ -357,7 +374,7 @@ var get_info_by_pubkey = function (coin, pubkey, page) {
357
374
  _a.trys.push([1, 3, , 4]);
358
375
  if (!page)
359
376
  page = "1";
360
- url = BLOCKBOOK_URLS[coin.toUpperCase()] + "/api/v2/xpub/" + pubkey + "?details=tokens&tokens=used&page=" + page;
377
+ url = BLOCKBOOK_URLS[coin.toUpperCase()] + "/api/v2/xpub/" + pubkey + "?details=tokens&tokens=derived&gap=20&page=" + page;
361
378
  log.debug(tag, "url: ", url);
362
379
  body = {
363
380
  method: 'GET',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pioneer-platform/blockbook",
3
- "version": "8.18.0",
3
+ "version": "8.20.0",
4
4
  "main": "./lib/index.js",
5
5
  "types": "./lib/index.d.ts",
6
6
  "scripts": {
@@ -12,8 +12,8 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "@pioneer-platform/loggerdog": "^8.11.0",
15
- "@pioneer-platform/nodes": "^8.17.0",
16
- "@pioneer-platform/pioneer-caip": "^9.17.0",
15
+ "@pioneer-platform/nodes": "^8.19.0",
16
+ "@pioneer-platform/pioneer-caip": "^9.19.0",
17
17
  "@types/request-promise-native": "^1.0.17",
18
18
  "axiom": "^0.1.6",
19
19
  "axios": "^1.6.0",