@pioneer-platform/pioneer-sdk 8.15.30 → 8.15.32

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
@@ -2639,7 +2639,21 @@ async function createUnsignedUxtoTx(caip, to, amount, memo, pubkeys, pioneer, pu
2639
2639
  }
2640
2640
  } catch (error) {
2641
2641
  console.error(`${tag}: Failed to get fee rates from Pioneer API:`, error.message || error);
2642
- throw new Error(`Unable to get fee rate for network ${networkId}: ${error.message || "API unavailable"}`);
2642
+ const defaultFees = {
2643
+ "bip122:000000000019d6689c085ae165831e93": { slow: 3, average: 5, fast: 10, fastest: 15 },
2644
+ "bip122:12a765e31ffd4059bada1e25190f6e98": { slow: 2, average: 3, fast: 5, fastest: 10 },
2645
+ "bip122:00000000001a91e3dace36e2be3bf030": { slow: 1, average: 1, fast: 2, fastest: 3 },
2646
+ "bip122:000000000000000000651ef99cb9fcbe": { slow: 1, average: 1, fast: 2, fastest: 3 },
2647
+ "bip122:000007d91d1254d60e2dd1ae58038307": { slow: 1, average: 1, fast: 2, fastest: 3 },
2648
+ "bip122:4da631f2ac1bed857bd968c67c913978": { slow: 75, average: 80, fast: 100, fastest: 120 },
2649
+ "bip122:00040fe8ec8471911baa1db1266ea15d": { slow: 1, average: 1, fast: 2, fastest: 3 }
2650
+ };
2651
+ if (defaultFees[networkId]) {
2652
+ console.warn(`${tag}: Using default fee rates for ${networkId}`);
2653
+ feeRateFromNode = defaultFees[networkId];
2654
+ } else {
2655
+ throw new Error(`Unable to get fee rate for network ${networkId}: ${error.message || "API unavailable"}`);
2656
+ }
2643
2657
  }
2644
2658
  }
2645
2659
  let effectiveFeeRate;
