@pioneer-platform/pioneer-sdk 8.15.14 → 8.15.16
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/dist/index.cjs +673 -649
- package/dist/index.es.js +674 -650
- package/dist/index.js +674 -650
- package/package.json +2 -3
- package/src/index.ts +223 -915
- package/src/utils/fee-reserves.ts +162 -0
- package/src/utils/logger.ts +21 -0
- package/src/utils/network-helpers.ts +85 -0
- package/src/utils/path-discovery.ts +67 -0
- package/src/utils/portfolio-helpers.ts +209 -0
- package/src/utils/pubkey-management.ts +124 -0
- package/src/utils/pubkey-sync.ts +85 -0
- package/src/utils/sync-state.ts +93 -0
package/src/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ import EventEmitter from 'events';
|
|
|
8
8
|
|
|
9
9
|
import { getCharts } from './charts/index.js';
|
|
10
10
|
//internal
|
|
11
|
+
import { logger } from './utils/logger.js';
|
|
11
12
|
import { getPubkey } from './getPubkey.js';
|
|
12
13
|
import { optimizedGetPubkeys } from './kkapi-batch-client.js';
|
|
13
14
|
import { OfflineClient } from './offline-client.js';
|
|
@@ -21,43 +22,47 @@ import { detectKkApiAvailability } from './utils/kkapi-detection.js';
|
|
|
21
22
|
import { formatTime } from './utils/format-time.js';
|
|
22
23
|
import { buildDashboardFromBalances } from './utils/build-dashboard.js';
|
|
23
24
|
import { syncMarket } from './utils/sync-market.js';
|
|
25
|
+
import {
|
|
26
|
+
getPubkeyKey,
|
|
27
|
+
deduplicatePubkeys,
|
|
28
|
+
validatePubkey,
|
|
29
|
+
findPubkeysForNetwork,
|
|
30
|
+
findPubkeyForNetwork,
|
|
31
|
+
validatePubkeysForNetwork,
|
|
32
|
+
filterPubkeysForAsset
|
|
33
|
+
} from './utils/pubkey-management.js';
|
|
34
|
+
import {
|
|
35
|
+
isCacheDataValid,
|
|
36
|
+
buildDashboardFromPortfolioData,
|
|
37
|
+
fetchMarketPrice,
|
|
38
|
+
extractPriceFromBalances,
|
|
39
|
+
aggregateBalances,
|
|
40
|
+
updateBalancesWithPrice
|
|
41
|
+
} from './utils/portfolio-helpers.js';
|
|
42
|
+
import {
|
|
43
|
+
createInitialSyncState,
|
|
44
|
+
createCacheSyncState,
|
|
45
|
+
createFreshSyncState,
|
|
46
|
+
type SyncState,
|
|
47
|
+
resolveAssetInfo
|
|
48
|
+
} from './utils/sync-state.js';
|
|
49
|
+
import { getMaxSendableAmount } from './utils/fee-reserves.js';
|
|
50
|
+
import {
|
|
51
|
+
matchesNetwork,
|
|
52
|
+
normalizeNetworkId,
|
|
53
|
+
validatePubkeysNetworks
|
|
54
|
+
} from './utils/network-helpers.js';
|
|
55
|
+
import {
|
|
56
|
+
buildAssetQuery,
|
|
57
|
+
logQueryDiagnostics,
|
|
58
|
+
enrichBalancesWithAssetInfo
|
|
59
|
+
} from './utils/portfolio-helpers.js';
|
|
60
|
+
import { ensurePathsForBlockchains } from './utils/path-discovery.js';
|
|
61
|
+
import { syncPubkeysForBlockchains } from './utils/pubkey-sync.js';
|
|
62
|
+
// import { ASSET_COLORS } from './constants/asset-colors.js'; // TODO: Create this file or remove usage
|
|
24
63
|
|
|
25
64
|
const TAG = ' | Pioneer-sdk | ';
|
|
26
65
|
|
|
27
|
-
// Color mappings for major assets (until pioneer-discovery includes colors)
|
|
28
|
-
const ASSET_COLORS: Record<string, string> = {
|
|
29
|
-
// Bitcoin
|
|
30
|
-
'bip122:000000000019d6689c085ae165831e93/slip44:0': '#FF9800',
|
|
31
|
-
// Ethereum
|
|
32
|
-
'eip155:1/slip44:60': '#627EEA',
|
|
33
|
-
// Polygon
|
|
34
|
-
'eip155:137/slip44:60': '#8247E5',
|
|
35
|
-
// Base
|
|
36
|
-
'eip155:8453/slip44:60': '#0052FF',
|
|
37
|
-
// BNB
|
|
38
|
-
'eip155:56/slip44:60': '#F3BA2F',
|
|
39
|
-
// Litecoin
|
|
40
|
-
'bip122:12a765e31ffd4059bada1e25190f6e98/slip44:2': '#BFBBBB',
|
|
41
|
-
// Dogecoin
|
|
42
|
-
'bip122:00000000001a91e3dace36e2be3bf030/slip44:3': '#C2A633',
|
|
43
|
-
// Bitcoin Cash
|
|
44
|
-
'bip122:000000000000000000651ef99cb9fcbe/slip44:145': '#8DC351',
|
|
45
|
-
// Dash
|
|
46
|
-
'bip122:000007d91d1254d60e2dd1ae58038307/slip44:5': '#008CE7',
|
|
47
|
-
// DigiByte
|
|
48
|
-
'bip122:4da631f2ac1bed857bd968c67c913978/slip44:20': '#006AD2',
|
|
49
|
-
// Cosmos
|
|
50
|
-
'cosmos:cosmoshub-4/slip44:118': '#2E3148',
|
|
51
|
-
// Osmosis
|
|
52
|
-
'cosmos:osmosis-1/slip44:118': '#9B1FD7',
|
|
53
|
-
// XRP
|
|
54
|
-
'ripple:4109c6f2045fc7eff4cde8f9905d19c2/slip44:144': '#23292F',
|
|
55
|
-
// Maya/CACAO
|
|
56
|
-
'cosmos:mayachain-mainnet-v1/slip44:931': '#00D4AA',
|
|
57
|
-
// Thorchain/RUNE
|
|
58
|
-
'cosmos:thorchain-mainnet-v1/slip44:931': '#00CCFF',
|
|
59
|
-
};
|
|
60
|
-
|
|
61
66
|
export interface PioneerSDKConfig {
|
|
62
67
|
appName: string;
|
|
63
68
|
appIcon: string;
|
|
@@ -84,18 +89,6 @@ export interface PioneerSDKConfig {
|
|
|
84
89
|
skipKeeperEndpoint?: boolean; // Skip vault endpoint detection
|
|
85
90
|
}
|
|
86
91
|
|
|
87
|
-
export interface SyncState {
|
|
88
|
-
isSynced: boolean; // Has fresh data from full sync?
|
|
89
|
-
isInitialSync: boolean; // First sync (no cache)?
|
|
90
|
-
cacheAge: number; // Seconds since last cache update
|
|
91
|
-
syncProgress: number; // 0-100 percentage of completion
|
|
92
|
-
syncedChains: number; // Number of chains synced
|
|
93
|
-
totalChains: number; // Total chains to sync
|
|
94
|
-
lastSyncTime: number | null; // Timestamp of last successful sync
|
|
95
|
-
syncSource: 'cache' | 'fresh' | 'none'; // Where data came from
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
|
|
99
92
|
export class SDK {
|
|
100
93
|
public status: string;
|
|
101
94
|
public username: string;
|
|
@@ -159,18 +152,8 @@ export class SDK {
|
|
|
159
152
|
public appName: string;
|
|
160
153
|
public appIcon: any;
|
|
161
154
|
public init: (walletsVerbose: any, setup: any) => Promise<any>;
|
|
162
|
-
// public initOffline: () => Promise<any>;
|
|
163
|
-
// public backgroundSync: () => Promise<void>;
|
|
164
155
|
public getUnifiedPortfolio: () => Promise<any>;
|
|
165
156
|
public offlineClient: OfflineClient | null;
|
|
166
|
-
// public verifyWallet: () => Promise<void>;
|
|
167
|
-
public convertVaultPubkeysToPioneerFormat: (vaultPubkeys: any[]) => any[];
|
|
168
|
-
// public deriveNetworksFromPath: (path: string) => string[];
|
|
169
|
-
// public getAddress: (options: {
|
|
170
|
-
// networkId?: string;
|
|
171
|
-
// showDevice?: boolean;
|
|
172
|
-
// path?: any;
|
|
173
|
-
// }) => Promise<string>;
|
|
174
157
|
public app: {
|
|
175
158
|
getAddress: (options: {
|
|
176
159
|
networkId?: string;
|
|
@@ -272,16 +255,7 @@ export class SDK {
|
|
|
272
255
|
this.contextType = '';
|
|
273
256
|
|
|
274
257
|
// Initialize sync state
|
|
275
|
-
this.syncState =
|
|
276
|
-
isSynced: false,
|
|
277
|
-
isInitialSync: true,
|
|
278
|
-
cacheAge: 0,
|
|
279
|
-
syncProgress: 0,
|
|
280
|
-
syncedChains: 0,
|
|
281
|
-
totalChains: this.blockchains.length,
|
|
282
|
-
lastSyncTime: null,
|
|
283
|
-
syncSource: 'none'
|
|
284
|
-
};
|
|
258
|
+
this.syncState = createInitialSyncState(this.blockchains.length);
|
|
285
259
|
|
|
286
260
|
// Initialize offline client if offline-first mode is enabled
|
|
287
261
|
this.offlineClient = config.offlineFirst
|
|
@@ -314,40 +288,17 @@ export class SDK {
|
|
|
314
288
|
});
|
|
315
289
|
};
|
|
316
290
|
|
|
317
|
-
//
|
|
318
|
-
this.getPubkeyKey =
|
|
319
|
-
|
|
320
|
-
};
|
|
321
|
-
|
|
322
|
-
// Helper method to deduplicate pubkeys array
|
|
323
|
-
this.deduplicatePubkeys = (pubkeys: any[]): any[] => {
|
|
324
|
-
const seen = new Set<string>();
|
|
325
|
-
const deduped = pubkeys.filter((pubkey) => {
|
|
326
|
-
const key = this.getPubkeyKey(pubkey);
|
|
327
|
-
if (seen.has(key)) {
|
|
328
|
-
return false;
|
|
329
|
-
}
|
|
330
|
-
seen.add(key);
|
|
331
|
-
return true;
|
|
332
|
-
});
|
|
333
|
-
return deduped;
|
|
334
|
-
};
|
|
291
|
+
// Pubkey helper methods using utilities
|
|
292
|
+
this.getPubkeyKey = getPubkeyKey;
|
|
293
|
+
this.deduplicatePubkeys = deduplicatePubkeys;
|
|
335
294
|
|
|
336
295
|
// Helper method to validate and add a single pubkey
|
|
337
296
|
this.addPubkey = (pubkey: any): boolean => {
|
|
338
|
-
|
|
339
|
-
if (!pubkey.pubkey || !pubkey.pathMaster) {
|
|
340
|
-
return false;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
const key = this.getPubkeyKey(pubkey);
|
|
297
|
+
if (!validatePubkey(pubkey)) return false;
|
|
344
298
|
|
|
345
|
-
|
|
346
|
-
if (this.pubkeySet.has(key))
|
|
347
|
-
return false;
|
|
348
|
-
}
|
|
299
|
+
const key = getPubkeyKey(pubkey);
|
|
300
|
+
if (this.pubkeySet.has(key)) return false;
|
|
349
301
|
|
|
350
|
-
// Add to both array and set
|
|
351
302
|
this.pubkeys.push(pubkey);
|
|
352
303
|
this.pubkeySet.add(key);
|
|
353
304
|
return true;
|
|
@@ -378,20 +329,6 @@ export class SDK {
|
|
|
378
329
|
this.pubkeys = [];
|
|
379
330
|
this.pubkeySet.clear();
|
|
380
331
|
}
|
|
381
|
-
|
|
382
|
-
// View-only mode helper methods
|
|
383
|
-
this.isViewOnlyMode = (): boolean => {
|
|
384
|
-
return this.viewOnlyMode;
|
|
385
|
-
};
|
|
386
|
-
|
|
387
|
-
this.canSignTransactions = (): boolean => {
|
|
388
|
-
return !this.viewOnlyMode && !!this.keepKeySdk;
|
|
389
|
-
};
|
|
390
|
-
|
|
391
|
-
this.isVaultAvailable = (): boolean => {
|
|
392
|
-
return !!this.keepkeyEndpoint && this.keepkeyEndpoint.isAvailable;
|
|
393
|
-
};
|
|
394
|
-
|
|
395
332
|
// Fast portfolio loading from kkapi:// cache
|
|
396
333
|
this.getUnifiedPortfolio = async function () {
|
|
397
334
|
const tag = `${TAG} | getUnifiedPortfolio | `;
|
|
@@ -439,10 +376,8 @@ export class SDK {
|
|
|
439
376
|
|
|
440
377
|
// Update pubkeys from cache
|
|
441
378
|
if (portfolioData.pubkeys && portfolioData.pubkeys.length > 0) {
|
|
442
|
-
// Convert vault pubkey format to pioneer-sdk format
|
|
443
|
-
const convertedPubkeys = this.convertVaultPubkeysToPioneerFormat(portfolioData.pubkeys);
|
|
444
379
|
// Use setPubkeys to ensure deduplication
|
|
445
|
-
this.setPubkeys(
|
|
380
|
+
this.setPubkeys(portfolioData.pubkeys);
|
|
446
381
|
this.events.emit('SET_PUBKEYS', this.pubkeys);
|
|
447
382
|
}
|
|
448
383
|
|
|
@@ -459,75 +394,12 @@ export class SDK {
|
|
|
459
394
|
}
|
|
460
395
|
|
|
461
396
|
// Validate cache data before using it
|
|
462
|
-
const isCacheDataValid = (portfolioData: any): boolean => {
|
|
463
|
-
// Check if networks data is reasonable (should be < 50 networks, not thousands)
|
|
464
|
-
if (!portfolioData.networks || !Array.isArray(portfolioData.networks)) {
|
|
465
|
-
console.warn('[CACHE VALIDATION] Networks is not an array');
|
|
466
|
-
return false;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
if (portfolioData.networks.length > 50) {
|
|
470
|
-
console.error(
|
|
471
|
-
`[CACHE VALIDATION] CORRUPTED: ${portfolioData.networks.length} networks (should be < 50)`,
|
|
472
|
-
);
|
|
473
|
-
return false;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
// Check if at least some networks have required fields
|
|
477
|
-
const validNetworks = portfolioData.networks.filter(
|
|
478
|
-
(n: any) => n.networkId && n.totalValueUsd !== undefined && n.gasAssetSymbol,
|
|
479
|
-
);
|
|
480
|
-
|
|
481
|
-
if (validNetworks.length === 0 && portfolioData.networks.length > 0) {
|
|
482
|
-
console.error('[CACHE VALIDATION] CORRUPTED: No networks have required fields');
|
|
483
|
-
return false;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
console.log(
|
|
487
|
-
`[CACHE VALIDATION] Found ${portfolioData.networks.length} networks, ${validNetworks.length} valid`,
|
|
488
|
-
);
|
|
489
|
-
return true;
|
|
490
|
-
};
|
|
491
|
-
|
|
492
|
-
// Only use cache data if it's valid
|
|
493
397
|
if (isCacheDataValid(portfolioData)) {
|
|
494
|
-
|
|
495
|
-
totalValueUsd: portfolioData.totalValueUsd,
|
|
496
|
-
pairedDevices: portfolioData.pairedDevices,
|
|
497
|
-
devices: portfolioData.devices || [],
|
|
498
|
-
networks: portfolioData.networks || [],
|
|
499
|
-
assets: portfolioData.assets || [],
|
|
500
|
-
statistics: portfolioData.statistics || {},
|
|
501
|
-
cached: portfolioData.cached,
|
|
502
|
-
lastUpdated: portfolioData.lastUpdated,
|
|
503
|
-
cacheAge: portfolioData.lastUpdated
|
|
504
|
-
? Math.floor((Date.now() - portfolioData.lastUpdated) / 1000)
|
|
505
|
-
: 0,
|
|
506
|
-
networkPercentages:
|
|
507
|
-
portfolioData.networks?.map((network: any) => ({
|
|
508
|
-
networkId: network.network_id || network.networkId,
|
|
509
|
-
percentage: network.percentage || 0,
|
|
510
|
-
})) || [],
|
|
511
|
-
};
|
|
512
|
-
|
|
513
|
-
this.dashboard = dashboardData;
|
|
398
|
+
this.dashboard = buildDashboardFromPortfolioData(portfolioData);
|
|
514
399
|
this.events.emit('SET_DASHBOARD', this.dashboard);
|
|
515
400
|
|
|
516
401
|
// Update sync state - cache hit
|
|
517
|
-
|
|
518
|
-
? Math.floor((Date.now() - portfolioData.lastUpdated) / 1000)
|
|
519
|
-
: 0;
|
|
520
|
-
|
|
521
|
-
this.syncState = {
|
|
522
|
-
isSynced: true,
|
|
523
|
-
isInitialSync: false,
|
|
524
|
-
cacheAge,
|
|
525
|
-
syncProgress: 100,
|
|
526
|
-
syncedChains: this.blockchains.length,
|
|
527
|
-
totalChains: this.blockchains.length,
|
|
528
|
-
lastSyncTime: portfolioData.lastUpdated || Date.now(),
|
|
529
|
-
syncSource: 'cache'
|
|
530
|
-
};
|
|
402
|
+
this.syncState = createCacheSyncState(portfolioData.lastUpdated, this.blockchains.length);
|
|
531
403
|
this.events.emit('SYNC_STATE_CHANGED', this.syncState);
|
|
532
404
|
} else {
|
|
533
405
|
console.warn(
|
|
@@ -784,249 +656,106 @@ export class SDK {
|
|
|
784
656
|
this.buildDashboardFromBalances = function () {
|
|
785
657
|
return buildDashboardFromBalances(this.balances, this.blockchains, this.assetsMap);
|
|
786
658
|
};
|
|
787
|
-
// ✅ PHASE 3: Infer asset type from CAIP structure (fallback helper)
|
|
788
|
-
this.inferTypeFromCaip = function (caip: string): string {
|
|
789
|
-
if (caip.includes('/slip44:')) return 'native';
|
|
790
|
-
if (caip.includes('/erc20:') || caip.includes('/bep20:') || caip.includes('/spl:')) return 'token';
|
|
791
|
-
if (caip.includes('/denom:')) return 'native'; // Cosmos denoms
|
|
792
|
-
return 'unknown';
|
|
793
|
-
};
|
|
794
659
|
this.syncMarket = async function () {
|
|
795
660
|
return syncMarket(this.balances, this.pioneer);
|
|
796
661
|
};
|
|
797
662
|
this.sync = async function () {
|
|
798
663
|
const tag = `${TAG} | sync | `;
|
|
664
|
+
const log = logger;
|
|
665
|
+
|
|
799
666
|
try {
|
|
800
|
-
//
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
if (networkId.startsWith('eip155:') && item.networks.includes('eip155:*')) return true;
|
|
805
|
-
return false;
|
|
667
|
+
// Update sync state: starting
|
|
668
|
+
this.syncState = {
|
|
669
|
+
...createInitialSyncState(this.blockchains.length),
|
|
670
|
+
syncProgress: 10
|
|
806
671
|
};
|
|
672
|
+
this.events.emit('SYNC_STATE_CHANGED', this.syncState);
|
|
807
673
|
|
|
808
|
-
//
|
|
674
|
+
// Step 1: Initial pubkey fetch
|
|
675
|
+
log.info(tag, 'Fetching initial pubkeys...');
|
|
809
676
|
await this.getPubkeys();
|
|
810
|
-
for (let i = 0; i < this.blockchains.length; i++) {
|
|
811
|
-
let networkId = this.blockchains[i];
|
|
812
|
-
if (networkId.indexOf('eip155:') >= 0) networkId = 'eip155:*';
|
|
813
|
-
|
|
814
|
-
let paths = this.paths.filter((path) => matchesNetwork(path, networkId));
|
|
815
|
-
if (paths.length === 0) {
|
|
816
|
-
//get paths for chain
|
|
817
|
-
let paths = getPaths([networkId]);
|
|
818
|
-
if (!paths || paths.length === 0) throw Error('Unable to find paths for: ' + networkId);
|
|
819
|
-
//add to paths
|
|
820
|
-
this.paths = this.paths.concat(paths);
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
677
|
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
678
|
+
this.syncState.syncProgress = 20;
|
|
679
|
+
this.events.emit('SYNC_STATE_CHANGED', this.syncState);
|
|
680
|
+
|
|
681
|
+
// Step 2: Ensure paths for all blockchains
|
|
682
|
+
log.info(tag, 'Discovering paths for blockchains...');
|
|
683
|
+
this.paths = await ensurePathsForBlockchains(
|
|
684
|
+
this.blockchains,
|
|
685
|
+
this.paths,
|
|
686
|
+
tag
|
|
687
|
+
);
|
|
688
|
+
|
|
689
|
+
this.syncState.syncProgress = 30;
|
|
690
|
+
this.events.emit('SYNC_STATE_CHANGED', this.syncState);
|
|
691
|
+
|
|
692
|
+
// Step 3: Sync pubkeys for all paths
|
|
693
|
+
log.info(tag, 'Synchronizing pubkeys...');
|
|
694
|
+
await syncPubkeysForBlockchains(
|
|
695
|
+
this.blockchains,
|
|
696
|
+
this.paths,
|
|
697
|
+
this.pubkeys,
|
|
698
|
+
this.keepKeySdk,
|
|
699
|
+
this.context,
|
|
700
|
+
getPubkey,
|
|
701
|
+
(pubkey) => this.addPubkey(pubkey),
|
|
702
|
+
tag
|
|
703
|
+
);
|
|
704
|
+
|
|
705
|
+
this.syncState.syncProgress = 50;
|
|
706
|
+
this.syncState.syncedChains = this.blockchains.length;
|
|
707
|
+
this.events.emit('SYNC_STATE_CHANGED', this.syncState);
|
|
708
|
+
|
|
709
|
+
// Step 4: Fetch balances
|
|
710
|
+
log.info(tag, 'Fetching balances...');
|
|
848
711
|
await this.getBalances();
|
|
849
712
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
713
|
+
this.syncState.syncProgress = 70;
|
|
714
|
+
this.events.emit('SYNC_STATE_CHANGED', this.syncState);
|
|
715
|
+
|
|
716
|
+
// Step 5: Load charts (critical for token discovery)
|
|
717
|
+
log.info(tag, 'Loading charts (tokens + portfolio)...');
|
|
853
718
|
await this.getCharts();
|
|
854
|
-
|
|
719
|
+
log.info(tag, `Charts loaded. Total balances: ${this.balances.length}`);
|
|
855
720
|
|
|
856
|
-
|
|
857
|
-
|
|
721
|
+
this.syncState.syncProgress = 85;
|
|
722
|
+
this.events.emit('SYNC_STATE_CHANGED', this.syncState);
|
|
858
723
|
|
|
859
|
-
//
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
networkId: string;
|
|
863
|
-
totalValueUsd: number;
|
|
864
|
-
gasAssetCaip: string | null;
|
|
865
|
-
gasAssetSymbol: string | null;
|
|
866
|
-
icon: string | null;
|
|
867
|
-
color: string | null;
|
|
868
|
-
totalNativeBalance: string;
|
|
869
|
-
}[];
|
|
870
|
-
totalValueUsd: number;
|
|
871
|
-
networkPercentages: { networkId: string; percentage: number }[];
|
|
872
|
-
} = {
|
|
873
|
-
networks: [],
|
|
874
|
-
totalValueUsd: 0,
|
|
875
|
-
networkPercentages: [],
|
|
876
|
-
};
|
|
724
|
+
// Step 6: Sync market prices
|
|
725
|
+
log.info(tag, 'Syncing market prices...');
|
|
726
|
+
await this.syncMarket();
|
|
877
727
|
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
networkId: string;
|
|
881
|
-
totalValueUsd: number;
|
|
882
|
-
gasAssetCaip: string | null;
|
|
883
|
-
gasAssetSymbol: string | null;
|
|
884
|
-
icon: string | null;
|
|
885
|
-
color: string | null;
|
|
886
|
-
totalNativeBalance: string;
|
|
887
|
-
}[] = [];
|
|
888
|
-
|
|
889
|
-
// Deduplicate blockchains before calculation to prevent double-counting
|
|
890
|
-
const uniqueBlockchains = [...new Set(this.blockchains)];
|
|
891
|
-
|
|
892
|
-
// Calculate totals for each blockchain
|
|
893
|
-
for (const blockchain of uniqueBlockchains) {
|
|
894
|
-
const filteredBalances = this.balances.filter((b) => {
|
|
895
|
-
const networkId = caipToNetworkId(b.caip);
|
|
896
|
-
return (
|
|
897
|
-
networkId === blockchain ||
|
|
898
|
-
(blockchain === 'eip155:*' && networkId.startsWith('eip155:'))
|
|
899
|
-
);
|
|
900
|
-
});
|
|
728
|
+
this.syncState.syncProgress = 95;
|
|
729
|
+
this.events.emit('SYNC_STATE_CHANGED', this.syncState);
|
|
901
730
|
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
const bitcoinByValue = new Map();
|
|
910
|
-
filteredBalances.forEach((balance) => {
|
|
911
|
-
const valueKey = `${balance.balance}_${balance.valueUsd}`;
|
|
912
|
-
if (!bitcoinByValue.has(valueKey)) {
|
|
913
|
-
bitcoinByValue.set(valueKey, []);
|
|
914
|
-
}
|
|
915
|
-
bitcoinByValue.get(valueKey).push(balance);
|
|
916
|
-
});
|
|
731
|
+
// Step 7: Build dashboard
|
|
732
|
+
log.info(tag, 'Building dashboard...');
|
|
733
|
+
this.dashboard = buildDashboardFromBalances(
|
|
734
|
+
this.balances,
|
|
735
|
+
[...new Set(this.blockchains)], // Deduplicate blockchains
|
|
736
|
+
this.assetsMap
|
|
737
|
+
);
|
|
917
738
|
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
const xpubBalance =
|
|
923
|
-
balances.find((b) => b.pubkey?.startsWith('xpub')) || balances[0];
|
|
924
|
-
const key = `${xpubBalance.caip}_${xpubBalance.pubkey || 'default'}`;
|
|
925
|
-
balanceMap.set(key, xpubBalance);
|
|
926
|
-
} else {
|
|
927
|
-
// Add all balances normally
|
|
928
|
-
balances.forEach((balance) => {
|
|
929
|
-
const key = `${balance.caip}_${balance.pubkey || 'default'}`;
|
|
930
|
-
balanceMap.set(key, balance);
|
|
931
|
-
});
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
} else {
|
|
935
|
-
// Standard deduplication for non-Bitcoin networks
|
|
936
|
-
filteredBalances.forEach((balance) => {
|
|
937
|
-
const key = `${balance.caip}_${balance.pubkey || 'default'}`;
|
|
938
|
-
// Only keep the first occurrence or the one with higher value
|
|
939
|
-
if (
|
|
940
|
-
!balanceMap.has(key) ||
|
|
941
|
-
parseFloat(balance.valueUsd || '0') >
|
|
942
|
-
parseFloat(balanceMap.get(key).valueUsd || '0')
|
|
943
|
-
) {
|
|
944
|
-
balanceMap.set(key, balance);
|
|
945
|
-
}
|
|
946
|
-
});
|
|
947
|
-
}
|
|
739
|
+
// Step 8: Update sync state - complete
|
|
740
|
+
this.syncState = createFreshSyncState(this.blockchains.length);
|
|
741
|
+
this.events.emit('SYNC_STATE_CHANGED', this.syncState);
|
|
742
|
+
this.events.emit('SYNC_COMPLETE', this.syncState);
|
|
948
743
|
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
// Ensure we're working with numbers for calculations
|
|
952
|
-
const networkTotal = networkBalances.reduce((sum, balance, idx) => {
|
|
953
|
-
const valueUsd =
|
|
954
|
-
typeof balance.valueUsd === 'string'
|
|
955
|
-
? parseFloat(balance.valueUsd)
|
|
956
|
-
: balance.valueUsd || 0;
|
|
957
|
-
|
|
958
|
-
return sum + valueUsd;
|
|
959
|
-
}, 0);
|
|
960
|
-
|
|
961
|
-
// Get native asset for this blockchain
|
|
962
|
-
const nativeAssetCaip = networkIdToCaip(blockchain);
|
|
963
|
-
const gasAsset = networkBalances.find((b) => b.caip === nativeAssetCaip);
|
|
964
|
-
|
|
965
|
-
// Calculate total native balance (sum of all balances for the native asset)
|
|
966
|
-
const totalNativeBalance = networkBalances
|
|
967
|
-
.filter((b) => b.caip === nativeAssetCaip)
|
|
968
|
-
.reduce((sum, balance) => {
|
|
969
|
-
const balanceNum =
|
|
970
|
-
typeof balance.balance === 'string'
|
|
971
|
-
? parseFloat(balance.balance)
|
|
972
|
-
: balance.balance || 0;
|
|
973
|
-
return sum + balanceNum;
|
|
974
|
-
}, 0)
|
|
975
|
-
.toString();
|
|
976
|
-
|
|
977
|
-
networksTemp.push({
|
|
978
|
-
networkId: blockchain,
|
|
979
|
-
totalValueUsd: networkTotal,
|
|
980
|
-
gasAssetCaip: nativeAssetCaip || null,
|
|
981
|
-
gasAssetSymbol: gasAsset?.symbol || null,
|
|
982
|
-
icon: gasAsset?.icon || null,
|
|
983
|
-
color: gasAsset?.color || null,
|
|
984
|
-
totalNativeBalance,
|
|
985
|
-
});
|
|
744
|
+
log.info(tag, '✅ Sync complete!');
|
|
745
|
+
return true;
|
|
986
746
|
|
|
987
|
-
|
|
988
|
-
|
|
747
|
+
} catch (e) {
|
|
748
|
+
log.error(tag, 'Sync failed:', e);
|
|
989
749
|
|
|
990
|
-
//
|
|
991
|
-
dashboardData.networks = networksTemp.sort((a, b) => b.totalValueUsd - a.totalValueUsd);
|
|
992
|
-
dashboardData.totalValueUsd = totalPortfolioValue;
|
|
993
|
-
|
|
994
|
-
// Calculate network percentages for pie chart
|
|
995
|
-
dashboardData.networkPercentages = dashboardData.networks
|
|
996
|
-
.map((network) => ({
|
|
997
|
-
networkId: network.networkId,
|
|
998
|
-
percentage:
|
|
999
|
-
totalPortfolioValue > 0
|
|
1000
|
-
? Number(((network.totalValueUsd / totalPortfolioValue) * 100).toFixed(2))
|
|
1001
|
-
: 0,
|
|
1002
|
-
}))
|
|
1003
|
-
.filter((entry) => entry.percentage > 0); // Remove zero percentages
|
|
1004
|
-
|
|
1005
|
-
/* console.log('Bitcoin balances:', btcBalances.map(b => ({
|
|
1006
|
-
pubkey: b.pubkey,
|
|
1007
|
-
balance: b.balance,
|
|
1008
|
-
valueUsd: b.valueUsd
|
|
1009
|
-
}))); */
|
|
1010
|
-
|
|
1011
|
-
this.dashboard = dashboardData;
|
|
1012
|
-
|
|
1013
|
-
// Update sync state - fresh sync complete
|
|
750
|
+
// Update sync state with error
|
|
1014
751
|
this.syncState = {
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
syncProgress: 100,
|
|
1019
|
-
syncedChains: this.blockchains.length,
|
|
1020
|
-
totalChains: this.blockchains.length,
|
|
1021
|
-
lastSyncTime: Date.now(),
|
|
1022
|
-
syncSource: 'fresh'
|
|
752
|
+
...this.syncState,
|
|
753
|
+
isSynced: false,
|
|
754
|
+
syncProgress: 0
|
|
1023
755
|
};
|
|
1024
756
|
this.events.emit('SYNC_STATE_CHANGED', this.syncState);
|
|
1025
|
-
this.events.emit('
|
|
757
|
+
this.events.emit('SYNC_ERROR', e);
|
|
1026
758
|
|
|
1027
|
-
return true;
|
|
1028
|
-
} catch (e) {
|
|
1029
|
-
console.error(tag, 'Error in sync:', e);
|
|
1030
759
|
throw e;
|
|
1031
760
|
}
|
|
1032
761
|
};
|
|
@@ -1213,16 +942,6 @@ export class SDK {
|
|
|
1213
942
|
|
|
1214
943
|
//get quote
|
|
1215
944
|
// Quote fetching logic
|
|
1216
|
-
// Helper function to check if pubkey matches network (handles EVM wildcard)
|
|
1217
|
-
const matchesNetwork = (pubkey: any, networkId: string) => {
|
|
1218
|
-
if (!pubkey.networks || !Array.isArray(pubkey.networks)) return false;
|
|
1219
|
-
// Exact match
|
|
1220
|
-
if (pubkey.networks.includes(networkId)) return true;
|
|
1221
|
-
// For EVM chains, check if pubkey has eip155:* wildcard
|
|
1222
|
-
if (networkId.startsWith('eip155:') && pubkey.networks.includes('eip155:*')) return true;
|
|
1223
|
-
return false;
|
|
1224
|
-
};
|
|
1225
|
-
|
|
1226
945
|
const pubkeys = this.pubkeys.filter((e: any) =>
|
|
1227
946
|
matchesNetwork(e, this.assetContext.networkId),
|
|
1228
947
|
);
|
|
@@ -1280,23 +999,12 @@ export class SDK {
|
|
|
1280
999
|
).toFixed(2);
|
|
1281
1000
|
console.log(tag, `Updated assetContext balance to aggregated total: ${totalBalance}`);
|
|
1282
1001
|
|
|
1283
|
-
//
|
|
1284
|
-
|
|
1285
|
-
const feeReserves: any = {
|
|
1286
|
-
'bip122:000000000019d6689c085ae165831e93/slip44:0': 0.00005, // BTC
|
|
1287
|
-
'eip155:1/slip44:60': 0.001, // ETH
|
|
1288
|
-
'cosmos:thorchain-mainnet-v1/slip44:931': 0.02, // RUNE
|
|
1289
|
-
'bip122:00000000001a91e3dace36e2be3bf030/slip44:3': 1, // DOGE
|
|
1290
|
-
'bip122:000007d91d1254d60e2dd1ae58038307/slip44:5': 0.001, // DASH
|
|
1291
|
-
'bip122:000000000000000000651ef99cb9fcbe/slip44:145': 0.0005, // BCH
|
|
1292
|
-
};
|
|
1293
|
-
|
|
1294
|
-
const reserve = feeReserves[swapPayload.caipIn] || 0.0001;
|
|
1295
|
-
inputAmount = Math.max(0, totalBalance - reserve);
|
|
1002
|
+
// Calculate max sendable amount using centralized fee reserve utility
|
|
1003
|
+
inputAmount = getMaxSendableAmount(totalBalance, swapPayload.caipIn);
|
|
1296
1004
|
|
|
1297
1005
|
console.log(
|
|
1298
1006
|
tag,
|
|
1299
|
-
`Using max amount for swap: ${inputAmount} (total balance: ${totalBalance}
|
|
1007
|
+
`Using max amount for swap: ${inputAmount} (total balance: ${totalBalance})`,
|
|
1300
1008
|
);
|
|
1301
1009
|
} else {
|
|
1302
1010
|
// Convert amount to number for type safety
|
|
@@ -1780,31 +1488,8 @@ export class SDK {
|
|
|
1780
1488
|
}
|
|
1781
1489
|
}
|
|
1782
1490
|
|
|
1783
|
-
//
|
|
1784
|
-
|
|
1785
|
-
// Add missing MAYA token manually until it's added to assetData
|
|
1786
|
-
const mayaTokenCaip = 'cosmos:mayachain-mainnet-v1/denom:maya';
|
|
1787
|
-
if (!this.assetsMap.has(mayaTokenCaip)) {
|
|
1788
|
-
const mayaToken = {
|
|
1789
|
-
caip: mayaTokenCaip,
|
|
1790
|
-
networkId: 'cosmos:mayachain-mainnet-v1',
|
|
1791
|
-
chainId: 'mayachain-mainnet-v1',
|
|
1792
|
-
symbol: 'MAYA',
|
|
1793
|
-
name: 'Maya Token',
|
|
1794
|
-
precision: 4,
|
|
1795
|
-
decimals: 4,
|
|
1796
|
-
color: '#00D4AA',
|
|
1797
|
-
icon: 'https://pioneers.dev/coins/maya.png',
|
|
1798
|
-
explorer: 'https://explorer.mayachain.info',
|
|
1799
|
-
explorerAddressLink: 'https://explorer.mayachain.info/address/{{address}}',
|
|
1800
|
-
explorerTxLink: 'https://explorer.mayachain.info/tx/{{txid}}',
|
|
1801
|
-
type: 'token',
|
|
1802
|
-
isToken: true,
|
|
1803
|
-
denom: 'maya',
|
|
1804
|
-
};
|
|
1805
|
-
this.assetsMap.set(mayaTokenCaip, mayaToken);
|
|
1806
|
-
console.log(tag, 'Added MAYA token to assetsMap');
|
|
1807
|
-
}
|
|
1491
|
+
// All gas assets and tokens are now loaded from assetData
|
|
1492
|
+
// MAYA token is included in generatedAssetData.json
|
|
1808
1493
|
|
|
1809
1494
|
return this.assetsMap;
|
|
1810
1495
|
} catch (e) {
|
|
@@ -1892,195 +1577,85 @@ export class SDK {
|
|
|
1892
1577
|
this.getBalancesForNetworks = async function (networkIds: string[], forceRefresh?: boolean) {
|
|
1893
1578
|
const tag = `${TAG} | getBalancesForNetworks | `;
|
|
1894
1579
|
try {
|
|
1895
|
-
// Add defensive check for pioneer initialization
|
|
1896
1580
|
if (!this.pioneer) {
|
|
1897
|
-
console.error(
|
|
1898
|
-
tag,
|
|
1899
|
-
'ERROR: Pioneer client not initialized! this.pioneer is:',
|
|
1900
|
-
this.pioneer,
|
|
1901
|
-
);
|
|
1902
1581
|
throw new Error('Pioneer client not initialized. Call init() first.');
|
|
1903
1582
|
}
|
|
1904
1583
|
|
|
1905
|
-
|
|
1906
|
-
if (forceRefresh) {
|
|
1907
|
-
console.log(tag, '🔄 Force refresh requested - bypassing balance cache');
|
|
1908
|
-
}
|
|
1909
|
-
|
|
1910
|
-
// DIAGNOSTIC: Log input
|
|
1911
|
-
console.log('🔍 [DIAGNOSTIC] Input networks:', networkIds);
|
|
1912
|
-
console.log('🔍 [DIAGNOSTIC] Total pubkeys:', this.pubkeys.length);
|
|
1913
|
-
|
|
1914
|
-
// DIAGNOSTIC: Check which pubkeys have networks field
|
|
1915
|
-
const pubkeysWithNetworks = this.pubkeys.filter(p => p.networks && Array.isArray(p.networks));
|
|
1916
|
-
const pubkeysWithoutNetworks = this.pubkeys.filter(p => !p.networks || !Array.isArray(p.networks));
|
|
1584
|
+
if (forceRefresh) console.log(tag, '🔄 Force refresh requested');
|
|
1917
1585
|
|
|
1918
|
-
|
|
1919
|
-
console.log('🔍 [DIAGNOSTIC]
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
console.warn('⚠️ [WARNING] Some pubkeys missing networks field:');
|
|
1923
|
-
pubkeysWithoutNetworks.forEach(pk => {
|
|
1924
|
-
console.warn(` - ${pk.note || pk.pubkey.slice(0, 10)}: networks=${pk.networks}`);
|
|
1925
|
-
});
|
|
1926
|
-
}
|
|
1586
|
+
// Validate pubkeys and log diagnostics
|
|
1587
|
+
console.log('🔍 [DIAGNOSTIC] Networks:', networkIds.length, 'Pubkeys:', this.pubkeys.length);
|
|
1588
|
+
const { valid, invalid } = validatePubkeysNetworks(this.pubkeys, '🔍 [DIAGNOSTIC]');
|
|
1589
|
+
console.log('🔍 [DIAGNOSTIC] Pubkeys:', { valid: valid.length, invalid: invalid.length });
|
|
1927
1590
|
|
|
1591
|
+
// Build asset query for all networks
|
|
1928
1592
|
const assetQuery: { caip: string; pubkey: string }[] = [];
|
|
1929
|
-
|
|
1930
1593
|
for (const networkId of networkIds) {
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
}
|
|
1594
|
+
const pubkeys = findPubkeysForNetwork(this.pubkeys, networkId, this.paths, tag);
|
|
1595
|
+
const caip = await networkIdToCaip(networkId);
|
|
1596
|
+
assetQuery.push(...buildAssetQuery(pubkeys, caip));
|
|
1597
|
+
}
|
|
1936
1598
|
|
|
1937
|
-
|
|
1938
|
-
let pubkeys = this.pubkeys.filter(
|
|
1939
|
-
(pubkey) =>
|
|
1940
|
-
pubkey.networks &&
|
|
1941
|
-
Array.isArray(pubkey.networks) &&
|
|
1942
|
-
pubkey.networks.some((network) => {
|
|
1943
|
-
if (isEip155) return network.startsWith('eip155:');
|
|
1944
|
-
return network === adjustedNetworkId;
|
|
1945
|
-
}),
|
|
1946
|
-
);
|
|
1599
|
+
logQueryDiagnostics(assetQuery, '🔍 [DIAGNOSTIC]');
|
|
1947
1600
|
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1601
|
+
// Fetch balances from API
|
|
1602
|
+
console.log(`⏱️ [PERF] Starting GetPortfolioBalances...`);
|
|
1603
|
+
const apiStart = performance.now();
|
|
1604
|
+
console.time('GetPortfolioBalances Response');
|
|
1952
1605
|
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
);
|
|
1958
|
-
|
|
1959
|
-
// Find pubkeys matching those paths
|
|
1960
|
-
for (const path of pathsForNetwork) {
|
|
1961
|
-
const matchingPubkey = this.pubkeys.find(pk =>
|
|
1962
|
-
JSON.stringify(pk.addressNList) === JSON.stringify(path.addressNList)
|
|
1963
|
-
);
|
|
1606
|
+
const marketInfo = await this.pioneer.GetPortfolioBalances(
|
|
1607
|
+
{ pubkeys: assetQuery },
|
|
1608
|
+
forceRefresh ? { forceRefresh: true } : undefined
|
|
1609
|
+
);
|
|
1964
1610
|
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
pubkeys.push(matchingPubkey);
|
|
1968
|
-
}
|
|
1969
|
-
}
|
|
1611
|
+
console.timeEnd('GetPortfolioBalances Response');
|
|
1612
|
+
console.log(`⏱️ [PERF] API completed in ${(performance.now() - apiStart).toFixed(0)}ms`);
|
|
1970
1613
|
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
console.error(tag, ` ❌ Fallback failed: No pubkeys found for ${networkId}`);
|
|
1975
|
-
}
|
|
1976
|
-
}
|
|
1614
|
+
// Enrich balances with asset metadata
|
|
1615
|
+
const enrichStart = performance.now();
|
|
1616
|
+
console.log(`⏱️ [PERF] Enriching ${marketInfo.data?.length || 0} balances...`);
|
|
1977
1617
|
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1618
|
+
const balances = enrichBalancesWithAssetInfo(
|
|
1619
|
+
marketInfo.data,
|
|
1620
|
+
this.assetsMap,
|
|
1621
|
+
caipToNetworkId
|
|
1622
|
+
);
|
|
1983
1623
|
|
|
1984
|
-
|
|
1985
|
-
console.log('🔍 [DIAGNOSTIC] Built assetQuery with', assetQuery.length, 'entries');
|
|
1986
|
-
console.log('🔍 [DIAGNOSTIC] Sample queries:', assetQuery.slice(0, 5));
|
|
1624
|
+
console.log(`⏱️ [PERF] Enrichment completed in ${(performance.now() - enrichStart).toFixed(0)}ms`);
|
|
1987
1625
|
|
|
1988
|
-
//
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
caipCounts.set(query.caip, (caipCounts.get(query.caip) || 0) + 1);
|
|
1992
|
-
}
|
|
1993
|
-
console.log('🔍 [DIAGNOSTIC] Queries by chain:');
|
|
1994
|
-
caipCounts.forEach((count, caip) => {
|
|
1995
|
-
console.log(` - ${caip}: ${count} queries`);
|
|
1996
|
-
});
|
|
1626
|
+
// Update state and emit events
|
|
1627
|
+
this.balances = balances;
|
|
1628
|
+
this.events.emit('SET_BALANCES', this.balances);
|
|
1997
1629
|
|
|
1998
|
-
|
|
1999
|
-
const
|
|
2000
|
-
|
|
1630
|
+
// Build and emit dashboard
|
|
1631
|
+
const dashStart = performance.now();
|
|
1632
|
+
this.dashboard = this.buildDashboardFromBalances();
|
|
1633
|
+
this.events.emit('SET_DASHBOARD', this.dashboard);
|
|
1634
|
+
console.log(`⏱️ [PERF] Dashboard built in ${(performance.now() - dashStart).toFixed(0)}ms`);
|
|
1635
|
+
console.log(`📊 Dashboard: ${this.dashboard?.networks?.length || 0} networks, $${this.dashboard?.totalValueUsd?.toFixed(2) || '0.00'}`);
|
|
2001
1636
|
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
// Pass forceRefresh as query parameter if provided
|
|
2005
|
-
let marketInfo = await this.pioneer.GetPortfolioBalances(
|
|
2006
|
-
{ pubkeys: assetQuery },
|
|
2007
|
-
forceRefresh ? { forceRefresh: true } : undefined
|
|
2008
|
-
);
|
|
2009
|
-
const apiCallTime = performance.now() - apiCallStart;
|
|
2010
|
-
console.timeEnd('GetPortfolioBalances Response Time');
|
|
2011
|
-
console.log(`⏱️ [PERF] API call completed in ${apiCallTime.toFixed(0)}ms`);
|
|
2012
|
-
|
|
2013
|
-
const enrichStart = performance.now();
|
|
2014
|
-
let balances = marketInfo.data;
|
|
2015
|
-
console.log(`⏱️ [PERF] Received ${balances?.length || 0} balances from server`);
|
|
2016
|
-
|
|
2017
|
-
// Enrich balances with asset info
|
|
2018
|
-
console.log(`⏱️ [PERF] Starting balance enrichment...`);
|
|
2019
|
-
for (let balance of balances) {
|
|
2020
|
-
const assetInfo = this.assetsMap.get(balance.caip.toLowerCase()) || this.assetsMap.get(balance.caip);
|
|
2021
|
-
|
|
2022
|
-
// ✅ PHASE 3: Add fallback for missing asset metadata
|
|
2023
|
-
if (!assetInfo) {
|
|
2024
|
-
console.warn(`⚠️ [ENRICHMENT] Asset metadata missing for ${balance.caip}, using fallback enrichment`);
|
|
2025
|
-
|
|
2026
|
-
// Fallback: Use API-provided values or infer from CAIP
|
|
2027
|
-
const inferredType = this.inferTypeFromCaip(balance.caip);
|
|
2028
|
-
Object.assign(balance, {
|
|
2029
|
-
type: balance.type || inferredType,
|
|
2030
|
-
isNative: balance.isNative ?? (inferredType === 'native'),
|
|
2031
|
-
networkId: caipToNetworkId(balance.caip),
|
|
2032
|
-
icon: 'https://pioneers.dev/coins/unknown.png',
|
|
2033
|
-
identifier: `${balance.caip}:${balance.pubkey}`,
|
|
2034
|
-
updated: Date.now(),
|
|
2035
|
-
});
|
|
2036
|
-
continue;
|
|
2037
|
-
}
|
|
1637
|
+
console.log(`⏱️ [PERF] Total: ${(performance.now() - apiStart).toFixed(0)}ms`);
|
|
1638
|
+
return this.balances;
|
|
2038
1639
|
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
Object.assign(balance, assetInfo, {
|
|
2043
|
-
type: balance.type || assetInfo.type, // ✅ PHASE 3: Prefer API type, fallback to assetInfo
|
|
2044
|
-
isNative: balance.isNative ?? assetInfo.isNative, // ✅ PHASE 3: Prefer API isNative
|
|
2045
|
-
networkId: caipToNetworkId(balance.caip),
|
|
2046
|
-
icon: assetInfo.icon || 'https://pioneers.dev/coins/etherum.png',
|
|
2047
|
-
identifier: `${balance.caip}:${balance.pubkey}`,
|
|
2048
|
-
updated: Date.now(), // Add timestamp for worker validation
|
|
2049
|
-
color, // Add color from mapping
|
|
2050
|
-
});
|
|
2051
|
-
}
|
|
2052
|
-
const enrichTime = performance.now() - enrichStart;
|
|
2053
|
-
console.log(`⏱️ [PERF] Enrichment completed in ${enrichTime.toFixed(0)}ms`);
|
|
2054
|
-
|
|
2055
|
-
this.balances = balances;
|
|
2056
|
-
this.events.emit('SET_BALANCES', this.balances);
|
|
2057
|
-
|
|
2058
|
-
// Build dashboard from balances
|
|
2059
|
-
console.log(`⏱️ [PERF] Building dashboard from ${balances.length} balances...`);
|
|
2060
|
-
const dashboardStart = performance.now();
|
|
2061
|
-
const dashboardData = this.buildDashboardFromBalances();
|
|
2062
|
-
this.dashboard = dashboardData;
|
|
2063
|
-
this.events.emit('SET_DASHBOARD', this.dashboard);
|
|
2064
|
-
console.log(`⏱️ [PERF] Dashboard built in ${(performance.now() - dashboardStart).toFixed(0)}ms`);
|
|
2065
|
-
console.log(`📊 Dashboard created: ${this.dashboard?.networks?.length || 0} networks, $${this.dashboard?.totalValueUsd?.toFixed(2) || '0.00'} total`);
|
|
2066
|
-
|
|
2067
|
-
console.log(`⏱️ [PERF] Total getBalancesForNetworks: ${(performance.now() - apiCallStart).toFixed(0)}ms`);
|
|
2068
|
-
return this.balances;
|
|
2069
|
-
} catch (apiError: any) {
|
|
2070
|
-
console.error(tag, 'GetPortfolioBalances API call failed:', apiError);
|
|
2071
|
-
throw new Error(
|
|
2072
|
-
`GetPortfolioBalances API call failed: ${apiError?.message || 'Unknown error'}`,
|
|
2073
|
-
);
|
|
2074
|
-
}
|
|
2075
|
-
} catch (e) {
|
|
2076
|
-
console.error(tag, 'Error: ', e);
|
|
1640
|
+
} catch (e: any) {
|
|
1641
|
+
console.error(tag, 'Error:', e?.message || e);
|
|
2077
1642
|
throw e;
|
|
2078
1643
|
}
|
|
2079
1644
|
};
|
|
2080
|
-
this.getBalances = async function (forceRefresh?: boolean) {
|
|
1645
|
+
this.getBalances = async function (forceRefresh?: boolean, caip?: string) {
|
|
2081
1646
|
const tag = `${TAG} | getBalances | `;
|
|
2082
1647
|
try {
|
|
2083
|
-
//
|
|
1648
|
+
// If CAIP is provided, refresh only that specific asset
|
|
1649
|
+
if (caip) {
|
|
1650
|
+
console.log(tag, `🎯 Refreshing single asset: ${caip}`);
|
|
1651
|
+
const networkId = caip.split('/')[0];
|
|
1652
|
+
console.log(tag, `📍 Target network: ${networkId}`);
|
|
1653
|
+
|
|
1654
|
+
const results = await this.getBalancesForNetworks([networkId], forceRefresh);
|
|
1655
|
+
return results.filter((b) => b.caip === caip || b.networkId === networkId);
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
// Default: refresh all blockchains
|
|
2084
1659
|
return await this.getBalancesForNetworks(this.blockchains, forceRefresh);
|
|
2085
1660
|
} catch (e) {
|
|
2086
1661
|
console.error(tag, 'Error in getBalances: ', e);
|
|
@@ -2090,16 +1665,8 @@ export class SDK {
|
|
|
2090
1665
|
this.getBalance = async function (networkId: string) {
|
|
2091
1666
|
const tag = `${TAG} | getBalance | `;
|
|
2092
1667
|
try {
|
|
2093
|
-
// If we need to handle special logic like eip155: inside getBalance,
|
|
2094
|
-
// we can do it here or just rely on getBalancesForNetworks to handle it.
|
|
2095
|
-
// For example:
|
|
2096
|
-
// if (networkId.includes('eip155:')) {
|
|
2097
|
-
// networkId = 'eip155:*';
|
|
2098
|
-
// }
|
|
2099
|
-
|
|
2100
1668
|
// Call the shared function with a single-network array
|
|
2101
1669
|
const results = await this.getBalancesForNetworks([networkId]);
|
|
2102
|
-
|
|
2103
1670
|
// If needed, you can filter only those that match the specific network
|
|
2104
1671
|
// (especially if you used wildcard eip155:*)
|
|
2105
1672
|
const filtered = results.filter(
|
|
@@ -2111,11 +1678,6 @@ export class SDK {
|
|
|
2111
1678
|
throw e;
|
|
2112
1679
|
}
|
|
2113
1680
|
};
|
|
2114
|
-
|
|
2115
|
-
/**
|
|
2116
|
-
* Get normalized fee rates for a specific network
|
|
2117
|
-
* This method handles all fee complexity and returns a clean, consistent format
|
|
2118
|
-
*/
|
|
2119
1681
|
this.getFees = async function (networkId: string): Promise<NormalizedFeeRates> {
|
|
2120
1682
|
const tag = `${TAG} | getFees | `;
|
|
2121
1683
|
try {
|
|
@@ -2239,171 +1801,47 @@ export class SDK {
|
|
|
2239
1801
|
if (!asset.caip) throw Error('Invalid Asset! missing caip!');
|
|
2240
1802
|
if (!asset.networkId) asset.networkId = caipToNetworkId(asset.caip);
|
|
2241
1803
|
|
|
2242
|
-
//
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
console.error(tag, errorMsg);
|
|
2246
|
-
throw new Error(errorMsg);
|
|
2247
|
-
}
|
|
2248
|
-
|
|
2249
|
-
// For EVM chains, check for wildcard eip155:* in addition to exact match
|
|
2250
|
-
const pubkeysForNetwork = this.pubkeys.filter((e: any) => {
|
|
2251
|
-
if (!e.networks || !Array.isArray(e.networks)) return false;
|
|
2252
|
-
|
|
2253
|
-
// Exact match
|
|
2254
|
-
if (e.networks.includes(asset.networkId)) return true;
|
|
2255
|
-
|
|
2256
|
-
// For EVM chains, check if pubkey has eip155:* wildcard
|
|
2257
|
-
if (asset.networkId.startsWith('eip155:') && e.networks.includes('eip155:*')) {
|
|
2258
|
-
return true;
|
|
2259
|
-
}
|
|
2260
|
-
|
|
2261
|
-
return false;
|
|
2262
|
-
});
|
|
2263
|
-
|
|
2264
|
-
if (pubkeysForNetwork.length === 0) {
|
|
2265
|
-
const errorMsg = `Cannot set asset context for ${asset.caip} - no address/xpub found for network ${asset.networkId}`;
|
|
2266
|
-
console.error(tag, errorMsg);
|
|
2267
|
-
console.error(tag, 'Available networks in pubkeys:', [
|
|
2268
|
-
...new Set(this.pubkeys.flatMap((p: any) => p.networks || [])),
|
|
2269
|
-
]);
|
|
2270
|
-
throw new Error(errorMsg);
|
|
2271
|
-
}
|
|
2272
|
-
|
|
2273
|
-
// For UTXO chains, verify we have xpub
|
|
2274
|
-
const isUtxoChain = asset.networkId.startsWith('bip122:');
|
|
2275
|
-
if (isUtxoChain) {
|
|
2276
|
-
const xpubFound = pubkeysForNetwork.some((p: any) => p.type === 'xpub' && p.pubkey);
|
|
2277
|
-
if (!xpubFound) {
|
|
2278
|
-
const errorMsg = `Cannot set asset context for UTXO chain ${asset.caip} - xpub required but not found`;
|
|
2279
|
-
console.error(tag, errorMsg);
|
|
2280
|
-
throw new Error(errorMsg);
|
|
2281
|
-
}
|
|
2282
|
-
}
|
|
2283
|
-
|
|
2284
|
-
// Verify we have a valid address or pubkey
|
|
2285
|
-
const hasValidAddress = pubkeysForNetwork.some(
|
|
2286
|
-
(p: any) => p.address || p.master || p.pubkey,
|
|
2287
|
-
);
|
|
2288
|
-
if (!hasValidAddress) {
|
|
2289
|
-
const errorMsg = `Cannot set asset context for ${asset.caip} - no valid address found in pubkeys`;
|
|
2290
|
-
console.error(tag, errorMsg);
|
|
2291
|
-
throw new Error(errorMsg);
|
|
2292
|
-
}
|
|
2293
|
-
|
|
1804
|
+
// Validate pubkeys for network (throws descriptive errors)
|
|
1805
|
+
validatePubkeysForNetwork(this.pubkeys, asset.networkId, asset.caip);
|
|
1806
|
+
const pubkeysForNetwork = findPubkeysForNetwork(this.pubkeys, asset.networkId);
|
|
2294
1807
|
console.log(
|
|
2295
1808
|
tag,
|
|
2296
1809
|
`✅ Validated: Found ${pubkeysForNetwork.length} addresses for ${asset.networkId}`,
|
|
2297
1810
|
);
|
|
2298
1811
|
|
|
2299
|
-
//
|
|
2300
|
-
|
|
2301
|
-
try {
|
|
2302
|
-
// Validate CAIP before calling API
|
|
2303
|
-
if (!asset.caip || typeof asset.caip !== 'string' || !asset.caip.includes(':')) {
|
|
2304
|
-
console.warn(tag, 'Invalid or missing CAIP, skipping market price fetch:', asset.caip);
|
|
2305
|
-
} else {
|
|
2306
|
-
console.log(tag, 'Fetching fresh market price for:', asset.caip);
|
|
2307
|
-
const marketData = await this.pioneer.GetMarketInfo([asset.caip]);
|
|
2308
|
-
console.log(tag, 'Market data response:', marketData);
|
|
2309
|
-
|
|
2310
|
-
if (marketData && marketData.data && marketData.data.length > 0) {
|
|
2311
|
-
freshPriceUsd = marketData.data[0];
|
|
2312
|
-
console.log(tag, '✅ Fresh market price:', freshPriceUsd);
|
|
2313
|
-
} else {
|
|
2314
|
-
console.warn(tag, 'No market data returned for:', asset.caip);
|
|
2315
|
-
}
|
|
2316
|
-
}
|
|
2317
|
-
} catch (marketError) {
|
|
2318
|
-
console.error(tag, 'Error fetching market price:', marketError);
|
|
2319
|
-
// Continue without fresh price, will try to use cached data
|
|
2320
|
-
}
|
|
1812
|
+
// Fetch fresh market price
|
|
1813
|
+
const freshPriceUsd = await fetchMarketPrice(this.pioneer, asset.caip);
|
|
2321
1814
|
|
|
2322
|
-
//
|
|
2323
|
-
let assetInfo = this.assetsMap
|
|
2324
|
-
console.log(tag, 'assetInfo: ', assetInfo);
|
|
2325
|
-
|
|
2326
|
-
//check discovery
|
|
2327
|
-
let assetInfoDiscovery = assetData[asset.caip];
|
|
2328
|
-
console.log(tag, 'assetInfoDiscovery: ', assetInfoDiscovery);
|
|
2329
|
-
if (assetInfoDiscovery) assetInfo = assetInfoDiscovery;
|
|
2330
|
-
|
|
2331
|
-
// If the asset is not found, create a placeholder object
|
|
2332
|
-
if (!assetInfo) {
|
|
2333
|
-
console.log(tag, 'Building placeholder asset!');
|
|
2334
|
-
// Create a placeholder asset if it's not found in Pioneer or locally
|
|
2335
|
-
assetInfo = {
|
|
2336
|
-
caip: asset.caip.toLowerCase(),
|
|
2337
|
-
networkId: asset.networkId,
|
|
2338
|
-
symbol: asset.symbol || 'UNKNOWN',
|
|
2339
|
-
name: asset.name || 'Unknown Asset',
|
|
2340
|
-
icon: asset.icon || 'https://pioneers.dev/coins/ethereum.png',
|
|
2341
|
-
};
|
|
2342
|
-
}
|
|
1815
|
+
// Resolve asset info from multiple sources
|
|
1816
|
+
let assetInfo = resolveAssetInfo(this.assetsMap, assetData, asset);
|
|
2343
1817
|
|
|
2344
|
-
//
|
|
2345
|
-
// CRITICAL: For UTXO chains, we need to aggregate ALL balances across all xpubs
|
|
1818
|
+
// Get matching balances
|
|
2346
1819
|
const matchingBalances = this.balances.filter((b) => b.caip === asset.caip);
|
|
2347
1820
|
|
|
1821
|
+
// Extract price from balances if available
|
|
2348
1822
|
if (matchingBalances.length > 0) {
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
let priceValue = matchingBalances[0].priceUsd || matchingBalances[0].price;
|
|
2352
|
-
|
|
2353
|
-
// If no price but we have valueUsd and balance, calculate the price
|
|
2354
|
-
if ((!priceValue || priceValue === 0) && matchingBalances[0].valueUsd && matchingBalances[0].balance) {
|
|
2355
|
-
const balance = parseFloat(matchingBalances[0].balance);
|
|
2356
|
-
const valueUsd = parseFloat(matchingBalances[0].valueUsd);
|
|
2357
|
-
if (balance > 0 && valueUsd > 0) {
|
|
2358
|
-
priceValue = valueUsd / balance;
|
|
2359
|
-
console.log(tag, 'calculated priceUsd from valueUsd/balance:', priceValue);
|
|
2360
|
-
}
|
|
2361
|
-
}
|
|
2362
|
-
|
|
2363
|
-
if (priceValue && priceValue > 0) {
|
|
1823
|
+
const priceValue = extractPriceFromBalances(matchingBalances);
|
|
1824
|
+
if (priceValue > 0) {
|
|
2364
1825
|
console.log(tag, 'detected priceUsd from balance:', priceValue);
|
|
2365
1826
|
assetInfo.priceUsd = priceValue;
|
|
2366
1827
|
}
|
|
2367
1828
|
}
|
|
2368
1829
|
|
|
2369
|
-
// Override with fresh price
|
|
2370
|
-
if (freshPriceUsd
|
|
1830
|
+
// Override with fresh price and aggregate balances
|
|
1831
|
+
if (freshPriceUsd > 0) {
|
|
2371
1832
|
assetInfo.priceUsd = freshPriceUsd;
|
|
2372
1833
|
console.log(tag, '✅ Using fresh market price:', freshPriceUsd);
|
|
2373
1834
|
|
|
2374
|
-
|
|
2375
|
-
let totalBalance = 0;
|
|
2376
|
-
let totalValueUsd = 0;
|
|
2377
|
-
|
|
2378
|
-
console.log(tag, `Found ${matchingBalances.length} balance entries for ${asset.caip}`);
|
|
2379
|
-
for (const balanceEntry of matchingBalances) {
|
|
2380
|
-
const balance = parseFloat(balanceEntry.balance) || 0;
|
|
2381
|
-
const valueUsd = parseFloat(balanceEntry.valueUsd) || 0;
|
|
2382
|
-
totalBalance += balance;
|
|
2383
|
-
totalValueUsd += valueUsd;
|
|
2384
|
-
console.log(tag, ` Balance entry: ${balance} (${valueUsd} USD)`);
|
|
2385
|
-
}
|
|
2386
|
-
|
|
1835
|
+
const { totalBalance, totalValueUsd } = aggregateBalances(matchingBalances, asset.caip);
|
|
2387
1836
|
assetInfo.balance = totalBalance.toString();
|
|
2388
1837
|
assetInfo.valueUsd = totalValueUsd.toFixed(2);
|
|
2389
|
-
console.log(tag, `Aggregated balance: ${totalBalance} (${totalValueUsd.toFixed(2)} USD)`);
|
|
2390
1838
|
}
|
|
2391
1839
|
|
|
2392
1840
|
// Filter balances and pubkeys for this asset
|
|
2393
1841
|
const assetBalances = this.balances.filter((b) => b.caip === asset.caip);
|
|
2394
|
-
const assetPubkeys = this.pubkeys.
|
|
2395
|
-
(p) =>
|
|
2396
|
-
(p.networks &&
|
|
2397
|
-
Array.isArray(p.networks) &&
|
|
2398
|
-
p.networks.includes(caipToNetworkId(asset.caip))) ||
|
|
2399
|
-
(caipToNetworkId(asset.caip).includes('eip155') &&
|
|
2400
|
-
p.networks &&
|
|
2401
|
-
Array.isArray(p.networks) &&
|
|
2402
|
-
p.networks.some((n) => n.startsWith('eip155'))),
|
|
2403
|
-
);
|
|
1842
|
+
const assetPubkeys = filterPubkeysForAsset(this.pubkeys, asset.caip, caipToNetworkId);
|
|
2404
1843
|
|
|
2405
1844
|
// Combine the user-provided asset with any additional info we have
|
|
2406
|
-
// IMPORTANT: Don't let a 0 priceUsd from input override a valid price from balance
|
|
2407
1845
|
const finalAssetContext = {
|
|
2408
1846
|
...assetInfo,
|
|
2409
1847
|
...asset,
|
|
@@ -2411,21 +1849,14 @@ export class SDK {
|
|
|
2411
1849
|
balances: assetBalances,
|
|
2412
1850
|
};
|
|
2413
1851
|
|
|
2414
|
-
// If input has priceUsd of 0 but we found a valid price
|
|
1852
|
+
// If input has priceUsd of 0 but we found a valid price, use the found price
|
|
2415
1853
|
if ((!asset.priceUsd || asset.priceUsd === 0) && assetInfo.priceUsd && assetInfo.priceUsd > 0) {
|
|
2416
1854
|
finalAssetContext.priceUsd = assetInfo.priceUsd;
|
|
2417
1855
|
}
|
|
2418
1856
|
|
|
2419
1857
|
// Update all matching balances with the fresh price
|
|
2420
|
-
if (freshPriceUsd
|
|
2421
|
-
|
|
2422
|
-
balance.price = freshPriceUsd;
|
|
2423
|
-
balance.priceUsd = freshPriceUsd;
|
|
2424
|
-
// Recalculate valueUsd with fresh price
|
|
2425
|
-
const balanceAmount = parseFloat(balance.balance || 0);
|
|
2426
|
-
balance.valueUsd = (balanceAmount * freshPriceUsd).toString();
|
|
2427
|
-
}
|
|
2428
|
-
console.log(tag, 'Updated all balances with fresh price data');
|
|
1858
|
+
if (freshPriceUsd > 0) {
|
|
1859
|
+
updateBalancesWithPrice(assetBalances, freshPriceUsd);
|
|
2429
1860
|
}
|
|
2430
1861
|
|
|
2431
1862
|
this.assetContext = finalAssetContext;
|
|
@@ -2439,38 +1870,6 @@ export class SDK {
|
|
|
2439
1870
|
) {
|
|
2440
1871
|
// Get the native asset for this network
|
|
2441
1872
|
const networkId = asset.networkId || assetInfo.networkId;
|
|
2442
|
-
|
|
2443
|
-
// Determine the native gas symbol based on the network
|
|
2444
|
-
let nativeSymbol = 'GAS'; // default fallback
|
|
2445
|
-
let nativeCaip = '';
|
|
2446
|
-
|
|
2447
|
-
//TODO removeme
|
|
2448
|
-
if (networkId.includes('mayachain')) {
|
|
2449
|
-
nativeSymbol = 'CACAO';
|
|
2450
|
-
nativeCaip = 'cosmos:mayachain-mainnet-v1/slip44:931';
|
|
2451
|
-
} else if (networkId.includes('thorchain')) {
|
|
2452
|
-
nativeSymbol = 'RUNE';
|
|
2453
|
-
nativeCaip = 'cosmos:thorchain-mainnet-v1/slip44:931';
|
|
2454
|
-
} else if (networkId.includes('cosmoshub')) {
|
|
2455
|
-
nativeSymbol = 'ATOM';
|
|
2456
|
-
nativeCaip = 'cosmos:cosmoshub-4/slip44:118';
|
|
2457
|
-
} else if (networkId.includes('osmosis')) {
|
|
2458
|
-
nativeSymbol = 'OSMO';
|
|
2459
|
-
nativeCaip = 'cosmos:osmosis-1/slip44:118';
|
|
2460
|
-
} else if (networkId.includes('eip155:1')) {
|
|
2461
|
-
nativeSymbol = 'ETH';
|
|
2462
|
-
nativeCaip = 'eip155:1/slip44:60';
|
|
2463
|
-
} else if (networkId.includes('eip155:137')) {
|
|
2464
|
-
nativeSymbol = 'MATIC';
|
|
2465
|
-
nativeCaip = 'eip155:137/slip44:60';
|
|
2466
|
-
} else if (networkId.includes('eip155:56')) {
|
|
2467
|
-
nativeSymbol = 'BNB';
|
|
2468
|
-
nativeCaip = 'eip155:56/slip44:60';
|
|
2469
|
-
} else if (networkId.includes('eip155:43114')) {
|
|
2470
|
-
nativeSymbol = 'AVAX';
|
|
2471
|
-
nativeCaip = 'eip155:43114/slip44:60';
|
|
2472
|
-
}
|
|
2473
|
-
|
|
2474
1873
|
// Set the native symbol
|
|
2475
1874
|
this.assetContext.nativeSymbol = nativeSymbol;
|
|
2476
1875
|
|
|
@@ -2569,125 +1968,54 @@ export class SDK {
|
|
|
2569
1968
|
this.setOutboundAssetContext = async function (asset?: any): Promise<any> {
|
|
2570
1969
|
const tag = `${TAG} | setOutputAssetContext | `;
|
|
2571
1970
|
try {
|
|
2572
|
-
console.log(tag, '
|
|
1971
|
+
console.log(tag, 'asset:', asset);
|
|
2573
1972
|
// Accept null
|
|
2574
1973
|
if (!asset) {
|
|
2575
1974
|
this.outboundAssetContext = null;
|
|
2576
1975
|
return;
|
|
2577
1976
|
}
|
|
2578
1977
|
|
|
2579
|
-
console.log(tag, '1 asset: ', asset);
|
|
2580
|
-
|
|
2581
1978
|
if (!asset.caip) throw Error('Invalid Asset! missing caip!');
|
|
2582
1979
|
if (!asset.networkId) asset.networkId = caipToNetworkId(asset.caip);
|
|
2583
1980
|
|
|
2584
|
-
console.log(tag, 'networkId:
|
|
2585
|
-
console.log(tag, 'this.pubkeys: ', this.pubkeys);
|
|
2586
|
-
//get a pubkey for network (handle EVM wildcard)
|
|
2587
|
-
const pubkey = this.pubkeys.find((p) => {
|
|
2588
|
-
if (!p.networks || !Array.isArray(p.networks)) return false;
|
|
2589
|
-
// Exact match
|
|
2590
|
-
if (p.networks.includes(asset.networkId)) return true;
|
|
2591
|
-
// For EVM chains, check if pubkey has eip155:* wildcard
|
|
2592
|
-
if (asset.networkId.startsWith('eip155:') && p.networks.includes('eip155:*')) return true;
|
|
2593
|
-
return false;
|
|
2594
|
-
});
|
|
2595
|
-
if (!pubkey) throw Error('Invalid network! missing pubkey for network! ' + asset.networkId);
|
|
1981
|
+
console.log(tag, 'networkId:', asset.networkId);
|
|
2596
1982
|
|
|
2597
|
-
//
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
// Validate CAIP before calling API
|
|
2601
|
-
if (!asset.caip || typeof asset.caip !== 'string' || !asset.caip.includes(':')) {
|
|
2602
|
-
console.warn(tag, 'Invalid or missing CAIP, skipping market price fetch:', asset.caip);
|
|
2603
|
-
} else {
|
|
2604
|
-
console.log(tag, 'Fetching fresh market price for:', asset.caip);
|
|
2605
|
-
const marketData = await this.pioneer.GetMarketInfo([asset.caip]);
|
|
2606
|
-
console.log(tag, 'Market data response:', marketData);
|
|
1983
|
+
// Get pubkey for network (uses utility helper)
|
|
1984
|
+
const pubkey = findPubkeyForNetwork(this.pubkeys, asset.networkId);
|
|
1985
|
+
if (!pubkey) throw Error('Invalid network! missing pubkey for network! ' + asset.networkId);
|
|
2607
1986
|
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
console.log(tag, '✅ Fresh market price:', freshPriceUsd);
|
|
2611
|
-
} else {
|
|
2612
|
-
console.warn(tag, 'No market data returned for:', asset.caip);
|
|
2613
|
-
}
|
|
2614
|
-
}
|
|
2615
|
-
} catch (marketError) {
|
|
2616
|
-
console.error(tag, 'Error fetching market price:', marketError);
|
|
2617
|
-
// Continue without fresh price, will try to use cached data
|
|
2618
|
-
}
|
|
1987
|
+
// Fetch fresh market price
|
|
1988
|
+
const freshPriceUsd = await fetchMarketPrice(this.pioneer, asset.caip);
|
|
2619
1989
|
|
|
2620
|
-
//
|
|
2621
|
-
let assetInfo = this.assetsMap
|
|
2622
|
-
console.log(tag, 'assetInfo: ', assetInfo);
|
|
2623
|
-
|
|
2624
|
-
// If the asset is not found, create a placeholder object
|
|
2625
|
-
if (!assetInfo) {
|
|
2626
|
-
// Create a placeholder asset if it's not found in Pioneer or locally
|
|
2627
|
-
assetInfo = {
|
|
2628
|
-
caip: asset.caip.toLowerCase(),
|
|
2629
|
-
networkId: asset.networkId,
|
|
2630
|
-
symbol: asset.symbol || 'UNKNOWN',
|
|
2631
|
-
name: asset.name || 'Unknown Asset',
|
|
2632
|
-
icon: asset.icon || 'https://pioneers.dev/coins/ethereum.png',
|
|
2633
|
-
};
|
|
2634
|
-
}
|
|
1990
|
+
// Resolve asset info from multiple sources
|
|
1991
|
+
let assetInfo = resolveAssetInfo(this.assetsMap, assetData, asset);
|
|
2635
1992
|
|
|
2636
|
-
//
|
|
2637
|
-
// CRITICAL: For UTXO chains, we need to aggregate ALL balances across all xpubs
|
|
1993
|
+
// Get matching balances
|
|
2638
1994
|
const matchingBalances = this.balances.filter((b) => b.caip === asset.caip);
|
|
2639
1995
|
|
|
1996
|
+
// Extract price from balances if available
|
|
2640
1997
|
if (matchingBalances.length > 0) {
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
let priceValue = matchingBalances[0].priceUsd || matchingBalances[0].price;
|
|
2644
|
-
|
|
2645
|
-
// If no price but we have valueUsd and balance, calculate the price
|
|
2646
|
-
if ((!priceValue || priceValue === 0) && matchingBalances[0].valueUsd && matchingBalances[0].balance) {
|
|
2647
|
-
const balance = parseFloat(matchingBalances[0].balance);
|
|
2648
|
-
const valueUsd = parseFloat(matchingBalances[0].valueUsd);
|
|
2649
|
-
if (balance > 0 && valueUsd > 0) {
|
|
2650
|
-
priceValue = valueUsd / balance;
|
|
2651
|
-
console.log(tag, 'calculated priceUsd from valueUsd/balance:', priceValue);
|
|
2652
|
-
}
|
|
2653
|
-
}
|
|
2654
|
-
|
|
2655
|
-
if (priceValue && priceValue > 0) {
|
|
1998
|
+
const priceValue = extractPriceFromBalances(matchingBalances);
|
|
1999
|
+
if (priceValue > 0) {
|
|
2656
2000
|
console.log(tag, 'detected priceUsd from balance:', priceValue);
|
|
2657
2001
|
assetInfo.priceUsd = priceValue;
|
|
2658
2002
|
}
|
|
2659
2003
|
}
|
|
2660
2004
|
|
|
2661
|
-
// Override with fresh price
|
|
2662
|
-
if (freshPriceUsd
|
|
2005
|
+
// Override with fresh price and aggregate balances
|
|
2006
|
+
if (freshPriceUsd > 0) {
|
|
2663
2007
|
assetInfo.priceUsd = freshPriceUsd;
|
|
2664
2008
|
console.log(tag, '✅ Using fresh market price:', freshPriceUsd);
|
|
2665
2009
|
|
|
2666
|
-
|
|
2667
|
-
let totalBalance = 0;
|
|
2668
|
-
let totalValueUsd = 0;
|
|
2669
|
-
|
|
2670
|
-
console.log(tag, `Found ${matchingBalances.length} balance entries for ${asset.caip}`);
|
|
2671
|
-
for (const balanceEntry of matchingBalances) {
|
|
2672
|
-
const balance = parseFloat(balanceEntry.balance) || 0;
|
|
2673
|
-
const valueUsd = parseFloat(balanceEntry.valueUsd) || 0;
|
|
2674
|
-
totalBalance += balance;
|
|
2675
|
-
totalValueUsd += valueUsd;
|
|
2676
|
-
console.log(tag, ` Balance entry: ${balance} (${valueUsd} USD)`);
|
|
2677
|
-
}
|
|
2678
|
-
|
|
2010
|
+
const { totalBalance, totalValueUsd } = aggregateBalances(matchingBalances, asset.caip);
|
|
2679
2011
|
assetInfo.balance = totalBalance.toString();
|
|
2680
2012
|
assetInfo.valueUsd = totalValueUsd.toFixed(2);
|
|
2681
|
-
console.log(tag, `Aggregated balance: ${totalBalance} (${totalValueUsd.toFixed(2)} USD)`);
|
|
2682
2013
|
}
|
|
2683
2014
|
|
|
2684
|
-
console.log(tag, 'CHECKPOINT 1');
|
|
2685
|
-
|
|
2686
2015
|
// Combine the user-provided asset with any additional info we have
|
|
2687
2016
|
this.outboundAssetContext = { ...assetInfo, ...asset, ...pubkey };
|
|
2688
2017
|
|
|
2689
|
-
console.log(tag, '
|
|
2690
|
-
console.log(tag, 'outboundAssetContext: assetInfo: ', assetInfo);
|
|
2018
|
+
console.log(tag, 'outboundAssetContext set:', this.outboundAssetContext.caip);
|
|
2691
2019
|
|
|
2692
2020
|
// Set outbound blockchain context based on asset
|
|
2693
2021
|
if (asset.caip) {
|
|
@@ -2717,18 +2045,8 @@ export class SDK {
|
|
|
2717
2045
|
ownerAddress: string;
|
|
2718
2046
|
spenderAddress: string;
|
|
2719
2047
|
}) => {
|
|
2720
|
-
const
|
|
2721
|
-
|
|
2722
|
-
console.log(tag, 'Checking ERC20 allowance:', params);
|
|
2723
|
-
|
|
2724
|
-
const result = await this.pioneer.GetTokenAllowance(params);
|
|
2725
|
-
|
|
2726
|
-
console.log(tag, 'Allowance result:', result);
|
|
2727
|
-
return result.data;
|
|
2728
|
-
} catch (e) {
|
|
2729
|
-
console.error(tag, 'Error checking ERC20 allowance:', e);
|
|
2730
|
-
throw e;
|
|
2731
|
-
}
|
|
2048
|
+
const result = await this.pioneer.GetTokenAllowance(params);
|
|
2049
|
+
return result.data;
|
|
2732
2050
|
};
|
|
2733
2051
|
|
|
2734
2052
|
/**
|
|
@@ -2742,18 +2060,8 @@ export class SDK {
|
|
|
2742
2060
|
ownerAddress: string;
|
|
2743
2061
|
amount: string;
|
|
2744
2062
|
}) => {
|
|
2745
|
-
const
|
|
2746
|
-
|
|
2747
|
-
console.log(tag, 'Building ERC20 approval transaction:', params);
|
|
2748
|
-
|
|
2749
|
-
const result = await this.pioneer.BuildApprovalTransaction(params);
|
|
2750
|
-
|
|
2751
|
-
console.log(tag, 'Approval tx built:', result);
|
|
2752
|
-
return result.data;
|
|
2753
|
-
} catch (e) {
|
|
2754
|
-
console.error(tag, 'Error building approval transaction:', e);
|
|
2755
|
-
throw e;
|
|
2756
|
-
}
|
|
2063
|
+
const result = await this.pioneer.BuildApprovalTransaction(params);
|
|
2064
|
+
return result.data;
|
|
2757
2065
|
};
|
|
2758
2066
|
}
|
|
2759
2067
|
|