@pioneer-platform/pioneer-sdk 8.15.32 → 8.15.34

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 CHANGED
@@ -436,6 +436,17 @@ function processPortfolioBalance(balance, primaryAddress, context, blockchains)
436
436
  console.log(tag2, `Calculated price from value/balance: ${calculatedPrice} for ${balance.caip}`);
437
437
  }
438
438
  }
439
+ const decimals = assetInfo?.decimals ?? assetInfo?.decimal ?? balance.decimals ?? balance.decimal;
440
+ if (decimals === undefined || decimals === null) {
441
+ throw new Error(`CRITICAL: Asset ${balance.caip} (${assetInfo?.symbol || balance.symbol || "UNKNOWN"}) has NO decimals/precision! ` + `This would cause incorrect balance calculations. ` + `Asset data: ${JSON.stringify({
442
+ caip: balance.caip,
443
+ symbol: assetInfo?.symbol || balance.symbol,
444
+ hasAssetInfoDecimals: !!assetInfo?.decimals,
445
+ hasAssetInfoDecimal: !!assetInfo?.decimal,
446
+ hasBalanceDecimals: !!balance.decimals,
447
+ hasBalanceDecimal: !!balance.decimal
448
+ })}`);
449
+ }
439
450
  const chartBalance = {
440
451
  context,
441
452
  chart: "pioneer",
@@ -452,7 +463,9 @@ function processPortfolioBalance(balance, primaryAddress, context, blockchains)
452
463
  symbol: assetInfo?.symbol || balance.symbol || "UNK",
453
464
  type: balanceType,
454
465
  token: isToken,
455
- decimal: assetInfo?.decimal || balance.decimal,
466
+ decimal: decimals,
467
+ decimals,
468
+ precision: decimals,
456
469
  balance: balance.balance.toString(),
457
470
  price: calculatedPrice,
458
471
  priceUsd: calculatedPrice,
@@ -483,6 +496,17 @@ function processPortfolioToken(token, primaryAddress, context, blockchains) {
483
496
  }
484
497
  const tokenAssetInfo = hydrateAssetData(token.assetCaip);
485
498
  const tokenPubkey = token.pubkey || primaryAddress;
499
+ const tokenDecimals = tokenAssetInfo?.decimals ?? tokenAssetInfo?.decimal ?? token.token?.decimals ?? token.token?.decimal;
500
+ if (tokenDecimals === undefined || tokenDecimals === null) {
501
+ throw new Error(`CRITICAL: Token ${token.assetCaip} (${tokenAssetInfo?.symbol || token.token?.symbol || "UNKNOWN"}) has NO decimals/precision! ` + `This would cause incorrect balance calculations. ` + `Token data: ${JSON.stringify({
502
+ caip: token.assetCaip,
503
+ symbol: tokenAssetInfo?.symbol || token.token?.symbol,
504
+ hasAssetInfoDecimals: !!tokenAssetInfo?.decimals,
505
+ hasAssetInfoDecimal: !!tokenAssetInfo?.decimal,
506
+ hasTokenDecimals: !!token.token?.decimals,
507
+ hasTokenDecimal: !!token.token?.decimal
508
+ })}`);
509
+ }
486
510
  const chartBalance = {
487
511
  context,
488
512
  chart: "pioneer",
@@ -498,7 +522,9 @@ function processPortfolioToken(token, primaryAddress, context, blockchains) {
498
522
  symbol: tokenAssetInfo?.symbol || token.token?.symbol || "UNK",
499
523
  type: tokenAssetInfo?.type || "token",
500
524
  token: true,
501
- decimal: tokenAssetInfo?.decimal || token.token?.decimal,
525
+ decimal: tokenDecimals,
526
+ decimals: tokenDecimals,
527
+ precision: tokenDecimals,
502
528
  balance: token.token?.balance?.toString() || "0",
503
529
  priceUsd: token.token?.price || 0,
504
530
  valueUsd: token.token?.balanceUSD || 0,
@@ -605,6 +631,17 @@ async function getMayaCharts(params, existingBalances) {
605
631
  }
606
632
  const mayaAssetInfo = hydrateAssetData(mayaBalance.caip);
607
633
  const isToken = mayaBalance.caip.includes("/denom:") && !mayaBalance.caip.endsWith("/denom:cacao");
634
+ const decimals = mayaAssetInfo?.decimals ?? mayaAssetInfo?.decimal ?? mayaBalance.decimals ?? mayaBalance.decimal;
635
+ if (decimals === undefined || decimals === null) {
636
+ throw new Error(`CRITICAL: Asset ${mayaBalance.caip} (${mayaAssetInfo?.symbol || "MAYA"}) has NO decimals/precision! ` + `This would cause incorrect balance calculations. ` + `Asset data: ${JSON.stringify({
637
+ caip: mayaBalance.caip,
638
+ symbol: mayaAssetInfo?.symbol,
639
+ hasAssetInfoDecimals: !!mayaAssetInfo?.decimals,
640
+ hasAssetInfoDecimal: !!mayaAssetInfo?.decimal,
641
+ hasBalanceDecimals: !!mayaBalance.decimals,
642
+ hasBalanceDecimal: !!mayaBalance.decimal
643
+ })}`);
644
+ }
608
645
  const mayaTokenBalance = {
609
646
  context,
610
647
  chart: "pioneer",
@@ -620,7 +657,9 @@ async function getMayaCharts(params, existingBalances) {
620
657
  symbol: mayaAssetInfo?.symbol || "MAYA",
621
658
  type: mayaAssetInfo?.type || "token",
622
659
  token: isToken,
623
- decimal: mayaAssetInfo?.decimal,
660
+ decimal: decimals,
661
+ decimals,
662
+ precision: decimals,
624
663
  balance: mayaBalance.balance?.toString() || "0",
625
664
  priceUsd: parseFloat(mayaBalance.priceUsd) || 0,
626
665
  valueUsd: parseFloat(mayaBalance.valueUsd) || 0,
@@ -4175,6 +4214,80 @@ function filterPubkeysForAsset(pubkeys, caip, caipToNetworkId7) {
4175
4214
 
4176
4215
  // src/utils/portfolio-helpers.ts
4177
4216
  var TAG9 = " | portfolio-helpers | ";
4217
+ var EXPLORER_BASE_URLS = {
4218
+ "eip155:1": {
4219
+ address: "https://etherscan.io/address/",
4220
+ tx: "https://etherscan.io/tx/"
4221
+ },
4222
+ "eip155:137": {
4223
+ address: "https://polygonscan.com/address/",
4224
+ tx: "https://polygonscan.com/tx/"
4225
+ },
4226
+ "eip155:8453": {
4227
+ address: "https://basescan.org/address/",
4228
+ tx: "https://basescan.org/tx/"
4229
+ },
4230
+ "eip155:56": {
4231
+ address: "https://bscscan.com/address/",
4232
+ tx: "https://bscscan.com/tx/"
4233
+ },
4234
+ "eip155:41454": {
4235
+ address: "https://explorer.monad.xyz/address/",
4236
+ tx: "https://explorer.monad.xyz/tx/"
4237
+ },
4238
+ "eip155:2868": {
4239
+ address: "https://app.hyperliquid.xyz/explorer/address/",
4240
+ tx: "https://app.hyperliquid.xyz/explorer/tx/"
4241
+ },
4242
+ "bip122:000000000019d6689c085ae165831e93": {
4243
+ address: "https://blockstream.info/address/",
4244
+ tx: "https://blockstream.info/tx/"
4245
+ },
4246
+ "bip122:12a765e31ffd4059bada1e25190f6e98": {
4247
+ address: "https://blockchair.com/litecoin/address/",
4248
+ tx: "https://blockchair.com/litecoin/transaction/"
4249
+ },
4250
+ "bip122:00000000001a91e3dace36e2be3bf030": {
4251
+ address: "https://dogechain.info/address/",
4252
+ tx: "https://dogechain.info/tx/"
4253
+ },
4254
+ "bip122:000000000000000000651ef99cb9fcbe": {
4255
+ address: "https://blockchair.com/bitcoin-cash/address/",
4256
+ tx: "https://blockchair.com/bitcoin-cash/transaction/"
4257
+ },
4258
+ "bip122:000007d91d1254d60e2dd1ae58038307": {
4259
+ address: "https://chainz.cryptoid.info/dash/address.dws?",
4260
+ tx: "https://chainz.cryptoid.info/dash/tx.dws?"
4261
+ },
4262
+ "bip122:4da631f2ac1bed857bd968c67c913978": {
4263
+ address: "https://digiexplorer.info/address/",
4264
+ tx: "https://digiexplorer.info/tx/"
4265
+ },
4266
+ "bip122:00040fe8ec8471911baa1db1266ea15d": {
4267
+ address: "https://explorer.zcha.in/accounts/",
4268
+ tx: "https://explorer.zcha.in/transactions/"
4269
+ },
4270
+ "cosmos:cosmoshub-4": {
4271
+ address: "https://www.mintscan.io/cosmos/address/",
4272
+ tx: "https://www.mintscan.io/cosmos/tx/"
4273
+ },
4274
+ "cosmos:osmosis-1": {
4275
+ address: "https://www.mintscan.io/osmosis/address/",
4276
+ tx: "https://www.mintscan.io/osmosis/tx/"
4277
+ },
4278
+ "cosmos:thorchain-mainnet-v1": {
4279
+ address: "https://thorchain.net/address/",
4280
+ tx: "https://thorchain.net/tx/"
4281
+ },
4282
+ "cosmos:mayachain-mainnet-v1": {
4283
+ address: "https://www.mayascan.org/address/",
4284
+ tx: "https://www.mayascan.org/tx/"
4285
+ },
4286
+ "ripple:4109c6f2045fc7eff4cde8f9905d19c2": {
4287
+ address: "https://xrpscan.com/account/",
4288
+ tx: "https://xrpscan.com/tx/"
4289
+ }
4290
+ };
4178
4291
  function isCacheDataValid(portfolioData) {
4179
4292
  if (!portfolioData.networks || !Array.isArray(portfolioData.networks)) {
4180
4293
  console.warn("[CACHE VALIDATION] Networks is not an array");
@@ -4326,14 +4439,27 @@ function enrichBalancesWithAssetInfo(balances, assetsMap, caipToNetworkId7) {
4326
4439
  } else if (typeof balance.priceUsd !== "number") {
4327
4440
  balance.priceUsd = 0;
4328
4441
  }
4442
+ const networkId = caipToNetworkId7(balance.caip);
4443
+ const explorerUrls = EXPLORER_BASE_URLS[networkId];
4444
+ const explorerAddressLink = explorerUrls && balance.pubkey ? explorerUrls.address + balance.pubkey : undefined;
4445
+ const explorerTxLink = explorerUrls?.tx;
4446
+ const decimals = assetInfo.decimals ?? balance.decimals;
4447
+ if (decimals === undefined || decimals === null) {
4448
+ throw new Error(`CRITICAL: Asset ${balance.caip} (${assetInfo.symbol || "UNKNOWN"}) has NO decimals/precision! ` + `AssetInfo is incomplete. This would cause incorrect balance calculations. ` + `Asset data: ${JSON.stringify({ caip: balance.caip, symbol: assetInfo.symbol, hasDecimals: !!assetInfo.decimals })}`);
4449
+ }
4329
4450
  Object.assign(balance, assetInfo, {
4330
4451
  type: balance.type || assetInfo.type,
4331
4452
  isNative: balance.isNative ?? assetInfo.isNative,
4332
- networkId: caipToNetworkId7(balance.caip),
4453
+ networkId,
4333
4454
  icon: assetInfo.icon || "https://pioneers.dev/coins/etherum.png",
4334
4455
  identifier: `${balance.caip}:${balance.pubkey}`,
4335
4456
  updated: Date.now(),
4336
4457
  color: assetInfo.color,
4458
+ decimals,
4459
+ precision: decimals,
4460
+ explorerAddressLink,
4461
+ explorerTxLink,
4462
+ explorer: explorerUrls?.address,
4337
4463
  valueUsd: balance.valueUsd,
4338
4464
  priceUsd: balance.priceUsd
4339
4465
  });
@@ -5761,7 +5887,7 @@ class SDK {
5761
5887
  const pubkeysForNetwork = findPubkeysForNetwork(this.pubkeys, asset.networkId);
5762
5888
  console.log(tag6, `✅ Validated: Found ${pubkeysForNetwork.length} addresses for ${asset.networkId}`);
5763
5889
  const freshPriceUsd = await fetchMarketPrice(this.pioneer, asset.caip);
5764
- let assetInfo = resolveAssetInfo(this.assetsMap, import_pioneer_discovery2.assetData, asset);
5890
+ let assetInfo = resolveAssetInfo(this.assetsMap, this.assets, asset);
5765
5891
  const matchingBalances = this.balances.filter((b) => b.caip === asset.caip);
5766
5892
  if (matchingBalances.length > 0) {
5767
5893
  const priceValue = extractPriceFromBalances(matchingBalances);
@@ -5777,14 +5903,47 @@ class SDK {
5777
5903
  assetInfo.balance = totalBalance.toString();
5778
5904
  assetInfo.valueUsd = totalValueUsd.toFixed(2);
5779
5905
  }
5906
+ const EXPLORER_BASE_URLS2 = {
5907
+ "eip155:1": { address: "https://etherscan.io/address/", tx: "https://etherscan.io/tx/" },
5908
+ "eip155:137": { address: "https://polygonscan.com/address/", tx: "https://polygonscan.com/tx/" },
5909
+ "eip155:8453": { address: "https://basescan.org/address/", tx: "https://basescan.org/tx/" },
5910
+ "eip155:56": { address: "https://bscscan.com/address/", tx: "https://bscscan.com/tx/" },
5911
+ "eip155:41454": { address: "https://explorer.monad.xyz/address/", tx: "https://explorer.monad.xyz/tx/" },
5912
+ "eip155:2868": { address: "https://app.hyperliquid.xyz/explorer/address/", tx: "https://app.hyperliquid.xyz/explorer/tx/" },
5913
+ "bip122:000000000019d6689c085ae165831e93": { address: "https://blockstream.info/address/", tx: "https://blockstream.info/tx/" },
5914
+ "bip122:12a765e31ffd4059bada1e25190f6e98": { address: "https://blockchair.com/litecoin/address/", tx: "https://blockchair.com/litecoin/transaction/" },
5915
+ "bip122:00000000001a91e3dace36e2be3bf030": { address: "https://dogechain.info/address/", tx: "https://dogechain.info/tx/" },
5916
+ "bip122:000000000000000000651ef99cb9fcbe": { address: "https://blockchair.com/bitcoin-cash/address/", tx: "https://blockchair.com/bitcoin-cash/transaction/" },
5917
+ "bip122:000007d91d1254d60e2dd1ae58038307": { address: "https://chainz.cryptoid.info/dash/address.dws?", tx: "https://chainz.cryptoid.info/dash/tx.dws?" },
5918
+ "bip122:4da631f2ac1bed857bd968c67c913978": { address: "https://digiexplorer.info/address/", tx: "https://digiexplorer.info/tx/" },
5919
+ "bip122:00040fe8ec8471911baa1db1266ea15d": { address: "https://explorer.zcha.in/accounts/", tx: "https://explorer.zcha.in/transactions/" },
5920
+ "cosmos:cosmoshub-4": { address: "https://www.mintscan.io/cosmos/address/", tx: "https://www.mintscan.io/cosmos/tx/" },
5921
+ "cosmos:osmosis-1": { address: "https://www.mintscan.io/osmosis/address/", tx: "https://www.mintscan.io/osmosis/tx/" },
5922
+ "cosmos:thorchain-mainnet-v1": { address: "https://thorchain.net/address/", tx: "https://thorchain.net/tx/" },
5923
+ "cosmos:mayachain-mainnet-v1": { address: "https://www.mayascan.org/address/", tx: "https://www.mayascan.org/tx/" },
5924
+ "ripple:4109c6f2045fc7eff4cde8f9905d19c2": { address: "https://xrpscan.com/account/", tx: "https://xrpscan.com/tx/" }
5925
+ };
5926
+ const networkId = import_pioneer_caip8.caipToNetworkId(asset.caip);
5927
+ const explorerUrls = EXPLORER_BASE_URLS2[networkId];
5928
+ const firstPubkey = pubkeysForNetwork && pubkeysForNetwork.length > 0 ? pubkeysForNetwork[0].address || pubkeysForNetwork[0].pubkey : null;
5929
+ if (explorerUrls && firstPubkey) {
5930
+ assetInfo.explorerAddressLink = explorerUrls.address + firstPubkey;
5931
+ assetInfo.explorerTxLink = explorerUrls.tx;
5932
+ assetInfo.explorer = explorerUrls.address;
5933
+ }
5780
5934
  const assetBalances = this.balances.filter((b) => b.caip === asset.caip);
5781
5935
  const assetPubkeys = filterPubkeysForAsset(this.pubkeys, asset.caip, import_pioneer_caip8.caipToNetworkId);
5936
+ const criticalFields = ["decimals", "precision", "explorerAddressLink", "explorerTxLink", "explorer"];
5782
5937
  const finalAssetContext = {
5783
5938
  ...assetInfo,
5784
- ...asset,
5939
+ ...Object.fromEntries(Object.entries(asset).filter(([k, v]) => v !== undefined && !criticalFields.includes(k))),
5785
5940
  pubkeys: assetPubkeys,
5786
5941
  balances: assetBalances
5787
5942
  };
5943
+ if (assetInfo.decimals !== undefined) {
5944
+ finalAssetContext.decimals = assetInfo.decimals;
5945
+ finalAssetContext.precision = assetInfo.decimals;
5946
+ }
5788
5947
  if ((!asset.priceUsd || asset.priceUsd === 0) && assetInfo.priceUsd && assetInfo.priceUsd > 0) {
5789
5948
  finalAssetContext.priceUsd = assetInfo.priceUsd;
5790
5949
  }
@@ -5793,15 +5952,22 @@ class SDK {
5793
5952
  }
5794
5953
  this.assetContext = finalAssetContext;
5795
5954
  if (asset.isToken || asset.type === "token" || assetInfo.isToken || assetInfo.type === "token") {
5796
- const networkId = asset.networkId || assetInfo.networkId;
5797
- this.assetContext.nativeSymbol = nativeSymbol;
5798
- if (nativeCaip) {
5955
+ const networkId2 = asset.networkId || assetInfo.networkId;
5956
+ try {
5957
+ const nativeCaip = import_pioneer_caip8.networkIdToCaip(networkId2);
5958
+ const nativeAssetInfo = import_pioneer_discovery2.assetData[nativeCaip];
5959
+ const nativeSymbol = nativeAssetInfo?.symbol || "GAS";
5960
+ this.assetContext.nativeSymbol = nativeSymbol;
5799
5961
  const nativeBalance = this.balances.find((b) => b.caip === nativeCaip);
5800
5962
  if (nativeBalance) {
5801
5963
  this.assetContext.nativeBalance = nativeBalance.balance || "0";
5802
5964
  } else {
5803
5965
  this.assetContext.nativeBalance = "0";
5804
5966
  }
5967
+ } catch (error) {
5968
+ console.error(`[Pioneer SDK] Unable to get native asset for networkId ${networkId2}:`, error);
5969
+ this.assetContext.nativeSymbol = "GAS";
5970
+ this.assetContext.nativeBalance = "0";
5805
5971
  }
5806
5972
  }
5807
5973
  if (asset.caip) {
@@ -5810,8 +5976,8 @@ class SDK {
5810
5976
  this.blockchainContext = asset.networkId;
5811
5977
  }
5812
5978
  if (assetPubkeys && assetPubkeys.length > 0) {
5813
- const networkId = import_pioneer_caip8.caipToNetworkId(asset.caip || asset.networkId);
5814
- const currentContextValid = this.pubkeyContext?.networks?.includes(networkId);
5979
+ const networkId2 = import_pioneer_caip8.caipToNetworkId(asset.caip || asset.networkId);
5980
+ const currentContextValid = this.pubkeyContext?.networks?.includes(networkId2);
5815
5981
  if (!this.pubkeyContext || !currentContextValid) {
5816
5982
  this.pubkeyContext = assetPubkeys[0];
5817
5983
  console.log(tag6, "Auto-set pubkey context for network:", this.pubkeyContext.address || this.pubkeyContext.pubkey);
@@ -5862,7 +6028,7 @@ class SDK {
5862
6028
  if (!pubkey)
5863
6029
  throw Error("Invalid network! missing pubkey for network! " + asset.networkId);
5864
6030
  const freshPriceUsd = await fetchMarketPrice(this.pioneer, asset.caip);
5865
- let assetInfo = resolveAssetInfo(this.assetsMap, import_pioneer_discovery2.assetData, asset);
6031
+ let assetInfo = resolveAssetInfo(this.assetsMap, this.assets, asset);
5866
6032
  const matchingBalances = this.balances.filter((b) => b.caip === asset.caip);
5867
6033
  if (matchingBalances.length > 0) {
5868
6034
  const priceValue = extractPriceFromBalances(matchingBalances);
package/dist/index.es.js CHANGED
@@ -427,6 +427,17 @@ function processPortfolioBalance(balance, primaryAddress, context, blockchains)
427
427
  console.log(tag2, `Calculated price from value/balance: ${calculatedPrice} for ${balance.caip}`);
428
428
  }
429
429
  }
430
+ const decimals = assetInfo?.decimals ?? assetInfo?.decimal ?? balance.decimals ?? balance.decimal;
431
+ if (decimals === undefined || decimals === null) {
432
+ throw new Error(`CRITICAL: Asset ${balance.caip} (${assetInfo?.symbol || balance.symbol || "UNKNOWN"}) has NO decimals/precision! ` + `This would cause incorrect balance calculations. ` + `Asset data: ${JSON.stringify({
433
+ caip: balance.caip,
434
+ symbol: assetInfo?.symbol || balance.symbol,
435
+ hasAssetInfoDecimals: !!assetInfo?.decimals,
436
+ hasAssetInfoDecimal: !!assetInfo?.decimal,
437
+ hasBalanceDecimals: !!balance.decimals,
438
+ hasBalanceDecimal: !!balance.decimal
439
+ })}`);
440
+ }
430
441
  const chartBalance = {
431
442
  context,
432
443
  chart: "pioneer",
@@ -443,7 +454,9 @@ function processPortfolioBalance(balance, primaryAddress, context, blockchains)
443
454
  symbol: assetInfo?.symbol || balance.symbol || "UNK",
444
455
  type: balanceType,
445
456
  token: isToken,
446
- decimal: assetInfo?.decimal || balance.decimal,
457
+ decimal: decimals,
458
+ decimals,
459
+ precision: decimals,
447
460
  balance: balance.balance.toString(),
448
461
  price: calculatedPrice,
449
462
  priceUsd: calculatedPrice,
@@ -474,6 +487,17 @@ function processPortfolioToken(token, primaryAddress, context, blockchains) {
474
487
  }
475
488
  const tokenAssetInfo = hydrateAssetData(token.assetCaip);
476
489
  const tokenPubkey = token.pubkey || primaryAddress;
490
+ const tokenDecimals = tokenAssetInfo?.decimals ?? tokenAssetInfo?.decimal ?? token.token?.decimals ?? token.token?.decimal;
491
+ if (tokenDecimals === undefined || tokenDecimals === null) {
492
+ throw new Error(`CRITICAL: Token ${token.assetCaip} (${tokenAssetInfo?.symbol || token.token?.symbol || "UNKNOWN"}) has NO decimals/precision! ` + `This would cause incorrect balance calculations. ` + `Token data: ${JSON.stringify({
493
+ caip: token.assetCaip,
494
+ symbol: tokenAssetInfo?.symbol || token.token?.symbol,
495
+ hasAssetInfoDecimals: !!tokenAssetInfo?.decimals,
496
+ hasAssetInfoDecimal: !!tokenAssetInfo?.decimal,
497
+ hasTokenDecimals: !!token.token?.decimals,
498
+ hasTokenDecimal: !!token.token?.decimal
499
+ })}`);
500
+ }
477
501
  const chartBalance = {
478
502
  context,
479
503
  chart: "pioneer",
@@ -489,7 +513,9 @@ function processPortfolioToken(token, primaryAddress, context, blockchains) {
489
513
  symbol: tokenAssetInfo?.symbol || token.token?.symbol || "UNK",
490
514
  type: tokenAssetInfo?.type || "token",
491
515
  token: true,
492
- decimal: tokenAssetInfo?.decimal || token.token?.decimal,
516
+ decimal: tokenDecimals,
517
+ decimals: tokenDecimals,
518
+ precision: tokenDecimals,
493
519
  balance: token.token?.balance?.toString() || "0",
494
520
  priceUsd: token.token?.price || 0,
495
521
  valueUsd: token.token?.balanceUSD || 0,
@@ -596,6 +622,17 @@ async function getMayaCharts(params, existingBalances) {
596
622
  }
597
623
  const mayaAssetInfo = hydrateAssetData(mayaBalance.caip);
598
624
  const isToken = mayaBalance.caip.includes("/denom:") && !mayaBalance.caip.endsWith("/denom:cacao");
625
+ const decimals = mayaAssetInfo?.decimals ?? mayaAssetInfo?.decimal ?? mayaBalance.decimals ?? mayaBalance.decimal;
626
+ if (decimals === undefined || decimals === null) {
627
+ throw new Error(`CRITICAL: Asset ${mayaBalance.caip} (${mayaAssetInfo?.symbol || "MAYA"}) has NO decimals/precision! ` + `This would cause incorrect balance calculations. ` + `Asset data: ${JSON.stringify({
628
+ caip: mayaBalance.caip,
629
+ symbol: mayaAssetInfo?.symbol,
630
+ hasAssetInfoDecimals: !!mayaAssetInfo?.decimals,
631
+ hasAssetInfoDecimal: !!mayaAssetInfo?.decimal,
632
+ hasBalanceDecimals: !!mayaBalance.decimals,
633
+ hasBalanceDecimal: !!mayaBalance.decimal
634
+ })}`);
635
+ }
599
636
  const mayaTokenBalance = {
600
637
  context,
601
638
  chart: "pioneer",
@@ -611,7 +648,9 @@ async function getMayaCharts(params, existingBalances) {
611
648
  symbol: mayaAssetInfo?.symbol || "MAYA",
612
649
  type: mayaAssetInfo?.type || "token",
613
650
  token: isToken,
614
- decimal: mayaAssetInfo?.decimal,
651
+ decimal: decimals,
652
+ decimals,
653
+ precision: decimals,
615
654
  balance: mayaBalance.balance?.toString() || "0",
616
655
  priceUsd: parseFloat(mayaBalance.priceUsd) || 0,
617
656
  valueUsd: parseFloat(mayaBalance.valueUsd) || 0,
@@ -4359,6 +4398,80 @@ function filterPubkeysForAsset(pubkeys, caip, caipToNetworkId7) {
4359
4398
 
4360
4399
  // src/utils/portfolio-helpers.ts
4361
4400
  var TAG9 = " | portfolio-helpers | ";
4401
+ var EXPLORER_BASE_URLS = {
4402
+ "eip155:1": {
4403
+ address: "https://etherscan.io/address/",
4404
+ tx: "https://etherscan.io/tx/"
4405
+ },
4406
+ "eip155:137": {
4407
+ address: "https://polygonscan.com/address/",
4408
+ tx: "https://polygonscan.com/tx/"
4409
+ },
4410
+ "eip155:8453": {
4411
+ address: "https://basescan.org/address/",
4412
+ tx: "https://basescan.org/tx/"
4413
+ },
4414
+ "eip155:56": {
4415
+ address: "https://bscscan.com/address/",
4416
+ tx: "https://bscscan.com/tx/"
4417
+ },
4418
+ "eip155:41454": {
4419
+ address: "https://explorer.monad.xyz/address/",
4420
+ tx: "https://explorer.monad.xyz/tx/"
4421
+ },
4422
+ "eip155:2868": {
4423
+ address: "https://app.hyperliquid.xyz/explorer/address/",
4424
+ tx: "https://app.hyperliquid.xyz/explorer/tx/"
4425
+ },
4426
+ "bip122:000000000019d6689c085ae165831e93": {
4427
+ address: "https://blockstream.info/address/",
4428
+ tx: "https://blockstream.info/tx/"
4429
+ },
4430
+ "bip122:12a765e31ffd4059bada1e25190f6e98": {
4431
+ address: "https://blockchair.com/litecoin/address/",
4432
+ tx: "https://blockchair.com/litecoin/transaction/"
4433
+ },
4434
+ "bip122:00000000001a91e3dace36e2be3bf030": {
4435
+ address: "https://dogechain.info/address/",
4436
+ tx: "https://dogechain.info/tx/"
4437
+ },
4438
+ "bip122:000000000000000000651ef99cb9fcbe": {
4439
+ address: "https://blockchair.com/bitcoin-cash/address/",
4440
+ tx: "https://blockchair.com/bitcoin-cash/transaction/"
4441
+ },
4442
+ "bip122:000007d91d1254d60e2dd1ae58038307": {
4443
+ address: "https://chainz.cryptoid.info/dash/address.dws?",
4444
+ tx: "https://chainz.cryptoid.info/dash/tx.dws?"
4445
+ },
4446
+ "bip122:4da631f2ac1bed857bd968c67c913978": {
4447
+ address: "https://digiexplorer.info/address/",
4448
+ tx: "https://digiexplorer.info/tx/"
4449
+ },
4450
+ "bip122:00040fe8ec8471911baa1db1266ea15d": {
4451
+ address: "https://explorer.zcha.in/accounts/",
4452
+ tx: "https://explorer.zcha.in/transactions/"
4453
+ },
4454
+ "cosmos:cosmoshub-4": {
4455
+ address: "https://www.mintscan.io/cosmos/address/",
4456
+ tx: "https://www.mintscan.io/cosmos/tx/"
4457
+ },
4458
+ "cosmos:osmosis-1": {
4459
+ address: "https://www.mintscan.io/osmosis/address/",
4460
+ tx: "https://www.mintscan.io/osmosis/tx/"
4461
+ },
4462
+ "cosmos:thorchain-mainnet-v1": {
4463
+ address: "https://thorchain.net/address/",
4464
+ tx: "https://thorchain.net/tx/"
4465
+ },
4466
+ "cosmos:mayachain-mainnet-v1": {
4467
+ address: "https://www.mayascan.org/address/",
4468
+ tx: "https://www.mayascan.org/tx/"
4469
+ },
4470
+ "ripple:4109c6f2045fc7eff4cde8f9905d19c2": {
4471
+ address: "https://xrpscan.com/account/",
4472
+ tx: "https://xrpscan.com/tx/"
4473
+ }
4474
+ };
4362
4475
  function isCacheDataValid(portfolioData) {
4363
4476
  if (!portfolioData.networks || !Array.isArray(portfolioData.networks)) {
4364
4477
  console.warn("[CACHE VALIDATION] Networks is not an array");
@@ -4510,14 +4623,27 @@ function enrichBalancesWithAssetInfo(balances, assetsMap, caipToNetworkId7) {
4510
4623
  } else if (typeof balance.priceUsd !== "number") {
4511
4624
  balance.priceUsd = 0;
4512
4625
  }
4626
+ const networkId = caipToNetworkId7(balance.caip);
4627
+ const explorerUrls = EXPLORER_BASE_URLS[networkId];
4628
+ const explorerAddressLink = explorerUrls && balance.pubkey ? explorerUrls.address + balance.pubkey : undefined;
4629
+ const explorerTxLink = explorerUrls?.tx;
4630
+ const decimals = assetInfo.decimals ?? balance.decimals;
4631
+ if (decimals === undefined || decimals === null) {
4632
+ throw new Error(`CRITICAL: Asset ${balance.caip} (${assetInfo.symbol || "UNKNOWN"}) has NO decimals/precision! ` + `AssetInfo is incomplete. This would cause incorrect balance calculations. ` + `Asset data: ${JSON.stringify({ caip: balance.caip, symbol: assetInfo.symbol, hasDecimals: !!assetInfo.decimals })}`);
4633
+ }
4513
4634
  Object.assign(balance, assetInfo, {
4514
4635
  type: balance.type || assetInfo.type,
4515
4636
  isNative: balance.isNative ?? assetInfo.isNative,
4516
- networkId: caipToNetworkId7(balance.caip),
4637
+ networkId,
4517
4638
  icon: assetInfo.icon || "https://pioneers.dev/coins/etherum.png",
4518
4639
  identifier: `${balance.caip}:${balance.pubkey}`,
4519
4640
  updated: Date.now(),
4520
4641
  color: assetInfo.color,
4642
+ decimals,
4643
+ precision: decimals,
4644
+ explorerAddressLink,
4645
+ explorerTxLink,
4646
+ explorer: explorerUrls?.address,
4521
4647
  valueUsd: balance.valueUsd,
4522
4648
  priceUsd: balance.priceUsd
4523
4649
  });
@@ -5945,7 +6071,7 @@ class SDK {
5945
6071
  const pubkeysForNetwork = findPubkeysForNetwork(this.pubkeys, asset.networkId);
5946
6072
  console.log(tag6, `✅ Validated: Found ${pubkeysForNetwork.length} addresses for ${asset.networkId}`);
5947
6073
  const freshPriceUsd = await fetchMarketPrice(this.pioneer, asset.caip);
5948
- let assetInfo = resolveAssetInfo(this.assetsMap, assetData2, asset);
6074
+ let assetInfo = resolveAssetInfo(this.assetsMap, this.assets, asset);
5949
6075
  const matchingBalances = this.balances.filter((b2) => b2.caip === asset.caip);
5950
6076
  if (matchingBalances.length > 0) {
5951
6077
  const priceValue = extractPriceFromBalances(matchingBalances);
@@ -5961,14 +6087,47 @@ class SDK {
5961
6087
  assetInfo.balance = totalBalance.toString();
5962
6088
  assetInfo.valueUsd = totalValueUsd.toFixed(2);
5963
6089
  }
6090
+ const EXPLORER_BASE_URLS2 = {
6091
+ "eip155:1": { address: "https://etherscan.io/address/", tx: "https://etherscan.io/tx/" },
6092
+ "eip155:137": { address: "https://polygonscan.com/address/", tx: "https://polygonscan.com/tx/" },
6093
+ "eip155:8453": { address: "https://basescan.org/address/", tx: "https://basescan.org/tx/" },
6094
+ "eip155:56": { address: "https://bscscan.com/address/", tx: "https://bscscan.com/tx/" },
6095
+ "eip155:41454": { address: "https://explorer.monad.xyz/address/", tx: "https://explorer.monad.xyz/tx/" },
6096
+ "eip155:2868": { address: "https://app.hyperliquid.xyz/explorer/address/", tx: "https://app.hyperliquid.xyz/explorer/tx/" },
6097
+ "bip122:000000000019d6689c085ae165831e93": { address: "https://blockstream.info/address/", tx: "https://blockstream.info/tx/" },
6098
+ "bip122:12a765e31ffd4059bada1e25190f6e98": { address: "https://blockchair.com/litecoin/address/", tx: "https://blockchair.com/litecoin/transaction/" },
6099
+ "bip122:00000000001a91e3dace36e2be3bf030": { address: "https://dogechain.info/address/", tx: "https://dogechain.info/tx/" },
6100
+ "bip122:000000000000000000651ef99cb9fcbe": { address: "https://blockchair.com/bitcoin-cash/address/", tx: "https://blockchair.com/bitcoin-cash/transaction/" },
6101
+ "bip122:000007d91d1254d60e2dd1ae58038307": { address: "https://chainz.cryptoid.info/dash/address.dws?", tx: "https://chainz.cryptoid.info/dash/tx.dws?" },
6102
+ "bip122:4da631f2ac1bed857bd968c67c913978": { address: "https://digiexplorer.info/address/", tx: "https://digiexplorer.info/tx/" },
6103
+ "bip122:00040fe8ec8471911baa1db1266ea15d": { address: "https://explorer.zcha.in/accounts/", tx: "https://explorer.zcha.in/transactions/" },
6104
+ "cosmos:cosmoshub-4": { address: "https://www.mintscan.io/cosmos/address/", tx: "https://www.mintscan.io/cosmos/tx/" },
6105
+ "cosmos:osmosis-1": { address: "https://www.mintscan.io/osmosis/address/", tx: "https://www.mintscan.io/osmosis/tx/" },
6106
+ "cosmos:thorchain-mainnet-v1": { address: "https://thorchain.net/address/", tx: "https://thorchain.net/tx/" },
6107
+ "cosmos:mayachain-mainnet-v1": { address: "https://www.mayascan.org/address/", tx: "https://www.mayascan.org/tx/" },
6108
+ "ripple:4109c6f2045fc7eff4cde8f9905d19c2": { address: "https://xrpscan.com/account/", tx: "https://xrpscan.com/tx/" }
6109
+ };
6110
+ const networkId = caipToNetworkId7(asset.caip);
6111
+ const explorerUrls = EXPLORER_BASE_URLS2[networkId];
6112
+ const firstPubkey = pubkeysForNetwork && pubkeysForNetwork.length > 0 ? pubkeysForNetwork[0].address || pubkeysForNetwork[0].pubkey : null;
6113
+ if (explorerUrls && firstPubkey) {
6114
+ assetInfo.explorerAddressLink = explorerUrls.address + firstPubkey;
6115
+ assetInfo.explorerTxLink = explorerUrls.tx;
6116
+ assetInfo.explorer = explorerUrls.address;
6117
+ }
5964
6118
  const assetBalances = this.balances.filter((b2) => b2.caip === asset.caip);
5965
6119
  const assetPubkeys = filterPubkeysForAsset(this.pubkeys, asset.caip, caipToNetworkId7);
6120
+ const criticalFields = ["decimals", "precision", "explorerAddressLink", "explorerTxLink", "explorer"];
5966
6121
  const finalAssetContext = {
5967
6122
  ...assetInfo,
5968
- ...asset,
6123
+ ...Object.fromEntries(Object.entries(asset).filter(([k, v2]) => v2 !== undefined && !criticalFields.includes(k))),
5969
6124
  pubkeys: assetPubkeys,
5970
6125
  balances: assetBalances
5971
6126
  };
6127
+ if (assetInfo.decimals !== undefined) {
6128
+ finalAssetContext.decimals = assetInfo.decimals;
6129
+ finalAssetContext.precision = assetInfo.decimals;
6130
+ }
5972
6131
  if ((!asset.priceUsd || asset.priceUsd === 0) && assetInfo.priceUsd && assetInfo.priceUsd > 0) {
5973
6132
  finalAssetContext.priceUsd = assetInfo.priceUsd;
5974
6133
  }
@@ -5977,15 +6136,22 @@ class SDK {
5977
6136
  }
5978
6137
  this.assetContext = finalAssetContext;
5979
6138
  if (asset.isToken || asset.type === "token" || assetInfo.isToken || assetInfo.type === "token") {
5980
- const networkId = asset.networkId || assetInfo.networkId;
5981
- this.assetContext.nativeSymbol = nativeSymbol;
5982
- if (nativeCaip) {
6139
+ const networkId2 = asset.networkId || assetInfo.networkId;
6140
+ try {
6141
+ const nativeCaip = networkIdToCaip2(networkId2);
6142
+ const nativeAssetInfo = assetData2[nativeCaip];
6143
+ const nativeSymbol = nativeAssetInfo?.symbol || "GAS";
6144
+ this.assetContext.nativeSymbol = nativeSymbol;
5983
6145
  const nativeBalance = this.balances.find((b2) => b2.caip === nativeCaip);
5984
6146
  if (nativeBalance) {
5985
6147
  this.assetContext.nativeBalance = nativeBalance.balance || "0";
5986
6148
  } else {
5987
6149
  this.assetContext.nativeBalance = "0";
5988
6150
  }
6151
+ } catch (error) {
6152
+ console.error(`[Pioneer SDK] Unable to get native asset for networkId ${networkId2}:`, error);
6153
+ this.assetContext.nativeSymbol = "GAS";
6154
+ this.assetContext.nativeBalance = "0";
5989
6155
  }
5990
6156
  }
5991
6157
  if (asset.caip) {
@@ -5994,8 +6160,8 @@ class SDK {
5994
6160
  this.blockchainContext = asset.networkId;
5995
6161
  }
5996
6162
  if (assetPubkeys && assetPubkeys.length > 0) {
5997
- const networkId = caipToNetworkId7(asset.caip || asset.networkId);
5998
- const currentContextValid = this.pubkeyContext?.networks?.includes(networkId);
6163
+ const networkId2 = caipToNetworkId7(asset.caip || asset.networkId);
6164
+ const currentContextValid = this.pubkeyContext?.networks?.includes(networkId2);
5999
6165
  if (!this.pubkeyContext || !currentContextValid) {
6000
6166
  this.pubkeyContext = assetPubkeys[0];
6001
6167
  console.log(tag6, "Auto-set pubkey context for network:", this.pubkeyContext.address || this.pubkeyContext.pubkey);
@@ -6046,7 +6212,7 @@ class SDK {
6046
6212
  if (!pubkey)
6047
6213
  throw Error("Invalid network! missing pubkey for network! " + asset.networkId);
6048
6214
  const freshPriceUsd = await fetchMarketPrice(this.pioneer, asset.caip);
6049
- let assetInfo = resolveAssetInfo(this.assetsMap, assetData2, asset);
6215
+ let assetInfo = resolveAssetInfo(this.assetsMap, this.assets, asset);
6050
6216
  const matchingBalances = this.balances.filter((b2) => b2.caip === asset.caip);
6051
6217
  if (matchingBalances.length > 0) {
6052
6218
  const priceValue = extractPriceFromBalances(matchingBalances);
package/dist/index.js CHANGED
@@ -427,6 +427,17 @@ function processPortfolioBalance(balance, primaryAddress, context, blockchains)
427
427
  console.log(tag2, `Calculated price from value/balance: ${calculatedPrice} for ${balance.caip}`);
428
428
  }
429
429
  }
430
+ const decimals = assetInfo?.decimals ?? assetInfo?.decimal ?? balance.decimals ?? balance.decimal;
431
+ if (decimals === undefined || decimals === null) {
432
+ throw new Error(`CRITICAL: Asset ${balance.caip} (${assetInfo?.symbol || balance.symbol || "UNKNOWN"}) has NO decimals/precision! ` + `This would cause incorrect balance calculations. ` + `Asset data: ${JSON.stringify({
433
+ caip: balance.caip,
434
+ symbol: assetInfo?.symbol || balance.symbol,
435
+ hasAssetInfoDecimals: !!assetInfo?.decimals,
436
+ hasAssetInfoDecimal: !!assetInfo?.decimal,
437
+ hasBalanceDecimals: !!balance.decimals,
438
+ hasBalanceDecimal: !!balance.decimal
439
+ })}`);
440
+ }
430
441
  const chartBalance = {
431
442
  context,
432
443
  chart: "pioneer",
@@ -443,7 +454,9 @@ function processPortfolioBalance(balance, primaryAddress, context, blockchains)
443
454
  symbol: assetInfo?.symbol || balance.symbol || "UNK",
444
455
  type: balanceType,
445
456
  token: isToken,
446
- decimal: assetInfo?.decimal || balance.decimal,
457
+ decimal: decimals,
458
+ decimals,
459
+ precision: decimals,
447
460
  balance: balance.balance.toString(),
448
461
  price: calculatedPrice,
449
462
  priceUsd: calculatedPrice,
@@ -474,6 +487,17 @@ function processPortfolioToken(token, primaryAddress, context, blockchains) {
474
487
  }
475
488
  const tokenAssetInfo = hydrateAssetData(token.assetCaip);
476
489
  const tokenPubkey = token.pubkey || primaryAddress;
490
+ const tokenDecimals = tokenAssetInfo?.decimals ?? tokenAssetInfo?.decimal ?? token.token?.decimals ?? token.token?.decimal;
491
+ if (tokenDecimals === undefined || tokenDecimals === null) {
492
+ throw new Error(`CRITICAL: Token ${token.assetCaip} (${tokenAssetInfo?.symbol || token.token?.symbol || "UNKNOWN"}) has NO decimals/precision! ` + `This would cause incorrect balance calculations. ` + `Token data: ${JSON.stringify({
493
+ caip: token.assetCaip,
494
+ symbol: tokenAssetInfo?.symbol || token.token?.symbol,
495
+ hasAssetInfoDecimals: !!tokenAssetInfo?.decimals,
496
+ hasAssetInfoDecimal: !!tokenAssetInfo?.decimal,
497
+ hasTokenDecimals: !!token.token?.decimals,
498
+ hasTokenDecimal: !!token.token?.decimal
499
+ })}`);
500
+ }
477
501
  const chartBalance = {
478
502
  context,
479
503
  chart: "pioneer",
@@ -489,7 +513,9 @@ function processPortfolioToken(token, primaryAddress, context, blockchains) {
489
513
  symbol: tokenAssetInfo?.symbol || token.token?.symbol || "UNK",
490
514
  type: tokenAssetInfo?.type || "token",
491
515
  token: true,
492
- decimal: tokenAssetInfo?.decimal || token.token?.decimal,
516
+ decimal: tokenDecimals,
517
+ decimals: tokenDecimals,
518
+ precision: tokenDecimals,
493
519
  balance: token.token?.balance?.toString() || "0",
494
520
  priceUsd: token.token?.price || 0,
495
521
  valueUsd: token.token?.balanceUSD || 0,
@@ -596,6 +622,17 @@ async function getMayaCharts(params, existingBalances) {
596
622
  }
597
623
  const mayaAssetInfo = hydrateAssetData(mayaBalance.caip);
598
624
  const isToken = mayaBalance.caip.includes("/denom:") && !mayaBalance.caip.endsWith("/denom:cacao");
625
+ const decimals = mayaAssetInfo?.decimals ?? mayaAssetInfo?.decimal ?? mayaBalance.decimals ?? mayaBalance.decimal;
626
+ if (decimals === undefined || decimals === null) {
627
+ throw new Error(`CRITICAL: Asset ${mayaBalance.caip} (${mayaAssetInfo?.symbol || "MAYA"}) has NO decimals/precision! ` + `This would cause incorrect balance calculations. ` + `Asset data: ${JSON.stringify({
628
+ caip: mayaBalance.caip,
629
+ symbol: mayaAssetInfo?.symbol,
630
+ hasAssetInfoDecimals: !!mayaAssetInfo?.decimals,
631
+ hasAssetInfoDecimal: !!mayaAssetInfo?.decimal,
632
+ hasBalanceDecimals: !!mayaBalance.decimals,
633
+ hasBalanceDecimal: !!mayaBalance.decimal
634
+ })}`);
635
+ }
599
636
  const mayaTokenBalance = {
600
637
  context,
601
638
  chart: "pioneer",
@@ -611,7 +648,9 @@ async function getMayaCharts(params, existingBalances) {
611
648
  symbol: mayaAssetInfo?.symbol || "MAYA",
612
649
  type: mayaAssetInfo?.type || "token",
613
650
  token: isToken,
614
- decimal: mayaAssetInfo?.decimal,
651
+ decimal: decimals,
652
+ decimals,
653
+ precision: decimals,
615
654
  balance: mayaBalance.balance?.toString() || "0",
616
655
  priceUsd: parseFloat(mayaBalance.priceUsd) || 0,
617
656
  valueUsd: parseFloat(mayaBalance.valueUsd) || 0,
@@ -4359,6 +4398,80 @@ function filterPubkeysForAsset(pubkeys, caip, caipToNetworkId7) {
4359
4398
 
4360
4399
  // src/utils/portfolio-helpers.ts
4361
4400
  var TAG9 = " | portfolio-helpers | ";
4401
+ var EXPLORER_BASE_URLS = {
4402
+ "eip155:1": {
4403
+ address: "https://etherscan.io/address/",
4404
+ tx: "https://etherscan.io/tx/"
4405
+ },
4406
+ "eip155:137": {
4407
+ address: "https://polygonscan.com/address/",
4408
+ tx: "https://polygonscan.com/tx/"
4409
+ },
4410
+ "eip155:8453": {
4411
+ address: "https://basescan.org/address/",
4412
+ tx: "https://basescan.org/tx/"
4413
+ },
4414
+ "eip155:56": {
4415
+ address: "https://bscscan.com/address/",
4416
+ tx: "https://bscscan.com/tx/"
4417
+ },
4418
+ "eip155:41454": {
4419
+ address: "https://explorer.monad.xyz/address/",
4420
+ tx: "https://explorer.monad.xyz/tx/"
4421
+ },
4422
+ "eip155:2868": {
4423
+ address: "https://app.hyperliquid.xyz/explorer/address/",
4424
+ tx: "https://app.hyperliquid.xyz/explorer/tx/"
4425
+ },
4426
+ "bip122:000000000019d6689c085ae165831e93": {
4427
+ address: "https://blockstream.info/address/",
4428
+ tx: "https://blockstream.info/tx/"
4429
+ },
4430
+ "bip122:12a765e31ffd4059bada1e25190f6e98": {
4431
+ address: "https://blockchair.com/litecoin/address/",
4432
+ tx: "https://blockchair.com/litecoin/transaction/"
4433
+ },
4434
+ "bip122:00000000001a91e3dace36e2be3bf030": {
4435
+ address: "https://dogechain.info/address/",
4436
+ tx: "https://dogechain.info/tx/"
4437
+ },
4438
+ "bip122:000000000000000000651ef99cb9fcbe": {
4439
+ address: "https://blockchair.com/bitcoin-cash/address/",
4440
+ tx: "https://blockchair.com/bitcoin-cash/transaction/"
4441
+ },
4442
+ "bip122:000007d91d1254d60e2dd1ae58038307": {
4443
+ address: "https://chainz.cryptoid.info/dash/address.dws?",
4444
+ tx: "https://chainz.cryptoid.info/dash/tx.dws?"
4445
+ },
4446
+ "bip122:4da631f2ac1bed857bd968c67c913978": {
4447
+ address: "https://digiexplorer.info/address/",
4448
+ tx: "https://digiexplorer.info/tx/"
4449
+ },
4450
+ "bip122:00040fe8ec8471911baa1db1266ea15d": {
4451
+ address: "https://explorer.zcha.in/accounts/",
4452
+ tx: "https://explorer.zcha.in/transactions/"
4453
+ },
4454
+ "cosmos:cosmoshub-4": {
4455
+ address: "https://www.mintscan.io/cosmos/address/",
4456
+ tx: "https://www.mintscan.io/cosmos/tx/"
4457
+ },
4458
+ "cosmos:osmosis-1": {
4459
+ address: "https://www.mintscan.io/osmosis/address/",
4460
+ tx: "https://www.mintscan.io/osmosis/tx/"
4461
+ },
4462
+ "cosmos:thorchain-mainnet-v1": {
4463
+ address: "https://thorchain.net/address/",
4464
+ tx: "https://thorchain.net/tx/"
4465
+ },
4466
+ "cosmos:mayachain-mainnet-v1": {
4467
+ address: "https://www.mayascan.org/address/",
4468
+ tx: "https://www.mayascan.org/tx/"
4469
+ },
4470
+ "ripple:4109c6f2045fc7eff4cde8f9905d19c2": {
4471
+ address: "https://xrpscan.com/account/",
4472
+ tx: "https://xrpscan.com/tx/"
4473
+ }
4474
+ };
4362
4475
  function isCacheDataValid(portfolioData) {
4363
4476
  if (!portfolioData.networks || !Array.isArray(portfolioData.networks)) {
4364
4477
  console.warn("[CACHE VALIDATION] Networks is not an array");
@@ -4510,14 +4623,27 @@ function enrichBalancesWithAssetInfo(balances, assetsMap, caipToNetworkId7) {
4510
4623
  } else if (typeof balance.priceUsd !== "number") {
4511
4624
  balance.priceUsd = 0;
4512
4625
  }
4626
+ const networkId = caipToNetworkId7(balance.caip);
4627
+ const explorerUrls = EXPLORER_BASE_URLS[networkId];
4628
+ const explorerAddressLink = explorerUrls && balance.pubkey ? explorerUrls.address + balance.pubkey : undefined;
4629
+ const explorerTxLink = explorerUrls?.tx;
4630
+ const decimals = assetInfo.decimals ?? balance.decimals;
4631
+ if (decimals === undefined || decimals === null) {
4632
+ throw new Error(`CRITICAL: Asset ${balance.caip} (${assetInfo.symbol || "UNKNOWN"}) has NO decimals/precision! ` + `AssetInfo is incomplete. This would cause incorrect balance calculations. ` + `Asset data: ${JSON.stringify({ caip: balance.caip, symbol: assetInfo.symbol, hasDecimals: !!assetInfo.decimals })}`);
4633
+ }
4513
4634
  Object.assign(balance, assetInfo, {
4514
4635
  type: balance.type || assetInfo.type,
4515
4636
  isNative: balance.isNative ?? assetInfo.isNative,
4516
- networkId: caipToNetworkId7(balance.caip),
4637
+ networkId,
4517
4638
  icon: assetInfo.icon || "https://pioneers.dev/coins/etherum.png",
4518
4639
  identifier: `${balance.caip}:${balance.pubkey}`,
4519
4640
  updated: Date.now(),
4520
4641
  color: assetInfo.color,
4642
+ decimals,
4643
+ precision: decimals,
4644
+ explorerAddressLink,
4645
+ explorerTxLink,
4646
+ explorer: explorerUrls?.address,
4521
4647
  valueUsd: balance.valueUsd,
4522
4648
  priceUsd: balance.priceUsd
4523
4649
  });
@@ -5945,7 +6071,7 @@ class SDK {
5945
6071
  const pubkeysForNetwork = findPubkeysForNetwork(this.pubkeys, asset.networkId);
5946
6072
  console.log(tag6, `✅ Validated: Found ${pubkeysForNetwork.length} addresses for ${asset.networkId}`);
5947
6073
  const freshPriceUsd = await fetchMarketPrice(this.pioneer, asset.caip);
5948
- let assetInfo = resolveAssetInfo(this.assetsMap, assetData2, asset);
6074
+ let assetInfo = resolveAssetInfo(this.assetsMap, this.assets, asset);
5949
6075
  const matchingBalances = this.balances.filter((b2) => b2.caip === asset.caip);
5950
6076
  if (matchingBalances.length > 0) {
5951
6077
  const priceValue = extractPriceFromBalances(matchingBalances);
@@ -5961,14 +6087,47 @@ class SDK {
5961
6087
  assetInfo.balance = totalBalance.toString();
5962
6088
  assetInfo.valueUsd = totalValueUsd.toFixed(2);
5963
6089
  }
6090
+ const EXPLORER_BASE_URLS2 = {
6091
+ "eip155:1": { address: "https://etherscan.io/address/", tx: "https://etherscan.io/tx/" },
6092
+ "eip155:137": { address: "https://polygonscan.com/address/", tx: "https://polygonscan.com/tx/" },
6093
+ "eip155:8453": { address: "https://basescan.org/address/", tx: "https://basescan.org/tx/" },
6094
+ "eip155:56": { address: "https://bscscan.com/address/", tx: "https://bscscan.com/tx/" },
6095
+ "eip155:41454": { address: "https://explorer.monad.xyz/address/", tx: "https://explorer.monad.xyz/tx/" },
6096
+ "eip155:2868": { address: "https://app.hyperliquid.xyz/explorer/address/", tx: "https://app.hyperliquid.xyz/explorer/tx/" },
6097
+ "bip122:000000000019d6689c085ae165831e93": { address: "https://blockstream.info/address/", tx: "https://blockstream.info/tx/" },
6098
+ "bip122:12a765e31ffd4059bada1e25190f6e98": { address: "https://blockchair.com/litecoin/address/", tx: "https://blockchair.com/litecoin/transaction/" },
6099
+ "bip122:00000000001a91e3dace36e2be3bf030": { address: "https://dogechain.info/address/", tx: "https://dogechain.info/tx/" },
6100
+ "bip122:000000000000000000651ef99cb9fcbe": { address: "https://blockchair.com/bitcoin-cash/address/", tx: "https://blockchair.com/bitcoin-cash/transaction/" },
6101
+ "bip122:000007d91d1254d60e2dd1ae58038307": { address: "https://chainz.cryptoid.info/dash/address.dws?", tx: "https://chainz.cryptoid.info/dash/tx.dws?" },
6102
+ "bip122:4da631f2ac1bed857bd968c67c913978": { address: "https://digiexplorer.info/address/", tx: "https://digiexplorer.info/tx/" },
6103
+ "bip122:00040fe8ec8471911baa1db1266ea15d": { address: "https://explorer.zcha.in/accounts/", tx: "https://explorer.zcha.in/transactions/" },
6104
+ "cosmos:cosmoshub-4": { address: "https://www.mintscan.io/cosmos/address/", tx: "https://www.mintscan.io/cosmos/tx/" },
6105
+ "cosmos:osmosis-1": { address: "https://www.mintscan.io/osmosis/address/", tx: "https://www.mintscan.io/osmosis/tx/" },
6106
+ "cosmos:thorchain-mainnet-v1": { address: "https://thorchain.net/address/", tx: "https://thorchain.net/tx/" },
6107
+ "cosmos:mayachain-mainnet-v1": { address: "https://www.mayascan.org/address/", tx: "https://www.mayascan.org/tx/" },
6108
+ "ripple:4109c6f2045fc7eff4cde8f9905d19c2": { address: "https://xrpscan.com/account/", tx: "https://xrpscan.com/tx/" }
6109
+ };
6110
+ const networkId = caipToNetworkId7(asset.caip);
6111
+ const explorerUrls = EXPLORER_BASE_URLS2[networkId];
6112
+ const firstPubkey = pubkeysForNetwork && pubkeysForNetwork.length > 0 ? pubkeysForNetwork[0].address || pubkeysForNetwork[0].pubkey : null;
6113
+ if (explorerUrls && firstPubkey) {
6114
+ assetInfo.explorerAddressLink = explorerUrls.address + firstPubkey;
6115
+ assetInfo.explorerTxLink = explorerUrls.tx;
6116
+ assetInfo.explorer = explorerUrls.address;
6117
+ }
5964
6118
  const assetBalances = this.balances.filter((b2) => b2.caip === asset.caip);
5965
6119
  const assetPubkeys = filterPubkeysForAsset(this.pubkeys, asset.caip, caipToNetworkId7);
6120
+ const criticalFields = ["decimals", "precision", "explorerAddressLink", "explorerTxLink", "explorer"];
5966
6121
  const finalAssetContext = {
5967
6122
  ...assetInfo,
5968
- ...asset,
6123
+ ...Object.fromEntries(Object.entries(asset).filter(([k, v2]) => v2 !== undefined && !criticalFields.includes(k))),
5969
6124
  pubkeys: assetPubkeys,
5970
6125
  balances: assetBalances
5971
6126
  };
6127
+ if (assetInfo.decimals !== undefined) {
6128
+ finalAssetContext.decimals = assetInfo.decimals;
6129
+ finalAssetContext.precision = assetInfo.decimals;
6130
+ }
5972
6131
  if ((!asset.priceUsd || asset.priceUsd === 0) && assetInfo.priceUsd && assetInfo.priceUsd > 0) {
5973
6132
  finalAssetContext.priceUsd = assetInfo.priceUsd;
5974
6133
  }
@@ -5977,15 +6136,22 @@ class SDK {
5977
6136
  }
5978
6137
  this.assetContext = finalAssetContext;
5979
6138
  if (asset.isToken || asset.type === "token" || assetInfo.isToken || assetInfo.type === "token") {
5980
- const networkId = asset.networkId || assetInfo.networkId;
5981
- this.assetContext.nativeSymbol = nativeSymbol;
5982
- if (nativeCaip) {
6139
+ const networkId2 = asset.networkId || assetInfo.networkId;
6140
+ try {
6141
+ const nativeCaip = networkIdToCaip2(networkId2);
6142
+ const nativeAssetInfo = assetData2[nativeCaip];
6143
+ const nativeSymbol = nativeAssetInfo?.symbol || "GAS";
6144
+ this.assetContext.nativeSymbol = nativeSymbol;
5983
6145
  const nativeBalance = this.balances.find((b2) => b2.caip === nativeCaip);
5984
6146
  if (nativeBalance) {
5985
6147
  this.assetContext.nativeBalance = nativeBalance.balance || "0";
5986
6148
  } else {
5987
6149
  this.assetContext.nativeBalance = "0";
5988
6150
  }
6151
+ } catch (error) {
6152
+ console.error(`[Pioneer SDK] Unable to get native asset for networkId ${networkId2}:`, error);
6153
+ this.assetContext.nativeSymbol = "GAS";
6154
+ this.assetContext.nativeBalance = "0";
5989
6155
  }
5990
6156
  }
5991
6157
  if (asset.caip) {
@@ -5994,8 +6160,8 @@ class SDK {
5994
6160
  this.blockchainContext = asset.networkId;
5995
6161
  }
5996
6162
  if (assetPubkeys && assetPubkeys.length > 0) {
5997
- const networkId = caipToNetworkId7(asset.caip || asset.networkId);
5998
- const currentContextValid = this.pubkeyContext?.networks?.includes(networkId);
6163
+ const networkId2 = caipToNetworkId7(asset.caip || asset.networkId);
6164
+ const currentContextValid = this.pubkeyContext?.networks?.includes(networkId2);
5999
6165
  if (!this.pubkeyContext || !currentContextValid) {
6000
6166
  this.pubkeyContext = assetPubkeys[0];
6001
6167
  console.log(tag6, "Auto-set pubkey context for network:", this.pubkeyContext.address || this.pubkeyContext.pubkey);
@@ -6046,7 +6212,7 @@ class SDK {
6046
6212
  if (!pubkey)
6047
6213
  throw Error("Invalid network! missing pubkey for network! " + asset.networkId);
6048
6214
  const freshPriceUsd = await fetchMarketPrice(this.pioneer, asset.caip);
6049
- let assetInfo = resolveAssetInfo(this.assetsMap, assetData2, asset);
6215
+ let assetInfo = resolveAssetInfo(this.assetsMap, this.assets, asset);
6050
6216
  const matchingBalances = this.balances.filter((b2) => b2.caip === asset.caip);
6051
6217
  if (matchingBalances.length > 0) {
6052
6218
  const priceValue = extractPriceFromBalances(matchingBalances);
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "author": "highlander",
3
3
  "name": "@pioneer-platform/pioneer-sdk",
4
- "version": "8.15.32",
4
+ "version": "8.15.34",
5
5
  "dependencies": {
6
6
  "@keepkey/keepkey-sdk": "^0.2.62",
7
- "@pioneer-platform/pioneer-caip": "^9.10.9",
8
- "@pioneer-platform/pioneer-client": "^9.10.15",
9
- "@pioneer-platform/pioneer-coins": "^9.11.9",
10
- "@pioneer-platform/pioneer-discovery": "^8.15.32",
11
- "@pioneer-platform/pioneer-events": "^8.12.4",
7
+ "@pioneer-platform/pioneer-caip": "^9.10.11",
8
+ "@pioneer-platform/pioneer-client": "^9.10.17",
9
+ "@pioneer-platform/pioneer-coins": "^9.11.11",
10
+ "@pioneer-platform/pioneer-discovery": "^8.15.34",
11
+ "@pioneer-platform/pioneer-events": "^8.12.6",
12
12
  "coinselect": "^3.1.13",
13
13
  "eventemitter3": "^5.0.1",
14
14
  "neotraverse": "^0.6.8",
package/src/charts/evm.ts CHANGED
@@ -178,6 +178,25 @@ function processPortfolioBalance(
178
178
  }
179
179
  }
180
180
 
181
+ // CRITICAL: FAIL FAST if decimals/precision is missing
182
+ // Extract decimals from multiple possible sources (assetInfo, balance API response)
183
+ const decimals = assetInfo?.decimals ?? assetInfo?.decimal ?? balance.decimals ?? balance.decimal;
184
+
185
+ if (decimals === undefined || decimals === null) {
186
+ throw new Error(
187
+ `CRITICAL: Asset ${balance.caip} (${assetInfo?.symbol || balance.symbol || 'UNKNOWN'}) has NO decimals/precision! ` +
188
+ `This would cause incorrect balance calculations. ` +
189
+ `Asset data: ${JSON.stringify({
190
+ caip: balance.caip,
191
+ symbol: assetInfo?.symbol || balance.symbol,
192
+ hasAssetInfoDecimals: !!assetInfo?.decimals,
193
+ hasAssetInfoDecimal: !!assetInfo?.decimal,
194
+ hasBalanceDecimals: !!balance.decimals,
195
+ hasBalanceDecimal: !!balance.decimal
196
+ })}`
197
+ );
198
+ }
199
+
181
200
  const chartBalance: ChartBalance = {
182
201
  context,
183
202
  chart: 'pioneer',
@@ -194,7 +213,10 @@ function processPortfolioBalance(
194
213
  symbol: assetInfo?.symbol || balance.symbol || 'UNK',
195
214
  type: balanceType,
196
215
  token: isToken,
197
- decimal: assetInfo?.decimal || balance.decimal,
216
+ // CRITICAL: Set BOTH decimal (legacy) and decimals/precision (new standard)
217
+ decimal: decimals,
218
+ decimals: decimals,
219
+ precision: decimals,
198
220
  balance: balance.balance.toString(),
199
221
  price: calculatedPrice,
200
222
  priceUsd: calculatedPrice,
@@ -244,10 +266,28 @@ function processPortfolioToken(
244
266
 
245
267
  // Hydrate token with assetData
246
268
  const tokenAssetInfo = hydrateAssetData(token.assetCaip);
247
-
269
+
248
270
  // CRITICAL FIX: Use consistent pubkey for tokens too
249
271
  const tokenPubkey = token.pubkey || primaryAddress;
250
272
 
273
+ // CRITICAL: FAIL FAST if decimals/precision is missing for tokens
274
+ const tokenDecimals = tokenAssetInfo?.decimals ?? tokenAssetInfo?.decimal ?? token.token?.decimals ?? token.token?.decimal;
275
+
276
+ if (tokenDecimals === undefined || tokenDecimals === null) {
277
+ throw new Error(
278
+ `CRITICAL: Token ${token.assetCaip} (${tokenAssetInfo?.symbol || token.token?.symbol || 'UNKNOWN'}) has NO decimals/precision! ` +
279
+ `This would cause incorrect balance calculations. ` +
280
+ `Token data: ${JSON.stringify({
281
+ caip: token.assetCaip,
282
+ symbol: tokenAssetInfo?.symbol || token.token?.symbol,
283
+ hasAssetInfoDecimals: !!tokenAssetInfo?.decimals,
284
+ hasAssetInfoDecimal: !!tokenAssetInfo?.decimal,
285
+ hasTokenDecimals: !!token.token?.decimals,
286
+ hasTokenDecimal: !!token.token?.decimal
287
+ })}`
288
+ );
289
+ }
290
+
251
291
  const chartBalance: ChartBalance = {
252
292
  context,
253
293
  chart: 'pioneer',
@@ -263,7 +303,10 @@ function processPortfolioToken(
263
303
  symbol: tokenAssetInfo?.symbol || token.token?.symbol || 'UNK',
264
304
  type: tokenAssetInfo?.type || 'token',
265
305
  token: true, // Tokens from portfolio.tokens are always tokens
266
- decimal: tokenAssetInfo?.decimal || token.token?.decimal,
306
+ // CRITICAL: Set ALL decimal fields for compatibility
307
+ decimal: tokenDecimals,
308
+ decimals: tokenDecimals,
309
+ precision: tokenDecimals,
267
310
  balance: token.token?.balance?.toString() || '0',
268
311
  priceUsd: token.token?.price || 0,
269
312
  valueUsd: token.token?.balanceUSD || 0,
@@ -79,7 +79,26 @@ export async function getMayaCharts(
79
79
 
80
80
  // CACAO is the native asset, MAYA is a token on the Maya chain
81
81
  const isToken = mayaBalance.caip.includes('/denom:') && !mayaBalance.caip.endsWith('/denom:cacao');
82
-
82
+
83
+ // CRITICAL: FAIL FAST if decimals/precision is missing
84
+ // Extract decimals from multiple possible sources (assetInfo, balance API response)
85
+ const decimals = mayaAssetInfo?.decimals ?? mayaAssetInfo?.decimal ?? mayaBalance.decimals ?? mayaBalance.decimal;
86
+
87
+ if (decimals === undefined || decimals === null) {
88
+ throw new Error(
89
+ `CRITICAL: Asset ${mayaBalance.caip} (${mayaAssetInfo?.symbol || 'MAYA'}) has NO decimals/precision! ` +
90
+ `This would cause incorrect balance calculations. ` +
91
+ `Asset data: ${JSON.stringify({
92
+ caip: mayaBalance.caip,
93
+ symbol: mayaAssetInfo?.symbol,
94
+ hasAssetInfoDecimals: !!mayaAssetInfo?.decimals,
95
+ hasAssetInfoDecimal: !!mayaAssetInfo?.decimal,
96
+ hasBalanceDecimals: !!mayaBalance.decimals,
97
+ hasBalanceDecimal: !!mayaBalance.decimal
98
+ })}`
99
+ );
100
+ }
101
+
83
102
  const mayaTokenBalance: ChartBalance = {
84
103
  context,
85
104
  chart: 'pioneer',
@@ -95,7 +114,10 @@ export async function getMayaCharts(
95
114
  symbol: mayaAssetInfo?.symbol || 'MAYA',
96
115
  type: mayaAssetInfo?.type || 'token',
97
116
  token: isToken, // MAYA is a token, CACAO is the native asset
98
- decimal: mayaAssetInfo?.decimal,
117
+ // CRITICAL: Set BOTH decimal (legacy) and decimals/precision (new standard)
118
+ decimal: decimals,
119
+ decimals: decimals,
120
+ precision: decimals,
99
121
  balance: mayaBalance.balance?.toString() || '0',
100
122
  priceUsd: parseFloat(mayaBalance.priceUsd) || 0,
101
123
  valueUsd: parseFloat(mayaBalance.valueUsd) || 0,
package/src/index.ts CHANGED
@@ -1832,7 +1832,7 @@ export class SDK {
1832
1832
  const freshPriceUsd = await fetchMarketPrice(this.pioneer, asset.caip);
1833
1833
 
1834
1834
  // Resolve asset info from multiple sources
1835
- let assetInfo = resolveAssetInfo(this.assetsMap, assetData, asset);
1835
+ let assetInfo = resolveAssetInfo(this.assetsMap, this.assets, asset);
1836
1836
 
1837
1837
  // Get matching balances
1838
1838
  const matchingBalances = this.balances.filter((b) => b.caip === asset.caip);
@@ -1856,18 +1856,63 @@ export class SDK {
1856
1856
  assetInfo.valueUsd = totalValueUsd.toFixed(2);
1857
1857
  }
1858
1858
 
1859
+ // CAIP-based explorer URL mapping
1860
+ const EXPLORER_BASE_URLS: Record<string, { address: string; tx: string }> = {
1861
+ 'eip155:1': { address: 'https://etherscan.io/address/', tx: 'https://etherscan.io/tx/' },
1862
+ 'eip155:137': { address: 'https://polygonscan.com/address/', tx: 'https://polygonscan.com/tx/' },
1863
+ 'eip155:8453': { address: 'https://basescan.org/address/', tx: 'https://basescan.org/tx/' },
1864
+ 'eip155:56': { address: 'https://bscscan.com/address/', tx: 'https://bscscan.com/tx/' },
1865
+ 'eip155:41454': { address: 'https://explorer.monad.xyz/address/', tx: 'https://explorer.monad.xyz/tx/' },
1866
+ 'eip155:2868': { address: 'https://app.hyperliquid.xyz/explorer/address/', tx: 'https://app.hyperliquid.xyz/explorer/tx/' },
1867
+ 'bip122:000000000019d6689c085ae165831e93': { address: 'https://blockstream.info/address/', tx: 'https://blockstream.info/tx/' },
1868
+ 'bip122:12a765e31ffd4059bada1e25190f6e98': { address: 'https://blockchair.com/litecoin/address/', tx: 'https://blockchair.com/litecoin/transaction/' },
1869
+ 'bip122:00000000001a91e3dace36e2be3bf030': { address: 'https://dogechain.info/address/', tx: 'https://dogechain.info/tx/' },
1870
+ 'bip122:000000000000000000651ef99cb9fcbe': { address: 'https://blockchair.com/bitcoin-cash/address/', tx: 'https://blockchair.com/bitcoin-cash/transaction/' },
1871
+ 'bip122:000007d91d1254d60e2dd1ae58038307': { address: 'https://chainz.cryptoid.info/dash/address.dws?', tx: 'https://chainz.cryptoid.info/dash/tx.dws?' },
1872
+ 'bip122:4da631f2ac1bed857bd968c67c913978': { address: 'https://digiexplorer.info/address/', tx: 'https://digiexplorer.info/tx/' },
1873
+ 'bip122:00040fe8ec8471911baa1db1266ea15d': { address: 'https://explorer.zcha.in/accounts/', tx: 'https://explorer.zcha.in/transactions/' },
1874
+ 'cosmos:cosmoshub-4': { address: 'https://www.mintscan.io/cosmos/address/', tx: 'https://www.mintscan.io/cosmos/tx/' },
1875
+ 'cosmos:osmosis-1': { address: 'https://www.mintscan.io/osmosis/address/', tx: 'https://www.mintscan.io/osmosis/tx/' },
1876
+ 'cosmos:thorchain-mainnet-v1': { address: 'https://thorchain.net/address/', tx: 'https://thorchain.net/tx/' },
1877
+ 'cosmos:mayachain-mainnet-v1': { address: 'https://www.mayascan.org/address/', tx: 'https://www.mayascan.org/tx/' },
1878
+ 'ripple:4109c6f2045fc7eff4cde8f9905d19c2': { address: 'https://xrpscan.com/account/', tx: 'https://xrpscan.com/tx/' },
1879
+ };
1880
+
1881
+ // Generate explorer links for this asset
1882
+ const networkId = caipToNetworkId(asset.caip);
1883
+ const explorerUrls = EXPLORER_BASE_URLS[networkId];
1884
+
1885
+ // Get the first pubkey for this asset to generate explorer link
1886
+ const firstPubkey = pubkeysForNetwork && pubkeysForNetwork.length > 0
1887
+ ? (pubkeysForNetwork[0].address || pubkeysForNetwork[0].pubkey)
1888
+ : null;
1889
+
1890
+ if (explorerUrls && firstPubkey) {
1891
+ assetInfo.explorerAddressLink = explorerUrls.address + firstPubkey;
1892
+ assetInfo.explorerTxLink = explorerUrls.tx;
1893
+ assetInfo.explorer = explorerUrls.address;
1894
+ }
1895
+
1859
1896
  // Filter balances and pubkeys for this asset
1860
1897
  const assetBalances = this.balances.filter((b) => b.caip === asset.caip);
1861
1898
  const assetPubkeys = filterPubkeysForAsset(this.pubkeys, asset.caip, caipToNetworkId);
1862
1899
 
1863
1900
  // Combine the user-provided asset with any additional info we have
1901
+ // CRITICAL: Preserve decimals, precision, and explorer links from assetInfo
1902
+ const criticalFields = ['decimals', 'precision', 'explorerAddressLink', 'explorerTxLink', 'explorer'];
1864
1903
  const finalAssetContext = {
1865
1904
  ...assetInfo,
1866
- ...asset,
1905
+ ...Object.fromEntries(Object.entries(asset).filter(([k, v]) => v !== undefined && !criticalFields.includes(k))),
1867
1906
  pubkeys: assetPubkeys,
1868
1907
  balances: assetBalances,
1869
1908
  };
1870
1909
 
1910
+ // Ensure precision matches decimals from assetInfo (for backward compatibility)
1911
+ if (assetInfo.decimals !== undefined) {
1912
+ finalAssetContext.decimals = assetInfo.decimals;
1913
+ finalAssetContext.precision = assetInfo.decimals;
1914
+ }
1915
+
1871
1916
  // If input has priceUsd of 0 but we found a valid price, use the found price
1872
1917
  if ((!asset.priceUsd || asset.priceUsd === 0) && assetInfo.priceUsd && assetInfo.priceUsd > 0) {
1873
1918
  finalAssetContext.priceUsd = assetInfo.priceUsd;
@@ -1887,19 +1932,32 @@ export class SDK {
1887
1932
  assetInfo.isToken ||
1888
1933
  assetInfo.type === 'token'
1889
1934
  ) {
1890
- // Get the native asset for this network
1935
+ // Get the native asset for this network using the proper CAIP utility
1891
1936
  const networkId = asset.networkId || assetInfo.networkId;
1892
- // Set the native symbol
1893
- this.assetContext.nativeSymbol = nativeSymbol;
1894
1937
 
1895
- // Try to find the native balance
1896
- if (nativeCaip) {
1938
+ try {
1939
+ // Use networkIdToCaip to get the native asset CAIP
1940
+ const nativeCaip = networkIdToCaip(networkId);
1941
+
1942
+ // Look up the native asset info from assetData
1943
+ const nativeAssetInfo = assetData[nativeCaip];
1944
+ const nativeSymbol = nativeAssetInfo?.symbol || 'GAS';
1945
+
1946
+ // Set the native symbol
1947
+ this.assetContext.nativeSymbol = nativeSymbol;
1948
+
1949
+ // Try to find the native balance
1897
1950
  const nativeBalance = this.balances.find((b) => b.caip === nativeCaip);
1898
1951
  if (nativeBalance) {
1899
1952
  this.assetContext.nativeBalance = nativeBalance.balance || '0';
1900
1953
  } else {
1901
1954
  this.assetContext.nativeBalance = '0';
1902
1955
  }
1956
+ } catch (error) {
1957
+ console.error(`[Pioneer SDK] Unable to get native asset for networkId ${networkId}:`, error);
1958
+ // Fallback to 'GAS' if we can't determine the native asset
1959
+ this.assetContext.nativeSymbol = 'GAS';
1960
+ this.assetContext.nativeBalance = '0';
1903
1961
  }
1904
1962
  }
1905
1963
 
@@ -2007,7 +2065,7 @@ export class SDK {
2007
2065
  const freshPriceUsd = await fetchMarketPrice(this.pioneer, asset.caip);
2008
2066
 
2009
2067
  // Resolve asset info from multiple sources
2010
- let assetInfo = resolveAssetInfo(this.assetsMap, assetData, asset);
2068
+ let assetInfo = resolveAssetInfo(this.assetsMap, this.assets, asset);
2011
2069
 
2012
2070
  // Get matching balances
2013
2071
  const matchingBalances = this.balances.filter((b) => b.caip === asset.caip);
@@ -3,6 +3,100 @@
3
3
  import { logger as log } from './logger.js';
4
4
  const TAG = ' | portfolio-helpers | ';
5
5
 
6
+ // CAIP-based explorer URL mapping
7
+ const EXPLORER_BASE_URLS: Record<string, { address: string; tx: string }> = {
8
+ // Ethereum mainnet
9
+ 'eip155:1': {
10
+ address: 'https://etherscan.io/address/',
11
+ tx: 'https://etherscan.io/tx/'
12
+ },
13
+ // Polygon
14
+ 'eip155:137': {
15
+ address: 'https://polygonscan.com/address/',
16
+ tx: 'https://polygonscan.com/tx/'
17
+ },
18
+ // Base
19
+ 'eip155:8453': {
20
+ address: 'https://basescan.org/address/',
21
+ tx: 'https://basescan.org/tx/'
22
+ },
23
+ // BSC
24
+ 'eip155:56': {
25
+ address: 'https://bscscan.com/address/',
26
+ tx: 'https://bscscan.com/tx/'
27
+ },
28
+ // Monad
29
+ 'eip155:41454': {
30
+ address: 'https://explorer.monad.xyz/address/',
31
+ tx: 'https://explorer.monad.xyz/tx/'
32
+ },
33
+ // Hyperliquid
34
+ 'eip155:2868': {
35
+ address: 'https://app.hyperliquid.xyz/explorer/address/',
36
+ tx: 'https://app.hyperliquid.xyz/explorer/tx/'
37
+ },
38
+ // Bitcoin
39
+ 'bip122:000000000019d6689c085ae165831e93': {
40
+ address: 'https://blockstream.info/address/',
41
+ tx: 'https://blockstream.info/tx/'
42
+ },
43
+ // Litecoin
44
+ 'bip122:12a765e31ffd4059bada1e25190f6e98': {
45
+ address: 'https://blockchair.com/litecoin/address/',
46
+ tx: 'https://blockchair.com/litecoin/transaction/'
47
+ },
48
+ // Dogecoin
49
+ 'bip122:00000000001a91e3dace36e2be3bf030': {
50
+ address: 'https://dogechain.info/address/',
51
+ tx: 'https://dogechain.info/tx/'
52
+ },
53
+ // Bitcoin Cash
54
+ 'bip122:000000000000000000651ef99cb9fcbe': {
55
+ address: 'https://blockchair.com/bitcoin-cash/address/',
56
+ tx: 'https://blockchair.com/bitcoin-cash/transaction/'
57
+ },
58
+ // Dash
59
+ 'bip122:000007d91d1254d60e2dd1ae58038307': {
60
+ address: 'https://chainz.cryptoid.info/dash/address.dws?',
61
+ tx: 'https://chainz.cryptoid.info/dash/tx.dws?'
62
+ },
63
+ // DigiByte
64
+ 'bip122:4da631f2ac1bed857bd968c67c913978': {
65
+ address: 'https://digiexplorer.info/address/',
66
+ tx: 'https://digiexplorer.info/tx/'
67
+ },
68
+ // Zcash
69
+ 'bip122:00040fe8ec8471911baa1db1266ea15d': {
70
+ address: 'https://explorer.zcha.in/accounts/',
71
+ tx: 'https://explorer.zcha.in/transactions/'
72
+ },
73
+ // Cosmos Hub
74
+ 'cosmos:cosmoshub-4': {
75
+ address: 'https://www.mintscan.io/cosmos/address/',
76
+ tx: 'https://www.mintscan.io/cosmos/tx/'
77
+ },
78
+ // Osmosis
79
+ 'cosmos:osmosis-1': {
80
+ address: 'https://www.mintscan.io/osmosis/address/',
81
+ tx: 'https://www.mintscan.io/osmosis/tx/'
82
+ },
83
+ // THORChain
84
+ 'cosmos:thorchain-mainnet-v1': {
85
+ address: 'https://thorchain.net/address/',
86
+ tx: 'https://thorchain.net/tx/'
87
+ },
88
+ // Maya Protocol
89
+ 'cosmos:mayachain-mainnet-v1': {
90
+ address: 'https://www.mayascan.org/address/',
91
+ tx: 'https://www.mayascan.org/tx/'
92
+ },
93
+ // Ripple
94
+ 'ripple:4109c6f2045fc7eff4cde8f9905d19c2': {
95
+ address: 'https://xrpscan.com/account/',
96
+ tx: 'https://xrpscan.com/tx/'
97
+ }
98
+ };
99
+
6
100
  export function isCacheDataValid(portfolioData: any): boolean {
7
101
  // Check if networks data is reasonable (should be < 50 networks, not thousands)
8
102
  if (!portfolioData.networks || !Array.isArray(portfolioData.networks)) {
@@ -240,14 +334,43 @@ export function enrichBalancesWithAssetInfo(
240
334
  balance.priceUsd = 0;
241
335
  }
242
336
 
337
+ // Get networkId for explorer lookup
338
+ const networkId = caipToNetworkId(balance.caip);
339
+ const explorerUrls = EXPLORER_BASE_URLS[networkId];
340
+
341
+ // Generate explorer links if we have a pubkey/address and explorer URLs for this network
342
+ const explorerAddressLink = (explorerUrls && balance.pubkey)
343
+ ? explorerUrls.address + balance.pubkey
344
+ : undefined;
345
+
346
+ const explorerTxLink = explorerUrls?.tx; // Base URL for txs, actual txid added later
347
+
348
+ // CRITICAL: NEVER default decimals to 8 - this causes incorrect balance calculations
349
+ // If decimals is missing, the asset data is incomplete and we should FAIL FAST
350
+ const decimals = assetInfo.decimals ?? balance.decimals;
351
+ if (decimals === undefined || decimals === null) {
352
+ throw new Error(
353
+ `CRITICAL: Asset ${balance.caip} (${assetInfo.symbol || 'UNKNOWN'}) has NO decimals/precision! ` +
354
+ `AssetInfo is incomplete. This would cause incorrect balance calculations. ` +
355
+ `Asset data: ${JSON.stringify({ caip: balance.caip, symbol: assetInfo.symbol, hasDecimals: !!assetInfo.decimals })}`
356
+ );
357
+ }
358
+
243
359
  Object.assign(balance, assetInfo, {
244
360
  type: balance.type || assetInfo.type,
245
361
  isNative: balance.isNative ?? assetInfo.isNative,
246
- networkId: caipToNetworkId(balance.caip),
362
+ networkId,
247
363
  icon: assetInfo.icon || 'https://pioneers.dev/coins/etherum.png',
248
364
  identifier: `${balance.caip}:${balance.pubkey}`,
249
365
  updated: Date.now(),
250
366
  color: assetInfo.color,
367
+ // CRITICAL: Use validated decimals
368
+ decimals,
369
+ precision: decimals, // Also set precision for compatibility
370
+ // Add explorer links
371
+ explorerAddressLink,
372
+ explorerTxLink,
373
+ explorer: explorerUrls?.address, // Base explorer URL
251
374
  // Ensure these are numbers (redundant but explicit)
252
375
  valueUsd: balance.valueUsd,
253
376
  priceUsd: balance.priceUsd,