@@ -3355,6 +3369,7 @@ function getNetworkName(networkId) {
3355
3369
  "bip122:00000000001a91e3dace36e2be3bf030": "Dogecoin",
3356
3370
  "bip122:000000000000000000651ef99cb9fcbe": "Bitcoin Cash",
3357
3371
  "bip122:000007d91d1254d60e2dd1ae58038307": "Dash",
3372
+ "bip122:00040fe8ec8471911baa1db1266ea15d": "Zcash",
3358
3373
  "bip122:4da631f2ac1bed857bd968c67c913978": "DigiByte",
3359
3374
  "eip155:1": "Ethereum",
3360
3375
  "eip155:56": "BNB Smart Chain",
@@ -3926,7 +3941,7 @@ function buildDashboardFromBalances(balances, blockchains, assetsMap) {
3926
3941
  totalValueUsd: 0,
3927
3942
  networkPercentages: []
3928
3943
  };
3929
- let totalPortfolioValue = 0;
3944
+ let totalPortfolioValueCents = 0;
3930
3945
  const networksTemp = [];
3931
3946
  for (const blockchain of blockchains) {
3932
3947
  const filteredBalances = balances.filter((b) => {
@@ -3934,50 +3949,46 @@ function buildDashboardFromBalances(balances, blockchains, assetsMap) {
3934
3949
  return networkId === blockchain || blockchain === "eip155:*" && networkId.startsWith("eip155:");
3935
3950
  });
3936
3951
  const balanceMap = new Map;
3937
- const isBitcoin = blockchain.includes("bip122:000000000019d6689c085ae165831e93");
3938
- if (isBitcoin) {
3939
- const bitcoinByValue = new Map;
3940
- filteredBalances.forEach((balance) => {
3941
- const valueKey = `${balance.balance}_${balance.valueUsd}`;
3942
- if (!bitcoinByValue.has(valueKey)) {
3943
- bitcoinByValue.set(valueKey, []);
3944
- }
3945
- bitcoinByValue.get(valueKey).push(balance);
3946
- });
3947
- for (const [valueKey, balances2] of bitcoinByValue.entries()) {
3948
- if (balances2.length === 3 && parseFloat(balances2[0].valueUsd || "0") > 0) {
3949
- const xpubBalance = balances2.find((b) => b.pubkey?.startsWith("xpub")) || balances2[0];
3950
- const key = `${xpubBalance.caip}_${xpubBalance.pubkey || "default"}`;
3951
- balanceMap.set(key, xpubBalance);
3952
- } else {
3953
- balances2.forEach((balance) => {
3954
- const key = `${balance.caip}_${balance.pubkey || "default"}`;
3955
- balanceMap.set(key, balance);
3956
- });
3957
- }
3952
+ filteredBalances.forEach((balance) => {
3953
+ const key = `${balance.caip}_${balance.pubkey || "default"}`;
3954
+ if (!balanceMap.has(key) || parseFloat(balance.valueUsd || "0") > parseFloat(balanceMap.get(key).valueUsd || "0")) {
3955
+ balanceMap.set(key, balance);
3956
+ } else {
3957
+ const existing = balanceMap.get(key);
3958
+ const existingValue = parseFloat(existing.valueUsd || "0");
3959
+ const newValue = parseFloat(balance.valueUsd || "0");
3960
+ console.log(`⚠️ [BUILD-DASHBOARD] ${blockchain} dedup skipped (same caip+pubkey):`, {
3961
+ caip: balance.caip,
3962
+ pubkey: (balance.pubkey || "default").substring(0, 20),
3963
+ existing: existingValue,
3964
+ new: newValue,
3965
+ diff: Math.abs(existingValue - newValue)
3966
+ });
3958
3967
  }
3959
- } else {
3960
- filteredBalances.forEach((balance) => {
3961
- const key = `${balance.caip}_${balance.pubkey || "default"}`;
3962
- if (!balanceMap.has(key) || parseFloat(balance.valueUsd || "0") > parseFloat(balanceMap.get(key).valueUsd || "0")) {
3963
- balanceMap.set(key, balance);
3964
- }
3965
- });
3966
- }
3968
+ });
3967
3969
  const networkBalances = Array.from(balanceMap.values());
3968
- const networkTotal = networkBalances.reduce((sum, balance, idx) => {
3969
- const valueUsd = typeof balance.valueUsd === "string" ? parseFloat(balance.valueUsd) : balance.valueUsd || 0;
3970
+ const networkTotalCents = networkBalances.reduce((sumCents, balance, idx) => {
3971
+ let valueUsd = typeof balance.valueUsd === "string" ? parseFloat(balance.valueUsd) : balance.valueUsd || 0;
3972
+ if (isNaN(valueUsd)) {
3973
+ console.warn(`⚠️ [BUILD-DASHBOARD] NaN value detected for ${balance.caip}:`, balance.valueUsd);
3974
+ return sumCents;
3975
+ }
3976
+ balance.valueUsd = valueUsd;
3977
+ const valueCents = Math.round(valueUsd * 100 * 1000) / 1000;
3970
3978
  if (idx < 2) {
3971
3979
  console.log(`\uD83D\uDCB0 [BUILD-DASHBOARD] ${blockchain} balance #${idx}:`, {
3972
3980
  caip: balance.caip,
3973
3981
  balance: balance.balance,
3974
3982
  valueUsd: balance.valueUsd,
3983
+ valueUsdType: typeof balance.valueUsd,
3975
3984
  parsedValueUsd: valueUsd,
3976
- runningSum: sum + valueUsd
3985
+ valueCents,
3986
+ runningSumCents: sumCents + valueCents
3977
3987
  });
3978
3988
  }
3979
- return sum + valueUsd;
3989
+ return sumCents + valueCents;
3980
3990
  }, 0);
3991
+ const networkTotal = networkTotalCents / 100;
3981
3992
  console.log(`\uD83D\uDCB0 [BUILD-DASHBOARD] ${blockchain} totals:`, {
3982
3993
  balancesCount: networkBalances.length,
3983
3994
  networkTotal,
@@ -4000,8 +4011,9 @@ function buildDashboardFromBalances(balances, blockchains, assetsMap) {
4000
4011
  color: gasAsset?.color || assetInfo?.color || null,
4001
4012
  totalNativeBalance
4002
4013
  });
4003
- totalPortfolioValue += networkTotal;
4014
+ totalPortfolioValueCents += networkTotalCents;
4004
4015
  }
4016
+ const totalPortfolioValue = totalPortfolioValueCents / 100;
4005
4017
  dashboardData.networks = networksTemp.sort((a, b) => b.totalValueUsd - a.totalValueUsd);
4006
4018
  dashboardData.totalValueUsd = totalPortfolioValue;
4007
4019
  dashboardData.networkPercentages = dashboardData.networks.map((network) => ({
@@ -4009,6 +4021,26 @@ function buildDashboardFromBalances(balances, blockchains, assetsMap) {
4009
4021
  percentage: totalPortfolioValue > 0 ? Number((network.totalValueUsd / totalPortfolioValue * 100).toFixed(2)) : 0
4010
4022
  })).filter((entry) => entry.percentage > 0);
4011
4023
  console.log(`[FAST DASHBOARD] ✅ Built dashboard: ${dashboardData.networks.length} networks, $${totalPortfolioValue.toFixed(2)} total`);
4024
+ const inputBalancesTotal = balances.reduce((sum, b) => {
4025
+ const value = typeof b.valueUsd === "string" ? parseFloat(b.valueUsd) : b.valueUsd || 0;
4026
+ return sum + (isNaN(value) ? 0 : value);
4027
+ }, 0);
4028
+ const difference = Math.abs(inputBalancesTotal - totalPortfolioValue);
4029
+ if (difference > 0.01) {
4030
+ console.error(`\uD83D\uDEA8 [BUILD-DASHBOARD] BALANCE MISMATCH DETECTED!`);
4031
+ console.error(` Input balances total: $${inputBalancesTotal.toFixed(2)} USD`);
4032
+ console.error(` Dashboard total: $${totalPortfolioValue.toFixed(2)} USD`);
4033
+ console.error(` Difference: $${difference.toFixed(2)} USD`);
4034
+ console.error(` This indicates lost balances during dashboard aggregation!`);
4035
+ console.error(` Network breakdown:`);
4036
+ dashboardData.networks.forEach((network) => {
4037
+ console.error(` ${network.networkId}: $${network.totalValueUsd.toFixed(2)}`);
4038
+ });
4039
+ } else if (difference > 0.001) {
4040
+ console.warn(`⚠️ [BUILD-DASHBOARD] Minor balance rounding difference: $${difference.toFixed(4)} USD`);
4041
+ } else {
4042
+ console.log(`✅ [BUILD-DASHBOARD] Balance reconciliation passed (diff: $${difference.toFixed(4)})`);
4043
+ }
4012
4044
  return dashboardData;
4013
4045
  }
4014
4046
 
@@ -4281,6 +4313,19 @@ function enrichBalancesWithAssetInfo(balances, assetsMap, caipToNetworkId7) {
4281
4313
  console.warn(tag, `Missing AssetInfo for CAIP: "${balance.caip}" - skipping this balance`);
4282
4314
  continue;
4283
4315
  }
4316
+ if (typeof balance.valueUsd === "string") {
4317
+ balance.valueUsd = parseFloat(balance.valueUsd) || 0;
4318
+ } else if (typeof balance.valueUsd !== "number") {
4319
+ balance.valueUsd = 0;
4320
+ }
4321
+ if (typeof balance.balance === "string") {
4322
+ balance.balance = balance.balance;
4323
+ }
4324
+ if (typeof balance.priceUsd === "string") {
4325
+ balance.priceUsd = parseFloat(balance.priceUsd) || 0;
4326
+ } else if (typeof balance.priceUsd !== "number") {
4327
+ balance.priceUsd = 0;
4328
+ }
4284
4329
  Object.assign(balance, assetInfo, {
4285
4330
  type: balance.type || assetInfo.type,
4286
4331
  isNative: balance.isNative ?? assetInfo.isNative,
@@ -4288,7 +4333,9 @@ function enrichBalancesWithAssetInfo(balances, assetsMap, caipToNetworkId7) {
4288
4333
  icon: assetInfo.icon || "https://pioneers.dev/coins/etherum.png",
4289
4334
  identifier: `${balance.caip}:${balance.pubkey}`,
4290
4335
  updated: Date.now(),
4291
- color: assetInfo.color
4336
+ color: assetInfo.color,
4337
+ valueUsd: balance.valueUsd,
4338
+ priceUsd: balance.priceUsd
4292
4339
  });
4293
4340
  enrichedBalances.push(balance);
4294
4341
  }
@@ -4823,6 +4870,20 @@ class SDK {
4823
4870
  this.balances[index] = balance;
4824
4871
  }
4825
4872
  this.events.emit("BALANCE_UPDATE", balance);
4873
+ console.log(tag7, "\uD83D\uDD04 Rebuilding dashboard after balance update...");
4874
+ const previousTotal = this.dashboard?.totalValueUsd || 0;
4875
+ this.dashboard = this.buildDashboardFromBalances();
4876
+ const newTotal = this.dashboard?.totalValueUsd || 0;
4877
+ if (Math.abs(newTotal - previousTotal) > 0.01) {
4878
+ console.log(tag7, `\uD83D\uDCB0 Portfolio value changed: $${previousTotal.toFixed(2)} → $${newTotal.toFixed(2)}`);
4879
+ }
4880
+ this.events.emit("DASHBOARD_UPDATE", {
4881
+ dashboard: this.dashboard,
4882
+ trigger: "balance_update",
4883
+ affectedAsset: balance.caip,
4884
+ previousTotal,
4885
+ newTotal
4886
+ });
4826
4887
  } catch (e) {
4827
4888
  console.error(tag7, "Error processing balance update:", e);
4828
4889
  }
package/dist/index.es.js CHANGED
@@ -2823,7 +2823,21 @@ async function createUnsignedUxtoTx(caip, to, amount, memo, pubkeys, pioneer, pu
2823
2823
  }
2824
2824
  } catch (error) {
2825
2825
  console.error(`${tag}: Failed to get fee rates from Pioneer API:`, error.message || error);
2826
- throw new Error(`Unable to get fee rate for network ${networkId}: ${error.message || "API unavailable"}`);
2826
+ const defaultFees = {
2827
+ "bip122:000000000019d6689c085ae165831e93": { slow: 3, average: 5, fast: 10, fastest: 15 },
2828
+ "bip122:12a765e31ffd4059bada1e25190f6e98": { slow: 2, average: 3, fast: 5, fastest: 10 },
2829
+ "bip122:00000000001a91e3dace36e2be3bf030": { slow: 1, average: 1, fast: 2, fastest: 3 },
2830
+ "bip122:000000000000000000651ef99cb9fcbe": { slow: 1, average: 1, fast: 2, fastest: 3 },
2831
+ "bip122:000007d91d1254d60e2dd1ae58038307": { slow: 1, average: 1, fast: 2, fastest: 3 },
2832
+ "bip122:4da631f2ac1bed857bd968c67c913978": { slow: 75, average: 80, fast: 100, fastest: 120 },
2833
+ "bip122:00040fe8ec8471911baa1db1266ea15d": { slow: 1, average: 1, fast: 2, fastest: 3 }
2834
+ };
2835
+ if (defaultFees[networkId]) {
2836
+ console.warn(`${tag}: Using default fee rates for ${networkId}`);
2837
+ feeRateFromNode = defaultFees[networkId];
2838
+ } else {
2839
+ throw new Error(`Unable to get fee rate for network ${networkId}: ${error.message || "API unavailable"}`);
2840
+ }
2827
2841
  }
2828
2842
  }
2829
2843
  let effectiveFeeRate;
@@ -3539,6 +3553,7 @@ function getNetworkName(networkId) {
3539
3553
  "bip122:00000000001a91e3dace36e2be3bf030": "Dogecoin",
3540
3554
  "bip122:000000000000000000651ef99cb9fcbe": "Bitcoin Cash",
3541
3555
  "bip122:000007d91d1254d60e2dd1ae58038307": "Dash",
3556
+ "bip122:00040fe8ec8471911baa1db1266ea15d": "Zcash",
3542
3557
  "bip122:4da631f2ac1bed857bd968c67c913978": "DigiByte",
3543
3558
  "eip155:1": "Ethereum",
3544
3559
  "eip155:56": "BNB Smart Chain",
@@ -4110,7 +4125,7 @@ function buildDashboardFromBalances(balances, blockchains, assetsMap) {
4110
4125
  totalValueUsd: 0,
4111
4126
  networkPercentages: []
4112
4127
  };
4113
- let totalPortfolioValue = 0;
4128
+ let totalPortfolioValueCents = 0;
4114
4129
  const networksTemp = [];
4115
4130
  for (const blockchain of blockchains) {
4116
4131
  const filteredBalances = balances.filter((b2) => {
@@ -4118,50 +4133,46 @@ function buildDashboardFromBalances(balances, blockchains, assetsMap) {
4118
4133
  return networkId === blockchain || blockchain === "eip155:*" && networkId.startsWith("eip155:");
4119
4134
  });
4120
4135
  const balanceMap = new Map;
4121
- const isBitcoin = blockchain.includes("bip122:000000000019d6689c085ae165831e93");
4122
- if (isBitcoin) {
4123
- const bitcoinByValue = new Map;
4124
- filteredBalances.forEach((balance) => {
4125
- const valueKey = `${balance.balance}_${balance.valueUsd}`;
4126
- if (!bitcoinByValue.has(valueKey)) {
4127
- bitcoinByValue.set(valueKey, []);
4128
- }
4129
- bitcoinByValue.get(valueKey).push(balance);
4130
- });
4131
- for (const [valueKey, balances2] of bitcoinByValue.entries()) {
4132
- if (balances2.length === 3 && parseFloat(balances2[0].valueUsd || "0") > 0) {
4133
- const xpubBalance = balances2.find((b2) => b2.pubkey?.startsWith("xpub")) || balances2[0];
4134
- const key = `${xpubBalance.caip}_${xpubBalance.pubkey || "default"}`;
4135
- balanceMap.set(key, xpubBalance);
4136
- } else {
4137
- balances2.forEach((balance) => {
4138
- const key = `${balance.caip}_${balance.pubkey || "default"}`;
4139
- balanceMap.set(key, balance);
4140
- });
4141
- }
4136
+ filteredBalances.forEach((balance) => {
4137
+ const key = `${balance.caip}_${balance.pubkey || "default"}`;
4138
+ if (!balanceMap.has(key) || parseFloat(balance.valueUsd || "0") > parseFloat(balanceMap.get(key).valueUsd || "0")) {
4139
+ balanceMap.set(key, balance);
4140
+ } else {
4141
+ const existing = balanceMap.get(key);
4142
+ const existingValue = parseFloat(existing.valueUsd || "0");
4143
+ const newValue = parseFloat(balance.valueUsd || "0");
4144
+ console.log(`⚠️ [BUILD-DASHBOARD] ${blockchain} dedup skipped (same caip+pubkey):`, {
4145
+ caip: balance.caip,
4146
+ pubkey: (balance.pubkey || "default").substring(0, 20),
4147
+ existing: existingValue,
4148
+ new: newValue,
4149
+ diff: Math.abs(existingValue - newValue)
4150
+ });
4142
4151
  }
4143
- } else {
4144
- filteredBalances.forEach((balance) => {
4145
- const key = `${balance.caip}_${balance.pubkey || "default"}`;
4146
- if (!balanceMap.has(key) || parseFloat(balance.valueUsd || "0") > parseFloat(balanceMap.get(key).valueUsd || "0")) {
4147
- balanceMap.set(key, balance);
4148
- }
4149
- });
4150
- }
4152
+ });
4151
4153
  const networkBalances = Array.from(balanceMap.values());
4152
- const networkTotal = networkBalances.reduce((sum, balance, idx) => {
4153
- const valueUsd = typeof balance.valueUsd === "string" ? parseFloat(balance.valueUsd) : balance.valueUsd || 0;
4154
+ const networkTotalCents = networkBalances.reduce((sumCents, balance, idx) => {
4155
+ let valueUsd = typeof balance.valueUsd === "string" ? parseFloat(balance.valueUsd) : balance.valueUsd || 0;
4156
+ if (isNaN(valueUsd)) {
4157
+ console.warn(`⚠️ [BUILD-DASHBOARD] NaN value detected for ${balance.caip}:`, balance.valueUsd);
4158
+ return sumCents;
4159
+ }
4160
+ balance.valueUsd = valueUsd;
4161
+ const valueCents = Math.round(valueUsd * 100 * 1000) / 1000;
4154
4162
  if (idx < 2) {
4155
4163
  console.log(`\uD83D\uDCB0 [BUILD-DASHBOARD] ${blockchain} balance #${idx}:`, {
4156
4164
  caip: balance.caip,
4157
4165
  balance: balance.balance,
4158
4166
  valueUsd: balance.valueUsd,
4167
+ valueUsdType: typeof balance.valueUsd,
4159
4168
  parsedValueUsd: valueUsd,
4160
- runningSum: sum + valueUsd
4169
+ valueCents,
4170
+ runningSumCents: sumCents + valueCents
4161
4171
  });
4162
4172
  }
4163
- return sum + valueUsd;
4173
+ return sumCents + valueCents;
4164
4174
  }, 0);
4175
+ const networkTotal = networkTotalCents / 100;
4165
4176
  console.log(`\uD83D\uDCB0 [BUILD-DASHBOARD] ${blockchain} totals:`, {
4166
4177
  balancesCount: networkBalances.length,
4167
4178
  networkTotal,
@@ -4184,8 +4195,9 @@ function buildDashboardFromBalances(balances, blockchains, assetsMap) {
4184
4195
  color: gasAsset?.color || assetInfo?.color || null,
4185
4196
  totalNativeBalance
4186
4197
  });
4187
- totalPortfolioValue += networkTotal;
4198
+ totalPortfolioValueCents += networkTotalCents;
4188
4199
  }
4200
+ const totalPortfolioValue = totalPortfolioValueCents / 100;
4189
4201
  dashboardData.networks = networksTemp.sort((a2, b2) => b2.totalValueUsd - a2.totalValueUsd);
4190
4202
  dashboardData.totalValueUsd = totalPortfolioValue;
4191
4203
  dashboardData.networkPercentages = dashboardData.networks.map((network) => ({
@@ -4193,6 +4205,26 @@ function buildDashboardFromBalances(balances, blockchains, assetsMap) {
4193
4205
  percentage: totalPortfolioValue > 0 ? Number((network.totalValueUsd / totalPortfolioValue * 100).toFixed(2)) : 0
4194
4206
  })).filter((entry) => entry.percentage > 0);
4195
4207
  console.log(`[FAST DASHBOARD] ✅ Built dashboard: ${dashboardData.networks.length} networks, $${totalPortfolioValue.toFixed(2)} total`);
4208
+ const inputBalancesTotal = balances.reduce((sum, b2) => {
4209
+ const value = typeof b2.valueUsd === "string" ? parseFloat(b2.valueUsd) : b2.valueUsd || 0;
4210
+ return sum + (isNaN(value) ? 0 : value);
4211
+ }, 0);
4212
+ const difference = Math.abs(inputBalancesTotal - totalPortfolioValue);
4213
+ if (difference > 0.01) {
4214
+ console.error(`\uD83D\uDEA8 [BUILD-DASHBOARD] BALANCE MISMATCH DETECTED!`);
4215
+ console.error(` Input balances total: $${inputBalancesTotal.toFixed(2)} USD`);
4216
+ console.error(` Dashboard total: $${totalPortfolioValue.toFixed(2)} USD`);
4217
+ console.error(` Difference: $${difference.toFixed(2)} USD`);
4218
+ console.error(` This indicates lost balances during dashboard aggregation!`);
4219
+ console.error(` Network breakdown:`);
4220
+ dashboardData.networks.forEach((network) => {
4221
+ console.error(` ${network.networkId}: $${network.totalValueUsd.toFixed(2)}`);
4222
+ });
4223
+ } else if (difference > 0.001) {
4224
+ console.warn(`⚠️ [BUILD-DASHBOARD] Minor balance rounding difference: $${difference.toFixed(4)} USD`);
4225
+ } else {
4226
+ console.log(`✅ [BUILD-DASHBOARD] Balance reconciliation passed (diff: $${difference.toFixed(4)})`);
4227
+ }
4196
4228
  return dashboardData;
4197
4229
  }
4198
4230
 
@@ -4465,6 +4497,19 @@ function enrichBalancesWithAssetInfo(balances, assetsMap, caipToNetworkId7) {
4465
4497
  console.warn(tag, `Missing AssetInfo for CAIP: "${balance.caip}" - skipping this balance`);
4466
4498
  continue;
4467
4499
  }
4500
+ if (typeof balance.valueUsd === "string") {
4501
+ balance.valueUsd = parseFloat(balance.valueUsd) || 0;
4502
+ } else if (typeof balance.valueUsd !== "number") {
4503
+ balance.valueUsd = 0;
4504
+ }
4505
+ if (typeof balance.balance === "string") {
4506
+ balance.balance = balance.balance;
4507
+ }
4508
+ if (typeof balance.priceUsd === "string") {
4509
+ balance.priceUsd = parseFloat(balance.priceUsd) || 0;
4510
+ } else if (typeof balance.priceUsd !== "number") {
4511
+ balance.priceUsd = 0;
4512
+ }
4468
4513
  Object.assign(balance, assetInfo, {
4469
4514
  type: balance.type || assetInfo.type,
4470
4515
  isNative: balance.isNative ?? assetInfo.isNative,
@@ -4472,7 +4517,9 @@ function enrichBalancesWithAssetInfo(balances, assetsMap, caipToNetworkId7) {
4472
4517
  icon: assetInfo.icon || "https://pioneers.dev/coins/etherum.png",
4473
4518
  identifier: `${balance.caip}:${balance.pubkey}`,
4474
4519
  updated: Date.now(),
4475
- color: assetInfo.color
4520
+ color: assetInfo.color,
4521
+ valueUsd: balance.valueUsd,
4522
+ priceUsd: balance.priceUsd
4476
4523
  });
4477
4524
  enrichedBalances.push(balance);
4478
4525
  }
@@ -5007,6 +5054,20 @@ class SDK {
5007
5054
  this.balances[index] = balance;
5008
5055
  }
5009
5056
  this.events.emit("BALANCE_UPDATE", balance);
5057
+ console.log(tag7, "\uD83D\uDD04 Rebuilding dashboard after balance update...");
5058
+ const previousTotal = this.dashboard?.totalValueUsd || 0;
5059
+ this.dashboard = this.buildDashboardFromBalances();
5060
+ const newTotal = this.dashboard?.totalValueUsd || 0;
5061
+ if (Math.abs(newTotal - previousTotal) > 0.01) {
5062
+ console.log(tag7, `\uD83D\uDCB0 Portfolio value changed: $${previousTotal.toFixed(2)} → $${newTotal.toFixed(2)}`);
5063
+ }
5064
+ this.events.emit("DASHBOARD_UPDATE", {
5065
+ dashboard: this.dashboard,
5066
+ trigger: "balance_update",
5067
+ affectedAsset: balance.caip,
5068
+ previousTotal,
5069
+ newTotal
5070
+ });
5010
5071
  } catch (e) {
5011
5072
  console.error(tag7, "Error processing balance update:", e);
5012
5073
  }
package/dist/index.js CHANGED
@@ -2823,7 +2823,21 @@ async function createUnsignedUxtoTx(caip, to, amount, memo, pubkeys, pioneer, pu
2823
2823
  }
2824
2824
  } catch (error) {
2825
2825
  console.error(`${tag}: Failed to get fee rates from Pioneer API:`, error.message || error);
2826
- throw new Error(`Unable to get fee rate for network ${networkId}: ${error.message || "API unavailable"}`);
2826
+ const defaultFees = {
2827
+ "bip122:000000000019d6689c085ae165831e93": { slow: 3, average: 5, fast: 10, fastest: 15 },
2828
+ "bip122:12a765e31ffd4059bada1e25190f6e98": { slow: 2, average: 3, fast: 5, fastest: 10 },
2829
+ "bip122:00000000001a91e3dace36e2be3bf030": { slow: 1, average: 1, fast: 2, fastest: 3 },
2830
+ "bip122:000000000000000000651ef99cb9fcbe": { slow: 1, average: 1, fast: 2, fastest: 3 },
2831
+ "bip122:000007d91d1254d60e2dd1ae58038307": { slow: 1, average: 1, fast: 2, fastest: 3 },
2832
+ "bip122:4da631f2ac1bed857bd968c67c913978": { slow: 75, average: 80, fast: 100, fastest: 120 },
2833
+ "bip122:00040fe8ec8471911baa1db1266ea15d": { slow: 1, average: 1, fast: 2, fastest: 3 }
2834
+ };
2835
+ if (defaultFees[networkId]) {
2836
+ console.warn(`${tag}: Using default fee rates for ${networkId}`);
2837
+ feeRateFromNode = defaultFees[networkId];
2838
+ } else {
2839
+ throw new Error(`Unable to get fee rate for network ${networkId}: ${error.message || "API unavailable"}`);
2840
+ }
2827
2841
  }
2828
2842
  }
2829
2843
  let effectiveFeeRate;
@@ -3539,6 +3553,7 @@ function getNetworkName(networkId) {
3539
3553
  "bip122:00000000001a91e3dace36e2be3bf030": "Dogecoin",
3540
3554
  "bip122:000000000000000000651ef99cb9fcbe": "Bitcoin Cash",
3541
3555
  "bip122:000007d91d1254d60e2dd1ae58038307": "Dash",
3556
+ "bip122:00040fe8ec8471911baa1db1266ea15d": "Zcash",
3542
3557
  "bip122:4da631f2ac1bed857bd968c67c913978": "DigiByte",
3543
3558
  "eip155:1": "Ethereum",
3544
3559
  "eip155:56": "BNB Smart Chain",
@@ -4110,7 +4125,7 @@ function buildDashboardFromBalances(balances, blockchains, assetsMap) {
4110
4125
  totalValueUsd: 0,
4111
4126
  networkPercentages: []
4112
4127
  };
4113
- let totalPortfolioValue = 0;
4128
+ let totalPortfolioValueCents = 0;
4114
4129
  const networksTemp = [];
4115
4130
  for (const blockchain of blockchains) {
4116
4131
  const filteredBalances = balances.filter((b2) => {
@@ -4118,50 +4133,46 @@ function buildDashboardFromBalances(balances, blockchains, assetsMap) {
4118
4133
  return networkId === blockchain || blockchain === "eip155:*" && networkId.startsWith("eip155:");
4119
4134
  });
4120
4135
  const balanceMap = new Map;
4121
- const isBitcoin = blockchain.includes("bip122:000000000019d6689c085ae165831e93");
4122
- if (isBitcoin) {
4123
- const bitcoinByValue = new Map;
4124
- filteredBalances.forEach((balance) => {
4125
- const valueKey = `${balance.balance}_${balance.valueUsd}`;
4126
- if (!bitcoinByValue.has(valueKey)) {
4127
- bitcoinByValue.set(valueKey, []);
4128
- }
4129
- bitcoinByValue.get(valueKey).push(balance);
4130
- });
4131
- for (const [valueKey, balances2] of bitcoinByValue.entries()) {
4132
- if (balances2.length === 3 && parseFloat(balances2[0].valueUsd || "0") > 0) {
4133
- const xpubBalance = balances2.find((b2) => b2.pubkey?.startsWith("xpub")) || balances2[0];
4134
- const key = `${xpubBalance.caip}_${xpubBalance.pubkey || "default"}`;
4135
- balanceMap.set(key, xpubBalance);
4136
- } else {
4137
- balances2.forEach((balance) => {
4138
- const key = `${balance.caip}_${balance.pubkey || "default"}`;
4139
- balanceMap.set(key, balance);
4140
- });
4141
- }
4136
+ filteredBalances.forEach((balance) => {
4137
+ const key = `${balance.caip}_${balance.pubkey || "default"}`;
4138
+ if (!balanceMap.has(key) || parseFloat(balance.valueUsd || "0") > parseFloat(balanceMap.get(key).valueUsd || "0")) {
4139
+ balanceMap.set(key, balance);
4140
+ } else {
4141
+ const existing = balanceMap.get(key);
4142
+ const existingValue = parseFloat(existing.valueUsd || "0");
4143
+ const newValue = parseFloat(balance.valueUsd || "0");
4144
+ console.log(`⚠️ [BUILD-DASHBOARD] ${blockchain} dedup skipped (same caip+pubkey):`, {
4145
+ caip: balance.caip,
4146
+ pubkey: (balance.pubkey || "default").substring(0, 20),
4147
+ existing: existingValue,
4148
+ new: newValue,
4149
+ diff: Math.abs(existingValue - newValue)
4150
+ });
4142
4151
  }
4143
- } else {
4144
- filteredBalances.forEach((balance) => {
4145
- const key = `${balance.caip}_${balance.pubkey || "default"}`;
4146
- if (!balanceMap.has(key) || parseFloat(balance.valueUsd || "0") > parseFloat(balanceMap.get(key).valueUsd || "0")) {
4147
- balanceMap.set(key, balance);
4148
- }
4149
- });
4150
- }
4152
+ });
4151
4153
  const networkBalances = Array.from(balanceMap.values());
4152
- const networkTotal = networkBalances.reduce((sum, balance, idx) => {
4153
- const valueUsd = typeof balance.valueUsd === "string" ? parseFloat(balance.valueUsd) : balance.valueUsd || 0;
4154
+ const networkTotalCents = networkBalances.reduce((sumCents, balance, idx) => {
4155
+ let valueUsd = typeof balance.valueUsd === "string" ? parseFloat(balance.valueUsd) : balance.valueUsd || 0;
4156
+ if (isNaN(valueUsd)) {
4157
+ console.warn(`⚠️ [BUILD-DASHBOARD] NaN value detected for ${balance.caip}:`, balance.valueUsd);
4158
+ return sumCents;
4159
+ }
4160
+ balance.valueUsd = valueUsd;
4161
+ const valueCents = Math.round(valueUsd * 100 * 1000) / 1000;
4154
4162
  if (idx < 2) {
4155
4163
  console.log(`\uD83D\uDCB0 [BUILD-DASHBOARD] ${blockchain} balance #${idx}:`, {
4156
4164
  caip: balance.caip,
4157
4165
  balance: balance.balance,
4158
4166
  valueUsd: balance.valueUsd,
4167
+ valueUsdType: typeof balance.valueUsd,
4159
4168
  parsedValueUsd: valueUsd,
4160
- runningSum: sum + valueUsd
4169
+ valueCents,
4170
+ runningSumCents: sumCents + valueCents
4161
4171
  });
4162
4172
  }
4163
- return sum + valueUsd;
4173
+ return sumCents + valueCents;
4164
4174
  }, 0);
4175
+ const networkTotal = networkTotalCents / 100;
4165
4176
  console.log(`\uD83D\uDCB0 [BUILD-DASHBOARD] ${blockchain} totals:`, {
4166
4177
  balancesCount: networkBalances.length,
4167
4178
  networkTotal,
@@ -4184,8 +4195,9 @@ function buildDashboardFromBalances(balances, blockchains, assetsMap) {
4184
4195
  color: gasAsset?.color || assetInfo?.color || null,
4185
4196
  totalNativeBalance
4186
4197
  });
4187
- totalPortfolioValue += networkTotal;
4198
+ totalPortfolioValueCents += networkTotalCents;
4188
4199
  }
4200
+ const totalPortfolioValue = totalPortfolioValueCents / 100;
4189
4201
  dashboardData.networks = networksTemp.sort((a2, b2) => b2.totalValueUsd - a2.totalValueUsd);
4190
4202
  dashboardData.totalValueUsd = totalPortfolioValue;
4191
4203
  dashboardData.networkPercentages = dashboardData.networks.map((network) => ({
@@ -4193,6 +4205,26 @@ function buildDashboardFromBalances(balances, blockchains, assetsMap) {
4193
4205
  percentage: totalPortfolioValue > 0 ? Number((network.totalValueUsd / totalPortfolioValue * 100).toFixed(2)) : 0
4194
4206
  })).filter((entry) => entry.percentage > 0);
4195
4207
  console.log(`[FAST DASHBOARD] ✅ Built dashboard: ${dashboardData.networks.length} networks, $${totalPortfolioValue.toFixed(2)} total`);
4208
+ const inputBalancesTotal = balances.reduce((sum, b2) => {
4209
+ const value = typeof b2.valueUsd === "string" ? parseFloat(b2.valueUsd) : b2.valueUsd || 0;
4210
+ return sum + (isNaN(value) ? 0 : value);
4211
+ }, 0);
4212
+ const difference = Math.abs(inputBalancesTotal - totalPortfolioValue);
4213
+ if (difference > 0.01) {
4214
+ console.error(`\uD83D\uDEA8 [BUILD-DASHBOARD] BALANCE MISMATCH DETECTED!`);
4215
+ console.error(` Input balances total: $${inputBalancesTotal.toFixed(2)} USD`);
4216
+ console.error(` Dashboard total: $${totalPortfolioValue.toFixed(2)} USD`);
4217
+ console.error(` Difference: $${difference.toFixed(2)} USD`);
4218
+ console.error(` This indicates lost balances during dashboard aggregation!`);
4219
+ console.error(` Network breakdown:`);
4220
+ dashboardData.networks.forEach((network) => {
4221
+ console.error(` ${network.networkId}: $${network.totalValueUsd.toFixed(2)}`);
4222
+ });
4223
+ } else if (difference > 0.001) {
4224
+ console.warn(`⚠️ [BUILD-DASHBOARD] Minor balance rounding difference: $${difference.toFixed(4)} USD`);
4225
+ } else {
4226
+ console.log(`✅ [BUILD-DASHBOARD] Balance reconciliation passed (diff: $${difference.toFixed(4)})`);
4227
+ }
4196
4228
  return dashboardData;
4197
4229
  }
4198
4230
 
@@ -4465,6 +4497,19 @@ function enrichBalancesWithAssetInfo(balances, assetsMap, caipToNetworkId7) {
4465
4497
  console.warn(tag, `Missing AssetInfo for CAIP: "${balance.caip}" - skipping this balance`);
4466
4498
  continue;
4467
4499
  }
4500
+ if (typeof balance.valueUsd === "string") {
4501
+ balance.valueUsd = parseFloat(balance.valueUsd) || 0;
4502
+ } else if (typeof balance.valueUsd !== "number") {
4503
+ balance.valueUsd = 0;
4504
+ }
4505
+ if (typeof balance.balance === "string") {
4506
+ balance.balance = balance.balance;
4507
+ }
4508
+ if (typeof balance.priceUsd === "string") {
4509
+ balance.priceUsd = parseFloat(balance.priceUsd) || 0;
4510
+ } else if (typeof balance.priceUsd !== "number") {
4511
+ balance.priceUsd = 0;
4512
+ }
4468
4513
  Object.assign(balance, assetInfo, {
4469
4514
  type: balance.type || assetInfo.type,
4470
4515
  isNative: balance.isNative ?? assetInfo.isNative,
@@ -4472,7 +4517,9 @@ function enrichBalancesWithAssetInfo(balances, assetsMap, caipToNetworkId7) {
4472
4517
  icon: assetInfo.icon || "https://pioneers.dev/coins/etherum.png",
4473
4518
  identifier: `${balance.caip}:${balance.pubkey}`,
4474
4519
  updated: Date.now(),
4475
- color: assetInfo.color
4520
+ color: assetInfo.color,
4521
+ valueUsd: balance.valueUsd,
4522
+ priceUsd: balance.priceUsd
4476
4523
  });
4477
4524
  enrichedBalances.push(balance);
4478
4525
  }
@@ -5007,6 +5054,20 @@ class SDK {
5007
5054
  this.balances[index] = balance;
5008
5055
  }
5009
5056
  this.events.emit("BALANCE_UPDATE", balance);
5057
+ console.log(tag7, "\uD83D\uDD04 Rebuilding dashboard after balance update...");
5058
+ const previousTotal = this.dashboard?.totalValueUsd || 0;
5059
+ this.dashboard = this.buildDashboardFromBalances();
5060
+ const newTotal = this.dashboard?.totalValueUsd || 0;
5061
+ if (Math.abs(newTotal - previousTotal) > 0.01) {
5062
+ console.log(tag7, `\uD83D\uDCB0 Portfolio value changed: $${previousTotal.toFixed(2)} → $${newTotal.toFixed(2)}`);
5063
+ }
5064
+ this.events.emit("DASHBOARD_UPDATE", {
5065
+ dashboard: this.dashboard,
5066
+ trigger: "balance_update",
5067
+ affectedAsset: balance.caip,
5068
+ previousTotal,
5069
+ newTotal
5070
+ });
5010
5071
  } catch (e) {
5011
5072
  console.error(tag7, "Error processing balance update:", e);
5012
5073
  }
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.30",
4
+ "version": "8.15.32",
5
5
  "dependencies": {
6
6
  "@keepkey/keepkey-sdk": "^0.2.62",
7
- "@pioneer-platform/pioneer-caip": "^9.10.7",
8
- "@pioneer-platform/pioneer-client": "^9.10.13",
9
- "@pioneer-platform/pioneer-coins": "^9.11.7",
10
- "@pioneer-platform/pioneer-discovery": "^8.15.30",
11
- "@pioneer-platform/pioneer-events": "^8.12.2",
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",
12
12
  "coinselect": "^3.1.13",
13
13
  "eventemitter3": "^5.0.1",
14
14
  "neotraverse": "^0.6.8",
package/src/fees/index.ts CHANGED
@@ -48,6 +48,7 @@ function getNetworkName(networkId: string): string {
48
48
  'bip122:00000000001a91e3dace36e2be3bf030': 'Dogecoin',
49
49
  'bip122:000000000000000000651ef99cb9fcbe': 'Bitcoin Cash',
50
50
  'bip122:000007d91d1254d60e2dd1ae58038307': 'Dash',
51
+ 'bip122:00040fe8ec8471911baa1db1266ea15d': 'Zcash',
51
52
  'bip122:4da631f2ac1bed857bd968c67c913978': 'DigiByte',
52
53
  'eip155:1': 'Ethereum',
53
54
  'eip155:56': 'BNB Smart Chain',
package/src/index.ts CHANGED
@@ -552,6 +552,25 @@ export class SDK {
552
552
 
553
553
  // Emit SDK event for UI updates
554
554
  this.events.emit('BALANCE_UPDATE', balance);
555
+
556
+ // CRITICAL FIX: Rebuild dashboard when balance changes
557
+ console.log(tag, '🔄 Rebuilding dashboard after balance update...');
558
+ const previousTotal = this.dashboard?.totalValueUsd || 0;
559
+ this.dashboard = this.buildDashboardFromBalances();
560
+ const newTotal = this.dashboard?.totalValueUsd || 0;
561
+
562
+ if (Math.abs(newTotal - previousTotal) > 0.01) {
563
+ console.log(tag, `💰 Portfolio value changed: $${previousTotal.toFixed(2)} → $${newTotal.toFixed(2)}`);
564
+ }
565
+
566
+ // Emit dashboard update event for real-time UI updates
567
+ this.events.emit('DASHBOARD_UPDATE', {
568
+ dashboard: this.dashboard,
569
+ trigger: 'balance_update',
570
+ affectedAsset: balance.caip,
571
+ previousTotal,
572
+ newTotal
573
+ });
555
574
  } catch (e) {
556
575
  console.error(tag, 'Error processing balance update:', e);
557
576
  }
@@ -287,9 +287,26 @@ export async function createUnsignedUxtoTx(
287
287
  }
288
288
  } catch (error: any) {
289
289
  console.error(`${tag}: Failed to get fee rates from Pioneer API:`, error.message || error);
290
- throw new Error(
291
- `Unable to get fee rate for network ${networkId}: ${error.message || 'API unavailable'}`,
292
- );
290
+
291
+ // Fallback to hardcoded default fees for known networks
292
+ const defaultFees: Record<string, { slow: number; average: number; fast: number; fastest: number }> = {
293
+ 'bip122:000000000019d6689c085ae165831e93': { slow: 3, average: 5, fast: 10, fastest: 15 }, // BTC
294
+ 'bip122:12a765e31ffd4059bada1e25190f6e98': { slow: 2, average: 3, fast: 5, fastest: 10 }, // LTC
295
+ 'bip122:00000000001a91e3dace36e2be3bf030': { slow: 1, average: 1, fast: 2, fastest: 3 }, // DOGE
296
+ 'bip122:000000000000000000651ef99cb9fcbe': { slow: 1, average: 1, fast: 2, fastest: 3 }, // BCH
297
+ 'bip122:000007d91d1254d60e2dd1ae58038307': { slow: 1, average: 1, fast: 2, fastest: 3 }, // DASH
298
+ 'bip122:4da631f2ac1bed857bd968c67c913978': { slow: 75, average: 80, fast: 100, fastest: 120 }, // DGB
299
+ 'bip122:00040fe8ec8471911baa1db1266ea15d': { slow: 1, average: 1, fast: 2, fastest: 3 }, // ZEC
300
+ };
301
+
302
+ if (defaultFees[networkId]) {
303
+ console.warn(`${tag}: Using default fee rates for ${networkId}`);
304
+ feeRateFromNode = defaultFees[networkId];
305
+ } else {
306
+ throw new Error(
307
+ `Unable to get fee rate for network ${networkId}: ${error.message || 'API unavailable'}`,
308
+ );
309
+ }
293
310
  }
294
311
  }
295
312
 
@@ -23,7 +23,7 @@ export function buildDashboardFromBalances(balances: any[], blockchains: string[
23
23
  networkPercentages: [],
24
24
  };
25
25
 
26
- let totalPortfolioValue = 0;
26
+ let totalPortfolioValueCents = 0; // Use cents for integer math
27
27
  const networksTemp: {
28
28
  networkId: string;
29
29
  totalValueUsd: number;
@@ -46,73 +46,81 @@ export function buildDashboardFromBalances(balances: any[], blockchains: string[
46
46
  });
47
47
 
48
48
  // Deduplicate balances based on caip + pubkey combination
49
+ // NOTE: Each pubkey (xpub/ypub/zpub) represents a DIFFERENT address space with potentially
50
+ // different balances - they are NOT duplicates! Keep all of them.
49
51
  const balanceMap = new Map();
50
52
 
51
- // Special handling for Bitcoin to work around API bug
52
- const isBitcoin = blockchain.includes('bip122:000000000019d6689c085ae165831e93');
53
- if (isBitcoin) {
54
- // Group Bitcoin balances by value to detect duplicates
55
- const bitcoinByValue = new Map();
56
- filteredBalances.forEach((balance) => {
57
- const valueKey = `${balance.balance}_${balance.valueUsd}`;
58
- if (!bitcoinByValue.has(valueKey)) {
59
- bitcoinByValue.set(valueKey, []);
60
- }
61
- bitcoinByValue.get(valueKey).push(balance);
62
- });
63
-
64
- // Check if all three address types have the same non-zero balance (API bug)
65
- for (const [valueKey, balances] of bitcoinByValue.entries()) {
66
- if (balances.length === 3 && parseFloat(balances[0].valueUsd || '0') > 0) {
67
- // Keep only the xpub (or first one if no xpub)
68
- const xpubBalance = balances.find((b) => b.pubkey?.startsWith('xpub')) || balances[0];
69
- const key = `${xpubBalance.caip}_${xpubBalance.pubkey || 'default'}`;
70
- balanceMap.set(key, xpubBalance);
71
- } else {
72
- // Add all balances normally
73
- balances.forEach((balance) => {
74
- const key = `${balance.caip}_${balance.pubkey || 'default'}`;
75
- balanceMap.set(key, balance);
76
- });
77
- }
53
+ // Standard deduplication for all networks (including Bitcoin)
54
+ // Only deduplicate if BOTH caip AND pubkey are identical (true duplicates)
55
+ filteredBalances.forEach((balance) => {
56
+ const key = `${balance.caip}_${balance.pubkey || 'default'}`;
57
+
58
+ // Only keep the first occurrence or the one with higher value for TRUE duplicates
59
+ if (
60
+ !balanceMap.has(key) ||
61
+ parseFloat(balance.valueUsd || '0') > parseFloat(balanceMap.get(key).valueUsd || '0')
62
+ ) {
63
+ balanceMap.set(key, balance);
64
+ } else {
65
+ // DEBUG: Log when we skip a balance due to deduplication
66
+ const existing = balanceMap.get(key);
67
+ const existingValue = parseFloat(existing.valueUsd || '0');
68
+ const newValue = parseFloat(balance.valueUsd || '0');
69
+
70
+ // Always log duplicates being skipped (removed the 0.01 threshold)
71
+ console.log(`⚠️ [BUILD-DASHBOARD] ${blockchain} dedup skipped (same caip+pubkey):`, {
72
+ caip: balance.caip,
73
+ pubkey: (balance.pubkey || 'default').substring(0, 20),
74
+ existing: existingValue,
75
+ new: newValue,
76
+ diff: Math.abs(existingValue - newValue)
77
+ });
78
78
  }
79
- } else {
80
- // Standard deduplication for non-Bitcoin networks
81
- filteredBalances.forEach((balance) => {
82
- const key = `${balance.caip}_${balance.pubkey || 'default'}`;
83
- // Only keep the first occurrence or the one with higher value
84
- if (
85
- !balanceMap.has(key) ||
86
- parseFloat(balance.valueUsd || '0') > parseFloat(balanceMap.get(key).valueUsd || '0')
87
- ) {
88
- balanceMap.set(key, balance);
89
- }
90
- });
91
- }
79
+ });
92
80
 
93
81
  const networkBalances = Array.from(balanceMap.values());
94
82
 
95
83
  // Ensure we're working with numbers for calculations
96
- const networkTotal = networkBalances.reduce((sum, balance, idx) => {
97
- const valueUsd =
84
+ // Accumulate in cents (integer math) to avoid floating point errors
85
+ const networkTotalCents = networkBalances.reduce((sumCents, balance, idx) => {
86
+ // Normalize valueUsd to number type
87
+ let valueUsd =
98
88
  typeof balance.valueUsd === 'string'
99
89
  ? parseFloat(balance.valueUsd)
100
90
  : balance.valueUsd || 0;
101
91
 
92
+ // Check for NaN values which can cause calculation errors
93
+ if (isNaN(valueUsd)) {
94
+ console.warn(`⚠️ [BUILD-DASHBOARD] NaN value detected for ${balance.caip}:`, balance.valueUsd);
95
+ return sumCents;
96
+ }
97
+
98
+ // Normalize the balance object to always use number type for valueUsd
99
+ balance.valueUsd = valueUsd;
100
+
101
+ // Convert to cents with higher precision to reduce rounding errors
102
+ // Use Math.round with extended precision, then scale back
103
+ const valueCents = Math.round(valueUsd * 100 * 1000) / 1000;
104
+
102
105
  // Debug first few balances for each network
103
106
  if (idx < 2) {
104
107
  console.log(`💰 [BUILD-DASHBOARD] ${blockchain} balance #${idx}:`, {
105
108
  caip: balance.caip,
106
109
  balance: balance.balance,
107
110
  valueUsd: balance.valueUsd,
111
+ valueUsdType: typeof balance.valueUsd,
108
112
  parsedValueUsd: valueUsd,
109
- runningSum: sum + valueUsd
113
+ valueCents: valueCents,
114
+ runningSumCents: sumCents + valueCents
110
115
  });
111
116
  }
112
117
 
113
- return sum + valueUsd;
118
+ return sumCents + valueCents;
114
119
  }, 0);
115
120
 
121
+ // Convert back to dollars
122
+ const networkTotal = networkTotalCents / 100;
123
+
116
124
  console.log(`💰 [BUILD-DASHBOARD] ${blockchain} totals:`, {
117
125
  balancesCount: networkBalances.length,
118
126
  networkTotal,
@@ -149,9 +157,13 @@ export function buildDashboardFromBalances(balances: any[], blockchains: string[
149
157
  totalNativeBalance,
150
158
  });
151
159
 
152
- totalPortfolioValue += networkTotal;
160
+ // Add to total using cents (integer math) to avoid floating point errors
161
+ totalPortfolioValueCents += networkTotalCents;
153
162
  }
154
163
 
164
+ // Convert total from cents to dollars
165
+ const totalPortfolioValue = totalPortfolioValueCents / 100;
166
+
155
167
  // Sort networks by USD value and assign to dashboard
156
168
  dashboardData.networks = networksTemp.sort((a, b) => b.totalValueUsd - a.totalValueUsd);
157
169
  dashboardData.totalValueUsd = totalPortfolioValue;
@@ -172,5 +184,33 @@ export function buildDashboardFromBalances(balances: any[], blockchains: string[
172
184
  dashboardData.networks.length
173
185
  } networks, $${totalPortfolioValue.toFixed(2)} total`,
174
186
  );
187
+
188
+ // ===== BALANCE RECONCILIATION CHECK =====
189
+ // Verify that dashboard total matches the sum of all input balances
190
+ const inputBalancesTotal = balances.reduce((sum, b) => {
191
+ const value = typeof b.valueUsd === 'string' ? parseFloat(b.valueUsd) : (b.valueUsd || 0);
192
+ return sum + (isNaN(value) ? 0 : value);
193
+ }, 0);
194
+
195
+ const difference = Math.abs(inputBalancesTotal - totalPortfolioValue);
196
+
197
+ if (difference > 0.01) {
198
+ console.error(`🚨 [BUILD-DASHBOARD] BALANCE MISMATCH DETECTED!`);
199
+ console.error(` Input balances total: $${inputBalancesTotal.toFixed(2)} USD`);
200
+ console.error(` Dashboard total: $${totalPortfolioValue.toFixed(2)} USD`);
201
+ console.error(` Difference: $${difference.toFixed(2)} USD`);
202
+ console.error(` This indicates lost balances during dashboard aggregation!`);
203
+
204
+ // Log per-network breakdown to help debug
205
+ console.error(` Network breakdown:`);
206
+ dashboardData.networks.forEach(network => {
207
+ console.error(` ${network.networkId}: $${network.totalValueUsd.toFixed(2)}`);
208
+ });
209
+ } else if (difference > 0.001) {
210
+ console.warn(`⚠️ [BUILD-DASHBOARD] Minor balance rounding difference: $${difference.toFixed(4)} USD`);
211
+ } else {
212
+ console.log(`✅ [BUILD-DASHBOARD] Balance reconciliation passed (diff: $${difference.toFixed(4)})`);
213
+ }
214
+
175
215
  return dashboardData;
176
216
  }
@@ -220,6 +220,26 @@ export function enrichBalancesWithAssetInfo(
220
220
  continue;
221
221
  }
222
222
 
223
+ // ===== CRITICAL: Normalize valueUsd to NUMBER type =====
224
+ // Ensures consistent type throughout the SDK to prevent calculation errors
225
+ if (typeof balance.valueUsd === 'string') {
226
+ balance.valueUsd = parseFloat(balance.valueUsd) || 0;
227
+ } else if (typeof balance.valueUsd !== 'number') {
228
+ balance.valueUsd = 0;
229
+ }
230
+
231
+ // Also normalize balance amount to number
232
+ if (typeof balance.balance === 'string') {
233
+ balance.balance = balance.balance; // Keep as string for precision (e.g., "0.00000001")
234
+ }
235
+
236
+ // Normalize priceUsd to number as well
237
+ if (typeof balance.priceUsd === 'string') {
238
+ balance.priceUsd = parseFloat(balance.priceUsd) || 0;
239
+ } else if (typeof balance.priceUsd !== 'number') {
240
+ balance.priceUsd = 0;
241
+ }
242
+
223
243
  Object.assign(balance, assetInfo, {
224
244
  type: balance.type || assetInfo.type,
225
245
  isNative: balance.isNative ?? assetInfo.isNative,
@@ -228,6 +248,9 @@ export function enrichBalancesWithAssetInfo(
228
248
  identifier: `${balance.caip}:${balance.pubkey}`,
229
249
  updated: Date.now(),
230
250
  color: assetInfo.color,
251
+ // Ensure these are numbers (redundant but explicit)
252
+ valueUsd: balance.valueUsd,
253
+ priceUsd: balance.priceUsd,
231
254
  });
232
255
 
233
256
  enrichedBalances.push(balance);