@dynamic-labs/sdk-react-core 4.81.0 → 4.82.0

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/package.cjs +1 -1
  3. package/package.js +1 -1
  4. package/package.json +12 -12
  5. package/src/lib/data/api/aleo/getAleoCuratedPrices.cjs +73 -0
  6. package/src/lib/data/api/aleo/getAleoCuratedPrices.d.ts +38 -0
  7. package/src/lib/data/api/aleo/getAleoCuratedPrices.js +69 -0
  8. package/src/lib/shared/assets/index.d.ts +2 -0
  9. package/src/lib/shared/assets/midnight-shielded.cjs +54 -0
  10. package/src/lib/shared/assets/midnight-shielded.js +30 -0
  11. package/src/lib/shared/assets/midnight-unshielded.cjs +54 -0
  12. package/src/lib/shared/assets/midnight-unshielded.js +30 -0
  13. package/src/lib/styles/index.shadow.cjs +1 -1
  14. package/src/lib/styles/index.shadow.js +1 -1
  15. package/src/lib/utils/functions/compareChains/compareChains.cjs +1 -0
  16. package/src/lib/utils/functions/compareChains/compareChains.js +1 -0
  17. package/src/lib/utils/functions/getTransactionLink/blockExplorerPatterns.cjs +12 -0
  18. package/src/lib/utils/functions/getTransactionLink/blockExplorerPatterns.js +12 -0
  19. package/src/lib/utils/hooks/useAleoAutoMergeRecords/index.d.ts +1 -0
  20. package/src/lib/utils/hooks/useAleoAutoMergeRecords/useAleoAutoMergeRecords.cjs +246 -0
  21. package/src/lib/utils/hooks/useAleoAutoMergeRecords/useAleoAutoMergeRecords.d.ts +17 -0
  22. package/src/lib/utils/hooks/useAleoAutoMergeRecords/useAleoAutoMergeRecords.js +242 -0
  23. package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/index.d.ts +1 -0
  24. package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/useAleoAutoShieldSponsoredTokens.cjs +263 -0
  25. package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/useAleoAutoShieldSponsoredTokens.d.ts +59 -0
  26. package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/useAleoAutoShieldSponsoredTokens.js +259 -0
  27. package/src/lib/utils/hooks/useAleoShieldedBalances/useAleoShieldedBalances.cjs +139 -68
  28. package/src/lib/utils/hooks/useAleoShieldedBalances/useAleoShieldedBalances.js +139 -68
  29. package/src/lib/views/BackupUnsuccessfulView/BackupUnsuccessfulView.cjs +12 -1
  30. package/src/lib/views/BackupUnsuccessfulView/BackupUnsuccessfulView.js +12 -1
  31. package/src/lib/widgets/DynamicWidget/components/ActiveMidnightWalletBalance/ActiveMidnightWalletBalance.cjs +193 -0
  32. package/src/lib/widgets/DynamicWidget/components/ActiveMidnightWalletBalance/ActiveMidnightWalletBalance.d.ts +7 -0
  33. package/src/lib/widgets/DynamicWidget/components/ActiveMidnightWalletBalance/ActiveMidnightWalletBalance.js +189 -0
  34. package/src/lib/widgets/DynamicWidget/components/ActiveMidnightWalletBalance/index.d.ts +1 -0
  35. package/src/lib/widgets/DynamicWidget/components/ActiveWalletBalance/ActiveWalletBalance.cjs +26 -1
  36. package/src/lib/widgets/DynamicWidget/components/ActiveWalletBalance/ActiveWalletBalance.js +26 -1
  37. package/src/lib/widgets/DynamicWidget/components/ActiveWalletInformation/ActiveMidnightWalletAddresses/ActiveMidnightWalletAddresses.cjs +124 -0
  38. package/src/lib/widgets/DynamicWidget/components/ActiveWalletInformation/ActiveMidnightWalletAddresses/ActiveMidnightWalletAddresses.d.ts +9 -0
  39. package/src/lib/widgets/DynamicWidget/components/ActiveWalletInformation/ActiveMidnightWalletAddresses/ActiveMidnightWalletAddresses.js +120 -0
  40. package/src/lib/widgets/DynamicWidget/components/ActiveWalletInformation/ActiveMidnightWalletAddresses/index.d.ts +1 -0
  41. package/src/lib/widgets/DynamicWidget/components/ActiveWalletInformation/ActiveWalletInformation.cjs +21 -10
  42. package/src/lib/widgets/DynamicWidget/components/ActiveWalletInformation/ActiveWalletInformation.js +22 -11
  43. package/src/lib/widgets/DynamicWidget/components/WalletDetailsCard/WalletDetailsCard.cjs +22 -2
  44. package/src/lib/widgets/DynamicWidget/components/WalletDetailsCard/WalletDetailsCard.d.ts +8 -1
  45. package/src/lib/widgets/DynamicWidget/components/WalletDetailsCard/WalletDetailsCard.js +23 -3
