@pioneer-platform/pioneer-sdk 0.0.82 → 4.14.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,620 @@
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
+ // For Ripple, always use hardcoded fees (API doesn't support it)
88
+ if (networkType === 'RIPPLE') {
89
+ console.log(tag, 'Using hardcoded fees for Ripple:', networkId);
90
+ return getRippleFees(networkId);
91
+ }
92
+
93
+ // Hardcode DOGE fees at 10 sat/byte (API and Blockbook often unreliable for DOGE)
94
+ if (networkId === 'bip122:00000000001a91e3dace36e2be3bf030') {
95
+ console.log(tag, 'Using hardcoded fees for Dogecoin: 10 sat/byte');
96
+ return {
97
+ slow: {
98
+ label: 'Slow',
99
+ value: '10',
100
+ unit: 'sat/byte',
101
+ description: 'Low priority - 30+ minutes',
102
+ estimatedTime: '~30 minutes',
103
+ priority: 'low'
104
+ },
105
+ average: {
106
+ label: 'Average',
107
+ value: '10',
108
+ unit: 'sat/byte',
109
+ description: 'Normal priority - 10-30 minutes',
110
+ estimatedTime: '~15 minutes',
111
+ priority: 'medium'
112
+ },
113
+ fastest: {
114
+ label: 'Fast',
115
+ value: '10',
116
+ unit: 'sat/byte',
117
+ description: 'High priority - next block',
118
+ estimatedTime: '~5 minutes',
119
+ priority: 'high'
120
+ },
121
+ networkId,
122
+ networkType: 'UTXO',
123
+ raw: { hardcoded: true, reason: 'DOGE fee estimation unreliable' }
124
+ };
125
+ }
126
+
127
+ // Get raw fee data from API
128
+ const feeResponse = await (pioneer.GetFeeRateByNetwork
129
+ ? pioneer.GetFeeRateByNetwork({ networkId })
130
+ : pioneer.GetFeeRate({ networkId }));
131
+
132
+ if (!feeResponse || !feeResponse.data) {
133
+ throw new Error(`No fee data returned for ${networkId}`);
134
+ }
135
+
136
+ const feeData = feeResponse.data;
137
+ console.log(tag, 'Raw fee data:', feeData);
138
+
139
+ // Check if API returned an error response
140
+ if (feeData.success === false || feeData.error) {
141
+ throw new Error(feeData.error || `API error for ${networkId}`);
142
+ }
143
+
144
+ // Network type already detected above, just get network name
145
+ const networkName = getNetworkName(networkId);
146
+
147
+ // Normalize the fee data based on format (pass networkId for sanity checks)
148
+ let normalizedFees = normalizeFeeData(feeData, networkType, networkName, networkId);
149
+
150
+ // Ensure fees are differentiated for better UX
151
+ normalizedFees = ensureFeeDifferentiation(normalizedFees, networkType);
152
+
153
+ // Add network metadata
154
+ normalizedFees.networkId = networkId;
155
+ normalizedFees.networkType = networkType;
156
+ normalizedFees.raw = feeData;
157
+
158
+ console.log(tag, 'Normalized fees:', normalizedFees);
159
+ return normalizedFees;
160
+
161
+ } catch (error: any) {
162
+ console.error(tag, 'Failed to fetch fees:', error);
163
+
164
+ // Return sensible defaults on error
165
+ return getFallbackFees(networkId);
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Normalize fee data from various API formats to consistent UI format
171
+ */
172
+ function normalizeFeeData(
173
+ feeData: any,
174
+ networkType: string,
175
+ networkName: string,
176
+ networkId?: string
177
+ ): NormalizedFeeRates {
178
+ // Check which format the API returned
179
+ const hasSlowAverageFastest = feeData.slow !== undefined &&
180
+ feeData.average !== undefined &&
181
+ feeData.fastest !== undefined;
182
+
183
+ const hasAverageFastFastest = feeData.average !== undefined &&
184
+ feeData.fast !== undefined &&
185
+ feeData.fastest !== undefined;
186
+
187
+ let slowValue: string, averageValue: string, fastestValue: string;
188
+
189
+ if (hasSlowAverageFastest) {
190
+ // Already in UI format
191
+ slowValue = feeData.slow.toString();
192
+ averageValue = feeData.average.toString();
193
+ fastestValue = feeData.fastest.toString();
194
+ } else if (hasAverageFastFastest) {
195
+ // Map API format to UI format
196
+ slowValue = feeData.average.toString();
197
+ averageValue = feeData.fast.toString();
198
+ fastestValue = feeData.fastest.toString();
199
+ } else {
200
+ throw new Error('Unknown fee data format');
201
+ }
202
+
203
+ // UNIT CONVERSION: Detect if API returned sat/kB instead of sat/byte
204
+ // If values are unreasonably high, they're likely in sat/kB and need to be divided by 1000
205
+ if (networkType === 'UTXO') {
206
+ const slowNum = parseFloat(slowValue);
207
+ const avgNum = parseFloat(averageValue);
208
+ const fastestNum = parseFloat(fastestValue);
209
+
210
+ // Detection threshold: if ANY value > 500, likely in wrong units (sat/kB instead of sat/byte)
211
+ // This is safe because even BTC rarely exceeds 500 sat/byte
212
+ const conversionThreshold = 500;
213
+
214
+ if (slowNum > conversionThreshold || avgNum > conversionThreshold || fastestNum > conversionThreshold) {
215
+ console.warn(`[FEES] Detected wrong units for ${networkName}: values appear to be in sat/kB instead of sat/byte`);
216
+ console.warn(`[FEES] Original values: slow=${slowNum}, avg=${avgNum}, fastest=${fastestNum}`);
217
+
218
+ // Convert from sat/kB to sat/byte
219
+ slowValue = (slowNum / 1000).toFixed(3);
220
+ averageValue = (avgNum / 1000).toFixed(3);
221
+ fastestValue = (fastestNum / 1000).toFixed(3);
222
+
223
+ console.warn(`[FEES] Converted to sat/byte: slow=${slowValue}, avg=${averageValue}, fastest=${fastestValue}`);
224
+ }
225
+ }
226
+
227
+ // Apply sanity checks for UTXO networks to prevent absurdly high fees
228
+ // The API sometimes returns stale or incorrect data (e.g., DOGE returning 50000 sat/byte instead of 50)
229
+ if (networkType === 'UTXO') {
230
+ const sanityLimits: Record<string, number> = {
231
+ 'bip122:00000000001a91e3dace36e2be3bf030': 100, // DOGE max 100 sat/byte (typical: 1-10)
232
+ 'bip122:12a765e31ffd4059bada1e25190f6e98': 500, // LTC max 500 sat/byte
233
+ 'bip122:000000000000000000651ef99cb9fcbe': 50, // BCH max 50 sat/byte (low fee chain)
234
+ 'bip122:000007d91d1254d60e2dd1ae58038307': 50, // DASH max 50 sat/byte (low fee chain)
235
+ 'bip122:000000000019d6689c085ae165831e93': 5000, // BTC max 5000 sat/byte (can spike during congestion)
236
+ };
237
+
238
+ const matchedNetworkId = networkId && sanityLimits[networkId] ? networkId :
239
+ Object.keys(sanityLimits).find(id => networkName.toLowerCase().includes(id.split(':')[1]?.substring(0, 8)));
240
+
241
+ if (matchedNetworkId && sanityLimits[matchedNetworkId]) {
242
+ const limit = sanityLimits[matchedNetworkId];
243
+ const slowNum = parseFloat(slowValue);
244
+ const avgNum = parseFloat(averageValue);
245
+ const fastestNum = parseFloat(fastestValue);
246
+
247
+ if (slowNum > limit || avgNum > limit || fastestNum > limit) {
248
+ console.warn(`[FEES] Detected absurdly high fees for ${networkName}: slow=${slowNum}, avg=${avgNum}, fastest=${fastestNum}`);
249
+ console.warn(`[FEES] Capping fees to reasonable limits (max: ${limit} sat/byte)`);
250
+
251
+ // Cap to reasonable values - use 10% of limit as conservative default
252
+ const safeFee = (limit * 0.1).toFixed(2);
253
+ const mediumFee = (limit * 0.15).toFixed(2);
254
+ const fastFee = (limit * 0.2).toFixed(2);
255
+
256
+ slowValue = safeFee;
257
+ averageValue = mediumFee;
258
+ fastestValue = fastFee;
259
+
260
+ console.warn(`[FEES] Adjusted to: slow=${slowValue}, avg=${averageValue}, fastest=${fastestValue}`);
261
+ }
262
+ }
263
+ }
264
+
265
+ // Get unit and descriptions based on network type
266
+ const unit = feeData.unit || getDefaultUnit(networkType);
267
+ const baseDescription = feeData.description || getDefaultDescription(networkType, networkName);
268
+
269
+ return {
270
+ slow: {
271
+ label: 'Economy',
272
+ value: slowValue,
273
+ unit,
274
+ description: `${baseDescription} - Lower priority, may take longer to confirm.`,
275
+ estimatedTime: getEstimatedTime(networkType, 'low'),
276
+ priority: 'low',
277
+ },
278
+ average: {
279
+ label: 'Standard',
280
+ value: averageValue,
281
+ unit,
282
+ description: `${baseDescription} - Normal priority, typical confirmation time.`,
283
+ estimatedTime: getEstimatedTime(networkType, 'medium'),
284
+ priority: 'medium',
285
+ },
286
+ fastest: {
287
+ label: 'Priority',
288
+ value: fastestValue,
289
+ unit,
290
+ description: `${baseDescription} - High priority, fastest confirmation.`,
291
+ estimatedTime: getEstimatedTime(networkType, 'high'),
292
+ priority: 'high',
293
+ },
294
+ networkId: '',
295
+ networkType: networkType as any,
296
+ raw: feeData,
297
+ };
298
+ }
299
+
300
+ /**
301
+ * Ensure fees are differentiated for better UX
302
+ */
303
+ function ensureFeeDifferentiation(
304
+ fees: NormalizedFeeRates,
305
+ networkType: string
306
+ ): NormalizedFeeRates {
307
+ const slowVal = parseFloat(fees.slow.value) || 0;
308
+ const avgVal = parseFloat(fees.average.value) || 0;
309
+ const fastestVal = parseFloat(fees.fastest.value) || 0;
310
+
311
+ // Check if all values are zero
312
+ if (slowVal === 0 && avgVal === 0 && fastestVal === 0) {
313
+ console.warn('All fee values are 0 - using fallback values');
314
+ // Return sensible defaults based on network type
315
+ if (networkType === 'UTXO') {
316
+ return {
317
+ ...fees,
318
+ slow: { ...fees.slow, value: '1' },
319
+ average: { ...fees.average, value: '2' },
320
+ fastest: { ...fees.fastest, value: '3' },
321
+ };
322
+ } else {
323
+ return {
324
+ ...fees,
325
+ slow: { ...fees.slow, value: '1' },
326
+ average: { ...fees.average, value: '1.5' },
327
+ fastest: { ...fees.fastest, value: '2' },
328
+ };
329
+ }
330
+ }
331
+
332
+ // For UTXO networks with very similar values (like 1, 1, 1.01)
333
+ if (networkType === 'UTXO') {
334
+ const diff = fastestVal - slowVal;
335
+ if (diff < 0.5) {
336
+ console.warn('UTXO fees too similar, adjusting for better UX');
337
+ return {
338
+ ...fees,
339
+ slow: { ...fees.slow, value: slowVal.toString() },
340
+ average: { ...fees.average, value: (slowVal + 1).toString() },
341
+ fastest: { ...fees.fastest, value: (slowVal + 2).toString() },
342
+ };
343
+ }
344
+ }
345
+
346
+ // For EVM networks, check if values are already well differentiated
347
+ // Don't adjust if there's already good separation
348
+ const slowToAvgRatio = avgVal / slowVal;
349
+ const avgToFastRatio = fastestVal / avgVal;
350
+
351
+ // If ratios show good differentiation (at least 10% difference), keep original
352
+ if (slowToAvgRatio >= 1.1 && avgToFastRatio >= 1.1) {
353
+ return fees; // Already well differentiated
354
+ }
355
+
356
+ // Only adjust if fees are too similar
357
+ console.warn('Fees not well differentiated, adjusting slightly');
358
+ return {
359
+ ...fees,
360
+ slow: { ...fees.slow, value: slowVal.toString() },
361
+ average: { ...fees.average, value: (slowVal * 1.2).toFixed(6) },
362
+ fastest: { ...fees.fastest, value: (slowVal * 1.5).toFixed(6) },
363
+ };
364
+ }
365
+
366
+ /**
367
+ * Get default unit based on network type
368
+ */
369
+ function getDefaultUnit(networkType: string): string {
370
+ switch (networkType) {
371
+ case 'UTXO':
372
+ return 'sat/vB';
373
+ case 'EVM':
374
+ return 'gwei';
375
+ case 'COSMOS':
376
+ return 'uatom';
377
+ case 'RIPPLE':
378
+ return 'XRP';
379
+ default:
380
+ return 'units';
381
+ }
382
+ }
383
+
384
+ /**
385
+ * Get default description based on network type
386
+ */
387
+ function getDefaultDescription(networkType: string, networkName: string): string {
388
+ switch (networkType) {
389
+ case 'UTXO':
390
+ return `Fee rate in satoshis per virtual byte for ${networkName}`;
391
+ case 'EVM':
392
+ return `Gas price in Gwei for ${networkName} (1 Gwei = 0.000000001 ETH)`;
393
+ case 'COSMOS':
394
+ return `Transaction fee for ${networkName}`;
395
+ case 'RIPPLE':
396
+ return `Fixed transaction fee for ${networkName}`;
397
+ default:
398
+ return `Transaction fee for ${networkName}`;
399
+ }
400
+ }
401
+
402
+ /**
403
+ * Get estimated confirmation time
404
+ */
405
+ function getEstimatedTime(networkType: string, priority: string): string {
406
+ const times: Record<string, Record<string, string>> = {
407
+ UTXO: {
408
+ low: '~60+ minutes',
409
+ medium: '~30 minutes',
410
+ high: '~10 minutes',
411
+ },
412
+ EVM: {
413
+ low: '~5 minutes',
414
+ medium: '~2 minutes',
415
+ high: '~30 seconds',
416
+ },
417
+ COSMOS: {
418
+ low: '~10 seconds',
419
+ medium: '~7 seconds',
420
+ high: '~5 seconds',
421
+ },
422
+ RIPPLE: {
423
+ low: '~4 seconds',
424
+ medium: '~4 seconds',
425
+ high: '~4 seconds',
426
+ },
427
+ };
428
+
429
+ return times[networkType]?.[priority] || '~varies';
430
+ }
431
+
432
+ /**
433
+ * Get hardcoded Cosmos fees based on network
434
+ */
435
+ function getCosmosFees(networkId: string): NormalizedFeeRates {
436
+ const networkName = getNetworkName(networkId);
437
+
438
+ // These match the fees in txbuilder/createUnsignedTendermintTx.ts
439
+ const cosmosFeesMap: Record<string, { base: number; unit: string; denom: string }> = {
440
+ 'cosmos:thorchain-mainnet-v1': { base: 0.02, unit: 'RUNE', denom: 'rune' },
441
+ 'cosmos:mayachain-mainnet-v1': { base: 0.2, unit: 'MAYA', denom: 'maya' },
442
+ 'cosmos:cosmoshub-4': { base: 0.005, unit: 'ATOM', denom: 'uatom' },
443
+ 'cosmos:osmosis-1': { base: 0.035, unit: 'OSMO', denom: 'uosmo' },
444
+ };
445
+
446
+ const feeConfig = cosmosFeesMap[networkId] || { base: 0.025, unit: 'units', denom: 'units' };
447
+
448
+ // For Cosmos, we provide the base fee with different priority multipliers
449
+ const slowFee = feeConfig.base.toString();
450
+ const avgFee = (feeConfig.base * 1.5).toFixed(4);
451
+ const fastFee = (feeConfig.base * 2).toFixed(4);
452
+
453
+ return {
454
+ slow: {
455
+ label: 'Economy',
456
+ value: slowFee,
457
+ unit: feeConfig.unit,
458
+ description: `Standard fee for ${networkName}. Gas is automatically calculated.`,
459
+ estimatedTime: '~10 seconds',
460
+ priority: 'low',
461
+ },
462
+ average: {
463
+ label: 'Standard',
464
+ value: avgFee,
465
+ unit: feeConfig.unit,
466
+ description: `Priority fee for ${networkName}. Slightly higher for faster processing.`,
467
+ estimatedTime: '~7 seconds',
468
+ priority: 'medium',
469
+ },
470
+ fastest: {
471
+ label: 'Priority',
472
+ value: fastFee,
473
+ unit: feeConfig.unit,
474
+ description: `Maximum priority for ${networkName}. Fastest possible confirmation.`,
475
+ estimatedTime: '~5 seconds',
476
+ priority: 'high',
477
+ },
478
+ networkId,
479
+ networkType: 'COSMOS',
480
+ raw: { hardcoded: true, base: feeConfig.base, unit: feeConfig.unit, denom: feeConfig.denom },
481
+ };
482
+ }
483
+
484
+ /**
485
+ * Get hardcoded Ripple fees
486
+ */
487
+ function getRippleFees(networkId: string): NormalizedFeeRates {
488
+ const networkName = getNetworkName(networkId);
489
+
490
+ // XRP has a fixed minimum fee of 0.00001 XRP (10 drops)
491
+ // This is hardcoded in the network protocol
492
+ const baseFee = '0.00001';
493
+
494
+ return {
495
+ slow: {
496
+ label: 'Standard',
497
+ value: baseFee,
498
+ unit: 'XRP',
499
+ description: `Fixed network fee for ${networkName}. All transactions use the same fee.`,
500
+ estimatedTime: '~4 seconds',
501
+ priority: 'low',
502
+ },
503
+ average: {
504
+ label: 'Standard',
505
+ value: baseFee,
506
+ unit: 'XRP',
507
+ description: `Fixed network fee for ${networkName}. All transactions use the same fee.`,
508
+ estimatedTime: '~4 seconds',
509
+ priority: 'medium',
510
+ },
511
+ fastest: {
512
+ label: 'Standard',
513
+ value: baseFee,
514
+ unit: 'XRP',
515
+ description: `Fixed network fee for ${networkName}. All transactions use the same fee.`,
516
+ estimatedTime: '~4 seconds',
517
+ priority: 'high',
518
+ },
519
+ networkId,
520
+ networkType: 'RIPPLE',
521
+ raw: { hardcoded: true, baseFee, reason: 'Ripple uses fixed network fees' },
522
+ };
523
+ }
524
+
525
+ /**
526
+ * Get fallback fees when API fails
527
+ */
528
+ function getFallbackFees(networkId: string): NormalizedFeeRates {
529
+ const networkType = getNetworkType(networkId);
530
+ const networkName = getNetworkName(networkId);
531
+
532
+ // For Cosmos chains, use hardcoded fees
533
+ if (networkType === 'COSMOS') {
534
+ return getCosmosFees(networkId);
535
+ }
536
+
537
+ // Default fallback values by network type
538
+ const fallbacks: Record<string, { slow: string; average: string; fastest: string; unit: string }> = {
539
+ UTXO: { slow: '1', average: '2', fastest: '3', unit: 'sat/vB' },
540
+ EVM: { slow: '1', average: '1.5', fastest: '2', unit: 'gwei' },
541
+ RIPPLE: { slow: '0.00001', average: '0.00001', fastest: '0.00001', unit: 'XRP' },
542
+ };
543
+
544
+ const fallback = fallbacks[networkType] || fallbacks.UTXO;
545
+
546
+ return {
547
+ slow: {
548
+ label: 'Economy',
549
+ value: fallback.slow,
550
+ unit: fallback.unit,
551
+ description: `Default fee for ${networkName} (API unavailable)`,
552
+ estimatedTime: getEstimatedTime(networkType, 'low'),
553
+ priority: 'low',
554
+ },
555
+ average: {
556
+ label: 'Standard',
557
+ value: fallback.average,
558
+ unit: fallback.unit,
559
+ description: `Default fee for ${networkName} (API unavailable)`,
560
+ estimatedTime: getEstimatedTime(networkType, 'medium'),
561
+ priority: 'medium',
562
+ },
563
+ fastest: {
564
+ label: 'Priority',
565
+ value: fallback.fastest,
566
+ unit: fallback.unit,
567
+ description: `Default fee for ${networkName} (API unavailable)`,
568
+ estimatedTime: getEstimatedTime(networkType, 'high'),
569
+ priority: 'high',
570
+ },
571
+ networkId,
572
+ networkType: networkType as any,
573
+ raw: null,
574
+ };
575
+ }
576
+
577
+ /**
578
+ * Calculate estimated transaction fee based on fee rate and transaction size
579
+ */
580
+ export function estimateTransactionFee(
581
+ feeRate: string,
582
+ unit: string,
583
+ networkType: string,
584
+ txSize?: number
585
+ ): FeeEstimate {
586
+ switch (networkType) {
587
+ case 'UTXO':
588
+ // For UTXO chains, multiply fee rate by transaction size
589
+ const sizeInBytes = txSize || 250; // Default estimate
590
+ const feeInSatoshis = parseFloat(feeRate) * sizeInBytes;
591
+ const feeInBTC = feeInSatoshis / 100000000;
592
+ return {
593
+ amount: feeInBTC.toFixed(8),
594
+ unit: 'BTC',
595
+ };
596
+
597
+ case 'EVM':
598
+ // For EVM chains, multiply gas price by gas limit
599
+ const gasLimit = 21000; // Standard transfer
600
+ const feeInGwei = parseFloat(feeRate) * gasLimit;
601
+ const feeInEth = feeInGwei / 1000000000;
602
+ return {
603
+ amount: feeInEth.toFixed(9),
604
+ unit: 'ETH',
605
+ };
606
+
607
+ case 'RIPPLE':
608
+ // Ripple has fixed fees
609
+ return {
610
+ amount: feeRate,
611
+ unit: 'XRP',
612
+ };
613
+
614
+ default:
615
+ return {
616
+ amount: feeRate,
617
+ unit: unit,
618
+ };
619
+ }
620
+ }