@pioneer-platform/pioneer-sdk 8.11.12 → 8.11.17

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/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "author": "highlander",
3
3
  "name": "@pioneer-platform/pioneer-sdk",
4
- "version": "8.11.12",
4
+ "version": "8.11.17",
5
5
  "dependencies": {
6
6
  "@keepkey/keepkey-sdk": "^0.2.62",
7
7
  "@pioneer-platform/loggerdog": "^8.11.0",
8
8
  "@pioneer-platform/pioneer-caip": "^9.10.0",
9
9
  "@pioneer-platform/pioneer-client": "^9.10.10",
10
10
  "@pioneer-platform/pioneer-coins": "^9.11.0",
11
- "@pioneer-platform/pioneer-discovery": "^8.11.11",
11
+ "@pioneer-platform/pioneer-discovery": "^8.11.14",
12
12
  "@pioneer-platform/pioneer-events": "^8.11.0",
13
13
  "coinselect": "^3.1.13",
14
14
  "eventemitter3": "^5.0.1",
@@ -8,6 +8,18 @@ export async function getCosmosStakingCharts(params: ChartParams): Promise<Chart
8
8
  const balances: ChartBalance[] = [];
9
9
 
10
10
  try {
11
+ // Fast-path: skip staking in test/e2e or when explicitly requested
12
+ try {
13
+ const fastFlag = typeof process !== 'undefined' && process.env && process.env.PIONEER_FAST === '1';
14
+ const isTestContext = typeof context === 'string' && /test|e2e/i.test(context);
15
+ if (fastFlag || isTestContext) {
16
+ console.log(tag, 'Fast mode detected (test/e2e). Skipping cosmos staking fetch.');
17
+ return balances;
18
+ }
19
+ } catch (_) {
20
+ // ignore
21
+ }
22
+
11
23
  console.log(tag, 'Adding Cosmos staking positions to charts...');
12
24
 
13
25
  // Find cosmos pubkeys that could have staking positions
@@ -27,31 +39,29 @@ export async function getCosmosStakingCharts(params: ChartParams): Promise<Chart
27
39
 
28
40
  console.log(tag, 'Found cosmos pubkeys for staking:', cosmosPubkeys.length);
29
41
 
30
- for (const cosmosPubkey of cosmosPubkeys) {
31
- if (!cosmosPubkey.address) {
32
- continue;
33
- }
42
+ await Promise.allSettled(
43
+ cosmosPubkeys.map(async (cosmosPubkey: any) => {
44
+ if (!cosmosPubkey.address) return;
34
45
 
35
- // Check which cosmos networks this pubkey supports
36
- const cosmosNetworks = cosmosPubkey.networks.filter(
37
- (n: string) => n.includes('cosmos:cosmoshub') || n.includes('cosmos:osmosis'),
38
- );
46
+ const cosmosNetworks = cosmosPubkey.networks.filter(
47
+ (n: string) => n.includes('cosmos:cosmoshub') || n.includes('cosmos:osmosis'),
48
+ );
39
49
 
40
- for (const networkId of cosmosNetworks) {
41
- // Only process if this network is in our blockchains list
42
- if (!blockchains.includes(networkId)) {
43
- continue;
44
- }
45
-
46
- await fetchStakingPositionsForNetwork(
47
- networkId,
48
- cosmosPubkey.address,
49
- context,
50
- pioneer,
51
- balances
50
+ await Promise.allSettled(
51
+ cosmosNetworks
52
+ .filter((networkId: string) => blockchains.includes(networkId))
53
+ .map(async (networkId: string) => {
54
+ await fetchStakingPositionsForNetwork(
55
+ networkId,
56
+ cosmosPubkey.address,
57
+ context,
58
+ pioneer,
59
+ balances
60
+ );
61
+ })
52
62
  );
53
- }
54
- }
63
+ })
64
+ );
55
65
  } catch (e) {
56
66
  console.error(tag, 'Error adding cosmos staking positions:', e);
57
67
  }
package/src/charts/evm.ts CHANGED
@@ -18,31 +18,72 @@ export async function getEvmCharts(params: ChartParams): Promise<ChartBalance[]>
18
18
  console.log(tag, 'Total pubkeys available:', pubkeys.length);
19
19
  console.log(tag, 'Blockchains to process:', blockchains);
20
20
 
21
- // Only call portfolio endpoint if we have an EVM address (Zapper requirement)
22
- if (!primaryAddress) {
23
- console.log(
24
- tag,
25
- 'No EVM address found, skipping portfolio lookup (Zapper only supports Ethereum)',
26
- );
27
- return balances;
21
+ // Build pubkeys array for batch request
22
+ // Map all pubkeys to { pubkey, caip } format for each supported blockchain
23
+ const pubkeysForBatch: { pubkey: string; caip: string }[] = [];
24
+
25
+ for (const pubkey of pubkeys) {
26
+ const address = pubkey.address || pubkey.master || pubkey.pubkey;
27
+ if (!address) continue;
28
+
29
+ // Get networks this pubkey supports
30
+ const supportedNetworks = pubkey.networks || [];
31
+
32
+ // For each blockchain we're processing
33
+ for (const blockchain of blockchains) {
34
+ // Check if this pubkey supports this blockchain
35
+ // Networks can be specific (eip155:1) or wildcard (eip155:*)
36
+ const supportsNetwork = supportedNetworks.some((net: string) =>
37
+ net === blockchain ||
38
+ (net.endsWith(':*') && blockchain.startsWith(net.replace(':*', ':')))
39
+ );
40
+
41
+ if (supportsNetwork) {
42
+ // Build the CAIP for this combination
43
+ // For EVM chains, use slip44:60 for ETH asset
44
+ // For non-EVM chains, we'd need different logic (not implemented yet)
45
+ let caip: string;
46
+ if (blockchain.startsWith('eip155:')) {
47
+ caip = `${blockchain}/slip44:60`;
48
+ } else {
49
+ // For non-EVM chains, use generic format
50
+ // TODO: Implement proper CAIP construction for other chains
51
+ caip = `${blockchain}/slip44:0`;
52
+ }
53
+
54
+ pubkeysForBatch.push({
55
+ pubkey: address,
56
+ caip: caip
57
+ });
58
+ }
59
+ }
28
60
  }
29
61
 
30
- console.log(tag, 'Using EVM address for portfolio:', primaryAddress);
62
+ console.log(tag, `Built ${pubkeysForBatch.length} pubkey-chain combinations for batch request`);
63
+
64
+ // Only call portfolio endpoint if we have pubkeys to query
65
+ if (pubkeysForBatch.length === 0) {
66
+ console.log(tag, 'No pubkeys to query, skipping portfolio lookup');
67
+ return balances;
68
+ }
31
69
 
32
70
  // REDUNDANCY: Fetch stable coins from dedicated endpoint for ALL EVM networks
33
- // This ensures USDC/USDT balances are always available even if Zapper fails
34
- await fetchStableCoins(pioneer, primaryAddress, blockchains, balances, context);
71
+ // This ensures USDC/USDT balances are always available even if cache is empty
72
+ if (primaryAddress) {
73
+ await fetchStableCoins(pioneer, primaryAddress, blockchains, balances, context);
74
+ }
35
75
 
36
76
  // CUSTOM TOKENS: Fetch user-defined custom tokens from MongoDB-backed endpoint
37
77
  // This ensures user's custom tokens are always included in their portfolio
38
78
  await fetchCustomTokens({ blockchains, pioneer, pubkeys, context }, balances);
39
79
 
40
80
  try {
41
- // NOTE: Zapper API returns portfolio data across ALL networks for a given address
42
- // We use eip155:1 as the networkId parameter, but Zapper will return data for all EVM chains
81
+ // BATCH + NON-BLOCKING: Call portfolio endpoint with ALL pubkeys in one request
82
+ // The endpoint returns immediately from cache, never blocks waiting for blockchain APIs
83
+ console.log(tag, `Calling GetPortfolio with ${pubkeysForBatch.length} pubkeys (batch + non-blocking)`);
84
+
43
85
  let portfolio = await pioneer.GetPortfolio({
44
- networkId: 'eip155:1', // Required by endpoint, but Zapper returns all networks
45
- address: primaryAddress
86
+ pubkeys: pubkeysForBatch
46
87
  });
47
88
 
48
89
  // Handle double-wrapped response from Swagger client
@@ -73,10 +114,10 @@ export async function getEvmCharts(params: ChartParams): Promise<ChartBalance[]>
73
114
  }
74
115
  console.log(tag, `Processed ${processedCount} balances, skipped ${skippedCount}`);
75
116
 
76
- // Process tokens from portfolio (if they exist)
117
+ // Process tokens from portfolio.tokens array (returned by backend with blocking token fetch)
77
118
  if (portfolio.tokens && portfolio.tokens.length > 0) {
78
119
  console.log(tag, 'Processing portfolio.tokens:', portfolio.tokens.length);
79
-
120
+
80
121
  for (const token of portfolio.tokens) {
81
122
  const processedToken = processPortfolioToken(token, primaryAddress, context, blockchains);
82
123
  if (processedToken && !checkDuplicateBalance(balances, processedToken.caip, processedToken.pubkey)) {
@@ -84,6 +125,8 @@ export async function getEvmCharts(params: ChartParams): Promise<ChartBalance[]>
84
125
  }
85
126
  }
86
127
  }
128
+
129
+ console.log(tag, `Total balances (native + tokens): ${balances.length}`);
87
130
  } catch (e) {
88
131
  console.error(tag, 'Error fetching portfolio:', e);
89
132
  }
@@ -243,6 +286,18 @@ async function fetchStableCoins(
243
286
  ): Promise<void> {
244
287
  console.log(tag, 'Fetching stable coins for redundancy...');
245
288
 
289
+ // Fast-path: skip redundancy in test/e2e or when explicitly requested
290
+ try {
291
+ const fastFlag = typeof process !== 'undefined' && process.env && process.env.PIONEER_FAST === '1';
292
+ const isTestContext = typeof context === 'string' && /test|e2e/i.test(context);
293
+ if (fastFlag || isTestContext) {
294
+ console.log(tag, 'Fast mode detected (test/e2e). Skipping stable coin redundancy.');
295
+ return;
296
+ }
297
+ } catch (_) {
298
+ // no-op if process/env not available
299
+ }
300
+
246
301
  // Networks that support stable coins endpoint
247
302
  const supportedNetworks = ['eip155:1', 'eip155:137', 'eip155:8453', 'eip155:56'];
248
303
 
@@ -256,30 +311,42 @@ async function fetchStableCoins(
256
311
 
257
312
  console.log(tag, `Checking stable coins on ${networksToCheck.length} networks`);
258
313
 
259
- // Fetch stable coins for each network
260
- for (const networkId of networksToCheck) {
261
- try {
262
- const response = await pioneer.GetStableCoins({ networkId, address: primaryAddress });
263
- const stableCoins = response?.data?.tokens || [];
264
-
265
- console.log(tag, `Found ${stableCoins.length} stable coins on ${networkId}`);
266
-
267
- // Process each stable coin
268
- for (const token of stableCoins) {
269
- // Convert to ChartBalance format
270
- const chartBalance = processPortfolioToken(token, primaryAddress, context, blockchains);
314
+ // Helper: timeout a promise
315
+ const withTimeout = <T>(p: Promise<T>, ms: number): Promise<T> => {
316
+ return new Promise<T>((resolve, reject) => {
317
+ const t = setTimeout(() => reject(new Error(`timeout ${ms}ms`)), ms);
318
+ p.then((v) => { clearTimeout(t); resolve(v); })
319
+ .catch((e) => { clearTimeout(t); reject(e); });
320
+ });
321
+ };
271
322
 
272
- // Add if not already in balances (avoid duplicates)
273
- if (chartBalance && !checkDuplicateBalance(balances, chartBalance.caip, chartBalance.pubkey)) {
274
- balances.push(chartBalance);
275
- console.log(tag, `Added stable coin: ${chartBalance.symbol} = ${chartBalance.balance}`);
323
+ // Fetch stable coins for each network in parallel with short timeouts
324
+ const results = await Promise.allSettled(
325
+ networksToCheck.map(async (networkId) => {
326
+ try {
327
+ const response = await withTimeout(
328
+ pioneer.GetStableCoins({ networkId, address: primaryAddress }),
329
+ 2000
330
+ );
331
+ const stableCoins = response?.data?.tokens || [];
332
+ console.log(tag, `Found ${stableCoins.length} stable coins on ${networkId}`);
333
+
334
+ for (const token of stableCoins) {
335
+ const chartBalance = processPortfolioToken(token, primaryAddress, context, blockchains);
336
+ if (chartBalance && !checkDuplicateBalance(balances, chartBalance.caip, chartBalance.pubkey)) {
337
+ balances.push(chartBalance);
338
+ console.log(tag, `Added stable coin: ${chartBalance.symbol} = ${chartBalance.balance}`);
339
+ }
276
340
  }
341
+ } catch (error: any) {
342
+ console.error(tag, `Error fetching stable coins for ${networkId}:`, error?.message || error);
277
343
  }
278
- } catch (error: any) {
279
- console.error(tag, `Error fetching stable coins for ${networkId}:`, error.message);
280
- // Continue with other networks even if one fails
281
- }
282
- }
344
+ })
345
+ );
346
+
347
+ // Optional: log aggregate failures (for diagnostics only)
348
+ const failures = results.filter(r => r.status === 'rejected').length;
349
+ if (failures > 0) console.log(tag, `Stable coin fetch had ${failures} failures (non-blocking)`);
283
350
 
284
351
  console.log(tag, `Stable coin redundancy complete. Total balances: ${balances.length}`);
285
352
  }
@@ -45,12 +45,15 @@ export async function getMayaCharts(
45
45
 
46
46
  // Try to get MAYA token balance via a separate call
47
47
  // This is a workaround for the portfolio API not returning MAYA tokens
48
- const mayaBalanceResponse = await pioneer.GetPortfolioBalances([
49
- {
50
- caip: 'cosmos:mayachain-mainnet-v1/denom:maya',
51
- pubkey: mayaPubkey.address,
52
- },
53
- ]);
48
+ // FIX: Wrap in object with pubkeys field to match server API
49
+ const mayaBalanceResponse = await pioneer.GetPortfolioBalances({
50
+ pubkeys: [
51
+ {
52
+ caip: 'cosmos:mayachain-mainnet-v1/denom:maya',
53
+ pubkey: mayaPubkey.address,
54
+ },
55
+ ],
56
+ });
54
57
 
55
58
  console.log(
56
59
  tag,
package/src/index.ts CHANGED
@@ -1830,15 +1830,23 @@ export class SDK {
1830
1830
  console.log(` - ${caip}: ${count} queries`);
1831
1831
  });
1832
1832
 
1833
+ console.log(`⏱️ [PERF] Starting GetPortfolioBalances API call...`);
1834
+ const apiCallStart = performance.now();
1833
1835
  console.time('GetPortfolioBalances Response Time');
1834
1836
 
1835
1837
  try {
1836
- let marketInfo = await this.pioneer.GetPortfolioBalances(assetQuery);
1838
+ // FIX: Wrap assetQuery in object with pubkeys field to match server API
1839
+ let marketInfo = await this.pioneer.GetPortfolioBalances({ pubkeys: assetQuery });
1840
+ const apiCallTime = performance.now() - apiCallStart;
1837
1841
  console.timeEnd('GetPortfolioBalances Response Time');
1842
+ console.log(`⏱️ [PERF] API call completed in ${apiCallTime.toFixed(0)}ms`);
1838
1843
 
1844
+ const enrichStart = performance.now();
1839
1845
  let balances = marketInfo.data;
1846
+ console.log(`⏱️ [PERF] Received ${balances?.length || 0} balances from server`);
1840
1847
 
1841
1848
  // Enrich balances with asset info
1849
+ console.log(`⏱️ [PERF] Starting balance enrichment...`);
1842
1850
  for (let balance of balances) {
1843
1851
  const assetInfo = this.assetsMap.get(balance.caip.toLowerCase()) || this.assetsMap.get(balance.caip);
1844
1852
  if (!assetInfo) continue;
@@ -1854,8 +1862,12 @@ export class SDK {
1854
1862
  color, // Add color from mapping
1855
1863
  });
1856
1864
  }
1865
+ const enrichTime = performance.now() - enrichStart;
1866
+ console.log(`⏱️ [PERF] Enrichment completed in ${enrichTime.toFixed(0)}ms`);
1867
+
1857
1868
  this.balances = balances;
1858
1869
  this.events.emit('SET_BALANCES', this.balances);
1870
+ console.log(`⏱️ [PERF] Total getBalancesForNetworks: ${(performance.now() - apiCallStart).toFixed(0)}ms`);
1859
1871
  return this.balances;
1860
1872
  } catch (apiError: any) {
1861
1873
  console.error(tag, 'GetPortfolioBalances API call failed:', apiError);
@@ -1937,32 +1949,68 @@ export class SDK {
1937
1949
  this.getCharts = async function () {
1938
1950
  const tag = `${TAG} | getCharts | `;
1939
1951
  try {
1940
- console.log(tag, 'Fetching charts');
1952
+ console.log(tag, 'Fetching charts from batch endpoint');
1941
1953
 
1942
- // Fetch balances from the `getCharts` function
1943
- const newBalances = await getCharts(
1944
- this.blockchains,
1945
- this.pioneer,
1946
- this.pubkeys,
1947
- this.context,
1948
- );
1949
- console.log(tag, 'newBalances: ', newBalances);
1954
+ // Build pubkeys array for batch request
1955
+ const pubkeysForBatch: { pubkey: string; caip: string }[] = [];
1956
+
1957
+ for (const pubkey of this.pubkeys) {
1958
+ const address = pubkey.address || pubkey.master || pubkey.pubkey;
1959
+ if (!address) continue;
1960
+
1961
+ // Get networks this pubkey supports
1962
+ const supportedNetworks = pubkey.networks || [];
1963
+
1964
+ // For each blockchain we're processing
1965
+ for (const blockchain of this.blockchains) {
1966
+ // Check if this pubkey supports this blockchain
1967
+ const supportsNetwork = supportedNetworks.some((net: string) =>
1968
+ net === blockchain ||
1969
+ (net.endsWith(':*') && blockchain.startsWith(net.replace(':*', ':')))
1970
+ );
1971
+
1972
+ if (supportsNetwork) {
1973
+ // Build the CAIP for this combination
1974
+ let caip: string;
1975
+ if (blockchain.startsWith('eip155:')) {
1976
+ caip = `${blockchain}/slip44:60`;
1977
+ } else {
1978
+ caip = `${blockchain}/slip44:0`; // Fallback for non-EVM
1979
+ }
1980
+
1981
+ pubkeysForBatch.push({
1982
+ pubkey: address,
1983
+ caip: caip
1984
+ });
1985
+ }
1986
+ }
1987
+ }
1988
+
1989
+ console.log(tag, `Calling batch GetCharts with ${pubkeysForBatch.length} pubkeys`);
1990
+
1991
+ // Single batch call to get ALL charts data
1992
+ const chartsResponse = await this.pioneer.GetCharts({
1993
+ pubkeys: pubkeysForBatch
1994
+ });
1995
+
1996
+ const newBalances = chartsResponse?.data?.balances || [];
1997
+ console.log(tag, `Received ${newBalances.length} balances from batch endpoint`);
1950
1998
 
1951
1999
  // Deduplicate balances using a Map with `identifier` as the key
1952
2000
  const uniqueBalances = new Map(
1953
2001
  [...this.balances, ...newBalances].map((balance: any) => [
1954
- balance.identifier,
2002
+ balance.identifier || `${balance.caip}:${balance.pubkey}`,
1955
2003
  {
1956
2004
  ...balance,
1957
2005
  type: balance.type || 'balance',
1958
2006
  },
1959
2007
  ]),
1960
2008
  );
1961
- console.log(tag, 'uniqueBalances: ', uniqueBalances);
2009
+ console.log(tag, 'uniqueBalances: ', uniqueBalances.size);
1962
2010
 
1963
2011
  // Convert Map back to array and set this.balances
1964
2012
  this.balances = Array.from(uniqueBalances.values());
1965
- console.log(tag, 'Updated this.balances: ', this.balances);
2013
+ console.log(tag, 'Updated this.balances: ', this.balances.length);
1966
2014
 
1967
2015
  return this.balances;
1968
2016
  } catch (e) {
@@ -470,13 +470,11 @@ export async function createUnsignedEvmTx(
470
470
  const contractAddress = extractContractAddressFromCaip(caip);
471
471
 
472
472
  // Get token decimals from contract - CRITICAL for correct amount calculation
473
- // Fetch decimals from contract directly (handles all tokens on all chains)
474
- console.log(tag, 'Fetching token decimals from contract:', contractAddress, 'on network:', networkId);
473
+ // FAIL FAST: No fallback to 18 decimals - if we can't fetch decimals, abort the transaction
474
+ console.log(tag, 'Fetching token decimals via pioneer-server API for', contractAddress, 'on', networkId);
475
475
 
476
476
  let tokenDecimals: number;
477
477
  try {
478
- // Call pioneer-server API to get token decimals (server handles RPC calls)
479
- console.log(tag, 'Fetching token decimals via pioneer-server API for', contractAddress, 'on', networkId);
480
478
  const decimalsResponse = await pioneer.GetTokenDecimals({
481
479
  networkId,
482
480
  contractAddress,
@@ -485,10 +483,8 @@ export async function createUnsignedEvmTx(
485
483
  tokenDecimals = Number(decimalsResponse.data.decimals);
486
484
  console.log(tag, '✅ Fetched decimals from pioneer-server:', tokenDecimals);
487
485
  } catch (error: any) {
488
- console.error(tag, 'Failed to fetch token decimals from pioneer-server:', error);
489
- // Fallback to 18 decimals as last resort (but log warning)
490
- console.warn(tag, '⚠️ FALLBACK: Using default 18 decimals - THIS MAY BE INCORRECT!');
491
- tokenDecimals = 18;
486
+ console.error(tag, '❌ CRITICAL ERROR: Failed to fetch token decimals from pioneer-server:', error);
487
+ throw new Error(`Cannot build transaction: Failed to fetch decimals for token ${contractAddress} on ${networkId}. Error: ${error.message}`);
492
488
  }
493
489
 
494
490
  // Use BigInt for precise decimal math (no float drift)