@dynamic-labs/sdk-react-core 4.84.0 → 4.85.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 (51) hide show
  1. package/CHANGELOG.md +35 -1
  2. package/package.cjs +3 -3
  3. package/package.js +3 -3
  4. package/package.json +14 -14
  5. package/src/index.cjs +2 -0
  6. package/src/index.d.ts +2 -2
  7. package/src/index.js +1 -0
  8. package/src/lib/components/SendBalanceForm/SendBalanceForm.cjs +26 -1
  9. package/src/lib/components/SendBalanceForm/SendBalanceForm.js +26 -1
  10. package/src/lib/components/SendBalancePageLayout/SendBalancePageLayout.cjs +6 -1
  11. package/src/lib/components/SendBalancePageLayout/SendBalancePageLayout.js +6 -1
  12. package/src/lib/components/SendBalancePageLayout/components/TokensBalanceDropdown/TokensBalanceDropdown.cjs +5 -1
  13. package/src/lib/components/SendBalancePageLayout/components/TokensBalanceDropdown/TokensBalanceDropdown.js +5 -1
  14. package/src/lib/context/OnrampContext/utils/getOnrampProviders.cjs +2 -6
  15. package/src/lib/context/OnrampContext/utils/getOnrampProviders.js +4 -8
  16. package/src/lib/data/api/onramp/onramp.cjs +26 -1
  17. package/src/lib/data/api/onramp/onramp.d.ts +11 -1
  18. package/src/lib/data/api/onramp/onramp.js +26 -1
  19. package/src/lib/utils/functions/onrampProviders/index.cjs +1 -0
  20. package/src/lib/utils/functions/onrampProviders/index.js +1 -0
  21. package/src/lib/utils/hooks/index.d.ts +2 -0
  22. package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/pollOnShielded.cjs +24 -4
  23. package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/pollOnShielded.d.ts +10 -2
  24. package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/pollOnShielded.js +24 -4
  25. package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/useAleoAutoShieldSponsoredTokens.cjs +14 -3
  26. package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/useAleoAutoShieldSponsoredTokens.d.ts +5 -1
  27. package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/useAleoAutoShieldSponsoredTokens.js +14 -3
  28. package/src/lib/utils/hooks/useAleoShieldedBalances/useAleoShieldedBalances.cjs +95 -10
  29. package/src/lib/utils/hooks/useAleoShieldedBalances/useAleoShieldedBalances.d.ts +18 -2
  30. package/src/lib/utils/hooks/useAleoShieldedBalances/useAleoShieldedBalances.js +96 -11
  31. package/src/lib/utils/hooks/useDynamicWaas/useDynamicWaas.cjs +4 -4
  32. package/src/lib/utils/hooks/useDynamicWaas/useDynamicWaas.js +4 -4
  33. package/src/lib/utils/hooks/usePrivateTokenBalances/index.d.ts +2 -0
  34. package/src/lib/utils/hooks/usePrivateTokenBalances/usePrivateTokenBalances.cjs +27 -0
  35. package/src/lib/utils/hooks/usePrivateTokenBalances/usePrivateTokenBalances.d.ts +24 -0
  36. package/src/lib/utils/hooks/usePrivateTokenBalances/usePrivateTokenBalances.js +23 -0
  37. package/src/lib/utils/hooks/useSyncDynamicWaas/instrumentWalletCreation.cjs +60 -0
  38. package/src/lib/utils/hooks/useSyncDynamicWaas/instrumentWalletCreation.d.ts +29 -0
  39. package/src/lib/utils/hooks/useSyncDynamicWaas/instrumentWalletCreation.js +55 -0
  40. package/src/lib/utils/hooks/useSyncDynamicWaas/useSyncDynamicWaas.cjs +14 -24
  41. package/src/lib/utils/hooks/useSyncDynamicWaas/useSyncDynamicWaas.js +14 -24
  42. package/src/lib/utils/hooks/useWalletDelegation/useWalletDelegation.cjs +19 -16
  43. package/src/lib/utils/hooks/useWalletDelegation/useWalletDelegation.d.ts +8 -0
  44. package/src/lib/utils/hooks/useWalletDelegation/useWalletDelegation.js +19 -17
  45. package/src/lib/views/TransactionConfirmationView/TransactionConfirmationView.cjs +17 -1
  46. package/src/lib/views/TransactionConfirmationView/TransactionConfirmationView.js +17 -1
  47. package/src/lib/widgets/DynamicWidget/components/ActiveWalletBalance/ActiveWalletBalance.cjs +138 -21
  48. package/src/lib/widgets/DynamicWidget/components/ActiveWalletBalance/ActiveWalletBalance.js +139 -22
  49. package/src/lib/widgets/DynamicWidget/components/ActiveWalletBalance/optimisticShield.cjs +150 -0
  50. package/src/lib/widgets/DynamicWidget/components/ActiveWalletBalance/optimisticShield.d.ts +74 -0
  51. package/src/lib/widgets/DynamicWidget/components/ActiveWalletBalance/optimisticShield.js +143 -0
@@ -5,7 +5,7 @@ import { __awaiter } from '../../../../../_virtual/_tslib.js';
5
5
  // when the relay accepts the broadcast, not when the public→private
6
6
  // transition confirms on-chain. Aleo block time + RecordScanner indexer
7
7
  // lag means the immediate post-broadcast fetch usually returns the
8
- // pre-confirmation balance, so we re-poll for ~45s before giving up.
8
+ // pre-confirmation balance, so we re-poll on a backoff schedule.
9
9
  //
10
10
  // Shared between the auto-shield hook (background) and the widget's
