@pioneer-platform/pioneer-sdk 4.20.0 → 4.20.2

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,510 @@
1
+ /**
2
+ * Fee Management Module for Pioneer SDK
3
+ *
4
+ * Handles all fee-related complexity, normalization, and provides
5
+ * a clean, consistent interface for the frontend.
6
+ */
7
+
8
+ const TAG = ' | Pioneer-sdk | fees | ';
9
+
10
+ export interface FeeLevel {
11
+ label: string;
12
+ value: string;
13
+ unit: string;
14
+ description: string;
15
+ estimatedTime?: string;
16
+ priority: 'low' | 'medium' | 'high';
17
+ }
18
+
19
+ export interface NormalizedFeeRates {
20
+ slow: FeeLevel;
21
+ average: FeeLevel;
22
+ fastest: FeeLevel;
23
+ networkId: string;
24
+ networkType: 'UTXO' | 'EVM' | 'COSMOS' | 'RIPPLE' | 'OTHER';
25
+ raw: any; // Original API response for debugging
26
+ }
27
+
28
+ export interface FeeEstimate {
29
+ amount: string;
30
+ unit: string;
31
+ usdValue?: string;
32
+ }
33
+
34
+ // Network type detection
35
+ function getNetworkType(networkId: string): 'UTXO' | 'EVM' | 'COSMOS' | 'RIPPLE' | 'OTHER' {
36
+ if (networkId.startsWith('bip122:')) return 'UTXO';
37
+ if (networkId.startsWith('eip155:')) return 'EVM';
38
+ if (networkId.startsWith('cosmos:')) return 'COSMOS';
39
+ if (networkId.startsWith('ripple:')) return 'RIPPLE';
40
+ return 'OTHER';
41
+ }
42
+
43
+ // Get human-readable network name
44
+ function getNetworkName(networkId: string): string {
45
+ const networkNames: Record<string, string> = {
46
+ 'bip122:000000000019d6689c085ae165831e93': 'Bitcoin',
47
+ 'bip122:12a765e31ffd4059bada1e25190f6e98': 'Litecoin',
48
+ 'bip122:00000000001a91e3dace36e2be3bf030': 'Dogecoin',
49
+ 'bip122:000000000000000000651ef99cb9fcbe': 'Bitcoin Cash',
50
+ 'bip122:000007d91d1254d60e2dd1ae58038307': 'Dash',
51
+ 'eip155:1': 'Ethereum',
52
+ 'eip155:56': 'BNB Smart Chain',
53
+ 'eip155:137': 'Polygon',
54
+ 'eip155:43114': 'Avalanche',
55
+ 'eip155:8453': 'Base',
56
+ 'eip155:10': 'Optimism',
57
+ 'cosmos:cosmoshub-4': 'Cosmos Hub',
58
+ 'cosmos:osmosis-1': 'Osmosis',
59
+ 'cosmos:thorchain-mainnet-v1': 'THORChain',
60
+ 'cosmos:mayachain-mainnet-v1': 'Maya',
61
+ 'ripple:4109c6f2045fc7eff4cde8f9905d19c2': 'Ripple',
62
+ };
63
+ return networkNames[networkId] || networkId;
64
+ }
65
+
66
+ /**
67
+ * Main fee fetching and normalization function
68
+ * Handles all the complexity of different API formats and returns
69
+ * a clean, normalized structure for the UI
70
+ */
71
+ export async function getFees(
72
+ pioneer: any,
73
+ networkId: string
74
+ ): Promise<NormalizedFeeRates> {
75
+ const tag = TAG + ' | getFees | ';
76
+
77
+ try {
78
+ console.log(tag, `Fetching fees for network: ${networkId}`);
79
+
80
+ // For Cosmos chains, always use hardcoded fees
81
+ const networkType = getNetworkType(networkId);
82
+ if (networkType === 'COSMOS') {
83
+ console.log(tag, 'Using hardcoded fees for Cosmos network:', networkId);
84
+ return getCosmosFees(networkId);
85
+ }
86
+
87
+ // Get raw fee data from API
88
+ const feeResponse = await (pioneer.GetFeeRateByNetwork
89
+ ? pioneer.GetFeeRateByNetwork({ networkId })
90
+ : pioneer.GetFeeRate({ networkId }));
91
+
92
+ if (!feeResponse || !feeResponse.data) {
93
+ throw new Error(`No fee data returned for ${networkId}`);
94
+ }
95
+
96
+ const feeData = feeResponse.data;
97
+ console.log(tag, 'Raw fee data:', feeData);
98
+
99
+ // Network type already detected above, just get network name
100
+ const networkName = getNetworkName(networkId);
101
+
102
+ // Normalize the fee data based on format (pass networkId for sanity checks)
103
+ let normalizedFees = normalizeFeeData(feeData, networkType, networkName, networkId);
104
+
105
+ // Ensure fees are differentiated for better UX
106
+ normalizedFees = ensureFeeDifferentiation(normalizedFees, networkType);
107
+
108
+ // Add network metadata
109
+ normalizedFees.networkId = networkId;
110
+ normalizedFees.networkType = networkType;
111
+ normalizedFees.raw = feeData;
112
+
113
+ console.log(tag, 'Normalized fees:', normalizedFees);
114
+ return normalizedFees;
115
+
116
+ } catch (error: any) {
117
+ console.error(tag, 'Failed to fetch fees:', error);
118
+
119
+ // Return sensible defaults on error
120
+ return getFallbackFees(networkId);
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Normalize fee data from various API formats to consistent UI format
126
+ */
127
+ function normalizeFeeData(
128
+ feeData: any,
129
+ networkType: string,
130
+ networkName: string,
131
+ networkId?: string
132
+ ): NormalizedFeeRates {
133
+ // Check which format the API returned
134
+ const hasSlowAverageFastest = feeData.slow !== undefined &&
135
+ feeData.average !== undefined &&
136
+ feeData.fastest !== undefined;
137
+
138
+ const hasAverageFastFastest = feeData.average !== undefined &&
139
+ feeData.fast !== undefined &&
140
+ feeData.fastest !== undefined;
141
+
142
+ let slowValue: string, averageValue: string, fastestValue: string;
143
+
144
+ if (hasSlowAverageFastest) {
145
+ // Already in UI format
146
+ slowValue = feeData.slow.toString();
147
+ averageValue = feeData.average.toString();
148
+ fastestValue = feeData.fastest.toString();
149
+ } else if (hasAverageFastFastest) {
150
+ // Map API format to UI format
151
+ slowValue = feeData.average.toString();
152
+ averageValue = feeData.fast.toString();
153
+ fastestValue = feeData.fastest.toString();
154
+ } else {
155
+ throw new Error('Unknown fee data format');
156
+ }
157
+
158
+ // Apply sanity checks for UTXO networks to prevent absurdly high fees
159
+ // The API sometimes returns stale or incorrect data (e.g., DOGE returning 50000 sat/byte instead of 50)
160
+ if (networkType === 'UTXO') {
161
+ const sanityLimits: Record<string, number> = {
162
+ 'bip122:00000000001a91e3dace36e2be3bf030': 100, // DOGE max 100 sat/byte (typical: 1-10)
163
+ 'bip122:12a765e31ffd4059bada1e25190f6e98': 500, // LTC max 500 sat/byte
164
+ 'bip122:000000000000000000651ef99cb9fcbe': 50, // BCH max 50 sat/byte (low fee chain)
165
+ 'bip122:000007d91d1254d60e2dd1ae58038307': 50, // DASH max 50 sat/byte (low fee chain)
166
+ 'bip122:000000000019d6689c085ae165831e93': 5000, // BTC max 5000 sat/byte (can spike during congestion)
167
+ };
168
+
169
+ const matchedNetworkId = networkId && sanityLimits[networkId] ? networkId :
170
+ Object.keys(sanityLimits).find(id => networkName.toLowerCase().includes(id.split(':')[1]?.substring(0, 8)));
171
+
172
+ if (matchedNetworkId && sanityLimits[matchedNetworkId]) {
173
+ const limit = sanityLimits[matchedNetworkId];
174
+ const slowNum = parseFloat(slowValue);
175
+ const avgNum = parseFloat(averageValue);
176
+ const fastestNum = parseFloat(fastestValue);
177
+
178
+ if (slowNum > limit || avgNum > limit || fastestNum > limit) {
179
+ console.warn(`[FEES] Detected absurdly high fees for ${networkName}: slow=${slowNum}, avg=${avgNum}, fastest=${fastestNum}`);
180
+ console.warn(`[FEES] Capping fees to reasonable limits (max: ${limit} sat/byte)`);
181
+
182
+ // Cap to reasonable values - use 10% of limit as conservative default
183
+ const safeFee = (limit * 0.1).toFixed(2);
184
+ const mediumFee = (limit * 0.15).toFixed(2);
185
+ const fastFee = (limit * 0.2).toFixed(2);
186
+
187
+ slowValue = safeFee;
188
+ averageValue = mediumFee;
189
+ fastestValue = fastFee;
190
+
191
+ console.warn(`[FEES] Adjusted to: slow=${slowValue}, avg=${averageValue}, fastest=${fastestValue}`);
192
+ }
193
+ }
194
+ }
195
+
196
+ // Get unit and descriptions based on network type
197
+ const unit = feeData.unit || getDefaultUnit(networkType);
198
+ const baseDescription = feeData.description || getDefaultDescription(networkType, networkName);
199
+
200
+ return {
201
+ slow: {
202
+ label: 'Economy',
203
+ value: slowValue,
204
+ unit,
205
+ description: `${baseDescription} - Lower priority, may take longer to confirm.`,
206
+ estimatedTime: getEstimatedTime(networkType, 'low'),
207
+ priority: 'low',
208
+ },
209
+ average: {
210
+ label: 'Standard',
211
+ value: averageValue,
212
+ unit,
213
+ description: `${baseDescription} - Normal priority, typical confirmation time.`,
214
+ estimatedTime: getEstimatedTime(networkType, 'medium'),
215
+ priority: 'medium',
216
+ },
217
+ fastest: {
218
+ label: 'Priority',
219
+ value: fastestValue,
220
+ unit,
221
+ description: `${baseDescription} - High priority, fastest confirmation.`,
222
+ estimatedTime: getEstimatedTime(networkType, 'high'),
223
+ priority: 'high',
224
+ },
225
+ networkId: '',
226
+ networkType: networkType as any,
227
+ raw: feeData,
228
+ };
229
+ }
230
+
231
+ /**
232
+ * Ensure fees are differentiated for better UX
233
+ */
234
+ function ensureFeeDifferentiation(
235
+ fees: NormalizedFeeRates,
236
+ networkType: string
237
+ ): NormalizedFeeRates {
238
+ const slowVal = parseFloat(fees.slow.value) || 0;
239
+ const avgVal = parseFloat(fees.average.value) || 0;
240
+ const fastestVal = parseFloat(fees.fastest.value) || 0;
241
+
242
+ // Check if all values are zero
243
+ if (slowVal === 0 && avgVal === 0 && fastestVal === 0) {
244
+ console.warn('All fee values are 0 - using fallback values');
245
+ // Return sensible defaults based on network type
246
+ if (networkType === 'UTXO') {
247
+ return {
248
+ ...fees,
249
+ slow: { ...fees.slow, value: '1' },
250
+ average: { ...fees.average, value: '2' },
251
+ fastest: { ...fees.fastest, value: '3' },
252
+ };
253
+ } else {
254
+ return {
255
+ ...fees,
256
+ slow: { ...fees.slow, value: '1' },
257
+ average: { ...fees.average, value: '1.5' },
258
+ fastest: { ...fees.fastest, value: '2' },
259
+ };
260
+ }
261
+ }
262
+
263
+ // For UTXO networks with very similar values (like 1, 1, 1.01)
264
+ if (networkType === 'UTXO') {
265
+ const diff = fastestVal - slowVal;
266
+ if (diff < 0.5) {
267
+ console.warn('UTXO fees too similar, adjusting for better UX');
268
+ return {
269
+ ...fees,
270
+ slow: { ...fees.slow, value: slowVal.toString() },
271
+ average: { ...fees.average, value: (slowVal + 1).toString() },
272
+ fastest: { ...fees.fastest, value: (slowVal + 2).toString() },
273
+ };
274
+ }
275
+ }
276
+
277
+ // For EVM networks, check if values are already well differentiated
278
+ // Don't adjust if there's already good separation
279
+ const slowToAvgRatio = avgVal / slowVal;
280
+ const avgToFastRatio = fastestVal / avgVal;
281
+
282
+ // If ratios show good differentiation (at least 10% difference), keep original
283
+ if (slowToAvgRatio >= 1.1 && avgToFastRatio >= 1.1) {
284
+ return fees; // Already well differentiated
285
+ }
286
+
287
+ // Only adjust if fees are too similar
288
+ console.warn('Fees not well differentiated, adjusting slightly');
289
+ return {
290
+ ...fees,
291
+ slow: { ...fees.slow, value: slowVal.toString() },
292
+ average: { ...fees.average, value: (slowVal * 1.2).toFixed(6) },
293
+ fastest: { ...fees.fastest, value: (slowVal * 1.5).toFixed(6) },
294
+ };
295
+ }
296
+
297
+ /**
298
+ * Get default unit based on network type
299
+ */
300
+ function getDefaultUnit(networkType: string): string {
301
+ switch (networkType) {
302
+ case 'UTXO':
303
+ return 'sat/vB';
304
+ case 'EVM':
305
+ return 'gwei';
306
+ case 'COSMOS':
307
+ return 'uatom';
308
+ case 'RIPPLE':
309
+ return 'XRP';
310
+ default:
311
+ return 'units';
312
+ }
313
+ }
314
+
315
+ /**
316
+ * Get default description based on network type
317
+ */
318
+ function getDefaultDescription(networkType: string, networkName: string): string {
319
+ switch (networkType) {
320
+ case 'UTXO':
321
+ return `Fee rate in satoshis per virtual byte for ${networkName}`;
322
+ case 'EVM':
323
+ return `Gas price in Gwei for ${networkName} (1 Gwei = 0.000000001 ETH)`;
324
+ case 'COSMOS':
325
+ return `Transaction fee for ${networkName}`;
326
+ case 'RIPPLE':
327
+ return `Fixed transaction fee for ${networkName}`;
328
+ default:
329
+ return `Transaction fee for ${networkName}`;
330
+ }
331
+ }
332
+
333
+ /**
334
+ * Get estimated confirmation time
335
+ */
336
+ function getEstimatedTime(networkType: string, priority: string): string {
337
+ const times: Record<string, Record<string, string>> = {
338
+ UTXO: {
339
+ low: '~60+ minutes',
340
+ medium: '~30 minutes',
341
+ high: '~10 minutes',
342
+ },
343
+ EVM: {
344
+ low: '~5 minutes',
345
+ medium: '~2 minutes',
346
+ high: '~30 seconds',
347
+ },
348
+ COSMOS: {
349
+ low: '~10 seconds',
350
+ medium: '~7 seconds',
351
+ high: '~5 seconds',
352
+ },
353
+ RIPPLE: {
354
+ low: '~4 seconds',
355
+ medium: '~4 seconds',
356
+ high: '~4 seconds',
357
+ },
358
+ };
359
+
360
+ return times[networkType]?.[priority] || '~varies';
361
+ }
362
+
363
+ /**
364
+ * Get hardcoded Cosmos fees based on network
365
+ */
366
+ function getCosmosFees(networkId: string): NormalizedFeeRates {
367
+ const networkName = getNetworkName(networkId);
368
+
369
+ // These match the fees in txbuilder/createUnsignedTendermintTx.ts
370
+ const cosmosFeesMap: Record<string, { base: number; unit: string; denom: string }> = {
371
+ 'cosmos:thorchain-mainnet-v1': { base: 0.02, unit: 'RUNE', denom: 'rune' },
372
+ 'cosmos:mayachain-mainnet-v1': { base: 0.2, unit: 'MAYA', denom: 'maya' },
373
+ 'cosmos:cosmoshub-4': { base: 0.005, unit: 'ATOM', denom: 'uatom' },
374
+ 'cosmos:osmosis-1': { base: 0.035, unit: 'OSMO', denom: 'uosmo' },
375
+ };
376
+
377
+ const feeConfig = cosmosFeesMap[networkId] || { base: 0.025, unit: 'units', denom: 'units' };
378
+
379
+ // For Cosmos, we provide the base fee with different priority multipliers
380
+ const slowFee = feeConfig.base.toString();
381
+ const avgFee = (feeConfig.base * 1.5).toFixed(4);
382
+ const fastFee = (feeConfig.base * 2).toFixed(4);
383
+
384
+ return {
385
+ slow: {
386
+ label: 'Economy',
387
+ value: slowFee,
388
+ unit: feeConfig.unit,
389
+ description: `Standard fee for ${networkName}. Gas is automatically calculated.`,
390
+ estimatedTime: '~10 seconds',
391
+ priority: 'low',
392
+ },
393
+ average: {
394
+ label: 'Standard',
395
+ value: avgFee,
396
+ unit: feeConfig.unit,
397
+ description: `Priority fee for ${networkName}. Slightly higher for faster processing.`,
398
+ estimatedTime: '~7 seconds',
399
+ priority: 'medium',
400
+ },
401
+ fastest: {
402
+ label: 'Priority',
403
+ value: fastFee,
404
+ unit: feeConfig.unit,
405
+ description: `Maximum priority for ${networkName}. Fastest possible confirmation.`,
406
+ estimatedTime: '~5 seconds',
407
+ priority: 'high',
408
+ },
409
+ networkId,
410
+ networkType: 'COSMOS',
411
+ raw: { hardcoded: true, base: feeConfig.base, unit: feeConfig.unit, denom: feeConfig.denom },
412
+ };
413
+ }
414
+
415
+ /**
416
+ * Get fallback fees when API fails
417
+ */
418
+ function getFallbackFees(networkId: string): NormalizedFeeRates {
419
+ const networkType = getNetworkType(networkId);
420
+ const networkName = getNetworkName(networkId);
421
+
422
+ // For Cosmos chains, use hardcoded fees
423
+ if (networkType === 'COSMOS') {
424
+ return getCosmosFees(networkId);
425
+ }
426
+
427
+ // Default fallback values by network type
428
+ const fallbacks: Record<string, { slow: string; average: string; fastest: string; unit: string }> = {
429
+ UTXO: { slow: '1', average: '2', fastest: '3', unit: 'sat/vB' },
430
+ EVM: { slow: '1', average: '1.5', fastest: '2', unit: 'gwei' },
431
+ RIPPLE: { slow: '0.00001', average: '0.00001', fastest: '0.00001', unit: 'XRP' },
432
+ };
433
+
434
+ const fallback = fallbacks[networkType] || fallbacks.UTXO;
435
+
436
+ return {
437
+ slow: {
438
+ label: 'Economy',
439
+ value: fallback.slow,
440
+ unit: fallback.unit,
441
+ description: `Default fee for ${networkName} (API unavailable)`,
442
+ estimatedTime: getEstimatedTime(networkType, 'low'),
443
+ priority: 'low',
444
+ },
445
+ average: {
446
+ label: 'Standard',
447
+ value: fallback.average,
448
+ unit: fallback.unit,
449
+ description: `Default fee for ${networkName} (API unavailable)`,
450
+ estimatedTime: getEstimatedTime(networkType, 'medium'),
451
+ priority: 'medium',
452
+ },
453
+ fastest: {
454
+ label: 'Priority',
455
+ value: fallback.fastest,
456
+ unit: fallback.unit,
457
+ description: `Default fee for ${networkName} (API unavailable)`,
458
+ estimatedTime: getEstimatedTime(networkType, 'high'),
459
+ priority: 'high',
460
+ },
461
+ networkId,
462
+ networkType: networkType as any,
463
+ raw: null,
464
+ };
465
+ }
466
+
467
+ /**
468
+ * Calculate estimated transaction fee based on fee rate and transaction size
469
+ */
470
+ export function estimateTransactionFee(
471
+ feeRate: string,
472
+ unit: string,
473
+ networkType: string,
474
+ txSize?: number
475
+ ): FeeEstimate {
476
+ switch (networkType) {
477
+ case 'UTXO':
478
+ // For UTXO chains, multiply fee rate by transaction size
479
+ const sizeInBytes = txSize || 250; // Default estimate
480
+ const feeInSatoshis = parseFloat(feeRate) * sizeInBytes;
481
+ const feeInBTC = feeInSatoshis / 100000000;
482
+ return {
483
+ amount: feeInBTC.toFixed(8),
484
+ unit: 'BTC',
485
+ };
486
+
487
+ case 'EVM':
488
+ // For EVM chains, multiply gas price by gas limit
489
+ const gasLimit = 21000; // Standard transfer
490
+ const feeInGwei = parseFloat(feeRate) * gasLimit;
491
+ const feeInEth = feeInGwei / 1000000000;
492
+ return {
493
+ amount: feeInEth.toFixed(9),
494
+ unit: 'ETH',
495
+ };
496
+
497
+ case 'RIPPLE':
498
+ // Ripple has fixed fees
499
+ return {
500
+ amount: feeRate,
501
+ unit: 'XRP',
502
+ };
503
+
504
+ default:
505
+ return {
506
+ amount: feeRate,
507
+ unit: unit,
508
+ };
509
+ }
510
+ }
package/src/getPubkey.ts CHANGED
@@ -47,45 +47,31 @@ export const getPubkey = async (networkId: string, path: any, sdk: any, context:
47
47
  }, 30000);
48
48
 
49
49
  try {
50
- let result;
51
50
  switch (networkType) {
52
51
  case 'UTXO':
53
- result = await sdk.address.utxoGetAddress(addressInfo);
52
+ ({ address } = await sdk.address.utxoGetAddress(addressInfo));
54
53
  break;
55
54
  case 'EVM':
56
- result = await sdk.address.ethereumGetAddress(addressInfo);
55
+ ({ address } = await sdk.address.ethereumGetAddress(addressInfo));
57
56
  break;
58
57
  case 'OSMOSIS':
59
- result = await sdk.address.osmosisGetAddress(addressInfo);
58
+ ({ address } = await sdk.address.osmosisGetAddress(addressInfo));
60
59
  break;
61
60
  case 'COSMOS':
62
- result = await sdk.address.cosmosGetAddress(addressInfo);
61
+ ({ address } = await sdk.address.cosmosGetAddress(addressInfo));
63
62
  break;
64
63
  case 'MAYACHAIN':
65
- result = await sdk.address.mayachainGetAddress(addressInfo);
64
+ ({ address } = await sdk.address.mayachainGetAddress(addressInfo));
66
65
  break;
67
66
  case 'THORCHAIN':
68
- result = await sdk.address.thorchainGetAddress(addressInfo);
67
+ ({ address } = await sdk.address.thorchainGetAddress(addressInfo));
69
68
  break;
70
69
  case 'XRP':
71
- result = await sdk.address.xrpGetAddress(addressInfo);
70
+ ({ address } = await sdk.address.xrpGetAddress(addressInfo));
72
71
  break;
73
72
  default:
74
73
  throw new Error(`Unsupported network type for networkId: ${networkId}`);
75
74
  }
76
-
77
- if (!result) {
78
- throw new Error(`Address call returned null/undefined for ${networkType} (${networkId})`);
79
- }
80
-
81
- if (typeof result === 'object' && result.address) {
82
- address = result.address;
83
- } else if (typeof result === 'string') {
84
- address = result;
85
- } else {
86
- throw new Error(`Invalid address response format for ${networkType} (${networkId}): ${JSON.stringify(result)}`);
87
- }
88
-
89
75
  clearTimeout(addressTimeout);
90
76
  } catch (addressError) {
91
77
  clearTimeout(addressTimeout);
@@ -99,6 +85,14 @@ export const getPubkey = async (networkId: string, path: any, sdk: any, context:
99
85
  pubkey.master = address;
100
86
  pubkey.address = address;
101
87
  if (['xpub', 'ypub', 'zpub'].includes(path.type)) {
88
+ // ========================================
89
+ // XPUB RETRIEVAL - CRITICAL FOR UTXO CHAINS
90
+ // ========================================
91
+ console.log('🔑 [XPUB] Starting xpub retrieval for path type:', path.type);
92
+ console.log('🔑 [XPUB] Network:', networkId);
93
+ console.log('🔑 [XPUB] Path:', addressNListToBIP32(path.addressNList));
94
+ console.log('🔑 [XPUB] Script type:', path.script_type);
95
+
102
96
  const pathQuery = {
103
97
  symbol: 'BTC',
104
98
  coin: 'Bitcoin',
@@ -106,30 +100,67 @@ export const getPubkey = async (networkId: string, path: any, sdk: any, context:
106
100
  address_n: path.addressNList,
107
101
  showDisplay: false,
108
102
  };
109
-
103
+
104
+ console.log('🔑 [XPUB] Calling sdk.system.info.getPublicKey with:', JSON.stringify(pathQuery, null, 2));
105
+
110
106
  const xpubTimeout = setTimeout(() => {
111
- console.error('getPublicKey timeout after 20 seconds');
107
+ console.error('⏰ [XPUB TIMEOUT] getPublicKey timeout after 20 seconds');
108
+ console.error('⏰ [XPUB TIMEOUT] Path:', addressNListToBIP32(path.addressNList));
109
+ console.error('⏰ [XPUB TIMEOUT] This is a CRITICAL FAILURE - UTXO balances require xpubs');
112
110
  }, 20000);
113
-
111
+
114
112
  try {
113
+ console.log('🔑 [XPUB] Calling getPublicKey...');
115
114
  const responsePubkey = await sdk.system.info.getPublicKey(pathQuery);
116
115
  clearTimeout(xpubTimeout);
117
116
 
117
+ console.log('✅ [XPUB] getPublicKey SUCCESS');
118
+ console.log('✅ [XPUB] Response:', JSON.stringify(responsePubkey, null, 2));
119
+
120
+ if (!responsePubkey || !responsePubkey.xpub) {
121
+ const error = new Error('FAIL FAST: getPublicKey returned null or missing xpub');
122
+ console.error('❌ [XPUB] CRITICAL:', error.message);
123
+ console.error('❌ [XPUB] Response was:', responsePubkey);
124
+ throw error;
125
+ }
126
+
118
127
  if (path.script_type === 'p2wpkh') {
128
+ console.log('🔑 [XPUB] Converting xpub to zpub for native segwit');
119
129
  responsePubkey.xpub = xpubConvert(responsePubkey.xpub, 'zpub');
120
130
  } else if (path.script_type === 'p2sh-p2wpkh') {
131
+ console.log('🔑 [XPUB] Converting xpub to ypub for wrapped segwit');
121
132
  responsePubkey.xpub = xpubConvert(responsePubkey.xpub, 'ypub');
122
133
  }
123
134
 
135
+ console.log('✅ [XPUB] Final xpub:', responsePubkey.xpub.substring(0, 20) + '...');
136
+
124
137
  pubkey.pubkey = responsePubkey.xpub;
125
138
  pubkey.path = addressNListToBIP32(path.addressNList);
126
139
  pubkey.pathMaster = addressNListToBIP32(path.addressNListMaster);
140
+
141
+ console.log('✅ [XPUB] Xpub retrieval COMPLETE for', path.note || path.type);
127
142
  } catch (xpubError) {
128
143
  clearTimeout(xpubTimeout);
129
- console.error('getPublicKey failed:', xpubError);
130
- throw xpubError;
144
+ console.error('❌ [XPUB] CRITICAL FAILURE - getPublicKey failed');
145
+ console.error('❌ [XPUB] Error:', xpubError);
146
+ console.error('❌ [XPUB] Error message:', xpubError.message);
147
+ console.error('❌ [XPUB] Error stack:', xpubError.stack);
148
+ console.error('❌ [XPUB] Path:', addressNListToBIP32(path.addressNList));
149
+ console.error('❌ [XPUB] Network:', networkId);
150
+ console.error('❌ [XPUB] Query:', JSON.stringify(pathQuery, null, 2));
151
+ console.error('❌ [XPUB] SDK available:', !!sdk);
152
+ console.error('❌ [XPUB] SDK.system available:', !!sdk?.system);
153
+ console.error('❌ [XPUB] SDK.system.info available:', !!sdk?.system?.info);
154
+ console.error('❌ [XPUB] SDK.system.info.getPublicKey available:', !!sdk?.system?.info?.getPublicKey);
155
+ console.error('');
156
+ console.error('🚨 FAIL FAST: Cannot proceed without xpub for UTXO chains');
157
+ console.error('🚨 UTXO balance queries REQUIRE extended public keys (xpubs)');
158
+ console.error('🚨 This is a device communication or SDK issue that must be resolved');
159
+ console.error('');
160
+ throw new Error(`FAIL FAST - xpub retrieval failed for ${networkId} at ${addressNListToBIP32(path.addressNList)}: ${xpubError.message}`);
131
161
  }
132
162
  } else {
163
+ console.log('🔑 [PUBKEY] Non-xpub path (address-based), using address as pubkey');
133
164
  pubkey.pubkey = address;
134
165
  pubkey.path = addressNListToBIP32(path.addressNList);
135
166
  pubkey.pathMaster = addressNListToBIP32(path.addressNListMaster);