@@ -29,7 +29,7 @@ require('eventemitter3');
29
29
  require('@dynamic-labs-sdk/client');
30
30
  require('../../../config/ApiEndpoint.cjs');
31
31
  require('@dynamic-labs/locale');
32
- require('../../../store/state/dynamicContextProps/dynamicContextProps.cjs');
32
+ var dynamicContextProps = require('../../../store/state/dynamicContextProps/dynamicContextProps.cjs');
33
33
  require('../../../store/state/primaryWalletId/primaryWalletId.cjs');
34
34
  require('../../../store/state/connectedWalletsInfo/connectedWalletsInfo.cjs');
35
35
  require('../../functions/getWaasAddressTypeLabel/getWaasAddressTypeLabel.cjs');
@@ -109,6 +109,7 @@ require('../../../store/state/multichainBalances.cjs');
109
109
  require('@dynamic-labs/store');
110
110
  require('../../../shared/utils/functions/getInitialUrl/getInitialUrl.cjs');
111
111
  var useInternalDynamicContext = require('../../../context/DynamicContext/useDynamicContext/useInternalDynamicContext/useInternalDynamicContext.cjs');
112
+ var getAleoCuratedPrices = require('../../../data/api/aleo/getAleoCuratedPrices.cjs');
112
113
 
113
114
  const MICROCREDITS_PER_CREDIT = 1000000;
114
115
  const ALEO_CREDITS_LOGO = 'https://app.dynamic.xyz/assets/networks/aleo.svg';
@@ -227,6 +228,117 @@ const findTokenSpec = (record) => {
227
228
  }
228
229
  return undefined;
229
230
  };
