@pioneer-platform/pioneer-sdk 8.15.13 → 8.15.15
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 +546 -648
- package/dist/index.es.js +546 -648
- package/dist/index.js +546 -648
- package/package.json +4 -4
- package/src/index.ts +222 -915
- package/src/utils/fee-reserves.ts +162 -0
- package/src/utils/network-helpers.ts +85 -0
- package/src/utils/path-discovery.ts +68 -0
- package/src/utils/portfolio-helpers.ts +209 -0
- package/src/utils/pubkey-management.ts +124 -0
- package/src/utils/pubkey-sync.ts +86 -0
- package/src/utils/sync-state.ts +93 -0
package/src/index.ts
CHANGED
|
@@ -21,43 +21,47 @@ import { detectKkApiAvailability } from './utils/kkapi-detection.js';
|
|
|
21
21
|
import { formatTime } from './utils/format-time.js';
|
|
22
22
|
import { buildDashboardFromBalances } from './utils/build-dashboard.js';
|
|
23
23
|
import { syncMarket } from './utils/sync-market.js';
|
|
24
|
+
import {
|
|
25
|
+
getPubkeyKey,
|
|
26
|
+
deduplicatePubkeys,
|
|
27
|
+
validatePubkey,
|
|
28
|
+
findPubkeysForNetwork,
|
|
29
|
+
findPubkeyForNetwork,
|
|
30
|
+
validatePubkeysForNetwork,
|
|
31
|
+
filterPubkeysForAsset
|
|
32
|
+
} from './utils/pubkey-management.js';
|
|
33
|
+
import {
|
|
34
|
+
isCacheDataValid,
|
|
35
|
+
buildDashboardFromPortfolioData,
|
|
36
|
+
fetchMarketPrice,
|
|
37
|
+
extractPriceFromBalances,
|
|
38
|
+
aggregateBalances,
|
|
39
|
+
updateBalancesWithPrice
|
|
40
|
+
} from './utils/portfolio-helpers.js';
|
|
41
|
+
import {
|
|
42
|
+
createInitialSyncState,
|
|
43
|
+
createCacheSyncState,
|
|
44
|
+
createFreshSyncState,
|
|
45
|
+
type SyncState,
|
|
46
|
+
resolveAssetInfo
|
|
47
|
+
} from './utils/sync-state.js';
|
|
48
|
+
import { getMaxSendableAmount } from './utils/fee-reserves.js';
|
|
49
|
+
import {
|
|
50
|
+
matchesNetwork,
|
|
51
|
+
normalizeNetworkId,
|
|
52
|
+
validatePubkeysNetworks
|
|
53
|
+
} from './utils/network-helpers.js';
|
|
54
|
+
import {
|
|
55
|
+
buildAssetQuery,
|
|
56
|
+
logQueryDiagnostics,
|
|
57
|
+
enrichBalancesWithAssetInfo
|
|
58
|
+
} from './utils/portfolio-helpers.js';
|
|
59
|
+
import { ensurePathsForBlockchains } from './utils/path-discovery.js';
|
|
60
|
+
import { syncPubkeysForBlockchains } from './utils/pubkey-sync.js';
|
|
61
|
+
// import { ASSET_COLORS } from './constants/asset-colors.js'; // TODO: Create this file or remove usage
|
|
24
62
|
|
|
25
63
|
const TAG = ' | Pioneer-sdk | ';
|
|
26
64
|
|
|
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
65
|
export interface PioneerSDKConfig {
|
|
62
66
|
appName: string;
|
|
63
67
|
appIcon: string;
|
|
@@ -84,18 +88,6 @@ export interface PioneerSDKConfig {
|
|
|
84
88
|
skipKeeperEndpoint?: boolean; // Skip vault endpoint detection
|
|
85
89
|
}
|
|
86
90
|
|
|
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
91
|
export class SDK {
|
|
100
92
|
public status: string;
|
|
101
93
|
public username: string;
|
|
@@ -159,18 +151,8 @@ export class SDK {
|
|
|
159
151
|
public appName: string;
|
|
160
152
|
public appIcon: any;
|
|
161
153
|
public init: (walletsVerbose: any, setup: any) => Promise<any>;
|
|
162
|
-
// public initOffline: () => Promise<any>;
|
|
163
|
-
// public backgroundSync: () => Promise<void>;
|
|
164
154
|
public getUnifiedPortfolio: () => Promise<any>;
|
|
165
155
|
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
156
|
public app: {
|
|
175
157
|
getAddress: (options: {
|
|
176
158
|
networkId?: string;
|
|
@@ -272,16 +254,7 @@ export class SDK {
|
|
|
272
254
|
this.contextType = '';
|
|
273
255
|
|
|
274
256
|
// 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
|
-
};
|
|
257
|
+
this.syncState = createInitialSyncState(this.blockchains.length);
|
|
285
258
|
|
|
286
259
|
// Initialize offline client if offline-first mode is enabled
|
|
287
260
|
this.offlineClient = config.offlineFirst
|
|
@@ -314,40 +287,17 @@ export class SDK {
|
|
|
314
287
|
});
|
|
315
288
|
};
|
|
316
289
|
|
|
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
|
-
};
|
|
290
|
+
// Pubkey helper methods using utilities
|
|
291
|
+
this.getPubkeyKey = getPubkeyKey;
|
|
292
|
+
this.deduplicatePubkeys = deduplicatePubkeys;
|
|
335
293
|
|
|
336
294
|
// Helper method to validate and add a single pubkey
|
|
337
295
|
this.addPubkey = (pubkey: any): boolean => {
|
|
338
|
-
|
|
339
|
-
if (!pubkey.pubkey || !pubkey.pathMaster) {
|
|
340
|
-
return false;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
const key = this.getPubkeyKey(pubkey);
|
|
296
|
+
if (!validatePubkey(pubkey)) return false;
|
|
344
297
|
|
|
345
|
-
|
|
346
|
-
if (this.pubkeySet.has(key))
|
|
347
|
-
return false;
|
|
348
|
-
}
|
|
298
|
+
const key = getPubkeyKey(pubkey);
|
|
299
|
+
if (this.pubkeySet.has(key)) return false;
|
|
349
300
|
|
|
350
|
-
// Add to both array and set
|
|
351
301
|
this.pubkeys.push(pubkey);
|
|
352
302
|
this.pubkeySet.add(key);
|
|
353
303
|
return true;
|
|
@@ -378,20 +328,6 @@ export class SDK {
|
|
|
378
328
|
this.pubkeys = [];
|
|
379
329
|
this.pubkeySet.clear();
|
|
380
330
|
}
|
|
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
331
|
// Fast portfolio loading from kkapi:// cache
|
|
396
332
|
this.getUnifiedPortfolio = async function () {
|
|
397
333
|
const tag = `${TAG} | getUnifiedPortfolio | `;
|
|
@@ -439,10 +375,8 @@ export class SDK {
|
|
|
439
375
|
|
|
440
376
|
// Update pubkeys from cache
|
|
441
377
|
if (portfolioData.pubkeys && portfolioData.pubkeys.length > 0) {
|
|
442
|
-
// Convert vault pubkey format to pioneer-sdk format
|
|
443
|
-
const convertedPubkeys = this.convertVaultPubkeysToPioneerFormat(portfolioData.pubkeys);
|
|
444
378
|
// Use setPubkeys to ensure deduplication
|
|
445
|
-
this.setPubkeys(
|
|
379
|
+
this.setPubkeys(portfolioData.pubkeys);
|
|
446
380
|
this.events.emit('SET_PUBKEYS', this.pubkeys);
|
|
447
381
|
}
|
|
448
382
|
|
|
@@ -459,75 +393,12 @@ export class SDK {
|
|
|
459
393
|
}
|
|
460
394
|
|
|
461
395
|
// 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
396
|
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;
|
|
397
|
+
this.dashboard = buildDashboardFromPortfolioData(portfolioData);
|
|
514
398
|
this.events.emit('SET_DASHBOARD', this.dashboard);
|
|
515
399
|
|
|
516
400
|
// 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
|
-
};
|
|
401
|
+
this.syncState = createCacheSyncState(portfolioData.lastUpdated, this.blockchains.length);
|
|
531
402
|
this.events.emit('SYNC_STATE_CHANGED', this.syncState);
|
|
532
403
|
} else {
|
|
533
404
|
console.warn(
|
|
@@ -784,249 +655,106 @@ export class SDK {
|
|
|
784
655
|
this.buildDashboardFromBalances = function () {
|
|
785
656
|
return buildDashboardFromBalances(this.balances, this.blockchains, this.assetsMap);
|
|
786
657
|
};
|
|
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
658
|
this.syncMarket = async function () {
|
|
795
659
|
return syncMarket(this.balances, this.pioneer);
|
|
796
660
|
};
|
|
797
661
|
this.sync = async function () {
|
|
798
662
|
const tag = `${TAG} | sync | `;
|
|
663
|
+
const log = require('@pioneer-platform/loggerdog')();
|
|
664
|
+
|
|
799
665
|
try {
|
|
800
|
-
//
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
if (networkId.startsWith('eip155:') && item.networks.includes('eip155:*')) return true;
|
|
805
|
-
return false;
|
|
666
|
+
// Update sync state: starting
|
|
667
|
+
this.syncState = {
|
|
668
|
+
...createInitialSyncState(this.blockchains.length),
|
|
669
|
+
syncProgress: 10
|
|
806
670
|
};
|
|
671
|
+
this.events.emit('SYNC_STATE_CHANGED', this.syncState);
|
|
807
672
|
|
|
808
|
-
//
|
|
673
|
+
// Step 1: Initial pubkey fetch
|
|
674
|
+
log.info(tag, 'Fetching initial pubkeys...');
|
|
809
675
|
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
676
|
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
677
|
+
this.syncState.syncProgress = 20;
|
|
678
|
+
this.events.emit('SYNC_STATE_CHANGED', this.syncState);
|
|
679
|
+
|
|
680
|
+
// Step 2: Ensure paths for all blockchains
|
|
681
|
+
log.info(tag, 'Discovering paths for blockchains...');
|
|
682
|
+
this.paths = await ensurePathsForBlockchains(
|
|
683
|
+
this.blockchains,
|
|
684
|
+
this.paths,
|
|
685
|
+
tag
|
|
686
|
+
);
|
|
687
|
+
|
|
688
|
+
this.syncState.syncProgress = 30;
|
|
689
|
+
this.events.emit('SYNC_STATE_CHANGED', this.syncState);
|
|
690
|
+
|
|
691
|
+
// Step 3: Sync pubkeys for all paths
|
|
692
|
+
log.info(tag, 'Synchronizing pubkeys...');
|
|
693
|
+
await syncPubkeysForBlockchains(
|
|
694
|
+
this.blockchains,
|
|
695
|
+
this.paths,
|
|
696
|
+
this.pubkeys,
|
|
697
|
+
this.keepKeySdk,
|
|
698
|
+
this.context,
|
|
699
|
+
getPubkey,
|
|
700
|
+
(pubkey) => this.addPubkey(pubkey),
|
|
701
|
+
tag
|
|
702
|
+
);
|
|
703
|
+
|
|
704
|
+
this.syncState.syncProgress = 50;
|
|
705
|
+
this.syncState.syncedChains = this.blockchains.length;
|
|
706
|
+
this.events.emit('SYNC_STATE_CHANGED', this.syncState);
|
|
707
|
+
|
|
708
|
+
// Step 4: Fetch balances
|
|
709
|
+
log.info(tag, 'Fetching balances...');
|
|
848
710
|
await this.getBalances();
|
|
849
711
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
712
|
+
this.syncState.syncProgress = 70;
|
|
713
|
+
this.events.emit('SYNC_STATE_CHANGED', this.syncState);
|
|
714
|
+
|
|
715
|
+
// Step 5: Load charts (critical for token discovery)
|
|
716
|
+
log.info(tag, 'Loading charts (tokens + portfolio)...');
|
|
853
717
|
await this.getCharts();
|
|
854
|
-
|
|
718
|
+
log.info(tag, `Charts loaded. Total balances: ${this.balances.length}`);
|
|
855
719
|
|
|
856
|
-
|
|
857
|
-
|
|
720
|
+
this.syncState.syncProgress = 85;
|
|
721
|
+
this.events.emit('SYNC_STATE_CHANGED', this.syncState);
|
|
858
722
|
|
|
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
|
-
};
|
|
723
|
+
// Step 6: Sync market prices
|
|
724
|
+
log.info(tag, 'Syncing market prices...');
|
|
725
|
+
await this.syncMarket();
|
|
877
726
|
|
|
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
|
-
});
|
|
727
|
+
this.syncState.syncProgress = 95;
|
|
728
|
+
this.events.emit('SYNC_STATE_CHANGED', this.syncState);
|
|
901
729
|
|
|
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
|
-
});
|
|
730
|
+
// Step 7: Build dashboard
|
|
731
|
+
log.info(tag, 'Building dashboard...');
|
|
732
|
+
this.dashboard = buildDashboardFromBalances(
|
|
733
|
+
this.balances,
|
|
734
|
+
[...new Set(this.blockchains)], // Deduplicate blockchains
|
|
735
|
+
this.assetsMap
|
|
736
|
+
);
|
|
917
737
|
|
|
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
|
-
}
|
|
738
|
+
// Step 8: Update sync state - complete
|
|
739
|
+
this.syncState = createFreshSyncState(this.blockchains.length);
|
|
740
|
+
this.events.emit('SYNC_STATE_CHANGED', this.syncState);
|
|
741
|
+
this.events.emit('SYNC_COMPLETE', this.syncState);
|
|
948
742
|
|
|
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
|
-
});
|
|
743
|
+
log.info(tag, '✅ Sync complete!');
|
|
744
|
+
return true;
|
|
986
745
|
|
|
987
|
-
|
|
988
|
-
|
|
746
|
+
} catch (e) {
|
|
747
|
+
log.error(tag, 'Sync failed:', e);
|
|
989
748
|
|
|
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
|
|
749
|
+
// Update sync state with error
|
|
1014
750
|
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'
|
|
751
|
+
...this.syncState,
|
|
752
|
+
isSynced: false,
|
|
753
|
+
syncProgress: 0
|
|
1023
754
|
};
|
|
1024
755
|
this.events.emit('SYNC_STATE_CHANGED', this.syncState);
|
|
1025
|
-
this.events.emit('
|
|
756
|
+
this.events.emit('SYNC_ERROR', e);
|
|
1026
757
|
|
|
1027
|
-
return true;
|
|
1028
|
-
} catch (e) {
|
|
1029
|
-
console.error(tag, 'Error in sync:', e);
|
|
1030
758
|
throw e;
|
|
1031
759
|
}
|
|
1032
760
|
};
|
|
@@ -1213,16 +941,6 @@ export class SDK {
|
|
|
1213
941
|
|
|
1214
942
|
//get quote
|
|
1215
943
|
// 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
944
|
const pubkeys = this.pubkeys.filter((e: any) =>
|
|
1227
945
|
matchesNetwork(e, this.assetContext.networkId),
|
|
1228
946
|
);
|
|
@@ -1280,23 +998,12 @@ export class SDK {
|
|
|
1280
998
|
).toFixed(2);
|
|
1281
999
|
console.log(tag, `Updated assetContext balance to aggregated total: ${totalBalance}`);
|
|
1282
1000
|
|
|
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);
|
|
1001
|
+
// Calculate max sendable amount using centralized fee reserve utility
|
|
1002
|
+
inputAmount = getMaxSendableAmount(totalBalance, swapPayload.caipIn);
|
|
1296
1003
|
|
|
1297
1004
|
console.log(
|
|
1298
1005
|
tag,
|
|
1299
|
-
`Using max amount for swap: ${inputAmount} (total balance: ${totalBalance}
|
|
1006
|
+
`Using max amount for swap: ${inputAmount} (total balance: ${totalBalance})`,
|
|
1300
1007
|
);
|
|
1301
1008
|
} else {
|
|
1302
1009
|
// Convert amount to number for type safety
|
|
@@ -1780,31 +1487,8 @@ export class SDK {
|
|
|
1780
1487
|
}
|
|
1781
1488
|
}
|
|
1782
1489
|
|
|
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
|
-
}
|
|
1490
|
+
// All gas assets and tokens are now loaded from assetData
|
|
1491
|
+
// MAYA token is included in generatedAssetData.json
|
|
1808
1492
|
|
|
1809
1493
|
return this.assetsMap;
|
|
1810
1494
|
} catch (e) {
|
|
@@ -1892,195 +1576,85 @@ export class SDK {
|
|
|
1892
1576
|
this.getBalancesForNetworks = async function (networkIds: string[], forceRefresh?: boolean) {
|
|
1893
1577
|
const tag = `${TAG} | getBalancesForNetworks | `;
|
|
1894
1578
|
try {
|
|
1895
|
-
// Add defensive check for pioneer initialization
|
|
1896
1579
|
if (!this.pioneer) {
|
|
1897
|
-
console.error(
|
|
1898
|
-
tag,
|
|
1899
|
-
'ERROR: Pioneer client not initialized! this.pioneer is:',
|
|
1900
|
-
this.pioneer,
|
|
1901
|
-
);
|
|
1902
1580
|
throw new Error('Pioneer client not initialized. Call init() first.');
|
|
1903
1581
|
}
|
|
1904
1582
|
|
|
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));
|
|
1583
|
+
if (forceRefresh) console.log(tag, '🔄 Force refresh requested');
|
|
1917
1584
|
|
|
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
|
-
}
|
|
1585
|
+
// Validate pubkeys and log diagnostics
|
|
1586
|
+
console.log('🔍 [DIAGNOSTIC] Networks:', networkIds.length, 'Pubkeys:', this.pubkeys.length);
|
|
1587
|
+
const { valid, invalid } = validatePubkeysNetworks(this.pubkeys, '🔍 [DIAGNOSTIC]');
|
|
1588
|
+
console.log('🔍 [DIAGNOSTIC] Pubkeys:', { valid: valid.length, invalid: invalid.length });
|
|
1927
1589
|
|
|
1590
|
+
// Build asset query for all networks
|
|
1928
1591
|
const assetQuery: { caip: string; pubkey: string }[] = [];
|
|
1929
|
-
|
|
1930
1592
|
for (const networkId of networkIds) {
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
}
|
|
1593
|
+
const pubkeys = findPubkeysForNetwork(this.pubkeys, networkId, this.paths, tag);
|
|
1594
|
+
const caip = await networkIdToCaip(networkId);
|
|
1595
|
+
assetQuery.push(...buildAssetQuery(pubkeys, caip));
|
|
1596
|
+
}
|
|
1936
1597
|
|
|
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
|
-
);
|
|
1598
|
+
logQueryDiagnostics(assetQuery, '🔍 [DIAGNOSTIC]');
|
|
1947
1599
|
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1600
|
+
// Fetch balances from API
|
|
1601
|
+
console.log(`⏱️ [PERF] Starting GetPortfolioBalances...`);
|
|
1602
|
+
const apiStart = performance.now();
|
|
1603
|
+
console.time('GetPortfolioBalances Response');
|
|
1952
1604
|
|
|
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
|
-
);
|
|
1605
|
+
const marketInfo = await this.pioneer.GetPortfolioBalances(
|
|
1606
|
+
{ pubkeys: assetQuery },
|
|
1607
|
+
forceRefresh ? { forceRefresh: true } : undefined
|
|
1608
|
+
);
|
|
1964
1609
|
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
pubkeys.push(matchingPubkey);
|
|
1968
|
-
}
|
|
1969
|
-
}
|
|
1610
|
+
console.timeEnd('GetPortfolioBalances Response');
|
|
1611
|
+
console.log(`⏱️ [PERF] API completed in ${(performance.now() - apiStart).toFixed(0)}ms`);
|
|
1970
1612
|
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
console.error(tag, ` ❌ Fallback failed: No pubkeys found for ${networkId}`);
|
|
1975
|
-
}
|
|
1976
|
-
}
|
|
1613
|
+
// Enrich balances with asset metadata
|
|
1614
|
+
const enrichStart = performance.now();
|
|
1615
|
+
console.log(`⏱️ [PERF] Enriching ${marketInfo.data?.length || 0} balances...`);
|
|
1977
1616
|
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1617
|
+
const balances = enrichBalancesWithAssetInfo(
|
|
1618
|
+
marketInfo.data,
|
|
1619
|
+
this.assetsMap,
|
|
1620
|
+
caipToNetworkId
|
|
1621
|
+
);
|
|
1983
1622
|
|
|
1984
|
-
|
|
1985
|
-
console.log('🔍 [DIAGNOSTIC] Built assetQuery with', assetQuery.length, 'entries');
|
|
1986
|
-
console.log('🔍 [DIAGNOSTIC] Sample queries:', assetQuery.slice(0, 5));
|
|
1623
|
+
console.log(`⏱️ [PERF] Enrichment completed in ${(performance.now() - enrichStart).toFixed(0)}ms`);
|
|
1987
1624
|
|
|
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
|
-
});
|
|
1625
|
+
// Update state and emit events
|
|
1626
|
+
this.balances = balances;
|
|
1627
|
+
this.events.emit('SET_BALANCES', this.balances);
|
|
1997
1628
|
|
|
1998
|
-
|
|
1999
|
-
const
|
|
2000
|
-
|
|
1629
|
+
// Build and emit dashboard
|
|
1630
|
+
const dashStart = performance.now();
|
|
1631
|
+
this.dashboard = this.buildDashboardFromBalances();
|
|
1632
|
+
this.events.emit('SET_DASHBOARD', this.dashboard);
|
|
1633
|
+
console.log(`⏱️ [PERF] Dashboard built in ${(performance.now() - dashStart).toFixed(0)}ms`);
|
|
1634
|
+
console.log(`📊 Dashboard: ${this.dashboard?.networks?.length || 0} networks, $${this.dashboard?.totalValueUsd?.toFixed(2) || '0.00'}`);
|
|
2001
1635
|
|
|
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
|
-
}
|
|
1636
|
+
console.log(`⏱️ [PERF] Total: ${(performance.now() - apiStart).toFixed(0)}ms`);
|
|
1637
|
+
return this.balances;
|
|
2038
1638
|
|
|
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);
|
|
1639
|
+
} catch (e: any) {
|
|
1640
|
+
console.error(tag, 'Error:', e?.message || e);
|
|
2077
1641
|
throw e;
|
|
2078
1642
|
}
|
|
2079
1643
|
};
|
|
2080
|
-
this.getBalances = async function (forceRefresh?: boolean) {
|
|
1644
|
+
this.getBalances = async function (forceRefresh?: boolean, caip?: string) {
|
|
2081
1645
|
const tag = `${TAG} | getBalances | `;
|
|
2082
1646
|
try {
|
|
2083
|
-
//
|
|
1647
|
+
// If CAIP is provided, refresh only that specific asset
|
|
1648
|
+
if (caip) {
|
|
1649
|
+
console.log(tag, `🎯 Refreshing single asset: ${caip}`);
|
|
1650
|
+
const networkId = caip.split('/')[0];
|
|
1651
|
+
console.log(tag, `📍 Target network: ${networkId}`);
|
|
1652
|
+
|
|
1653
|
+
const results = await this.getBalancesForNetworks([networkId], forceRefresh);
|
|
1654
|
+
return results.filter((b) => b.caip === caip || b.networkId === networkId);
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
// Default: refresh all blockchains
|
|
2084
1658
|
return await this.getBalancesForNetworks(this.blockchains, forceRefresh);
|
|
2085
1659
|
} catch (e) {
|
|
2086
1660
|
console.error(tag, 'Error in getBalances: ', e);
|
|
@@ -2090,16 +1664,8 @@ export class SDK {
|
|
|
2090
1664
|
this.getBalance = async function (networkId: string) {
|
|
2091
1665
|
const tag = `${TAG} | getBalance | `;
|
|
2092
1666
|
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
1667
|
// Call the shared function with a single-network array
|
|
2101
1668
|
const results = await this.getBalancesForNetworks([networkId]);
|
|
2102
|
-
|
|
2103
1669
|
// If needed, you can filter only those that match the specific network
|
|
2104
1670
|
// (especially if you used wildcard eip155:*)
|
|
2105
1671
|
const filtered = results.filter(
|
|
@@ -2111,11 +1677,6 @@ export class SDK {
|
|
|
2111
1677
|
throw e;
|
|
2112
1678
|
}
|
|
2113
1679
|
};
|
|
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
1680
|
this.getFees = async function (networkId: string): Promise<NormalizedFeeRates> {
|
|
2120
1681
|
const tag = `${TAG} | getFees | `;
|
|
2121
1682
|
try {
|
|
@@ -2239,171 +1800,47 @@ export class SDK {
|
|
|
2239
1800
|
if (!asset.caip) throw Error('Invalid Asset! missing caip!');
|
|
2240
1801
|
if (!asset.networkId) asset.networkId = caipToNetworkId(asset.caip);
|
|
2241
1802
|
|
|
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
|
-
|
|
1803
|
+
// Validate pubkeys for network (throws descriptive errors)
|
|
1804
|
+
validatePubkeysForNetwork(this.pubkeys, asset.networkId, asset.caip);
|
|
1805
|
+
const pubkeysForNetwork = findPubkeysForNetwork(this.pubkeys, asset.networkId);
|
|
2294
1806
|
console.log(
|
|
2295
1807
|
tag,
|
|
2296
1808
|
`✅ Validated: Found ${pubkeysForNetwork.length} addresses for ${asset.networkId}`,
|
|
2297
1809
|
);
|
|
2298
1810
|
|
|
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
|
-
}
|
|
1811
|
+
// Fetch fresh market price
|
|
1812
|
+
const freshPriceUsd = await fetchMarketPrice(this.pioneer, asset.caip);
|
|
2321
1813
|
|
|
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
|
-
}
|
|
1814
|
+
// Resolve asset info from multiple sources
|
|
1815
|
+
let assetInfo = resolveAssetInfo(this.assetsMap, assetData, asset);
|
|
2343
1816
|
|
|
2344
|
-
//
|
|
2345
|
-
// CRITICAL: For UTXO chains, we need to aggregate ALL balances across all xpubs
|
|
1817
|
+
// Get matching balances
|
|
2346
1818
|
const matchingBalances = this.balances.filter((b) => b.caip === asset.caip);
|
|
2347
1819
|
|
|
1820
|
+
// Extract price from balances if available
|
|
2348
1821
|
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) {
|
|
1822
|
+
const priceValue = extractPriceFromBalances(matchingBalances);
|
|
1823
|
+
if (priceValue > 0) {
|
|
2364
1824
|
console.log(tag, 'detected priceUsd from balance:', priceValue);
|
|
2365
1825
|
assetInfo.priceUsd = priceValue;
|
|
2366
1826
|
}
|
|
2367
1827
|
}
|
|
2368
1828
|
|
|
2369
|
-
// Override with fresh price
|
|
2370
|
-
if (freshPriceUsd
|
|
1829
|
+
// Override with fresh price and aggregate balances
|
|
1830
|
+
if (freshPriceUsd > 0) {
|
|
2371
1831
|
assetInfo.priceUsd = freshPriceUsd;
|
|
2372
1832
|
console.log(tag, '✅ Using fresh market price:', freshPriceUsd);
|
|
2373
1833
|
|
|
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
|
-
|
|
1834
|
+
const { totalBalance, totalValueUsd } = aggregateBalances(matchingBalances, asset.caip);
|
|
2387
1835
|
assetInfo.balance = totalBalance.toString();
|
|
2388
1836
|
assetInfo.valueUsd = totalValueUsd.toFixed(2);
|
|
2389
|
-
console.log(tag, `Aggregated balance: ${totalBalance} (${totalValueUsd.toFixed(2)} USD)`);
|
|
2390
1837
|
}
|
|
2391
1838
|
|
|
2392
1839
|
// Filter balances and pubkeys for this asset
|
|
2393
1840
|
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
|
-
);
|
|
1841
|
+
const assetPubkeys = filterPubkeysForAsset(this.pubkeys, asset.caip, caipToNetworkId);
|
|
2404
1842
|
|
|
2405
1843
|
// 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
1844
|
const finalAssetContext = {
|
|
2408
1845
|
...assetInfo,
|
|
2409
1846
|
...asset,
|
|
@@ -2411,21 +1848,14 @@ export class SDK {
|
|
|
2411
1848
|
balances: assetBalances,
|
|
2412
1849
|
};
|
|
2413
1850
|
|
|
2414
|
-
// If input has priceUsd of 0 but we found a valid price
|
|
1851
|
+
// If input has priceUsd of 0 but we found a valid price, use the found price
|
|
2415
1852
|
if ((!asset.priceUsd || asset.priceUsd === 0) && assetInfo.priceUsd && assetInfo.priceUsd > 0) {
|
|
2416
1853
|
finalAssetContext.priceUsd = assetInfo.priceUsd;
|
|
2417
1854
|
}
|
|
2418
1855
|
|
|
2419
1856
|
// 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');
|
|
1857
|
+
if (freshPriceUsd > 0) {
|
|
1858
|
+
updateBalancesWithPrice(assetBalances, freshPriceUsd);
|
|
2429
1859
|
}
|
|
2430
1860
|
|
|
2431
1861
|
this.assetContext = finalAssetContext;
|
|
@@ -2439,38 +1869,6 @@ export class SDK {
|
|
|
2439
1869
|
) {
|
|
2440
1870
|
// Get the native asset for this network
|
|
2441
1871
|
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
1872
|
// Set the native symbol
|
|
2475
1873
|
this.assetContext.nativeSymbol = nativeSymbol;
|
|
2476
1874
|
|
|
@@ -2569,125 +1967,54 @@ export class SDK {
|
|
|
2569
1967
|
this.setOutboundAssetContext = async function (asset?: any): Promise<any> {
|
|
2570
1968
|
const tag = `${TAG} | setOutputAssetContext | `;
|
|
2571
1969
|
try {
|
|
2572
|
-
console.log(tag, '
|
|
1970
|
+
console.log(tag, 'asset:', asset);
|
|
2573
1971
|
// Accept null
|
|
2574
1972
|
if (!asset) {
|
|
2575
1973
|
this.outboundAssetContext = null;
|
|
2576
1974
|
return;
|
|
2577
1975
|
}
|
|
2578
1976
|
|
|
2579
|
-
console.log(tag, '1 asset: ', asset);
|
|
2580
|
-
|
|
2581
1977
|
if (!asset.caip) throw Error('Invalid Asset! missing caip!');
|
|
2582
1978
|
if (!asset.networkId) asset.networkId = caipToNetworkId(asset.caip);
|
|
2583
1979
|
|
|
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);
|
|
1980
|
+
console.log(tag, 'networkId:', asset.networkId);
|
|
2596
1981
|
|
|
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);
|
|
1982
|
+
// Get pubkey for network (uses utility helper)
|
|
1983
|
+
const pubkey = findPubkeyForNetwork(this.pubkeys, asset.networkId);
|
|
1984
|
+
if (!pubkey) throw Error('Invalid network! missing pubkey for network! ' + asset.networkId);
|
|
2607
1985
|
|
|
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
|
-
}
|
|
1986
|
+
// Fetch fresh market price
|
|
1987
|
+
const freshPriceUsd = await fetchMarketPrice(this.pioneer, asset.caip);
|
|
2619
1988
|
|
|
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
|
-
}
|
|
1989
|
+
// Resolve asset info from multiple sources
|
|
1990
|
+
let assetInfo = resolveAssetInfo(this.assetsMap, assetData, asset);
|
|
2635
1991
|
|
|
2636
|
-
//
|
|
2637
|
-
// CRITICAL: For UTXO chains, we need to aggregate ALL balances across all xpubs
|
|
1992
|
+
// Get matching balances
|
|
2638
1993
|
const matchingBalances = this.balances.filter((b) => b.caip === asset.caip);
|
|
2639
1994
|
|
|
1995
|
+
// Extract price from balances if available
|
|
2640
1996
|
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) {
|
|
1997
|
+
const priceValue = extractPriceFromBalances(matchingBalances);
|
|
1998
|
+
if (priceValue > 0) {
|
|
2656
1999
|
console.log(tag, 'detected priceUsd from balance:', priceValue);
|
|
2657
2000
|
assetInfo.priceUsd = priceValue;
|
|
2658
2001
|
}
|
|
2659
2002
|
}
|
|
2660
2003
|
|
|
2661
|
-
// Override with fresh price
|
|
2662
|
-
if (freshPriceUsd
|
|
2004
|
+
// Override with fresh price and aggregate balances
|
|
2005
|
+
if (freshPriceUsd > 0) {
|
|
2663
2006
|
assetInfo.priceUsd = freshPriceUsd;
|
|
2664
2007
|
console.log(tag, '✅ Using fresh market price:', freshPriceUsd);
|
|
2665
2008
|
|
|
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
|
-
|
|
2009
|
+
const { totalBalance, totalValueUsd } = aggregateBalances(matchingBalances, asset.caip);
|
|
2679
2010
|
assetInfo.balance = totalBalance.toString();
|
|
2680
2011
|
assetInfo.valueUsd = totalValueUsd.toFixed(2);
|
|
2681
|
-
console.log(tag, `Aggregated balance: ${totalBalance} (${totalValueUsd.toFixed(2)} USD)`);
|
|
2682
2012
|
}
|
|
2683
2013
|
|
|
2684
|
-
console.log(tag, 'CHECKPOINT 1');
|
|
2685
|
-
|
|
2686
2014
|
// Combine the user-provided asset with any additional info we have
|
|
2687
2015
|
this.outboundAssetContext = { ...assetInfo, ...asset, ...pubkey };
|
|
2688
2016
|
|
|
2689
|
-
console.log(tag, '
|
|
2690
|
-
console.log(tag, 'outboundAssetContext: assetInfo: ', assetInfo);
|
|
2017
|
+
console.log(tag, 'outboundAssetContext set:', this.outboundAssetContext.caip);
|
|
2691
2018
|
|
|
2692
2019
|
// Set outbound blockchain context based on asset
|
|
2693
2020
|
if (asset.caip) {
|
|
@@ -2717,18 +2044,8 @@ export class SDK {
|
|
|
2717
2044
|
ownerAddress: string;
|
|
2718
2045
|
spenderAddress: string;
|
|
2719
2046
|
}) => {
|
|
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
|
-
}
|
|
2047
|
+
const result = await this.pioneer.GetTokenAllowance(params);
|
|
2048
|
+
return result.data;
|
|
2732
2049
|
};
|
|
2733
2050
|
|
|
2734
2051
|
/**
|
|
@@ -2742,18 +2059,8 @@ export class SDK {
|
|
|
2742
2059
|
ownerAddress: string;
|
|
2743
2060
|
amount: string;
|
|
2744
2061
|
}) => {
|
|
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
|
-
}
|
|
2062
|
+
const result = await this.pioneer.BuildApprovalTransaction(params);
|
|
2063
|
+
return result.data;
|
|
2757
2064
|
};
|
|
2758
2065
|
}
|
|
2759
2066
|
|