@pioneer-platform/blockbook 8.10.0 → 8.11.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/BLOCKBOOK_API.md +347 -0
- package/CHANGELOG.md +19 -0
- package/lib/index.d.ts +1 -1
- package/lib/index.js +92 -25
- package/package.json +5 -5
package/BLOCKBOOK_API.md
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
# Blockbook API Documentation
|
|
2
|
+
|
|
3
|
+
Comprehensive documentation of Blockbook REST API endpoints relevant to UTXO blockchain operations.
|
|
4
|
+
|
|
5
|
+
## API Base URL Structure
|
|
6
|
+
|
|
7
|
+
Blockbook servers use the following URL structure:
|
|
8
|
+
```
|
|
9
|
+
https://{server}/api/v2/{endpoint}
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Core Endpoints
|
|
13
|
+
|
|
14
|
+
### 1. XPUB Information (`/api/v2/xpub/{xpub}`)
|
|
15
|
+
|
|
16
|
+
Get extended public key (xpub) information including derived addresses, balances, and transaction history.
|
|
17
|
+
|
|
18
|
+
**Endpoint**: `GET /api/v2/xpub/{xpub}`
|
|
19
|
+
|
|
20
|
+
**Query Parameters**:
|
|
21
|
+
|
|
22
|
+
| Parameter | Type | Values | Default | Description |
|
|
23
|
+
|-----------|------|--------|---------|-------------|
|
|
24
|
+
| `page` | int | ≥0 | 0 | Page number for pagination |
|
|
25
|
+
| `pageSize` | int | 1-1000 | 1000 | Number of transactions per page |
|
|
26
|
+
| `from` | int | block height | 0 | Filter transactions from this block height |
|
|
27
|
+
| `to` | int | block height | 0 | Filter transactions to this block height |
|
|
28
|
+
| `details` | string | `basic`, `tokens`, `tokenBalances`, `txids`, `txslight`, `txs` | `txids` | Level of detail to return |
|
|
29
|
+
| `tokens` | string | `nonzero`, `used`, `derived` | `nonzero` | Which derived addresses to return |
|
|
30
|
+
| `gap` | int | ≥0 | 0 | BIP44 gap limit for address derivation |
|
|
31
|
+
| `secondary` | string | fiat currency | - | Secondary currency for fiat conversion |
|
|
32
|
+
|
|
33
|
+
**Details Parameter Values**:
|
|
34
|
+
- `basic` - Only basic account information (balance, txs count)
|
|
35
|
+
- `tokens` - Basic info + list of derived addresses (tokens array)
|
|
36
|
+
- `tokenBalances` - Basic info + addresses with their balances
|
|
37
|
+
- `txids` - Basic info + addresses + transaction IDs (paginated)
|
|
38
|
+
- `txslight` - Basic info + addresses + light transaction data (paginated)
|
|
39
|
+
- `txs` - Basic info + addresses + full transaction data (paginated)
|
|
40
|
+
|
|
41
|
+
**Tokens Parameter Values** (⚠️ CRITICAL for address index calculation):
|
|
42
|
+
- `nonzero` - **DEFAULT** - Only returns addresses with current non-zero balance
|
|
43
|
+
- `used` - Returns addresses that have been used (had transactions), even if balance is zero now
|
|
44
|
+
- `derived` - Returns ALL derived addresses up to gap limit
|
|
45
|
+
|
|
46
|
+
**Response Structure**:
|
|
47
|
+
```typescript
|
|
48
|
+
{
|
|
49
|
+
address: string, // The xpub itself
|
|
50
|
+
balance: string, // Current balance in satoshis
|
|
51
|
+
totalReceived: string, // Total ever received
|
|
52
|
+
totalSent: string, // Total ever sent
|
|
53
|
+
unconfirmedBalance: string, // Unconfirmed balance
|
|
54
|
+
unconfirmedTxs: number, // Number of unconfirmed txs
|
|
55
|
+
txs: number, // Total transaction count
|
|
56
|
+
usedTokens: number, // Number of addresses that have been used
|
|
57
|
+
tokens: Array<{
|
|
58
|
+
type: string, // "XPUBAddress" (string, not numeric!)
|
|
59
|
+
name: string, // The derived address
|
|
60
|
+
path: string, // BIP44 path: m/purpose'/coin'/account'/change/index
|
|
61
|
+
transfers: number, // Number of transfers for this address
|
|
62
|
+
decimals: number // Token decimals (8 for BTC)
|
|
63
|
+
}>
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**BIP44 Path Format**: `m/purpose'/coin_type'/account'/change/address_index`
|
|
68
|
+
- `purpose` - 44 (legacy), 49 (wrapped segwit), 84 (native segwit)
|
|
69
|
+
- `coin_type` - 0 (BTC), 2 (LTC), 3 (DOGE), 5 (DASH), 145 (BCH), etc.
|
|
70
|
+
- `account` - Usually 0
|
|
71
|
+
- `change` - **0 for receive addresses, 1 for change addresses**
|
|
72
|
+
- `address_index` - Sequential index starting from 0
|
|
73
|
+
|
|
74
|
+
**CRITICAL ISSUE**: By default, blockbook uses `tokens=nonzero` which only returns addresses with current balance. For wallets that have spent all their funds, this returns NO addresses even if they have transaction history!
|
|
75
|
+
|
|
76
|
+
**SOLUTION**: Always use `?details=tokens&tokens=used` or `?details=tokens&tokens=derived` to get complete address information.
|
|
77
|
+
|
|
78
|
+
**Example Requests**:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# Get basic info only (no addresses)
|
|
82
|
+
GET /api/v2/xpub/{xpub}?details=basic
|
|
83
|
+
|
|
84
|
+
# Get ALL used addresses (RECOMMENDED for address index calculation)
|
|
85
|
+
GET /api/v2/xpub/{xpub}?details=tokens&tokens=used
|
|
86
|
+
|
|
87
|
+
# Get ALL derived addresses up to gap limit
|
|
88
|
+
GET /api/v2/xpub/{xpub}?details=tokens&tokens=derived&gap=20
|
|
89
|
+
|
|
90
|
+
# Get full transaction history with used addresses
|
|
91
|
+
GET /api/v2/xpub/{xpub}?details=txs&tokens=used&page=0&pageSize=50
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 2. Address Information (`/api/v2/address/{address}`)
|
|
95
|
+
|
|
96
|
+
Get information about a single address.
|
|
97
|
+
|
|
98
|
+
**Endpoint**: `GET /api/v2/address/{address}`
|
|
99
|
+
|
|
100
|
+
**Query Parameters**: Same as xpub endpoint
|
|
101
|
+
|
|
102
|
+
**Response Structure**: Similar to xpub but for single address
|
|
103
|
+
|
|
104
|
+
### 3. UTXO Query (`/api/v2/utxo/{xpub_or_address}`)
|
|
105
|
+
|
|
106
|
+
Get unspent transaction outputs for an address or xpub.
|
|
107
|
+
|
|
108
|
+
**Endpoint**: `GET /api/v2/utxo/{xpub_or_address}`
|
|
109
|
+
|
|
110
|
+
**Query Parameters**:
|
|
111
|
+
|
|
112
|
+
| Parameter | Type | Values | Default | Description |
|
|
113
|
+
|-----------|------|--------|---------|-------------|
|
|
114
|
+
| `confirmed` | boolean | true/false | false | Return only confirmed UTXOs |
|
|
115
|
+
| `gap` | int | ≥0 | 0 | BIP44 gap limit for xpub derivation |
|
|
116
|
+
|
|
117
|
+
**Response Structure**:
|
|
118
|
+
```typescript
|
|
119
|
+
Array<{
|
|
120
|
+
txid: string,
|
|
121
|
+
vout: number,
|
|
122
|
+
value: string, // Value in satoshis
|
|
123
|
+
height: number,
|
|
124
|
+
confirmations: number,
|
|
125
|
+
address: string,
|
|
126
|
+
path: string, // BIP44 path (for xpub queries)
|
|
127
|
+
lockTime: number,
|
|
128
|
+
coinbase: boolean
|
|
129
|
+
}>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 4. Transaction Query (`/api/v2/tx/{txid}`)
|
|
133
|
+
|
|
134
|
+
Get detailed information about a specific transaction.
|
|
135
|
+
|
|
136
|
+
**Endpoint**: `GET /api/v2/tx/{txid}`
|
|
137
|
+
|
|
138
|
+
**Response**: Full transaction details including inputs, outputs, confirmations
|
|
139
|
+
|
|
140
|
+
### 5. Fee Estimation (`/api/v2/estimatefee/{blocks}`)
|
|
141
|
+
|
|
142
|
+
Get estimated fee rate for confirmation within specified number of blocks.
|
|
143
|
+
|
|
144
|
+
**Endpoint**: `GET /api/v2/estimatefee/{blocks}`
|
|
145
|
+
|
|
146
|
+
**Example**: `/api/v2/estimatefee/1` (fastest), `/api/v2/estimatefee/6` (average)
|
|
147
|
+
|
|
148
|
+
**Response**:
|
|
149
|
+
```typescript
|
|
150
|
+
{
|
|
151
|
+
result: string // Fee rate in BTC/kB (or -1 if unavailable)
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Note**: Fee estimation may return `-1` if the node doesn't have enough data. Always have fallback default fees.
|
|
156
|
+
|
|
157
|
+
### 6. Fee Statistics (`/api/v2/feestats/`)
|
|
158
|
+
|
|
159
|
+
Get comprehensive fee statistics from recent blocks.
|
|
160
|
+
|
|
161
|
+
**Endpoint**: `GET /api/v2/feestats/`
|
|
162
|
+
|
|
163
|
+
**Response**: Detailed fee statistics for different confirmation targets
|
|
164
|
+
|
|
165
|
+
### 7. Broadcast Transaction (`/api/v2/sendtx/`)
|
|
166
|
+
|
|
167
|
+
Broadcast a signed transaction to the network.
|
|
168
|
+
|
|
169
|
+
**Endpoint**: `POST /api/v2/sendtx/`
|
|
170
|
+
|
|
171
|
+
**Body**: Raw transaction hex (text/plain)
|
|
172
|
+
|
|
173
|
+
**Response**:
|
|
174
|
+
```typescript
|
|
175
|
+
{
|
|
176
|
+
result: string // Transaction ID (txid) if successful
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### 8. Block Information (`/api/v2/block/{height_or_hash}`)
|
|
181
|
+
|
|
182
|
+
Get information about a specific block.
|
|
183
|
+
|
|
184
|
+
**Endpoint**: `GET /api/v2/block/{height_or_hash}`
|
|
185
|
+
|
|
186
|
+
### 9. Balance History (`/api/v2/balancehistory/{xpub_or_address}`)
|
|
187
|
+
|
|
188
|
+
Get historical balance data for charting and analytics.
|
|
189
|
+
|
|
190
|
+
**Endpoint**: `GET /api/v2/balancehistory/{xpub_or_address}`
|
|
191
|
+
|
|
192
|
+
**Query Parameters**:
|
|
193
|
+
|
|
194
|
+
| Parameter | Type | Description |
|
|
195
|
+
|-----------|------|-------------|
|
|
196
|
+
| `from` | timestamp | Start time (unix timestamp) |
|
|
197
|
+
| `to` | timestamp | End time (unix timestamp) |
|
|
198
|
+
| `groupBy` | int | Group interval in seconds (default: 3600) |
|
|
199
|
+
| `fiatcurrency` | string | Currency code for fiat conversion |
|
|
200
|
+
| `gap` | int | BIP44 gap limit for xpub |
|
|
201
|
+
|
|
202
|
+
## Common Issues and Solutions
|
|
203
|
+
|
|
204
|
+
### Issue 1: Empty `tokens` Array Despite Transaction History
|
|
205
|
+
|
|
206
|
+
**Problem**: Calling `/api/v2/xpub/{xpub}?details=tokens` returns `usedTokens: 0` and empty `tokens: []` array even though `txs: 25`.
|
|
207
|
+
|
|
208
|
+
**Cause**: Default `tokens=nonzero` parameter only returns addresses with current non-zero balance. If wallet has spent all funds, no addresses are returned.
|
|
209
|
+
|
|
210
|
+
**Solution**: Always use `tokens=used` or `tokens=derived`:
|
|
211
|
+
```bash
|
|
212
|
+
GET /api/v2/xpub/{xpub}?details=tokens&tokens=used
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Issue 2: Incorrect Change Address Index Calculation
|
|
216
|
+
|
|
217
|
+
**Problem**: Change address index is calculated as 0 when it should be higher.
|
|
218
|
+
|
|
219
|
+
**Cause**: Using the `type` field (which is a string "XPUBAddress") instead of parsing the BIP44 path.
|
|
220
|
+
|
|
221
|
+
**Solution**: Parse the `path` field to determine if address is receive (change=0) or change (change=1):
|
|
222
|
+
```javascript
|
|
223
|
+
const pathParts = token.path.split('/'); // e.g. "m/44'/0'/0'/1/5"
|
|
224
|
+
const changeIndicator = parseInt(pathParts[3], 10); // 0 = receive, 1 = change
|
|
225
|
+
const addressIndex = parseInt(pathParts[4], 10); // 5
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Issue 3: Missing Pagination for Large Wallets
|
|
229
|
+
|
|
230
|
+
**Problem**: Only getting first page of transactions for active wallets.
|
|
231
|
+
|
|
232
|
+
**Cause**: Not implementing pagination logic.
|
|
233
|
+
|
|
234
|
+
**Solution**: Check `totalPages` in response and loop through all pages:
|
|
235
|
+
```javascript
|
|
236
|
+
let page = 0;
|
|
237
|
+
let allTxs = [];
|
|
238
|
+
do {
|
|
239
|
+
const response = await getXpubInfo(xpub, {details: 'txs', page, pageSize: 50});
|
|
240
|
+
allTxs = allTxs.concat(response.transactions);
|
|
241
|
+
page++;
|
|
242
|
+
} while (page < response.totalPages);
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Performance Considerations
|
|
246
|
+
|
|
247
|
+
### Node Selection and Failover
|
|
248
|
+
|
|
249
|
+
- NowNodes: Priority 0 (fastest, rate limited)
|
|
250
|
+
- Zelcore: Priority 10 (slower, unlimited)
|
|
251
|
+
- ShapeShift: Priority 20 (slower, public)
|
|
252
|
+
|
|
253
|
+
Implement failover logic to try multiple nodes in priority order.
|
|
254
|
+
|
|
255
|
+
### Response Time Optimization
|
|
256
|
+
|
|
257
|
+
- Use `details=basic` or `details=tokens` for quick queries (no transaction data)
|
|
258
|
+
- Use `details=txids` instead of `details=txs` when only txids are needed
|
|
259
|
+
- Implement caching for frequently accessed xpubs
|
|
260
|
+
- Use pagination to avoid large responses
|
|
261
|
+
|
|
262
|
+
### Rate Limiting
|
|
263
|
+
|
|
264
|
+
Public blockbook nodes have rate limits:
|
|
265
|
+
- Implement exponential backoff for retries
|
|
266
|
+
- Distribute requests across multiple nodes
|
|
267
|
+
- Cache responses appropriately
|
|
268
|
+
|
|
269
|
+
## Integration Best Practices
|
|
270
|
+
|
|
271
|
+
### 1. Always Request Used Addresses
|
|
272
|
+
|
|
273
|
+
```javascript
|
|
274
|
+
const xpubInfo = await blockbook.getPubkeyInfo(coin, xpub);
|
|
275
|
+
// Add tokens=used to the URL
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### 2. Parse Paths, Not Type Field
|
|
279
|
+
|
|
280
|
+
```javascript
|
|
281
|
+
// WRONG - type is a string "XPUBAddress"
|
|
282
|
+
if (token.type === 1) { /* change address */ }
|
|
283
|
+
|
|
284
|
+
// CORRECT - parse the BIP44 path
|
|
285
|
+
const pathParts = token.path.split('/');
|
|
286
|
+
const changeIndicator = parseInt(pathParts[3].replace("'", ""), 10);
|
|
287
|
+
if (changeIndicator === 1) { /* change address */ }
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### 3. Handle Empty Responses
|
|
291
|
+
|
|
292
|
+
```javascript
|
|
293
|
+
if (!data.tokens || data.tokens.length === 0) {
|
|
294
|
+
if (data.txs > 0) {
|
|
295
|
+
console.warn(`Wallet has ${data.txs} transactions but no address data!`);
|
|
296
|
+
console.warn(`Did you forget tokens=used parameter?`);
|
|
297
|
+
}
|
|
298
|
+
return { changeIndex: 0 }; // Safe fallback
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### 4. Implement Failover Logic
|
|
303
|
+
|
|
304
|
+
```javascript
|
|
305
|
+
const nodes = ['node1.example.com', 'node2.example.com', 'node3.example.com'];
|
|
306
|
+
for (const node of nodes) {
|
|
307
|
+
try {
|
|
308
|
+
return await fetchFromNode(node, xpub);
|
|
309
|
+
} catch (err) {
|
|
310
|
+
console.error(`Node ${node} failed:`, err);
|
|
311
|
+
continue; // Try next node
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
throw new Error('All nodes failed');
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
## Migration Guide
|
|
318
|
+
|
|
319
|
+
### From Current Implementation to Fixed Implementation
|
|
320
|
+
|
|
321
|
+
**Current (Broken)**:
|
|
322
|
+
```javascript
|
|
323
|
+
// Doesn't request used addresses
|
|
324
|
+
const url = `${BLOCKBOOK_URL}/api/v2/xpub/${xpub}`;
|
|
325
|
+
|
|
326
|
+
// Relies on numeric type field
|
|
327
|
+
if (token.type === 1) { /* change */ }
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**Fixed (Working)**:
|
|
331
|
+
```javascript
|
|
332
|
+
// Requests all used addresses
|
|
333
|
+
const url = `${BLOCKBOOK_URL}/api/v2/xpub/${xpub}?details=tokens&tokens=used`;
|
|
334
|
+
|
|
335
|
+
// Parses BIP44 path correctly
|
|
336
|
+
const pathParts = token.path.split('/');
|
|
337
|
+
const changeIndicator = parseInt(pathParts[3].replace("'", ""), 10);
|
|
338
|
+
if (changeIndicator === 1) { /* change */ }
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
## Additional Resources
|
|
342
|
+
|
|
343
|
+
- [Blockbook GitHub Repository](https://github.com/trezor/blockbook)
|
|
344
|
+
- [BIP32 - Hierarchical Deterministic Wallets](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)
|
|
345
|
+
- [BIP44 - Multi-Account Hierarchy](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki)
|
|
346
|
+
- [BIP49 - Derivation for P2WPKH-nested-in-P2SH](https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki)
|
|
347
|
+
- [BIP84 - Derivation for P2WPKH](https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki)
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @pioneer-platform/blockbook
|
|
2
2
|
|
|
3
|
+
## 8.11.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- cache work
|
|
8
|
+
|
|
9
|
+
## 8.11.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- Automated minor version bump for all packages
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Updated dependencies
|
|
18
|
+
- @pioneer-platform/loggerdog@8.11.0
|
|
19
|
+
- @pioneer-platform/nodes@8.11.0
|
|
20
|
+
- @pioneer-platform/pioneer-caip@9.10.0
|
|
21
|
+
|
|
3
22
|
## 8.10.0
|
|
4
23
|
|
|
5
24
|
### Minor Changes
|
package/lib/index.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ declare let init_network: (servers?: any[]) => Promise<boolean>;
|
|
|
36
36
|
declare let get_fees: (coin: string) => Promise<any>;
|
|
37
37
|
declare let get_info_by_pubkey: (coin: string, pubkey: string, page?: string | undefined) => Promise<any>;
|
|
38
38
|
declare let get_txids_by_address: (coin: string, address: string, page?: number) => Promise<any>;
|
|
39
|
-
declare let get_info_by_address: (coin: string, address: string, filter?: string) => Promise<any>;
|
|
39
|
+
declare let get_info_by_address: (coin: string, address: string, filter?: string, options?: any) => Promise<any>;
|
|
40
40
|
declare let get_txs_by_xpub: (coin: string, xpub: string) => Promise<any>;
|
|
41
41
|
declare let broadcast_transaction: (coin: string, hex: string) => Promise<{
|
|
42
42
|
success: boolean;
|
package/lib/index.js
CHANGED
|
@@ -94,8 +94,8 @@ module.exports = {
|
|
|
94
94
|
getTransaction: function (coin, txid) {
|
|
95
95
|
return get_transaction(coin, txid);
|
|
96
96
|
},
|
|
97
|
-
getAddressInfo: function (coin, address, filter) {
|
|
98
|
-
return get_info_by_address(coin, address, filter);
|
|
97
|
+
getAddressInfo: function (coin, address, filter, options) {
|
|
98
|
+
return get_info_by_address(coin, address, filter, options);
|
|
99
99
|
},
|
|
100
100
|
getPubkeyInfo: function (coin, pubkey, filter) {
|
|
101
101
|
return get_info_by_pubkey(coin, pubkey, filter);
|
|
@@ -357,7 +357,7 @@ var get_info_by_pubkey = function (coin, pubkey, page) {
|
|
|
357
357
|
_a.trys.push([1, 3, , 4]);
|
|
358
358
|
if (!page)
|
|
359
359
|
page = "1";
|
|
360
|
-
url = BLOCKBOOK_URLS[coin.toUpperCase()] + "/api/v2/xpub/" + pubkey;
|
|
360
|
+
url = BLOCKBOOK_URLS[coin.toUpperCase()] + "/api/v2/xpub/" + pubkey + "?details=tokens&tokens=used&page=" + page;
|
|
361
361
|
log.debug(tag, "url: ", url);
|
|
362
362
|
body = {
|
|
363
363
|
method: 'GET',
|
|
@@ -371,7 +371,15 @@ var get_info_by_pubkey = function (coin, pubkey, page) {
|
|
|
371
371
|
case 2:
|
|
372
372
|
resp = _a.sent();
|
|
373
373
|
log.debug(tag, "resp: ", resp);
|
|
374
|
-
//
|
|
374
|
+
// Validate that we got the tokens array
|
|
375
|
+
if (!resp.data.tokens || !Array.isArray(resp.data.tokens)) {
|
|
376
|
+
log.warn(tag, "\u26A0\uFE0F Blockbook response missing tokens array for ".concat(coin, " xpub ").concat(pubkey.substring(0, 20), "..."));
|
|
377
|
+
log.warn(tag, "Response has ".concat(resp.data.txs || 0, " transactions but no address details"));
|
|
378
|
+
log.warn(tag, "This should not happen with tokens=used parameter!");
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
log.info(tag, "\u2705 Got ".concat(resp.data.tokens.length, " used address tokens for xpub (txs: ").concat(resp.data.txs, ", usedTokens: ").concat(resp.data.usedTokens, ")"));
|
|
382
|
+
}
|
|
375
383
|
return [2 /*return*/, resp.data];
|
|
376
384
|
case 3:
|
|
377
385
|
e_3 = _a.sent();
|
|
@@ -420,9 +428,9 @@ var get_txids_by_address = function (coin, address, page) {
|
|
|
420
428
|
});
|
|
421
429
|
});
|
|
422
430
|
};
|
|
423
|
-
var get_info_by_address = function (coin, address, filter) {
|
|
431
|
+
var get_info_by_address = function (coin, address, filter, options) {
|
|
424
432
|
return __awaiter(this, void 0, void 0, function () {
|
|
425
|
-
var tag, url, body, resp, e_5;
|
|
433
|
+
var tag, url, params, body, resp, e_5;
|
|
426
434
|
return __generator(this, function (_a) {
|
|
427
435
|
switch (_a.label) {
|
|
428
436
|
case 0:
|
|
@@ -432,10 +440,25 @@ var get_info_by_address = function (coin, address, filter) {
|
|
|
432
440
|
_a.trys.push([1, 3, , 4]);
|
|
433
441
|
if (!filter)
|
|
434
442
|
filter = "all";
|
|
435
|
-
//let url = ETH_BLOCKBOOK_URL+"/api/v2/address/"+address+"?="+filter
|
|
436
443
|
if (!BLOCKBOOK_URLS[coin.toUpperCase()])
|
|
437
444
|
throw Error("invalid coin: " + coin);
|
|
438
|
-
url = BLOCKBOOK_URLS[coin.toUpperCase()] + "/api/v2/address/" + address
|
|
445
|
+
url = BLOCKBOOK_URLS[coin.toUpperCase()] + "/api/v2/address/" + address;
|
|
446
|
+
params = [];
|
|
447
|
+
if (filter === 'txs' || filter === 'all') {
|
|
448
|
+
params.push('details=txs');
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
params.push('details=' + filter);
|
|
452
|
+
}
|
|
453
|
+
// Add pagination if provided
|
|
454
|
+
if (options === null || options === void 0 ? void 0 : options.page)
|
|
455
|
+
params.push('page=' + options.page);
|
|
456
|
+
if (options === null || options === void 0 ? void 0 : options.pageSize)
|
|
457
|
+
params.push('pageSize=' + options.pageSize);
|
|
458
|
+
if (params.length > 0) {
|
|
459
|
+
url += '?' + params.join('&');
|
|
460
|
+
}
|
|
461
|
+
log.debug(tag, "Fetching ".concat(coin, " address ").concat(address.substring(0, 10), "... from: ").concat(url));
|
|
439
462
|
body = {
|
|
440
463
|
method: 'GET',
|
|
441
464
|
url: url,
|
|
@@ -444,12 +467,9 @@ var get_info_by_address = function (coin, address, filter) {
|
|
|
444
467
|
'User-Agent': fakeUa()
|
|
445
468
|
},
|
|
446
469
|
};
|
|
447
|
-
return [4 /*yield*/, axios(body)
|
|
448
|
-
//TODO paginate?
|
|
449
|
-
];
|
|
470
|
+
return [4 /*yield*/, axios(body)];
|
|
450
471
|
case 2:
|
|
451
472
|
resp = _a.sent();
|
|
452
|
-
//TODO paginate?
|
|
453
473
|
return [2 /*return*/, resp.data];
|
|
454
474
|
case 3:
|
|
455
475
|
e_5 = _a.sent();
|
|
@@ -470,7 +490,7 @@ var get_txs_by_xpub = function (coin, xpub) {
|
|
|
470
490
|
_a.label = 1;
|
|
471
491
|
case 1:
|
|
472
492
|
_a.trys.push([1, 3, , 4]);
|
|
473
|
-
url = BLOCKBOOK_URLS[coin.toUpperCase()] + "/api/v2/xpub/" + xpub + "?details=
|
|
493
|
+
url = BLOCKBOOK_URLS[coin.toUpperCase()] + "/api/v2/xpub/" + xpub + "?details=txs";
|
|
474
494
|
body = {
|
|
475
495
|
method: 'GET',
|
|
476
496
|
url: url,
|
|
@@ -660,16 +680,20 @@ var get_transaction = function (coin, txid) {
|
|
|
660
680
|
// Enhanced UTXO function with priority-based sequential failover
|
|
661
681
|
var get_utxos_by_xpub = function (coin, xpub) {
|
|
662
682
|
return __awaiter(this, void 0, void 0, function () {
|
|
663
|
-
var tag, symbol, nodes_3, activeNodes, i, node, startTime, url, body, resp, responseTime, error_2, responseTime, errorMessage, e_9;
|
|
664
|
-
|
|
665
|
-
|
|
683
|
+
var tag, symbol, isBitcoin, nodes_3, activeNodes, i, node, startTime, url, body, resp, responseTime, error_2, responseTime, errorMessage, e_9;
|
|
684
|
+
var _a;
|
|
685
|
+
return __generator(this, function (_b) {
|
|
686
|
+
switch (_b.label) {
|
|
666
687
|
case 0:
|
|
667
688
|
tag = TAG + " | get_utxos_by_xpub | ";
|
|
668
|
-
_a.label = 1;
|
|
669
|
-
case 1:
|
|
670
|
-
_a.trys.push([1, 8, , 9]);
|
|
671
689
|
symbol = coin.toUpperCase();
|
|
690
|
+
isBitcoin = symbol === 'BTC';
|
|
691
|
+
_b.label = 1;
|
|
692
|
+
case 1:
|
|
693
|
+
_b.trys.push([1, 8, , 9]);
|
|
672
694
|
nodes_3 = BLOCKBOOK_NODES[symbol];
|
|
695
|
+
if (isBitcoin)
|
|
696
|
+
log.info(tag, '🔍 [BITCOIN BLOCKBOOK] Starting UTXO query for xpub:', xpub.substring(0, 20) + '...');
|
|
673
697
|
if (!nodes_3 || nodes_3.length === 0) {
|
|
674
698
|
throw new Error("No nodes configured for ".concat(symbol));
|
|
675
699
|
}
|
|
@@ -678,37 +702,77 @@ var get_utxos_by_xpub = function (coin, xpub) {
|
|
|
678
702
|
throw new Error("No active nodes available for ".concat(symbol));
|
|
679
703
|
}
|
|
680
704
|
log.info(tag, "Attempting UTXO query for ".concat(symbol, " with ").concat(activeNodes.length, " nodes available"));
|
|
705
|
+
if (isBitcoin) {
|
|
706
|
+
log.info(tag, '🔍 [BITCOIN BLOCKBOOK] Available nodes:', activeNodes.map(function (n) { return ({
|
|
707
|
+
url: n.url,
|
|
708
|
+
priority: n.priority,
|
|
709
|
+
isActive: n.isActive
|
|
710
|
+
}); }));
|
|
711
|
+
}
|
|
681
712
|
i = 0;
|
|
682
|
-
|
|
713
|
+
_b.label = 2;
|
|
683
714
|
case 2:
|
|
684
715
|
if (!(i < activeNodes.length)) return [3 /*break*/, 7];
|
|
685
716
|
node = activeNodes[i];
|
|
686
717
|
startTime = Date.now();
|
|
687
|
-
|
|
718
|
+
_b.label = 3;
|
|
688
719
|
case 3:
|
|
689
|
-
|
|
720
|
+
_b.trys.push([3, 5, , 6]);
|
|
690
721
|
log.info(tag, "Trying node ".concat(i + 1, "/").concat(activeNodes.length, ": ").concat(node.url, " (priority: ").concat(node.priority, ")"));
|
|
691
722
|
url = node.url + "/api/v2/utxo/" + xpub + "?confirmed=false";
|
|
692
723
|
body = {
|
|
693
724
|
method: 'GET',
|
|
694
725
|
url: url,
|
|
726
|
+
headers: {
|
|
727
|
+
'content-type': 'application/json',
|
|
728
|
+
'User-Agent': fakeUa()
|
|
729
|
+
},
|
|
695
730
|
timeout: 15000 // 15s timeout per node
|
|
696
731
|
};
|
|
732
|
+
if (isBitcoin) {
|
|
733
|
+
log.info(tag, '🔍 [BITCOIN BLOCKBOOK] Request details:', {
|
|
734
|
+
url: url,
|
|
735
|
+
method: body.method,
|
|
736
|
+
timeout: body.timeout,
|
|
737
|
+
headers: body.headers
|
|
738
|
+
});
|
|
739
|
+
}
|
|
697
740
|
return [4 /*yield*/, axios(body)];
|
|
698
741
|
case 4:
|
|
699
|
-
resp =
|
|
742
|
+
resp = _b.sent();
|
|
700
743
|
responseTime = Date.now() - startTime;
|
|
744
|
+
if (isBitcoin) {
|
|
745
|
+
log.info(tag, '🔍 [BITCOIN BLOCKBOOK] Raw API response:', {
|
|
746
|
+
status: resp.status,
|
|
747
|
+
statusText: resp.statusText,
|
|
748
|
+
dataType: typeof resp.data,
|
|
749
|
+
dataIsArray: Array.isArray(resp.data),
|
|
750
|
+
dataLength: (_a = resp.data) === null || _a === void 0 ? void 0 : _a.length,
|
|
751
|
+
data: resp.data
|
|
752
|
+
});
|
|
753
|
+
}
|
|
701
754
|
// Update node performance metrics
|
|
702
755
|
update_node_performance(symbol, node.url, true, responseTime);
|
|
703
756
|
log.info(tag, "\u2705 Node ".concat(i + 1, " succeeded in ").concat(responseTime, "ms, returned ").concat(resp.data.length, " UTXOs"));
|
|
757
|
+
if (isBitcoin) {
|
|
758
|
+
log.info(tag, '🔍 [BITCOIN BLOCKBOOK] Returning UTXOs:', resp.data);
|
|
759
|
+
}
|
|
704
760
|
return [2 /*return*/, resp.data];
|
|
705
761
|
case 5:
|
|
706
|
-
error_2 =
|
|
762
|
+
error_2 = _b.sent();
|
|
707
763
|
responseTime = Date.now() - startTime;
|
|
708
764
|
// Update node performance metrics
|
|
709
765
|
update_node_performance(symbol, node.url, false, responseTime);
|
|
710
766
|
errorMessage = error_2 instanceof Error ? error_2.message : String(error_2);
|
|
711
767
|
log.warn(tag, "\u274C Node ".concat(i + 1, " failed after ").concat(responseTime, "ms:"), errorMessage);
|
|
768
|
+
if (isBitcoin) {
|
|
769
|
+
log.error(tag, '🔍 [BITCOIN BLOCKBOOK] Node failed with error:', {
|
|
770
|
+
nodeUrl: node.url,
|
|
771
|
+
errorMessage: errorMessage,
|
|
772
|
+
errorType: error_2 instanceof Error ? error_2.constructor.name : typeof error_2,
|
|
773
|
+
fullError: error_2
|
|
774
|
+
});
|
|
775
|
+
}
|
|
712
776
|
// If this is the last node, throw the error
|
|
713
777
|
if (i === activeNodes.length - 1) {
|
|
714
778
|
throw new Error("All ".concat(activeNodes.length, " nodes failed for ").concat(symbol, ". Last error: ").concat(errorMessage));
|
|
@@ -720,7 +784,10 @@ var get_utxos_by_xpub = function (coin, xpub) {
|
|
|
720
784
|
return [3 /*break*/, 2];
|
|
721
785
|
case 7: return [3 /*break*/, 9];
|
|
722
786
|
case 8:
|
|
723
|
-
e_9 =
|
|
787
|
+
e_9 = _b.sent();
|
|
788
|
+
if (isBitcoin) {
|
|
789
|
+
log.error(tag, '🔍 [BITCOIN BLOCKBOOK] Final error after all nodes failed:', e_9);
|
|
790
|
+
}
|
|
724
791
|
console.error(tag, e_9);
|
|
725
792
|
throw e_9;
|
|
726
793
|
case 9: return [2 /*return*/];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pioneer-platform/blockbook",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.11.1",
|
|
4
4
|
"main": "./lib/index.js",
|
|
5
5
|
"types": "./lib/index.d.ts",
|
|
6
6
|
"scripts": {
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
"build:live": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/index.ts"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@pioneer-platform/loggerdog": "^8.
|
|
15
|
-
"@pioneer-platform/nodes": "^8.10
|
|
16
|
-
"@pioneer-platform/pioneer-caip": "^9.
|
|
14
|
+
"@pioneer-platform/loggerdog": "^8.11.0",
|
|
15
|
+
"@pioneer-platform/nodes": "^8.11.10",
|
|
16
|
+
"@pioneer-platform/pioneer-caip": "^9.10.0",
|
|
17
17
|
"@types/request-promise-native": "^1.0.17",
|
|
18
18
|
"axiom": "^0.1.6",
|
|
19
19
|
"axios": "^1.6.0",
|
|
@@ -32,4 +32,4 @@
|
|
|
32
32
|
],
|
|
33
33
|
"author": "highlander",
|
|
34
34
|
"license": "MIT"
|
|
35
|
-
}
|
|
35
|
+
}
|