231
+ /**
232
+ * Coerces the widget-context `network` value into an Aleo network id
233
+ * (`0 = mainnet`, `1 = testnet`) and the matching param string for the
234
+ * `/waas/aleo/prices` endpoint. Pulled out of the callback to flatten
235
+ * cognitive complexity AND to avoid a SonarCloud-flagged nested
236
+ * ternary.
237
+ */
238
+ const resolveAleoNetwork = (networkKey) => {
239
+ const numeric = Number(networkKey);
240
+ const safeNetworkId = Number.isFinite(numeric) ? numeric : undefined;
241
+ if (safeNetworkId === 0)
242
+ return { aleoNetworkParam: 'mainnet', safeNetworkId };
243
+ if (safeNetworkId === 1)
244
+ return { aleoNetworkParam: 'testnet', safeNetworkId };
245
+ return { aleoNetworkParam: undefined, safeNetworkId };
246
+ };
247
+ /**
248
+ * Build a `(address, isNative) → price` lookup from the curated-prices
249
+ * endpoint response. The key shape matches the multichain balance feed
250
+ * (native ALEO at `'0x0' + isNative=true`, every other token at its
251
+ * program id + `isNative=false`) so the join is a straight read.
252
+ */
253
+ const buildPriceLookup = (priceList) => {
254
+ const priceByKey = new Map();
255
+ for (const entry of priceList) {
256
+ priceByKey.set(`${entry.address}|${entry.isNative ? 1 : 0}`, entry.price);
257
+ }
258
+ return (address, isNative) => { var _a; return (_a = priceByKey.get(`${address}|${isNative ? 1 : 0}`)) !== null && _a !== void 0 ? _a : null; };
259
+ };
260
+ /**
261
+ * Aggregate `credits.aleo / credits` records into the native ALEO
262
+ * `TokenBalance`. Returns `undefined` when the wallet owns no credits
263
+ * records so the caller can omit the row entirely.
264
+ */
265
+ const buildCreditsBalance = (records, networkId, priceFor) => {
266
+ const creditsRecords = records.filter((r) => (r === null || r === void 0 ? void 0 : r.program_name) === 'credits.aleo' &&
267
+ (r === null || r === void 0 ? void 0 : r.record_name) === 'credits' &&
268
+ typeof (r === null || r === void 0 ? void 0 : r.microcredits) === 'string');
269
+ if (creditsRecords.length === 0)
270
+ return undefined;
271
+ const totalMicrocredits = creditsRecords.reduce((sum, r) => sum + BigInt(r.microcredits), BigInt(0));
272
+ // BigInt → number for the TokenBalance shape. Aleo balances stay well
273
+ // below Number.MAX_SAFE_INTEGER for a single wallet (max u64 supply is
274
+ // 1.5B credits = 1.5e15 microcredits; Number can hold up to ~9e15).
275
+ const rawBalance = Number(totalMicrocredits);
276
+ const balance = rawBalance / MICROCREDITS_PER_CREDIT;
277
+ const price = priceFor('0x0', true);
278
+ return {
279
+ address: '0x0',
280
+ balance,
281
+ decimals: 6,
282
+ isNative: true,
283
+ logoURI: ALEO_CREDITS_LOGO,
284
+ marketValue: price !== null ? balance * price : undefined,
285
+ name: 'Aleo Credits',
286
+ networkId,
287
+ price: price !== null ? price : undefined,
288
+ rawBalance,
289
+ symbol: 'ALEO',
290
+ };
291
+ };
292
+ /**
293
+ * Aggregate stablecoin + ARC-21 `Token` records by `(program, tokenId)`
294
+ * and emit one `TokenBalance` per matched spec. Records that don't
295
+ * match a curated spec, lack an `amount`, or have a malformed amount
296
+ * string are silently skipped.
297
+ */
298
+ const buildTokenBalances = (records, networkId, priceFor) => {
299
+ var _a, _b;
300
+ const sumsByContract = new Map();
301
+ const specsByContract = new Map();
302
+ for (const r of records) {
303
+ const spec = findTokenSpec(r);
304
+ if (!spec || typeof r.amount !== 'string')
305
+ continue;
306
+ try {
307
+ const prev = (_a = sumsByContract.get(spec.contractAddress)) !== null && _a !== void 0 ? _a : BigInt(0);
308
+ sumsByContract.set(spec.contractAddress, prev + BigInt(r.amount));
309
+ specsByContract.set(spec.contractAddress, spec);
310
+ }
311
+ catch (_c) {
312
+ /* ignore — malformed amount string */
313
+ }
314
+ }
315
+ const out = [];
316
+ for (const [contractAddress, total] of sumsByContract.entries()) {
317
+ const spec = specsByContract.get(contractAddress);
318
+ if (!spec)
319
+ continue;
320
+ const rawBalance = Number(total);
321
+ const balance = rawBalance / Math.pow(10, spec.decimals);
322
+ const price = priceFor(spec.contractAddress, false);
323
+ out.push({
324
+ address: spec.contractAddress,
325
+ balance,
326
+ decimals: spec.decimals,
327
+ // Use redcoast's `DEFAULT_TOKEN_LOGO_URI` for stablecoins + ARC-21
328
+ // so they render the same dark "?" icon the Unshielded tab shows
329
+ // for these tokens (the multichain endpoint applies that fallback
330
+ // itself; we mirror it here for visual parity).
331
+ logoURI: (_b = spec.logoURI) !== null && _b !== void 0 ? _b : UNKNOWN_TOKEN_LOGO,
332
+ marketValue: price !== null ? balance * price : undefined,
333
+ name: spec.name,
334
+ networkId,
335
+ price: price !== null ? price : undefined,
336
+ rawBalance,
337
+ symbol: spec.symbol,
338
+ });
339
+ }
340
+ return out;
341
+ };
230
342
  /**
231
343
  * Hook that returns the active wallet's shielded (private) Aleo token
232
344
  * balances as a `TokenBalance[]` so the widget can render them through the
@@ -262,7 +374,7 @@ const useAleoShieldedBalances = () => {
262
374
  const connectorKey = connector === null || connector === void 0 ? void 0 : connector.key;
263
375
  const networkKey = network !== undefined && network !== null ? String(network) : undefined;
264
376
  const fetchShielded = React.useCallback(() => _tslib.__awaiter(void 0, void 0, void 0, function* () {
265
- var _a, _b, _c;
377
+ var _a;
266
378
  const liveConnector = connectorRef.current;
267
379
  if (!supportsShielded || !liveConnector) {
268
380
  setTokenBalances((prev) => (prev.length === 0 ? prev : []));
@@ -271,74 +383,33 @@ const useAleoShieldedBalances = () => {
271
383
  setIsLoading(true);
272
384
  setError(undefined);
273
385
  try {
274
- const result = yield liveConnector.listOwnedRecords();
386
+ // Fetch records and the curated-token price list in parallel. The
387
+ // price list comes from the Aleo-only `/waas/aleo/prices` endpoint
388
+ // (independent of balance) so the join works even when the user
389
+ // holds a shielded token with zero unshielded balance — the
390
+ // multichain `/accountBalances` endpoint would have stripped that
391
+ // token row, leaving the widget with no price source. Prices are
392
+ // best-effort: a failure leaves balances unpriced rather than
393
+ // dropping the shielded list entirely.
394
+ const { safeNetworkId, aleoNetworkParam } = resolveAleoNetwork(networkKey);
395
+ const environmentId = dynamicContextProps.getEnvironmentId();
396
+ const shouldFetchPrices = Boolean(aleoNetworkParam && environmentId);
397
+ const [result, priceList] = yield Promise.all([
398
+ liveConnector.listOwnedRecords(),
399
+ shouldFetchPrices && aleoNetworkParam
400
+ ? getAleoCuratedPrices.getAleoCuratedPrices({
401
+ environmentId,
402
+ network: aleoNetworkParam,
403
+ }).catch(() => [])
404
+ : Promise.resolve([]),
405
+ ]);
275
406
  const records = (_a = result === null || result === void 0 ? void 0 : result.records) !== null && _a !== void 0 ? _a : [];
276
- const networkIdNumeric = Number(networkKey);
277
- const safeNetworkId = Number.isFinite(networkIdNumeric)
278
- ? networkIdNumeric
279
- : undefined;
407
+ const priceFor = buildPriceLookup(priceList);
280
408
  const balances = [];
281
- // Tier 1: native credits sum microcredits across credits.aleo records.
282
- const creditsRecords = records.filter((r) => (r === null || r === void 0 ? void 0 : r.program_name) === 'credits.aleo' &&
283
- (r === null || r === void 0 ? void 0 : r.record_name) === 'credits' &&
284
- typeof (r === null || r === void 0 ? void 0 : r.microcredits) === 'string');
285
- if (creditsRecords.length > 0) {
286
- const totalMicrocredits = creditsRecords.reduce((sum, r) => sum + BigInt(r.microcredits), BigInt(0));
287
- // BigInt → number for the TokenBalance shape. Aleo balances stay well
288
- // below Number.MAX_SAFE_INTEGER for a single wallet (max u64 supply is
289
- // 1.5B credits = 1.5e15 microcredits; Number can hold up to ~9e15).
290
- const rawBalance = Number(totalMicrocredits);
291
- balances.push({
292
- address: '0x0',
293
- balance: rawBalance / MICROCREDITS_PER_CREDIT,
294
- decimals: 6,
295
- isNative: true,
296
- logoURI: ALEO_CREDITS_LOGO,
297
- name: 'Aleo Credits',
298
- networkId: safeNetworkId,
299
- rawBalance,
300
- symbol: 'ALEO',
301
- });
302
- }
303
- // Tier 3 + ARC-21: Token records. Each record matches at most one
304
- // spec (stablecoin programs disambiguate by program; ARC-21 records
305
- // share `token_registry.aleo / Token` and disambiguate by tokenId
306
- // inside the plaintext). Sum the `amount` field per spec.
307
- const sumsByContract = new Map();
308
- const specsByContract = new Map();
309
- for (const r of records) {
310
- const spec = findTokenSpec(r);
311
- if (!spec || typeof r.amount !== 'string')
312
- continue;
313
- try {
314
- const prev = (_b = sumsByContract.get(spec.contractAddress)) !== null && _b !== void 0 ? _b : BigInt(0);
315
- sumsByContract.set(spec.contractAddress, prev + BigInt(r.amount));
316
- specsByContract.set(spec.contractAddress, spec);
317
- }
318
- catch (_d) {
319
- /* ignore — malformed amount string */
320
- }
321
- }
322
- for (const [contractAddress, total] of sumsByContract.entries()) {
323
- const spec = specsByContract.get(contractAddress);
324
- if (!spec)
325
- continue;
326
- const rawBalance = Number(total);
327
- balances.push({
328
- address: spec.contractAddress,
329
- balance: rawBalance / Math.pow(10, spec.decimals),
330
- decimals: spec.decimals,
331
- // Use redcoast's `DEFAULT_TOKEN_LOGO_URI` for stablecoins + ARC-21
332
- // so they render the same dark "?" icon the Unshielded tab shows
333
- // for these tokens (the multichain endpoint applies that fallback
334
- // itself; we mirror it here for visual parity).
335
- logoURI: (_c = spec.logoURI) !== null && _c !== void 0 ? _c : UNKNOWN_TOKEN_LOGO,
336
- name: spec.name,
337
- networkId: safeNetworkId,
338
- rawBalance,
339
- symbol: spec.symbol,
340
- });
341
- }
409
+ const credits = buildCreditsBalance(records, safeNetworkId, priceFor);
410
+ if (credits)
411
+ balances.push(credits);
412
+ balances.push(...buildTokenBalances(records, safeNetworkId, priceFor));
342
413
  setTokenBalances(balances);