11
11
  // manual Shield Manually CTA (foreground), so both flows update the
@@ -13,23 +13,41 @@ import { __awaiter } from '../../../../../_virtual/_tslib.js';
13
13
  // the manual flow refreshed exactly once and the unshielded row
14
14
  // stayed at its pre-broadcast value for the full RecordScanner lag,
15
15
  // making a successful shield look like a no-op.
16
+ //
17
+ // Total window ≈106s. Provable's mapping endpoint typically reflects
18
+ // a `transfer_public_to_private` within ~30s of broadcast, but the
19
+ // RecordScanner index lag for the new private record can stretch to
20
+ // ~90s in the worst case. The tail entries (25s, 35s) keep the poll
21
+ // alive across the upper end of that range without spamming requests
22
+ // during the common case (most balances converge inside the first
23
+ // three iterations and the convergence predicate exits the loop).
16
24
  const REFRESH_DELAYS_MS = [
17
- 3000, 5000, 8000, 12000, 18000,
25
+ 3000, 5000, 8000, 12000, 18000, 25000, 35000,
18
26
  ];
19
27
  /**
20
28
  * After a successful shield broadcast, poll `onShielded` on a backoff
21
29
  * schedule so the consumer sees both the shielded and unshielded balances
22
- * update without a manual refresh click. Stops early on cancel; swallows
30
+ * update without a manual refresh click. Stops early on cancel or when
31
+ * the optional `hasConverged` predicate returns true; swallows
23
32
  * per-iteration errors (refresh is best-effort).
24
33
  *
25
34
  * `isCancelled` is a thunk so callers can hook it up to whatever liveness
26
35
  * signal they have (effect cleanup `cancelled` flag, abort signal, mount
27
36
  * ref, etc.) without this module having to know about React lifecycle.
37
+ *
38
+ * `hasConverged` is optional and lets callers exit early once the
39
+ * post-shield state has actually materialized (e.g. the just-shielded
40
+ * token's unshielded balance has dropped). When omitted, the schedule
41
+ * runs to completion. Checked both before each delay and after each
42
+ * refresh so a refresh that produces the converged state ends the loop
43
+ * immediately.
28
44
  */