343
414
  }
344
415
  catch (err) {
@@ -25,7 +25,7 @@ import 'eventemitter3';
25
25
  import '@dynamic-labs-sdk/client';
26
26
  import '../../../config/ApiEndpoint.js';
27
27
  import '@dynamic-labs/locale';
28
- import '../../../store/state/dynamicContextProps/dynamicContextProps.js';
28
+ import { getEnvironmentId } from '../../../store/state/dynamicContextProps/dynamicContextProps.js';
29
29
  import '../../../store/state/primaryWalletId/primaryWalletId.js';
30
30
  import '../../../store/state/connectedWalletsInfo/connectedWalletsInfo.js';
31
31
  import '../../functions/getWaasAddressTypeLabel/getWaasAddressTypeLabel.js';
@@ -105,6 +105,7 @@ import '../../../store/state/multichainBalances.js';
105
105
  import '@dynamic-labs/store';
106
106
  import '../../../shared/utils/functions/getInitialUrl/getInitialUrl.js';
107
107
  import { useInternalDynamicContext } from '../../../context/DynamicContext/useDynamicContext/useInternalDynamicContext/useInternalDynamicContext.js';
108
+ import { getAleoCuratedPrices } from '../../../data/api/aleo/getAleoCuratedPrices.js';
108
109
 
109
110
  const MICROCREDITS_PER_CREDIT = 1000000;
110
111
  const ALEO_CREDITS_LOGO = 'https://app.dynamic.xyz/assets/networks/aleo.svg';
@@ -223,6 +224,117 @@ const findTokenSpec = (record) => {
223
224
  }
224
225
  return undefined;
225
226
  };
227
+ /**
228
+ * Coerces the widget-context `network` value into an Aleo network id
229
+ * (`0 = mainnet`, `1 = testnet`) and the matching param string for the
230
+ * `/waas/aleo/prices` endpoint. Pulled out of the callback to flatten
231
+ * cognitive complexity AND to avoid a SonarCloud-flagged nested
232
+ * ternary.
233
+ */
234
+ const resolveAleoNetwork = (networkKey) => {
235
+ const numeric = Number(networkKey);
236
+ const safeNetworkId = Number.isFinite(numeric) ? numeric : undefined;
237
+ if (safeNetworkId === 0)
238
+ return { aleoNetworkParam: 'mainnet', safeNetworkId };
239
+ if (safeNetworkId === 1)
240
+ return { aleoNetworkParam: 'testnet', safeNetworkId };
241
+ return { aleoNetworkParam: undefined, safeNetworkId };
242
+ };
243
+ /**
244
+ * Build a `(address, isNative) → price` lookup from the curated-prices
245
+ * endpoint response. The key shape matches the multichain balance feed
246
+ * (native ALEO at `'0x0' + isNative=true`, every other token at its
247
+ * program id + `isNative=false`) so the join is a straight read.
248
+ */
249
+ const buildPriceLookup = (priceList) => {
250
+ const priceByKey = new Map();
251
+ for (const entry of priceList) {
252
+ priceByKey.set(`${entry.address}|${entry.isNative ? 1 : 0}`, entry.price);
253
+ }
254
+ return (address, isNative) => { var _a; return (_a = priceByKey.get(`${address}|${isNative ? 1 : 0}`)) !== null && _a !== void 0 ? _a : null; };
255
+ };
256
+ /**
257
+ * Aggregate `credits.aleo / credits` records into the native ALEO
258
+ * `TokenBalance`. Returns `undefined` when the wallet owns no credits
259
+ * records so the caller can omit the row entirely.
260
+ */
261
+ const buildCreditsBalance = (records, networkId, priceFor) => {
262
+ const creditsRecords = records.filter((r) => (r === null || r === void 0 ? void 0 : r.program_name) === 'credits.aleo' &&
263
+ (r === null || r === void 0 ? void 0 : r.record_name) === 'credits' &&
264
+ typeof (r === null || r === void 0 ? void 0 : r.microcredits) === 'string');
265
+ if (creditsRecords.length === 0)
266
+ return undefined;
267
+ const totalMicrocredits = creditsRecords.reduce((sum, r) => sum + BigInt(r.microcredits), BigInt(0));
268
+ // BigInt → number for the TokenBalance shape. Aleo balances stay well
269
+ // below Number.MAX_SAFE_INTEGER for a single wallet (max u64 supply is
270
+ // 1.5B credits = 1.5e15 microcredits; Number can hold up to ~9e15).
271
+ const rawBalance = Number(totalMicrocredits);
272
+ const balance = rawBalance / MICROCREDITS_PER_CREDIT;
273
+ const price = priceFor('0x0', true);
274
+ return {
275
+ address: '0x0',
276
+ balance,
277
+ decimals: 6,
278
+ isNative: true,
279
+ logoURI: ALEO_CREDITS_LOGO,
280
+ marketValue: price !== null ? balance * price : undefined,
281
+ name: 'Aleo Credits',
282
+ networkId,
283
+ price: price !== null ? price : undefined,
284
+ rawBalance,
285
+ symbol: 'ALEO',
286
+ };
287
+ };
288
+ /**
289
+ * Aggregate stablecoin + ARC-21 `Token` records by `(program, tokenId)`
290
+ * and emit one `TokenBalance` per matched spec. Records that don't
291
+ * match a curated spec, lack an `amount`, or have a malformed amount
292
+ * string are silently skipped.
293
+ */
294
+ const buildTokenBalances = (records, networkId, priceFor) => {
295
+ var _a, _b;
296
+ const sumsByContract = new Map();
297
+ const specsByContract = new Map();
298
+ for (const r of records) {
299
+ const spec = findTokenSpec(r);
300
+ if (!spec || typeof r.amount !== 'string')
301
+ continue;
302
+ try {
303
+ const prev = (_a = sumsByContract.get(spec.contractAddress)) !== null && _a !== void 0 ? _a : BigInt(0);
304
+ sumsByContract.set(spec.contractAddress, prev + BigInt(r.amount));
305
+ specsByContract.set(spec.contractAddress, spec);
306
+ }
307
+ catch (_c) {
308
+ /* ignore — malformed amount string */
309
+ }
310
+ }
311
+ const out = [];
312
+ for (const [contractAddress, total] of sumsByContract.entries()) {
313
+ const spec = specsByContract.get(contractAddress);
314
+ if (!spec)
315
+ continue;
316
+ const rawBalance = Number(total);
317
+ const balance = rawBalance / Math.pow(10, spec.decimals);
318
+ const price = priceFor(spec.contractAddress, false);
319
+ out.push({
320
+ address: spec.contractAddress,
321
+ balance,
322
+ decimals: spec.decimals,
323
+ // Use redcoast's `DEFAULT_TOKEN_LOGO_URI` for stablecoins + ARC-21
324
+ // so they render the same dark "?" icon the Unshielded tab shows
325
+ // for these tokens (the multichain endpoint applies that fallback
326
+ // itself; we mirror it here for visual parity).
327
+ logoURI: (_b = spec.logoURI) !== null && _b !== void 0 ? _b : UNKNOWN_TOKEN_LOGO,
328
+ marketValue: price !== null ? balance * price : undefined,
329
+ name: spec.name,
330
+ networkId,
331
+ price: price !== null ? price : undefined,
332
+ rawBalance,
333
+ symbol: spec.symbol,
334
+ });
335
+ }
336
+ return out;
337
+ };
226
338
  /**
227
339
  * Hook that returns the active wallet's shielded (private) Aleo token
228
340
  * balances as a `TokenBalance[]` so the widget can render them through the
@@ -258,7 +370,7 @@ const useAleoShieldedBalances = () => {
258
370
  const connectorKey = connector === null || connector === void 0 ? void 0 : connector.key;
259
371
  const networkKey = network !== undefined && network !== null ? String(network) : undefined;
260
372
  const fetchShielded = useCallback(() => __awaiter(void 0, void 0, void 0, function* () {
261
- var _a, _b, _c;
373
+ var _a;
262
374
  const liveConnector = connectorRef.current;
263
375
  if (!supportsShielded || !liveConnector) {
264
376
  setTokenBalances((prev) => (prev.length === 0 ? prev : []));
@@ -267,74 +379,33 @@ const useAleoShieldedBalances = () => {
267
379
  setIsLoading(true);
268
380
  setError(undefined);
269
381
  try {
270
- const result = yield liveConnector.listOwnedRecords();
382
+ // Fetch records and the curated-token price list in parallel. The
383
+ // price list comes from the Aleo-only `/waas/aleo/prices` endpoint
384
+ // (independent of balance) so the join works even when the user
385
+ // holds a shielded token with zero unshielded balance — the
386
+ // multichain `/accountBalances` endpoint would have stripped that
387
+ // token row, leaving the widget with no price source. Prices are
388
+ // best-effort: a failure leaves balances unpriced rather than
389
+ // dropping the shielded list entirely.
390
+ const { safeNetworkId, aleoNetworkParam } = resolveAleoNetwork(networkKey);
391
+ const environmentId = getEnvironmentId();
392
+ const shouldFetchPrices = Boolean(aleoNetworkParam && environmentId);
393
+ const [result, priceList] = yield Promise.all([
394
+ liveConnector.listOwnedRecords(),
395
+ shouldFetchPrices && aleoNetworkParam
396
+ ? getAleoCuratedPrices({
397
+ environmentId,
398
+ network: aleoNetworkParam,
399
+ }).catch(() => [])
400
+ : Promise.resolve([]),
401
+ ]);
271
402
  const records = (_a = result === null || result === void 0 ? void 0 : result.records) !== null && _a !== void 0 ? _a : [];
272
- const networkIdNumeric = Number(networkKey);
273
- const safeNetworkId = Number.isFinite(networkIdNumeric)
274
- ? networkIdNumeric
275
- : undefined;
403
+ const priceFor = buildPriceLookup(priceList);
276
404
  const balances = [];
277
- // Tier 1: native credits sum microcredits across credits.aleo records.
278
- const creditsRecords = records.filter((r) => (r === null || r === void 0 ? void 0 : r.program_name) === 'credits.aleo' &&
279
- (r === null || r === void 0 ? void 0 : r.record_name) === 'credits' &&
280
- typeof (r === null || r === void 0 ? void 0 : r.microcredits) === 'string');
281
- if (creditsRecords.length > 0) {
282
- const totalMicrocredits = creditsRecords.reduce((sum, r) => sum + BigInt(r.microcredits), BigInt(0));
283
- // BigInt → number for the TokenBalance shape. Aleo balances stay well
284
- // below Number.MAX_SAFE_INTEGER for a single wallet (max u64 supply is
285
- // 1.5B credits = 1.5e15 microcredits; Number can hold up to ~9e15).
286
- const rawBalance = Number(totalMicrocredits);
287
- balances.push({
288
- address: '0x0',
289
- balance: rawBalance / MICROCREDITS_PER_CREDIT,
290
- decimals: 6,
291
- isNative: true,
292
- logoURI: ALEO_CREDITS_LOGO,
293
- name: 'Aleo Credits',
294
- networkId: safeNetworkId,
295
- rawBalance,
296
- symbol: 'ALEO',
297
- });
298
- }
299
- // Tier 3 + ARC-21: Token records. Each record matches at most one
300
- // spec (stablecoin programs disambiguate by program; ARC-21 records
301
- // share `token_registry.aleo / Token` and disambiguate by tokenId
302
- // inside the plaintext). Sum the `amount` field per spec.
303
- const sumsByContract = new Map();
304
- const specsByContract = new Map();
305
- for (const r of records) {
306
- const spec = findTokenSpec(r);
307
- if (!spec || typeof r.amount !== 'string')
308
- continue;
309
- try {
310
- const prev = (_b = sumsByContract.get(spec.contractAddress)) !== null && _b !== void 0 ? _b : BigInt(0);
311
- sumsByContract.set(spec.contractAddress, prev + BigInt(r.amount));
312
- specsByContract.set(spec.contractAddress, spec);
313
- }
314
- catch (_d) {
315
- /* ignore — malformed amount string */
316
- }
317
- }
318
- for (const [contractAddress, total] of sumsByContract.entries()) {
319
- const spec = specsByContract.get(contractAddress);
320
- if (!spec)
321
- continue;
322
- const rawBalance = Number(total);
323
- balances.push({
324
- address: spec.contractAddress,
325
- balance: rawBalance / Math.pow(10, spec.decimals),
326
- decimals: spec.decimals,
327
- // Use redcoast's `DEFAULT_TOKEN_LOGO_URI` for stablecoins + ARC-21
328
- // so they render the same dark "?" icon the Unshielded tab shows
329
- // for these tokens (the multichain endpoint applies that fallback
330
- // itself; we mirror it here for visual parity).
331
- logoURI: (_c = spec.logoURI) !== null && _c !== void 0 ? _c : UNKNOWN_TOKEN_LOGO,
332
- name: spec.name,
333
- networkId: safeNetworkId,
334
- rawBalance,
335
- symbol: spec.symbol,
336
- });
337
- }
405
+ const credits = buildCreditsBalance(records, safeNetworkId, priceFor);
406
+ if (credits)
407
+ balances.push(credits);
408
+ balances.push(...buildTokenBalances(records, safeNetworkId, priceFor));
338
409
  setTokenBalances(balances);