29
- const pollOnShielded = (onShielded, isCancelled) => __awaiter(void 0, void 0, void 0, function* () {
45
+ const pollOnShielded = (onShielded, isCancelled, hasConverged) => __awaiter(void 0, void 0, void 0, function* () {
30
46
  for (const delay of REFRESH_DELAYS_MS) {
31
47
  if (isCancelled())
32
48
  return;
49
+ if (hasConverged === null || hasConverged === void 0 ? void 0 : hasConverged())
50
+ return;
33
51
  yield new Promise((resolve) => setTimeout(resolve, delay));
34
52
  if (isCancelled())
35
53
  return;
@@ -39,6 +57,8 @@ const pollOnShielded = (onShielded, isCancelled) => __awaiter(void 0, void 0, vo
39
57
  catch (_a) {
40
58
  /* swallow — refresh is best-effort */
41
59
  }
60
+ if (hasConverged === null || hasConverged === void 0 ? void 0 : hasConverged())
61
+ return;
42
62
  }
43
63
  });
44
64
 
@@ -47,7 +47,7 @@ const buildShieldCandidates = (args) => {
47
47
  */
48
48
  const tryShieldOneToken = (token, deps) => _tslib.__awaiter(void 0, void 0, void 0, function* () {
49
49
  var _a, _b, _c;
50
- const { accountAddress, shieldHandle, seenKeys, isCancelled, inFlightTokenKeys, } = deps;
50
+ const { accountAddress, shieldHandle, seenKeys, isCancelled, inFlightTokenKeys, onShieldDispatched, } = deps;
51
51
  const tokenKey = buildTokenKey.buildTokenKey(token) || nativeTokenKey.nativeTokenKey;
52
52
  const key = `${accountAddress}:${tokenKey}`;
53
53
  seenKeys.add(key);
@@ -80,9 +80,19 @@ const tryShieldOneToken = (token, deps) => _tslib.__awaiter(void 0, void 0, void
80
80
  isNative: token.isNative,
81
81
  tokenAddress: token.address,
82
82
  });
83
+ // Optimistic-UI hand-off: notify the consumer the broadcast was
84
+ // accepted so it can deduct the unshielded balance locally and
85
+ // prepend a synthesised shielded row. Synchronous and best-effort;
86
+ // failures inside the callback don't roll back the shield.
87
+ try {
88
+ onShieldDispatched === null || onShieldDispatched === void 0 ? void 0 : onShieldDispatched({ amount: atomic, token });
89
+ }
90
+ catch (_e) {
91
+ /* swallow — optimistic hand-off is best-effort */
92
+ }
83
93
  return true;
84
94
  }
85
- catch (_e) {
95
+ catch (_f) {
86
96
  seenKeys.delete(key);
87
97
  return false;
88
98
  }
@@ -90,7 +100,7 @@ const tryShieldOneToken = (token, deps) => _tslib.__awaiter(void 0, void 0, void
90
100
  inFlightTokenKeys === null || inFlightTokenKeys === void 0 ? void 0 : inFlightTokenKeys.delete(tokenKey);
91
101
  }
92
102
  });
93
- const useAleoAutoShieldSponsoredTokens = ({ accountAddress, unshieldedTokenBalances, shieldHandle, onShielded, }) => {
103
+ const useAleoAutoShieldSponsoredTokens = ({ accountAddress, unshieldedTokenBalances, shieldHandle, onShielded, onShieldDispatched, }) => {
94
104
  // Tracks `(accountAddress:tokenAddress)` we've already attempted in this
95
105
  // session. Cleared implicitly on a full page reload.
96
106
  const seenKeysRef = React.useRef(new Set());
@@ -218,6 +228,7 @@ const useAleoAutoShieldSponsoredTokens = ({ accountAddress, unshieldedTokenBalan
218
228
  accountAddress,
219
229
  inFlightTokenKeys: inFlightKeysAdapter,
220
230
  isCancelled,
231
+ onShieldDispatched,
221
232
  seenKeys: seenKeysRef.current,
222
233
  shieldHandle,
223
234
  });
@@ -25,6 +25,10 @@ type UseAleoAutoShieldSponsoredTokensArgs = {
25
25
  unshieldedTokenBalances: TokenBalance[];
26
26
  shieldHandle: AleoShieldHandle | undefined;
27
27
  onShielded?: () => Promise<void> | void;
28
+ onShieldDispatched?: (info: {
29
+ token: TokenBalance;
30
+ amount: bigint;
31
+ }) => void;
28
32
  };
29
33
  /**
30
34
  * On Aleo wallet load (and on each subsequent unshielded-balance refresh),
@@ -66,5 +70,5 @@ export type UseAleoAutoShieldSponsoredTokensResult = {
66
70
  */
67
71
  currentlyShieldingTokenKeys: ReadonlySet<string>;
68
72
  };
69
- export declare const useAleoAutoShieldSponsoredTokens: ({ accountAddress, unshieldedTokenBalances, shieldHandle, onShielded, }: UseAleoAutoShieldSponsoredTokensArgs) => UseAleoAutoShieldSponsoredTokensResult;
73
+ export declare const useAleoAutoShieldSponsoredTokens: ({ accountAddress, unshieldedTokenBalances, shieldHandle, onShielded, onShieldDispatched, }: UseAleoAutoShieldSponsoredTokensArgs) => UseAleoAutoShieldSponsoredTokensResult;
70
74
  export {};
@@ -43,7 +43,7 @@ const buildShieldCandidates = (args) => {
43
43
  */
44
44
  const tryShieldOneToken = (token, deps) => __awaiter(void 0, void 0, void 0, function* () {
45
45
  var _a, _b, _c;
46
- const { accountAddress, shieldHandle, seenKeys, isCancelled, inFlightTokenKeys, } = deps;
46
+ const { accountAddress, shieldHandle, seenKeys, isCancelled, inFlightTokenKeys, onShieldDispatched, } = deps;
47
47
  const tokenKey = buildTokenKey(token) || nativeTokenKey;
48
48
  const key = `${accountAddress}:${tokenKey}`;
49
49
  seenKeys.add(key);
@@ -76,9 +76,19 @@ const tryShieldOneToken = (token, deps) => __awaiter(void 0, void 0, void 0, fun
76
76
  isNative: token.isNative,
77
77
  tokenAddress: token.address,
78
78
  });
79
+ // Optimistic-UI hand-off: notify the consumer the broadcast was
80
+ // accepted so it can deduct the unshielded balance locally and
81
+ // prepend a synthesised shielded row. Synchronous and best-effort;
82
+ // failures inside the callback don't roll back the shield.
83
+ try {
84
+ onShieldDispatched === null || onShieldDispatched === void 0 ? void 0 : onShieldDispatched({ amount: atomic, token });
85
+ }
86
+ catch (_e) {
87
+ /* swallow — optimistic hand-off is best-effort */
88
+ }
79
89
  return true;
80
90
  }
81
- catch (_e) {
91
+ catch (_f) {
82
92
  seenKeys.delete(key);
83
93
  return false;
84
94
  }
@@ -86,7 +96,7 @@ const tryShieldOneToken = (token, deps) => __awaiter(void 0, void 0, void 0, fun
86
96
  inFlightTokenKeys === null || inFlightTokenKeys === void 0 ? void 0 : inFlightTokenKeys.delete(tokenKey);
87
97
  }
88
98
  });
89
- const useAleoAutoShieldSponsoredTokens = ({ accountAddress, unshieldedTokenBalances, shieldHandle, onShielded, }) => {
99
+ const useAleoAutoShieldSponsoredTokens = ({ accountAddress, unshieldedTokenBalances, shieldHandle, onShielded, onShieldDispatched, }) => {
90
100
  // Tracks `(accountAddress:tokenAddress)` we've already attempted in this
91
101
  // session. Cleared implicitly on a full page reload.
92
102
  const seenKeysRef = useRef(new Set());
@@ -214,6 +224,7 @@ const useAleoAutoShieldSponsoredTokens = ({ accountAddress, unshieldedTokenBalan
214
224
  accountAddress,
215
225
  inFlightTokenKeys: inFlightKeysAdapter,
216
226
  isCancelled,
227
+ onShieldDispatched,
217
228
  seenKeys: seenKeysRef.current,
218
229
  shieldHandle,
219
230
  });
@@ -8,6 +8,7 @@ var React = require('react');
8
8
  var sdkApiCore = require('@dynamic-labs/sdk-api-core');
9
9
  require('../../../context/DynamicContext/useDynamicContext/useDynamicContext.cjs');
10
10
  var useInternalDynamicContext = require('../../../context/DynamicContext/useDynamicContext/useInternalDynamicContext/useInternalDynamicContext.cjs');
11
+ var UserWalletsContext = require('../../../context/UserWalletsContext/UserWalletsContext.cjs');
11
12
  var getAleoCuratedPrices = require('../../../data/api/aleo/getAleoCuratedPrices.cjs');
12
13
  var dynamicContextProps = require('../../../store/state/dynamicContextProps/dynamicContextProps.cjs');
13
14
 
@@ -173,6 +174,12 @@ const buildCreditsBalance = (records, networkId, lookupCurated) => {
173
174
  if (creditsRecords.length === 0)
174
175
  return undefined;
175
176
  const totalMicrocredits = creditsRecords.reduce((sum, r) => sum + BigInt(r.microcredits), BigInt(0));
177
+ // Skip the row entirely when every credits record summed to zero
178
+ // (typically partially-spent change records the indexer hasn't
179
+ // pruned yet). A "0 ALEO" entry in the Shielded tab is just noise
180
+ // the user can't act on.
181
+ if (totalMicrocredits === BigInt(0))
182
+ return undefined;
176
183
  // BigInt → number for the TokenBalance shape. Aleo balances stay well
177
184
  // below Number.MAX_SAFE_INTEGER for a single wallet (max u64 supply is
178
185
  // 1.5B credits = 1.5e15 microcredits; Number can hold up to ~9e15).
@@ -227,6 +234,14 @@ const buildTokenBalances = (records, networkId, lookupCurated) => {
227
234
  const spec = specsByContract.get(contractAddress);
228
235
  if (!spec)
229
236
  continue;
237
+ // Skip tokens whose every matched record summed to zero. Provable
238
+ // occasionally surfaces serialized records with `amount: 0u128`
239
+ // (e.g. partially-spent change records that haven't been pruned by
240
+ // the indexer yet); without this filter the Shielded tab shows a
241
+ // row with "0 USAD" / "0 USDCx" that the user can't act on, which
242
+ // is just noise.
243
+ if (total === BigInt(0))
244
+ continue;
230
245
  const rawBalance = Number(total);
231
246
  const rawBalanceString = total.toString();
232
247
  const balance = rawBalance / Math.pow(10, spec.decimals);
@@ -269,14 +284,37 @@ const buildTokenBalances = (records, networkId, lookupCurated) => {
269
284
  * - The connector doesn't expose `listOwnedRecords` (e.g. external Aleo wallet)
270
285
  * - The user owns no records that match a known program/record pair
271
286
  */
272
- const useAleoShieldedBalances = () => {
287
+ const useAleoShieldedBalances = (args = {}) => {
288
+ const { accountAddress, tokenAddresses, includeNativeBalance = true, includeFiat = true, chainName, networkId, } = args;
273
289
  const { primaryWallet, network } = useInternalDynamicContext.useInternalDynamicContext();
290
+ const { userWallets } = UserWalletsContext.useInternalUserWallets();
274
291
  const [tokenBalances, setTokenBalances] = React.useState([]);
275
292
  const [isLoading, setIsLoading] = React.useState(false);
276
293
  const [error, setError] = React.useState();
277
- const connector = primaryWallet === null || primaryWallet === void 0 ? void 0 : primaryWallet.connector;
294
+ // `accountAddress` is treated as an override of the primary wallet:
295
+ // walk the user-wallets list looking for a matching address (case-
296
+ // insensitive to be tolerant of how the address is formatted at the
297
+ // call site — Aleo addresses are bech32 and casing-insensitive in
298
+ // practice). When no `accountAddress` is provided, fall back to the
299
+ // primary wallet to preserve the existing single-wallet semantics.
300
+ const targetWallet = React.useMemo(() => {
301
+ var _a;
302
+ if (!accountAddress)
303
+ return primaryWallet;
304
+ const normalized = accountAddress.toLowerCase();
305
+ return ((_a = userWallets.find((w) => { var _a; return ((_a = w.address) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === normalized; })) !== null && _a !== void 0 ? _a : undefined);
306
+ }, [accountAddress, primaryWallet, userWallets]);
307
+ const connector = targetWallet === null || targetWallet === void 0 ? void 0 : targetWallet.connector;
278
308
  const isAleo = (connector === null || connector === void 0 ? void 0 : connector.connectedChain) === sdkApiCore.ChainEnum.Aleo;
279
- const supportsShielded = isAleo &&
309
+ // When a `chainName` override is passed, treat anything other than
310
+ // `ChainEnum.Aleo` as "the caller explicitly asked for a non-Aleo
311
+ // chain we don't support yet" and short-circuit. Today Aleo is the
312
+ // only chain with a private-balance path, but customers should be
313
+ // able to call the public hook with whatever `chainName` matches
314
+ // their app's active chain and have it transparently no-op.
315
+ const chainNameSupported = chainName === undefined || chainName === sdkApiCore.ChainEnum.Aleo;
316
+ const supportsShielded = chainNameSupported &&
317
+ isAleo &&
280
318
  typeof (connector === null || connector === void 0 ? void 0 : connector.listOwnedRecords) === 'function';
281
319
  // Mirror the live connector into a ref so `fetchShielded` can read it
282
320
  // without depending on the (often unstable) object reference. Some test
@@ -286,7 +324,36 @@ const useAleoShieldedBalances = () => {
286
324
  const connectorRef = React.useRef(connector);
287
325
  connectorRef.current = connector;
288
326
  const connectorKey = connector === null || connector === void 0 ? void 0 : connector.key;
289
- const networkKey = network !== undefined && network !== null ? String(network) : undefined;
327
+ // When a `networkId` override is provided, use it instead of the
328
+ // context's `network` value when resolving the Aleo network param.
329
+ // Empty string falls back to undefined so `resolveAleoNetwork`
330
+ // returns `aleoNetworkParam: undefined` and the prices fetch is
331
+ // skipped (matching the behaviour when no network is known).
332
+ const effectiveNetwork = networkId !== null && networkId !== void 0 ? networkId : network;
333
+ const networkKey = effectiveNetwork !== undefined && effectiveNetwork !== null
334
+ ? String(effectiveNetwork)
335
+ : undefined;
336
+ // Normalise the address filter once per render into a stable
337
+ // primitive key (sorted, comma-joined, lowercased) so the
338
+ // `useCallback` dep below doesn't invalidate on every render when the
339
+ // caller passes a fresh array literal each time (which is the typical
340
+ // pattern in React function components). The matching Set is derived
341
+ // from the same key to keep both memos in lockstep.
342
+ const tokenAddressFilterKey = React.useMemo(() => {
343
+ if (!tokenAddresses)
344
+ return undefined;
345
+ return [...tokenAddresses]
346
+ .map((a) => a.toLowerCase())
347
+ .sort((a, b) => a.localeCompare(b))
348
+ .join('|');
349
+ }, [tokenAddresses]);
350
+ const tokenAddressFilter = React.useMemo(() => {
351
+ if (tokenAddressFilterKey === undefined)
352
+ return undefined;
353
+ if (tokenAddressFilterKey === '')
354
+ return new Set();
355
+ return new Set(tokenAddressFilterKey.split('|'));
356
+ }, [tokenAddressFilterKey]);
290
357
  const fetchShielded = React.useCallback(() => _tslib.__awaiter(void 0, void 0, void 0, function* () {
291
358
  var _a;
292
359
  const liveConnector = connectorRef.current;
@@ -305,9 +372,15 @@ const useAleoShieldedBalances = () => {
305
372
  // token row, leaving the widget with no price source. Prices are
306
373
  // best-effort: a failure leaves balances unpriced rather than
307
374
  // dropping the shielded list entirely.
375
+ //
376
+ // When the caller opts out of fiat (`includeFiat: false`) we skip
377
+ // the curated-prices fetch entirely — same semantic as
378
+ // `useTokenBalances` honouring its own `includeFiat` flag. Logos
379
+ // for matched tokens then fall through to the bundled defaults
380
+ // (`ALEO_CREDITS_LOGO`, `UNKNOWN_TOKEN_LOGO`, spec overrides).
308
381
  const { safeNetworkId, aleoNetworkParam } = resolveAleoNetwork(networkKey);
309
382
  const environmentId = dynamicContextProps.getEnvironmentId();
310
- const shouldFetchPrices = Boolean(aleoNetworkParam && environmentId);
383
+ const shouldFetchPrices = Boolean(includeFiat && aleoNetworkParam && environmentId);
311
384
  const [result, priceList] = yield Promise.all([
312
385
  liveConnector.listOwnedRecords(),
313
386
  shouldFetchPrices && aleoNetworkParam
@@ -320,11 +393,16 @@ const useAleoShieldedBalances = () => {
320
393
  const records = (_a = result === null || result === void 0 ? void 0 : result.records) !== null && _a !== void 0 ? _a : [];
321
394
  const lookupCurated = buildCuratedTokenLookup(priceList);
322
395
  const balances = [];
323
- const credits = buildCreditsBalance(records, safeNetworkId, lookupCurated);
324
- if (credits)
325
- balances.push(credits);
396
+ if (includeNativeBalance) {
397
+ const credits = buildCreditsBalance(records, safeNetworkId, lookupCurated);
398
+ if (credits)
399
+ balances.push(credits);
400
+ }
326
401
  balances.push(...buildTokenBalances(records, safeNetworkId, lookupCurated));
327
- setTokenBalances(balances);
402
+ const filtered = tokenAddressFilter
403
+ ? balances.filter((b) => { var _a; return tokenAddressFilter.has(((_a = b.address) !== null && _a !== void 0 ? _a : '').toLowerCase()); })
404
+ : balances;
405
+ setTokenBalances(filtered);
328
406
  }
329
407
  catch (err) {
330
408
  const msg = err instanceof Error ? err.message : String(err);
@@ -334,7 +412,14 @@ const useAleoShieldedBalances = () => {
334
412
  finally {
335
413
  setIsLoading(false);
336
414
  }
337
- }), [connectorKey, networkKey, supportsShielded]);
415
+ }), [
416
+ connectorKey,
417
+ includeFiat,
418
+ includeNativeBalance,
419
+ networkKey,
420
+ supportsShielded,
421
+ tokenAddressFilter,
422
+ ]);
338
423
  React.useEffect(() => {
339
424
  if (!supportsShielded) {
340
425
  setTokenBalances((prev) => (prev.length === 0 ? prev : []));
@@ -1,4 +1,20 @@
1
- import { TokenBalance } from '@dynamic-labs/sdk-api-core';
1
+ import { ChainEnum, TokenBalance } from '@dynamic-labs/sdk-api-core';
2
+ /**
3
+ * Optional inputs that mirror the corresponding `useTokenBalances`
4
+ * params so the public `usePrivateTokenBalances` hook can pass through
5
+ * the same shape. Defaults here are tuned for the **widget's existing
6
+ * consumer** (`ActiveWalletBalance`), which expects the native ALEO
7
+ * row + fiat lookup to be on by default. The public hook overrides
8
+ * these defaults to match `useTokenBalances`' false / false.
9
+ */
10
+ export type UseAleoShieldedBalancesArgs = {
11
+ accountAddress?: string;
12
+ tokenAddresses?: string[];
13
+ includeNativeBalance?: boolean;
14
+ includeFiat?: boolean;
15
+ chainName?: ChainEnum;
16
+ networkId?: number;
17
+ };
2
18
  /**
3
19
  * Hook that returns the active wallet's shielded (private) Aleo token
4
20
  * balances as a `TokenBalance[]` so the widget can render them through the
@@ -15,7 +31,7 @@ import { TokenBalance } from '@dynamic-labs/sdk-api-core';
15
31
  * - The connector doesn't expose `listOwnedRecords` (e.g. external Aleo wallet)
16
32
  * - The user owns no records that match a known program/record pair
17
33
  */
18
- export declare const useAleoShieldedBalances: () => {
34
+ export declare const useAleoShieldedBalances: (args?: UseAleoShieldedBalancesArgs) => {
19
35
  tokenBalances: TokenBalance[];
20
36
  isLoading: boolean;
21
37
  error: string | undefined;
@@ -1,9 +1,10 @@
1
1
  'use client'
2
2
  import { __awaiter } from '../../../../../_virtual/_tslib.js';
3
- import { useState, useRef, useCallback, useEffect } from 'react';
3
+ import { useState, useMemo, useRef, useCallback, useEffect } from 'react';
4
4
  import { ChainEnum } from '@dynamic-labs/sdk-api-core';
5
5
  import '../../../context/DynamicContext/useDynamicContext/useDynamicContext.js';
6
6
  import { useInternalDynamicContext } from '../../../context/DynamicContext/useDynamicContext/useInternalDynamicContext/useInternalDynamicContext.js';
7
+ import { useInternalUserWallets } from '../../../context/UserWalletsContext/UserWalletsContext.js';
7
8
  import { getAleoCuratedPrices } from '../../../data/api/aleo/getAleoCuratedPrices.js';
8
9
  import { getEnvironmentId } from '../../../store/state/dynamicContextProps/dynamicContextProps.js';
9
10
 
@@ -169,6 +170,12 @@ const buildCreditsBalance = (records, networkId, lookupCurated) => {
169
170
  if (creditsRecords.length === 0)
170
171
  return undefined;
171
172
  const totalMicrocredits = creditsRecords.reduce((sum, r) => sum + BigInt(r.microcredits), BigInt(0));
173
+ // Skip the row entirely when every credits record summed to zero
174
+ // (typically partially-spent change records the indexer hasn't
175
+ // pruned yet). A "0 ALEO" entry in the Shielded tab is just noise
176
+ // the user can't act on.
177
+ if (totalMicrocredits === BigInt(0))
178
+ return undefined;
172
179
  // BigInt → number for the TokenBalance shape. Aleo balances stay well
173
180
  // below Number.MAX_SAFE_INTEGER for a single wallet (max u64 supply is
174
181
  // 1.5B credits = 1.5e15 microcredits; Number can hold up to ~9e15).
@@ -223,6 +230,14 @@ const buildTokenBalances = (records, networkId, lookupCurated) => {
223
230
  const spec = specsByContract.get(contractAddress);
224
231
  if (!spec)
225
232
  continue;
233
+ // Skip tokens whose every matched record summed to zero. Provable
234
+ // occasionally surfaces serialized records with `amount: 0u128`
235
+ // (e.g. partially-spent change records that haven't been pruned by
236
+ // the indexer yet); without this filter the Shielded tab shows a
237
+ // row with "0 USAD" / "0 USDCx" that the user can't act on, which
238
+ // is just noise.
239
+ if (total === BigInt(0))
240
+ continue;
226
241
  const rawBalance = Number(total);
227
242
  const rawBalanceString = total.toString();
228
243
  const balance = rawBalance / Math.pow(10, spec.decimals);
@@ -265,14 +280,37 @@ const buildTokenBalances = (records, networkId, lookupCurated) => {
265
280
  * - The connector doesn't expose `listOwnedRecords` (e.g. external Aleo wallet)
266
281
  * - The user owns no records that match a known program/record pair
267
282
  */
268
- const useAleoShieldedBalances = () => {
283
+ const useAleoShieldedBalances = (args = {}) => {
284
+ const { accountAddress, tokenAddresses, includeNativeBalance = true, includeFiat = true, chainName, networkId, } = args;
269
285
  const { primaryWallet, network } = useInternalDynamicContext();
286
+ const { userWallets } = useInternalUserWallets();
270
287
  const [tokenBalances, setTokenBalances] = useState([]);
271
288
  const [isLoading, setIsLoading] = useState(false);
272
289
  const [error, setError] = useState();
273
- const connector = primaryWallet === null || primaryWallet === void 0 ? void 0 : primaryWallet.connector;
290
+ // `accountAddress` is treated as an override of the primary wallet:
291
+ // walk the user-wallets list looking for a matching address (case-
292
+ // insensitive to be tolerant of how the address is formatted at the
293
+ // call site — Aleo addresses are bech32 and casing-insensitive in
294
+ // practice). When no `accountAddress` is provided, fall back to the
295
+ // primary wallet to preserve the existing single-wallet semantics.
296
+ const targetWallet = useMemo(() => {
297
+ var _a;
298
+ if (!accountAddress)
299
+ return primaryWallet;
300
+ const normalized = accountAddress.toLowerCase();
301
+ return ((_a = userWallets.find((w) => { var _a; return ((_a = w.address) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === normalized; })) !== null && _a !== void 0 ? _a : undefined);
302
+ }, [accountAddress, primaryWallet, userWallets]);
303
+ const connector = targetWallet === null || targetWallet === void 0 ? void 0 : targetWallet.connector;
274
304
  const isAleo = (connector === null || connector === void 0 ? void 0 : connector.connectedChain) === ChainEnum.Aleo;
275
- const supportsShielded = isAleo &&
305
+ // When a `chainName` override is passed, treat anything other than
306
+ // `ChainEnum.Aleo` as "the caller explicitly asked for a non-Aleo
307
+ // chain we don't support yet" and short-circuit. Today Aleo is the
308
+ // only chain with a private-balance path, but customers should be
309
+ // able to call the public hook with whatever `chainName` matches
310
+ // their app's active chain and have it transparently no-op.
311
+ const chainNameSupported = chainName === undefined || chainName === ChainEnum.Aleo;
312
+ const supportsShielded = chainNameSupported &&
313
+ isAleo &&
276
314
  typeof (connector === null || connector === void 0 ? void 0 : connector.listOwnedRecords) === 'function';
277
315
  // Mirror the live connector into a ref so `fetchShielded` can read it
278
316
  // without depending on the (often unstable) object reference. Some test
@@ -282,7 +320,36 @@ const useAleoShieldedBalances = () => {
282
320
  const connectorRef = useRef(connector);
283
321
  connectorRef.current = connector;
284
322
  const connectorKey = connector === null || connector === void 0 ? void 0 : connector.key;
285
- const networkKey = network !== undefined && network !== null ? String(network) : undefined;
323
+ // When a `networkId` override is provided, use it instead of the
324
+ // context's `network` value when resolving the Aleo network param.
325
+ // Empty string falls back to undefined so `resolveAleoNetwork`
326
+ // returns `aleoNetworkParam: undefined` and the prices fetch is
327
+ // skipped (matching the behaviour when no network is known).
328
+ const effectiveNetwork = networkId !== null && networkId !== void 0 ? networkId : network;
329
+ const networkKey = effectiveNetwork !== undefined && effectiveNetwork !== null
330
+ ? String(effectiveNetwork)
331
+ : undefined;
332
+ // Normalise the address filter once per render into a stable
333
+ // primitive key (sorted, comma-joined, lowercased) so the
334
+ // `useCallback` dep below doesn't invalidate on every render when the
335
+ // caller passes a fresh array literal each time (which is the typical
336
+ // pattern in React function components). The matching Set is derived
337
+ // from the same key to keep both memos in lockstep.
338
+ const tokenAddressFilterKey = useMemo(() => {
339
+ if (!tokenAddresses)
340
+ return undefined;
341
+ return [...tokenAddresses]
342
+ .map((a) => a.toLowerCase())
343
+ .sort((a, b) => a.localeCompare(b))
344
+ .join('|');
345
+ }, [tokenAddresses]);
346
+ const tokenAddressFilter = useMemo(() => {
347
+ if (tokenAddressFilterKey === undefined)
348
+ return undefined;
349
+ if (tokenAddressFilterKey === '')
350
+ return new Set();
351
+ return new Set(tokenAddressFilterKey.split('|'));
352
+ }, [tokenAddressFilterKey]);
286
353
  const fetchShielded = useCallback(() => __awaiter(void 0, void 0, void 0, function* () {
287
354
  var _a;
288
355
  const liveConnector = connectorRef.current;
@@ -301,9 +368,15 @@ const useAleoShieldedBalances = () => {
301
368
  // token row, leaving the widget with no price source. Prices are
302
369
  // best-effort: a failure leaves balances unpriced rather than
303
370
  // dropping the shielded list entirely.
371
+ //
372
+ // When the caller opts out of fiat (`includeFiat: false`) we skip
373
+ // the curated-prices fetch entirely — same semantic as
374
+ // `useTokenBalances` honouring its own `includeFiat` flag. Logos
375
+ // for matched tokens then fall through to the bundled defaults
376
+ // (`ALEO_CREDITS_LOGO`, `UNKNOWN_TOKEN_LOGO`, spec overrides).
304
377
  const { safeNetworkId, aleoNetworkParam } = resolveAleoNetwork(networkKey);
305
378
  const environmentId = getEnvironmentId();
306
- const shouldFetchPrices = Boolean(aleoNetworkParam && environmentId);
379
+ const shouldFetchPrices = Boolean(includeFiat && aleoNetworkParam && environmentId);
307
380
  const [result, priceList] = yield Promise.all([
308
381
  liveConnector.listOwnedRecords(),
309
382
  shouldFetchPrices && aleoNetworkParam
@@ -316,11 +389,16 @@ const useAleoShieldedBalances = () => {
316
389
  const records = (_a = result === null || result === void 0 ? void 0 : result.records) !== null && _a !== void 0 ? _a : [];
317
390
  const lookupCurated = buildCuratedTokenLookup(priceList);
318
391
  const balances = [];
319
- const credits = buildCreditsBalance(records, safeNetworkId, lookupCurated);
320
- if (credits)
321
- balances.push(credits);
392
+ if (includeNativeBalance) {
393
+ const credits = buildCreditsBalance(records, safeNetworkId, lookupCurated);
394
+ if (credits)
395
+ balances.push(credits);
396
+ }
322
397
  balances.push(...buildTokenBalances(records, safeNetworkId, lookupCurated));
323
- setTokenBalances(balances);
398
+ const filtered = tokenAddressFilter
399
+ ? balances.filter((b) => { var _a; return tokenAddressFilter.has(((_a = b.address) !== null && _a !== void 0 ? _a : '').toLowerCase()); })
400
+ : balances;
401
+ setTokenBalances(filtered);
324
402
  }
325
403
  catch (err) {
326
404
  const msg = err instanceof Error ? err.message : String(err);
@@ -330,7 +408,14 @@ const useAleoShieldedBalances = () => {
330
408
  finally {
331
409
  setIsLoading(false);
332
410
  }
333
- }), [connectorKey, networkKey, supportsShielded]);
411
+ }), [
412
+ connectorKey,
413
+ includeFiat,
414
+ includeNativeBalance,
415
+ networkKey,
416
+ supportsShielded,
417
+ tokenAddressFilter,
418
+ ]);
334
419
  useEffect(() => {
335
420
  if (!supportsShielded) {
336
421
  setTokenBalances((prev) => (prev.length === 0 ? prev : []));
@@ -88,7 +88,7 @@ const configWaasWalletConnector = ({ walletConnector, environmentId, apiBaseUrl,
88
88
  };
89
89
  const useDynamicWaas = () => {
90
90
  var _a, _b, _c, _d, _e, _f, _g;
91
- const { setShowAuthFlow, primaryWallet } = useInternalDynamicContext.useInternalDynamicContext();
91
+ const { setShowAuthFlow } = useInternalDynamicContext.useInternalDynamicContext();
92
92
  const { addedWalletsIds, userWallets } = UserWalletsContext.useInternalUserWallets();
93
93
  const user = useUser.useUser();
94
94
  const apiBaseUrl = dynamicContextProps.useApiBaseUrl();
@@ -468,8 +468,8 @@ const useDynamicWaas = () => {
468
468
  });
469
469
  const upgradeToDynamicWaas = React.useCallback((_j) => _tslib.__awaiter(void 0, [_j], void 0, function* ({ privateKey, wallet, password, }) {
470
470
  isUpgrading.current = true;
471
- if (!primaryWallet) {
472
- throw new utils.DynamicError('Primary wallet not found');
471
+ if (!wallet) {
472
+ throw new utils.DynamicError('Wallet to upgrade was not provided');
473
473
  }
474
474
  const chainName = wallet.chain;
475
475
  const walletId = wallet.id;
@@ -514,7 +514,7 @@ const useDynamicWaas = () => {
514
514
  }
515
515
  throw new utils.DynamicError('Upgrade failed, please try again.');
516
516
  }
517
- }), [environmentId, getWaasWalletConnector, primaryWallet, refresh]);
517
+ }), [environmentId, getWaasWalletConnector, refresh]);
518
518
  /**
519
519
  * Returns WaaS wallet instances from `userWallets` filtered by `key === 'dynamicwaas'`.
520
520
  *
@@ -84,7 +84,7 @@ const configWaasWalletConnector = ({ walletConnector, environmentId, apiBaseUrl,
84
84
  };
85
85
  const useDynamicWaas = () => {
86
86
  var _a, _b, _c, _d, _e, _f, _g;
87
- const { setShowAuthFlow, primaryWallet } = useInternalDynamicContext();
87
+ const { setShowAuthFlow } = useInternalDynamicContext();
88
88
  const { addedWalletsIds, userWallets } = useInternalUserWallets();
89
89
  const user = useUser();
90
90
  const apiBaseUrl = useApiBaseUrl();
@@ -464,8 +464,8 @@ const useDynamicWaas = () => {
464
464
  });
465
465
  const upgradeToDynamicWaas = useCallback((_j) => __awaiter(void 0, [_j], void 0, function* ({ privateKey, wallet, password, }) {
466
466
  isUpgrading.current = true;
467
- if (!primaryWallet) {
468
- throw new DynamicError('Primary wallet not found');
467
+ if (!wallet) {
468
+ throw new DynamicError('Wallet to upgrade was not provided');
469
469
  }
470
470
  const chainName = wallet.chain;
471
471
  const walletId = wallet.id;
@@ -510,7 +510,7 @@ const useDynamicWaas = () => {
510
510
  }
511
511
  throw new DynamicError('Upgrade failed, please try again.');
512
512
  }
513
- }), [environmentId, getWaasWalletConnector, primaryWallet, refresh]);
513
+ }), [environmentId, getWaasWalletConnector, refresh]);
514
514
  /**
515
515
  * Returns WaaS wallet instances from `userWallets` filtered by `key === 'dynamicwaas'`.
516
516
  *
@@ -0,0 +1,2 @@
1
+ export { usePrivateTokenBalances } from './usePrivateTokenBalances';
2
+ export type { UsePrivateTokenBalancesArgs, UsePrivateTokenBalancesReturn, } from './usePrivateTokenBalances';
@@ -0,0 +1,27 @@
1
+ 'use client'
2
+ 'use strict';
3
+
4
+ Object.defineProperty(exports, '__esModule', { value: true });
5
+
6
+ var useAleoShieldedBalances = require('../useAleoShieldedBalances/useAleoShieldedBalances.cjs');
7
+
8
+ const usePrivateTokenBalances = (args = {}) => {
9
+ const { accountAddress, tokenAddresses, includeNativeBalance = false, includeFiat = false, chainName, networkId, } = args;
10
+ const { tokenBalances, isLoading, error, refetch, supportsShielded } = useAleoShieldedBalances.useAleoShieldedBalances({
11
+ accountAddress,
12
+ chainName,
13
+ includeFiat,
14
+ includeNativeBalance,
15
+ networkId,
16
+ tokenAddresses,
17
+ });
18
+ return {
19
+ error,
20
+ isLoading,
21
+ refetch,
22
+ supportsPrivateBalances: supportsShielded,
23
+ tokenBalances,
24
+ };
25
+ };
26
+
27
+ exports.usePrivateTokenBalances = usePrivateTokenBalances;