339
410
  }
340
411
  catch (err) {
@@ -132,8 +132,19 @@ const BackupUnsuccessfulView = () => {
132
132
  const refreshPage = () => {
133
133
  window.location.reload();
134
134
  };
135
+ const onTryAgainClick = () => {
136
+ logger.logger.instrument('Wallet creation retry modal try again clicked', {
137
+ key: 'wallet_creation_retry_modal_try_again_clicked',
138
+ time: 0,
139
+ });
140
+ refreshPage();
141
+ };
135
142
  // force refresh on component unmount
136
143
  React.useEffect(() => {
144
+ logger.logger.instrument('Wallet creation retry modal shown', {
145
+ key: 'wallet_creation_retry_modal_shown',
146
+ time: 0,
147
+ });
137
148
  const timer = setTimeout(() => {
138
149
  isProperlyMounted.current = true;
139
150
  }, 100);
@@ -154,7 +165,7 @@ const BackupUnsuccessfulView = () => {
154
165
  transform: 'translate(-50%, -50%)',
155
166
  } })] })] }), jsxRuntime.jsx(Typography.Typography, { variant: 'body_normal', className: 'backup-message', color: 'primary', copykey: 'dyn_waas.backup_unsuccessful.description', children: t('dyn_waas.backup_unsuccessful.description') }), jsxRuntime.jsx("div", { className: classNames.classNames('button-group', {
156
167
  'button-group--with-help-section': hasContactInfo,
157
- }), children: jsxRuntime.jsx(TypographyButton.TypographyButton, { buttonClassName: 'retry-button', buttonVariant: 'brand-primary', type: 'submit', onClick: () => refreshPage(), copykey: 'dyn_waas.backup_unsuccessful.try_again', typographyProps: {
168
+ }), children: jsxRuntime.jsx(TypographyButton.TypographyButton, { buttonClassName: 'retry-button', buttonVariant: 'brand-primary', type: 'submit', onClick: onTryAgainClick, copykey: 'dyn_waas.backup_unsuccessful.try_again', typographyProps: {
158
169
  color: 'inherit',
159
170
  }, children: t('dyn_waas.backup_unsuccessful.try_again') }) }), jsxRuntime.jsx("div", { className: classNames.classNames('button-group', 'logout-group', {
160
171
  'button-group--with-help-section': hasContactInfo,
@@ -128,8 +128,19 @@ const BackupUnsuccessfulView = () => {
128
128
  const refreshPage = () => {
129
129
  window.location.reload();
130
130
  };
131
+ const onTryAgainClick = () => {
132
+ logger.instrument('Wallet creation retry modal try again clicked', {
133
+ key: 'wallet_creation_retry_modal_try_again_clicked',
134
+ time: 0,
135
+ });
136
+ refreshPage();
137
+ };
131
138
  // force refresh on component unmount
132
139
  useEffect(() => {
140
+ logger.instrument('Wallet creation retry modal shown', {
141
+ key: 'wallet_creation_retry_modal_shown',
142
+ time: 0,
143
+ });
133
144
  const timer = setTimeout(() => {
134
145
  isProperlyMounted.current = true;
135
146
  }, 100);
@@ -150,7 +161,7 @@ const BackupUnsuccessfulView = () => {
150
161
  transform: 'translate(-50%, -50%)',
151
162
  } })] })] }), jsx(Typography, { variant: 'body_normal', className: 'backup-message', color: 'primary', copykey: 'dyn_waas.backup_unsuccessful.description', children: t('dyn_waas.backup_unsuccessful.description') }), jsx("div", { className: classNames('button-group', {
152
163
  'button-group--with-help-section': hasContactInfo,
153
- }), children: jsx(TypographyButton, { buttonClassName: 'retry-button', buttonVariant: 'brand-primary', type: 'submit', onClick: () => refreshPage(), copykey: 'dyn_waas.backup_unsuccessful.try_again', typographyProps: {
164
+ }), children: jsx(TypographyButton, { buttonClassName: 'retry-button', buttonVariant: 'brand-primary', type: 'submit', onClick: onTryAgainClick, copykey: 'dyn_waas.backup_unsuccessful.try_again', typographyProps: {
154
165
  color: 'inherit',
155
166
  }, children: t('dyn_waas.backup_unsuccessful.try_again') }) }), jsx("div", { className: classNames('button-group', 'logout-group', {
156
167
  'button-group--with-help-section': hasContactInfo,