@dynamic-labs/sdk-react-core 4.92.3 → 4.93.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 (42) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/package.cjs +5 -5
  3. package/package.js +5 -5
  4. package/package.json +16 -16
  5. package/src/lib/components/SendBalanceForm/SendBalanceForm.cjs +28 -4
  6. package/src/lib/components/SendBalanceForm/SendBalanceForm.js +28 -4
  7. package/src/lib/context/CaptchaContext/CaptchaContext.cjs +10 -2
  8. package/src/lib/context/CaptchaContext/CaptchaContext.d.ts +2 -1
  9. package/src/lib/context/CaptchaContext/CaptchaContext.js +10 -2
  10. package/src/lib/context/DynamicContext/types/DynamicContextProps.d.ts +1 -1
  11. package/src/lib/store/state/dynamicContextProps/defaultDynamicSettings.cjs +1 -1
  12. package/src/lib/store/state/dynamicContextProps/defaultDynamicSettings.d.ts +1 -1
  13. package/src/lib/store/state/dynamicContextProps/defaultDynamicSettings.js +1 -1
  14. package/src/lib/utils/functions/getEmbeddedBitcoinActiveAddress/getEmbeddedBitcoinActiveAddress.cjs +24 -0
  15. package/src/lib/utils/functions/getEmbeddedBitcoinActiveAddress/getEmbeddedBitcoinActiveAddress.d.ts +11 -0
  16. package/src/lib/utils/functions/getEmbeddedBitcoinActiveAddress/getEmbeddedBitcoinActiveAddress.js +20 -0
  17. package/src/lib/utils/functions/getEmbeddedBitcoinActiveAddress/index.d.ts +1 -0
  18. package/src/lib/utils/functions/getTransactionLink/blockExplorerPatterns.cjs +12 -0
  19. package/src/lib/utils/functions/getTransactionLink/blockExplorerPatterns.js +12 -0
  20. package/src/lib/utils/functions/getWalletAdditionalAddressByType/getWalletAdditionalAddressByType.cjs +16 -3
  21. package/src/lib/utils/functions/getWalletAdditionalAddressByType/getWalletAdditionalAddressByType.d.ts +8 -1
  22. package/src/lib/utils/functions/getWalletAdditionalAddressByType/getWalletAdditionalAddressByType.js +16 -3
  23. package/src/lib/utils/functions/index.d.ts +1 -0
  24. package/src/lib/utils/hooks/useDynamicWaas/useDynamicWaas.cjs +50 -2
  25. package/src/lib/utils/hooks/useDynamicWaas/useDynamicWaas.js +50 -2
  26. package/src/lib/utils/hooks/useSocialAuth/useSocialAuth.cjs +12 -0
  27. package/src/lib/utils/hooks/useSocialAuth/useSocialAuth.js +12 -0
  28. package/src/lib/utils/hooks/useTokenBalances/useTokenBalances.cjs +9 -5
  29. package/src/lib/utils/hooks/useTokenBalances/useTokenBalances.js +9 -5
  30. package/src/lib/utils/hooks/useWalletItemActions/useHandleWalletItem/useHandleWalletItem.cjs +6 -1
  31. package/src/lib/utils/hooks/useWalletItemActions/useHandleWalletItem/useHandleWalletItem.js +6 -1
  32. package/src/lib/widgets/DynamicWidget/components/ActiveMidnightWalletBalance/ActiveMidnightWalletBalance.cjs +40 -8
  33. package/src/lib/widgets/DynamicWidget/components/ActiveMidnightWalletBalance/ActiveMidnightWalletBalance.js +41 -9
  34. package/src/lib/widgets/DynamicWidget/components/ActiveWalletInformation/ActiveMidnightWalletAddresses/ActiveMidnightWalletAddresses.cjs +20 -6
  35. package/src/lib/widgets/DynamicWidget/components/ActiveWalletInformation/ActiveMidnightWalletAddresses/ActiveMidnightWalletAddresses.d.ts +7 -1
  36. package/src/lib/widgets/DynamicWidget/components/ActiveWalletInformation/ActiveMidnightWalletAddresses/ActiveMidnightWalletAddresses.js +20 -6
  37. package/src/lib/widgets/DynamicWidget/components/ActiveWalletInformation/ActiveWalletAddress/ActiveWalletAddress.cjs +5 -2
  38. package/src/lib/widgets/DynamicWidget/components/ActiveWalletInformation/ActiveWalletAddress/ActiveWalletAddress.js +5 -2
  39. package/src/lib/widgets/DynamicWidget/components/ActiveWalletInformation/ActiveWalletInformation.cjs +17 -9
  40. package/src/lib/widgets/DynamicWidget/components/ActiveWalletInformation/ActiveWalletInformation.js +18 -10
  41. package/src/lib/widgets/DynamicWidget/views/DepositView/DepositView.cjs +21 -6
  42. package/src/lib/widgets/DynamicWidget/views/DepositView/DepositView.js +22 -7
@@ -472,25 +472,51 @@ const useDynamicWaas = () => {
472
472
  return result;
473
473
  });
474
474
  const upgradeToDynamicWaas = React.useCallback((_j) => _tslib.__awaiter(void 0, [_j], void 0, function* ({ privateKey, wallet, password, }) {
475
- isUpgrading.current = true;
476
475
  if (!wallet) {
477
476
  throw new utils.DynamicError('Wallet to upgrade was not provided', 'wallet_to_upgrade_was_not_provided');
478
477
  }
478
+ // Guard before any destructive work: an empty/whitespace private key
479
+ // would otherwise soft-delete the legacy wallet and only fail on import.
480
+ if (!(privateKey === null || privateKey === void 0 ? void 0 : privateKey.trim())) {
481
+ throw new utils.DynamicError('Private key is required', 'private_key_is_required');
482
+ }
479
483
  const chainName = wallet.chain;
480
484
  const walletId = wallet.id;
481
485
  const walletConnector = getWaasWalletConnector(chainName);
482
486
  if (!walletConnector) {
483
487
  throw new utils.DynamicError('Wallet connector not found', 'wallet_connector_not_found');
484
488
  }
489
+ isUpgrading.current = true;
485
490
  let softDeleteSucceeded = false;
486
491
  // 7702 wallets have a -zerodev suffix because it's a credential created on the fly
487
492
  const normalizedWalletId = (walletId === null || walletId === void 0 ? void 0 : walletId.replace('-zerodev', '')) || walletId;
493
+ walletConnectorCore.logger.info('Upgrade to WaaS - migration started', {
494
+ chain: chainName,
495
+ environmentId,
496
+ key: 'upgradeToWaasStarted',
497
+ userId: user === null || user === void 0 ? void 0 : user.id,
498
+ walletId: normalizedWalletId,
499
+ });
488
500
  try {
501
+ walletConnectorCore.logger.info('Upgrade to WaaS - soft deleting legacy wallet', {
502
+ chain: chainName,
503
+ environmentId,
504
+ key: 'upgradeToWaasSoftDelete',
505
+ userId: user === null || user === void 0 ? void 0 : user.id,
506
+ walletId: normalizedWalletId,
507
+ });
489
508
  yield wallets.softDeleteEmbeddedWallet({
490
509
  environmentId,
491
510
  walletId: normalizedWalletId,
492
511
  });
493
512
  softDeleteSucceeded = true;
513
+ walletConnectorCore.logger.info('Upgrade to WaaS - importing private key', {
514
+ chain: chainName,
515
+ environmentId,
516
+ key: 'upgradeToWaasImportPrivateKey',
517
+ userId: user === null || user === void 0 ? void 0 : user.id,
518
+ walletId: normalizedWalletId,
519
+ });
494
520
  yield walletConnector.importPrivateKey({
495
521
  chainName,
496
522
  legacyWalletId: normalizedWalletId,
@@ -499,10 +525,32 @@ const useDynamicWaas = () => {
499
525
  publicAddressCheck: wallet.address,
500
526
  });
501
527
  isUpgrading.current = false;
528
+ walletConnectorCore.logger.info('Upgrade to WaaS - migration completed', {
529
+ chain: chainName,
530
+ environmentId,
531
+ key: 'upgradeToWaasCompleted',
532
+ userId: user === null || user === void 0 ? void 0 : user.id,
533
+ walletId: normalizedWalletId,
534
+ });
502
535
  }
503
536
  catch (error) {
537
+ walletConnectorCore.logger.error('Upgrade to WaaS - migration failed', {
538
+ chain: chainName,
539
+ environmentId,
540
+ error,
541
+ softDeleteSucceeded,
542
+ userId: user === null || user === void 0 ? void 0 : user.id,
543
+ walletId: normalizedWalletId,
544
+ });
504
545
  // If soft delete succeeded but import failed, try to restore the wallet
505
546
  if (softDeleteSucceeded) {
547
+ walletConnectorCore.logger.info('Upgrade to WaaS - restoring legacy wallet after failed import', {
548
+ chain: chainName,
549
+ environmentId,
550
+ key: 'upgradeToWaasRestore',
551
+ userId: user === null || user === void 0 ? void 0 : user.id,
552
+ walletId: normalizedWalletId,
553
+ });
506
554
  yield wallets.restoreEmbeddedWallet({
507
555
  environmentId,
508
556
  walletId: normalizedWalletId,
@@ -519,7 +567,7 @@ const useDynamicWaas = () => {
519
567
  }
520
568
  throw new utils.DynamicError('Upgrade failed, please try again.', 'upgrade_failed');
521
569
  }
522
- }), [environmentId, getWaasWalletConnector, refresh]);
570
+ }), [environmentId, getWaasWalletConnector, refresh, user === null || user === void 0 ? void 0 : user.id]);
523
571
  /**
524
572
  * Returns WaaS wallet instances from `userWallets` filtered by `key === 'dynamicwaas'`.
525
573
  *
@@ -468,25 +468,51 @@ const useDynamicWaas = () => {
468
468
  return result;
469
469
  });
470
470
  const upgradeToDynamicWaas = useCallback((_j) => __awaiter(void 0, [_j], void 0, function* ({ privateKey, wallet, password, }) {
471
- isUpgrading.current = true;
472
471
  if (!wallet) {
473
472
  throw new DynamicError('Wallet to upgrade was not provided', 'wallet_to_upgrade_was_not_provided');
474
473
  }
474
+ // Guard before any destructive work: an empty/whitespace private key
475
+ // would otherwise soft-delete the legacy wallet and only fail on import.
476
+ if (!(privateKey === null || privateKey === void 0 ? void 0 : privateKey.trim())) {
477
+ throw new DynamicError('Private key is required', 'private_key_is_required');
478
+ }
475
479
  const chainName = wallet.chain;
476
480
  const walletId = wallet.id;
477
481
  const walletConnector = getWaasWalletConnector(chainName);
478
482
  if (!walletConnector) {
479
483
  throw new DynamicError('Wallet connector not found', 'wallet_connector_not_found');
480
484
  }
485
+ isUpgrading.current = true;
481
486
  let softDeleteSucceeded = false;
482
487
  // 7702 wallets have a -zerodev suffix because it's a credential created on the fly
483
488
  const normalizedWalletId = (walletId === null || walletId === void 0 ? void 0 : walletId.replace('-zerodev', '')) || walletId;
489
+ logger.info('Upgrade to WaaS - migration started', {
490
+ chain: chainName,
491
+ environmentId,
492
+ key: 'upgradeToWaasStarted',
493
+ userId: user === null || user === void 0 ? void 0 : user.id,
494
+ walletId: normalizedWalletId,
495
+ });
484
496
  try {
497
+ logger.info('Upgrade to WaaS - soft deleting legacy wallet', {
498
+ chain: chainName,
499
+ environmentId,
500
+ key: 'upgradeToWaasSoftDelete',
501
+ userId: user === null || user === void 0 ? void 0 : user.id,
502
+ walletId: normalizedWalletId,
503
+ });
485
504
  yield softDeleteEmbeddedWallet({
486
505
  environmentId,
487
506
  walletId: normalizedWalletId,
488
507
  });
489
508
  softDeleteSucceeded = true;
509
+ logger.info('Upgrade to WaaS - importing private key', {
510
+ chain: chainName,
511
+ environmentId,
512
+ key: 'upgradeToWaasImportPrivateKey',
513
+ userId: user === null || user === void 0 ? void 0 : user.id,
514
+ walletId: normalizedWalletId,
515
+ });
490
516
  yield walletConnector.importPrivateKey({
491
517
  chainName,
492
518
  legacyWalletId: normalizedWalletId,
@@ -495,10 +521,32 @@ const useDynamicWaas = () => {
495
521
  publicAddressCheck: wallet.address,
496
522
  });
497
523
  isUpgrading.current = false;
524
+ logger.info('Upgrade to WaaS - migration completed', {
525
+ chain: chainName,
526
+ environmentId,
527
+ key: 'upgradeToWaasCompleted',
528
+ userId: user === null || user === void 0 ? void 0 : user.id,
529
+ walletId: normalizedWalletId,
530
+ });
498
531
  }
499
532
  catch (error) {
533
+ logger.error('Upgrade to WaaS - migration failed', {
534
+ chain: chainName,
535
+ environmentId,
536
+ error,
537
+ softDeleteSucceeded,
538
+ userId: user === null || user === void 0 ? void 0 : user.id,
539
+ walletId: normalizedWalletId,
540
+ });
500
541
  // If soft delete succeeded but import failed, try to restore the wallet
501
542
  if (softDeleteSucceeded) {
543
+ logger.info('Upgrade to WaaS - restoring legacy wallet after failed import', {
544
+ chain: chainName,
545
+ environmentId,
546
+ key: 'upgradeToWaasRestore',
547
+ userId: user === null || user === void 0 ? void 0 : user.id,
548
+ walletId: normalizedWalletId,
549
+ });
502
550
  yield restoreEmbeddedWallet({
503
551
  environmentId,
504
552
  walletId: normalizedWalletId,
@@ -515,7 +563,7 @@ const useDynamicWaas = () => {
515
563
  }
516
564
  throw new DynamicError('Upgrade failed, please try again.', 'upgrade_failed');
517
565
  }
518
- }), [environmentId, getWaasWalletConnector, refresh]);
566
+ }), [environmentId, getWaasWalletConnector, refresh, user === null || user === void 0 ? void 0 : user.id]);
519
567
  /**
520
568
  * Returns WaaS wallet instances from `userWallets` filtered by `key === 'dynamicwaas'`.
521
569
  *
@@ -479,6 +479,17 @@ const useSocialAuth = ({ onSettled, onError, onFarcasterUrl, }) => {
479
479
  });
480
480
  engageCaptcha({
481
481
  authMethod: 'social',
482
+ // Backing out of the captcha view abandons onCaptchaSuccess, so the
483
+ // social flow must settle here — otherwise the social-redirect-view
484
+ // beneath it spins forever waiting for a connection that never starts
485
+ onCaptchaCancel: () => {
486
+ onFailed(props.provider, {
487
+ error: {
488
+ code: types.SocialOAuthErrorCode.USER_CANCELLED,
489
+ message: 'User cancelled connection',
490
+ },
491
+ }, { raiseAuthFailure: true });
492
+ },
482
493
  onCaptchaSuccess: (captchaToken) => _tslib.__awaiter(void 0, void 0, void 0, function* () {
483
494
  yield connectSocialAccount(Object.assign(Object.assign({}, props), { captchaToken }));
484
495
  setShowAuthFlow(false);
@@ -488,6 +499,7 @@ const useSocialAuth = ({ onSettled, onError, onFarcasterUrl, }) => {
488
499
  setShowAuthFlow,
489
500
  connectSocialAccount,
490
501
  engageCaptcha,
502
+ onFailed,
491
503
  projectSettings === null || projectSettings === void 0 ? void 0 : projectSettings.security,
492
504
  ]);
493
505
  return React.useMemo(() => ({
@@ -475,6 +475,17 @@ const useSocialAuth = ({ onSettled, onError, onFarcasterUrl, }) => {
475
475
  });
476
476
  engageCaptcha({
477
477
  authMethod: 'social',
478
+ // Backing out of the captcha view abandons onCaptchaSuccess, so the
479
+ // social flow must settle here — otherwise the social-redirect-view
480
+ // beneath it spins forever waiting for a connection that never starts
481
+ onCaptchaCancel: () => {
482
+ onFailed(props.provider, {
483
+ error: {
484
+ code: SocialOAuthErrorCode.USER_CANCELLED,
485
+ message: 'User cancelled connection',
486
+ },
487
+ }, { raiseAuthFailure: true });
488
+ },
478
489
  onCaptchaSuccess: (captchaToken) => __awaiter(void 0, void 0, void 0, function* () {
479
490
  yield connectSocialAccount(Object.assign(Object.assign({}, props), { captchaToken }));
480
491
  setShowAuthFlow(false);
@@ -484,6 +495,7 @@ const useSocialAuth = ({ onSettled, onError, onFarcasterUrl, }) => {
484
495
  setShowAuthFlow,
485
496
  connectSocialAccount,
486
497
  engageCaptcha,
498
+ onFailed,
487
499
  projectSettings === null || projectSettings === void 0 ? void 0 : projectSettings.security,
488
500
  ]);
489
501
  return useMemo(() => ({
@@ -25,6 +25,7 @@ require('@dynamic-labs/multi-wallet');
25
25
  require('react-international-phone');
26
26
  require('../../../store/state/nonce/nonce.cjs');
27
27
  require('@dynamic-labs-wallet/browser-wallet-client');
28
+ var getEmbeddedBitcoinActiveAddress = require('../../functions/getEmbeddedBitcoinActiveAddress/getEmbeddedBitcoinActiveAddress.cjs');
28
29
  var utils$1 = require('../../../data/api/utils.cjs');
29
30
  require('@dynamic-labs/locale');
30
31
  var dynamicContextProps = require('../../../store/state/dynamicContextProps/dynamicContextProps.cjs');
@@ -44,15 +45,16 @@ var utils = require('./utils.cjs');
44
45
 
45
46
  // Hook exposed to customers and used internally to fetch account balances
46
47
  const useTokenBalances = ({ accountAddress, networkId, chainName, tokenAddresses, includeFiat = false, includeNativeBalance = false, filterSpamTokens = true, forceRefresh = false, whitelistedContracts, } = {}) => {
47
- var _a;
48
+ var _a, _b;
48
49
  const tokenBalancesState = tokenBalances.useTokenBalancesState();
49
50
  const { tokenBalances: tokenBalances$1, isLoading, error } = tokenBalancesState;
50
51
  const { primaryWallet, network } = useInternalDynamicContext.useInternalDynamicContext();
51
52
  const walletConnector = primaryWallet === null || primaryWallet === void 0 ? void 0 : primaryWallet.connector;
52
- const requestAccount = accountAddress !== null && accountAddress !== void 0 ? accountAddress : primaryWallet === null || primaryWallet === void 0 ? void 0 : primaryWallet.address;
53
- const requestChainName = (_a = chainName !== null && chainName !== void 0 ? chainName : walletConnector === null || walletConnector === void 0 ? void 0 : walletConnector.connectedChain) !== null && _a !== void 0 ? _a : sdkApiCore.ChainEnum.Evm;
53
+ // Per-network address: balances must query the selected network's encoding.
54
+ const requestAccount = (_a = accountAddress !== null && accountAddress !== void 0 ? accountAddress : getEmbeddedBitcoinActiveAddress.getEmbeddedBitcoinActiveAddress(walletConnector)) !== null && _a !== void 0 ? _a : primaryWallet === null || primaryWallet === void 0 ? void 0 : primaryWallet.address;
55
+ const requestChainName = (_b = chainName !== null && chainName !== void 0 ? chainName : walletConnector === null || walletConnector === void 0 ? void 0 : walletConnector.connectedChain) !== null && _b !== void 0 ? _b : sdkApiCore.ChainEnum.Evm;
54
56
  const fetchAccountBalances = React.useCallback((...args_1) => _tslib.__awaiter(void 0, [...args_1], void 0, function* (forceRefresh = false) {
55
- var _b;
57
+ var _c;
56
58
  const user = getUserProfile.getUserProfile();
57
59
  const authMode$1 = authMode.getAuthMode();
58
60
  if (authMode$1 !== 'connect-only' && !user) {
@@ -61,7 +63,7 @@ const useTokenBalances = ({ accountAddress, networkId, chainName, tokenAddresses
61
63
  }
62
64
  const projectSettings = getProjectSettings.getProjectSettings();
63
65
  if (authMode$1 === 'connect-only' &&
64
- !((_b = projectSettings === null || projectSettings === void 0 ? void 0 : projectSettings.sdk.featureFlags) === null || _b === void 0 ? void 0 : _b.connectOnlyMultiAsset)) {
66
+ !((_c = projectSettings === null || projectSettings === void 0 ? void 0 : projectSettings.sdk.featureFlags) === null || _c === void 0 ? void 0 : _c.connectOnlyMultiAsset)) {
65
67
  return;
66
68
  }
67
69
  // the reason why we need to get the network here again instead of using the value
@@ -126,6 +128,8 @@ const useTokenBalances = ({ accountAddress, networkId, chainName, tokenAddresses
126
128
  filterSpamTokens,
127
129
  networkId,
128
130
  walletConnector,
131
+ // re-fetch when the picker's network changes (per-network addresses)
132
+ network,
129
133
  ]);
130
134
  useDynamicEvents.useInternalDynamicEvents('tokenBalancesChanged', () => fetchAccountBalances(true));
131
135
  React.useEffect(() => {
@@ -21,6 +21,7 @@ import '@dynamic-labs/multi-wallet';
21
21
  import 'react-international-phone';
22
22
  import '../../../store/state/nonce/nonce.js';
23
23
  import '@dynamic-labs-wallet/browser-wallet-client';
24
+ import { getEmbeddedBitcoinActiveAddress } from '../../functions/getEmbeddedBitcoinActiveAddress/getEmbeddedBitcoinActiveAddress.js';
24
25
  import { logResponseError } from '../../../data/api/utils.js';
25
26
  import '@dynamic-labs/locale';
26
27
  import { useDynamicSettings, getEnvironmentId } from '../../../store/state/dynamicContextProps/dynamicContextProps.js';
@@ -40,15 +41,16 @@ import { resolveNetworkIdForRequest, getTokenBalanceErrorMessage } from './utils
40
41
 
41
42
  // Hook exposed to customers and used internally to fetch account balances
42
43
  const useTokenBalances = ({ accountAddress, networkId, chainName, tokenAddresses, includeFiat = false, includeNativeBalance = false, filterSpamTokens = true, forceRefresh = false, whitelistedContracts, } = {}) => {
43
- var _a;
44
+ var _a, _b;
44
45
  const tokenBalancesState = useTokenBalancesState();
45
46
  const { tokenBalances, isLoading, error } = tokenBalancesState;
46
47
  const { primaryWallet, network } = useInternalDynamicContext();
47
48
  const walletConnector = primaryWallet === null || primaryWallet === void 0 ? void 0 : primaryWallet.connector;
48
- const requestAccount = accountAddress !== null && accountAddress !== void 0 ? accountAddress : primaryWallet === null || primaryWallet === void 0 ? void 0 : primaryWallet.address;
49
- const requestChainName = (_a = chainName !== null && chainName !== void 0 ? chainName : walletConnector === null || walletConnector === void 0 ? void 0 : walletConnector.connectedChain) !== null && _a !== void 0 ? _a : ChainEnum.Evm;
49
+ // Per-network address: balances must query the selected network's encoding.
50
+ const requestAccount = (_a = accountAddress !== null && accountAddress !== void 0 ? accountAddress : getEmbeddedBitcoinActiveAddress(walletConnector)) !== null && _a !== void 0 ? _a : primaryWallet === null || primaryWallet === void 0 ? void 0 : primaryWallet.address;
51
+ const requestChainName = (_b = chainName !== null && chainName !== void 0 ? chainName : walletConnector === null || walletConnector === void 0 ? void 0 : walletConnector.connectedChain) !== null && _b !== void 0 ? _b : ChainEnum.Evm;
50
52
  const fetchAccountBalances = useCallback((...args_1) => __awaiter(void 0, [...args_1], void 0, function* (forceRefresh = false) {
51
- var _b;
53
+ var _c;
52
54
  const user = getUserProfile();
53
55
  const authMode = getAuthMode();
54
56
  if (authMode !== 'connect-only' && !user) {
@@ -57,7 +59,7 @@ const useTokenBalances = ({ accountAddress, networkId, chainName, tokenAddresses
57
59
  }
58
60
  const projectSettings = getProjectSettings();
59
61
  if (authMode === 'connect-only' &&
60
- !((_b = projectSettings === null || projectSettings === void 0 ? void 0 : projectSettings.sdk.featureFlags) === null || _b === void 0 ? void 0 : _b.connectOnlyMultiAsset)) {
62
+ !((_c = projectSettings === null || projectSettings === void 0 ? void 0 : projectSettings.sdk.featureFlags) === null || _c === void 0 ? void 0 : _c.connectOnlyMultiAsset)) {
61
63
  return;
62
64
  }
63
65
  // the reason why we need to get the network here again instead of using the value
@@ -122,6 +124,8 @@ const useTokenBalances = ({ accountAddress, networkId, chainName, tokenAddresses
122
124
  filterSpamTokens,
123
125
  networkId,
124
126
  walletConnector,
127
+ // re-fetch when the picker's network changes (per-network addresses)
128
+ network,
125
129
  ]);
126
130
  useInternalDynamicEvents('tokenBalancesChanged', () => fetchAccountBalances(true));
127
131
  useEffect(() => {
@@ -171,13 +171,18 @@ const useHandleWalletItem = ({ allowAlreadyConnectedWallet, onQrCodeConnect, onC
171
171
  };
172
172
  const handleWalletItemClick = (wallet) => _tslib.__awaiter(void 0, void 0, void 0, function* () {
173
173
  const { walletConnector, isInstalledOnBrowser } = wallet;
174
+ // MetaMask SDK sets canHandleMultipleConnections=false for mobile/QR flows,
175
+ // but when the extension is installed on desktop it handles multiple
176
+ // connections natively — skip the session teardown in that case.
177
+ const isMetaMaskExtensionInstalled = walletConnector.key.startsWith('metamask') && isInstalledOnBrowser;
174
178
  const shouldEndWalletConnectorSession = walletConnector.connectedChain === 'FLOW' ||
175
179
  walletConnectorCore.isPhantomRedirectConnector(walletConnector) ||
176
180
  // because some wallet connectors only supports one connection at a time,
177
181
  // we need to end the session (disconnect) when the user selects
178
182
  // a different wallet to connect with
179
183
  // a new connection is established a few steps further down
180
- !walletConnector.canHandleMultipleConnections;
184
+ (!walletConnector.canHandleMultipleConnections &&
185
+ !isMetaMaskExtensionInstalled);
181
186
  logger.logger.logVerboseTroubleshootingMessage('[handleWalletItemClick]', {
182
187
  mockedSDK,
183
188
  shouldEndWalletConnectorSession,
@@ -167,13 +167,18 @@ const useHandleWalletItem = ({ allowAlreadyConnectedWallet, onQrCodeConnect, onC
167
167
  };
168
168
  const handleWalletItemClick = (wallet) => __awaiter(void 0, void 0, void 0, function* () {
169
169
  const { walletConnector, isInstalledOnBrowser } = wallet;
170
+ // MetaMask SDK sets canHandleMultipleConnections=false for mobile/QR flows,
171
+ // but when the extension is installed on desktop it handles multiple
172
+ // connections natively — skip the session teardown in that case.
173
+ const isMetaMaskExtensionInstalled = walletConnector.key.startsWith('metamask') && isInstalledOnBrowser;
170
174
  const shouldEndWalletConnectorSession = walletConnector.connectedChain === 'FLOW' ||
171
175
  isPhantomRedirectConnector(walletConnector) ||
172
176
  // because some wallet connectors only supports one connection at a time,
173
177
  // we need to end the session (disconnect) when the user selects
174
178
  // a different wallet to connect with
175
179
  // a new connection is established a few steps further down
176
- !walletConnector.canHandleMultipleConnections;
180
+ (!walletConnector.canHandleMultipleConnections &&
181
+ !isMetaMaskExtensionInstalled);
177
182
  logger.logVerboseTroubleshootingMessage('[handleWalletItemClick]', {
178
183
  mockedSDK,
179
184
  shouldEndWalletConnectorSession,
@@ -61,7 +61,7 @@ const TABS = [
61
61
  ];
62
62
  const ActiveMidnightWalletBalance = ({ isLoading = false, }) => {
63
63
  const { t } = reactI18next.useTranslation();
64
- const { primaryWallet } = useInternalDynamicContext.useInternalDynamicContext();
64
+ const { primaryWallet, network } = useInternalDynamicContext.useInternalDynamicContext();
65
65
  const [activeTab, setActiveTab] = React.useState('shielded');
66
66
  const walletAddress = primaryWallet === null || primaryWallet === void 0 ? void 0 : primaryWallet.address;
67
67
  const enabled = Boolean(!isLoading && primaryWallet && walletConnectorCore.isMidnightConnector(primaryWallet.connector));
@@ -77,27 +77,57 @@ const ActiveMidnightWalletBalance = ({ isLoading = false, }) => {
77
77
  }
78
78
  return primaryWallet.getFormattedBalances();
79
79
  }), {
80
- deps: [walletAddress, primaryWallet, dustPollTick],
80
+ // `network` is in the deps so switching the picker re-fetches against the
81
+ // newly-selected network (the connector passes it through to the wallet
82
+ // client, which syncs/serves that network's balances). `balancesLoading`
83
+ // then drives the syncing/skeleton state while the new network syncs.
84
+ deps: [walletAddress, primaryWallet, dustPollTick, network],
81
85
  enabled,
82
86
  });
83
87
  const { dustBalance, shieldedTokenCount, unshieldedBalance, dustSyncing } = balances !== null && balances !== void 0 ? balances : {};
88
+ // Track which network the currently-held `balances` belong to. On a network
89
+ // switch `usePromise` keeps the previous network's data while it refetches, so
90
+ // without this we'd briefly show the old network's balance. We treat the view
91
+ // as "switching" until a fetch COMPLETES for the selected network — gated on
92
+ // the loading true→false transition: at the switch commit this effect still
93
+ // sees the previous fetch's settled `balancesLoading=false` in its closure,
94
+ // so an ungated update would re-arm immediately and let the stale value
95
+ // render. Background dust polls don't change `network`, so they never
96
+ // trigger this.
97
+ const [balancesNetwork, setBalancesNetwork] = React.useState(network);
98
+ const balancesWereLoadingRef = React.useRef(balancesLoading);
84
99
  React.useEffect(() => {
85
- if (!dustSyncing)
100
+ if (balancesWereLoadingRef.current && !balancesLoading && balances) {
101
+ setBalancesNetwork(network);
102
+ }
103
+ balancesWereLoadingRef.current = balancesLoading;
104
+ }, [balancesLoading, balances, network]);
105
+ const isNetworkSwitching = Boolean(balances) && balancesNetwork !== network;
106
+ React.useEffect(() => {
107
+ // Don't poll while switching networks: the held `dustSyncing` still reflects
108
+ // the previous network, and the new network's wallet is (re)syncing — extra
109
+ // polls would just pile redundant balance reads onto the switch.
110
+ if (!dustSyncing || isNetworkSwitching)
86
111
  return undefined;
87
112
  // Poll frequently while syncing so the gauge appears within a couple
88
113
  // seconds of dust completing (sync now finishes in ~4s). The effect stops
89
114
  // the moment `dustSyncing` flips false, so this adds no steady-state load.
90
115
  const id = setInterval(() => setDustPollTick((tick) => tick + 1), 2500);
91
116
  return () => clearInterval(id);
92
- }, [dustSyncing]);
117
+ }, [dustSyncing, isNetworkSwitching]);
93
118
  // `registerDust` is a WaaS-only connector method (external/injected Midnight
94
119
  // wallets manage dust themselves). When the embedded wallet has no dust
95
120
  // registered yet, we surface a "Register DUST" button instead of the gauge.
96
121
  const isWaasWallet = Boolean(primaryWallet && walletConnectorCore.isDynamicWaasConnector(primaryWallet.connector));
97
122
  // True once registered. balance/cap can't tell us this on load (cap reads 0
98
123
  // on current wallet-client builds), so we track it after a register call to
99
- // flip from the "Register DUST" prompt to the generating state.
124
+ // flip from the "Register DUST" prompt to the generating state. Dust is
125
+ // per-network, so a switch resets the flag — otherwise the register prompt
126
+ // stays hidden on a network where dust was never registered.
100
127
  const [dustRegistered, setDustRegistered] = React.useState(false);
128
+ React.useEffect(() => {
129
+ setDustRegistered(false);
130
+ }, [network]);
101
131
  // Returns the promise so <Button> shows its built-in spinner while the heavy
102
132
  // registerDust call runs.
103
133
  const handleRegisterDust = () => _tslib.__awaiter(void 0, void 0, void 0, function* () {
@@ -118,9 +148,11 @@ const ActiveMidnightWalletBalance = ({ isLoading = false, }) => {
118
148
  logger.logger.error('Failed to register dust', err);
119
149
  }
120
150
  });
121
- // Only show the full skeleton on the very first load; background dust polls
122
- // keep `balances` populated so the view never flashes back to a skeleton.
123
- if ((isLoading || balancesLoading) && !balances) {
151
+ // Show the full skeleton on the very first load AND while switching networks
152
+ // (so the previous network's stale value isn't shown until the new one
153
+ // arrives). Background dust polls keep `balances` for the same network, so
154
+ // they never flash the skeleton.
155
+ if (((isLoading || balancesLoading) && !balances) || isNetworkSwitching) {
124
156
  return (jsxRuntime.jsx("div", { className: 'active-midnight-balance', children: jsxRuntime.jsx(Skeleton.Skeleton, { className: 'active-midnight-balance__skeleton' }) }));
125
157
  }
126
158
  const dustFill = dustBalance
@@ -1,7 +1,7 @@
1
1
  'use client'
2
2
  import { __awaiter } from '../../../../../../_virtual/_tslib.js';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
- import { useState, useEffect } from 'react';
4
+ import { useState, useRef, useEffect } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
  import { isMidnightConnector, isMidnightWallet, isDynamicWaasConnector } from '@dynamic-labs/wallet-connector-core';
7
7
  import { Button } from '../../../../components/Button/Button.js';
@@ -57,7 +57,7 @@ const TABS = [
57
57
  ];
58
58
  const ActiveMidnightWalletBalance = ({ isLoading = false, }) => {
59
59
  const { t } = useTranslation();
60
- const { primaryWallet } = useInternalDynamicContext();
60
+ const { primaryWallet, network } = useInternalDynamicContext();
61
61
  const [activeTab, setActiveTab] = useState('shielded');
62
62
  const walletAddress = primaryWallet === null || primaryWallet === void 0 ? void 0 : primaryWallet.address;
63
63
  const enabled = Boolean(!isLoading && primaryWallet && isMidnightConnector(primaryWallet.connector));
@@ -73,27 +73,57 @@ const ActiveMidnightWalletBalance = ({ isLoading = false, }) => {
73
73
  }
74
74
  return primaryWallet.getFormattedBalances();
75
75
  }), {
76
- deps: [walletAddress, primaryWallet, dustPollTick],
76
+ // `network` is in the deps so switching the picker re-fetches against the
77
+ // newly-selected network (the connector passes it through to the wallet
78
+ // client, which syncs/serves that network's balances). `balancesLoading`
79
+ // then drives the syncing/skeleton state while the new network syncs.
80
+ deps: [walletAddress, primaryWallet, dustPollTick, network],
77
81
  enabled,
78
82
  });
79
83
  const { dustBalance, shieldedTokenCount, unshieldedBalance, dustSyncing } = balances !== null && balances !== void 0 ? balances : {};
84
+ // Track which network the currently-held `balances` belong to. On a network
85
+ // switch `usePromise` keeps the previous network's data while it refetches, so
86
+ // without this we'd briefly show the old network's balance. We treat the view
87
+ // as "switching" until a fetch COMPLETES for the selected network — gated on
88
+ // the loading true→false transition: at the switch commit this effect still
89
+ // sees the previous fetch's settled `balancesLoading=false` in its closure,
90
+ // so an ungated update would re-arm immediately and let the stale value
91
+ // render. Background dust polls don't change `network`, so they never
92
+ // trigger this.
93
+ const [balancesNetwork, setBalancesNetwork] = useState(network);
94
+ const balancesWereLoadingRef = useRef(balancesLoading);
80
95
  useEffect(() => {
81
- if (!dustSyncing)
96
+ if (balancesWereLoadingRef.current && !balancesLoading && balances) {
97
+ setBalancesNetwork(network);
98
+ }
99
+ balancesWereLoadingRef.current = balancesLoading;
100
+ }, [balancesLoading, balances, network]);
101
+ const isNetworkSwitching = Boolean(balances) && balancesNetwork !== network;
102
+ useEffect(() => {
103
+ // Don't poll while switching networks: the held `dustSyncing` still reflects
104
+ // the previous network, and the new network's wallet is (re)syncing — extra
105
+ // polls would just pile redundant balance reads onto the switch.
106
+ if (!dustSyncing || isNetworkSwitching)
82
107
  return undefined;
83
108
  // Poll frequently while syncing so the gauge appears within a couple
84
109
  // seconds of dust completing (sync now finishes in ~4s). The effect stops
85
110
  // the moment `dustSyncing` flips false, so this adds no steady-state load.
86
111
  const id = setInterval(() => setDustPollTick((tick) => tick + 1), 2500);
87
112
  return () => clearInterval(id);
88
- }, [dustSyncing]);
113
+ }, [dustSyncing, isNetworkSwitching]);
89
114
  // `registerDust` is a WaaS-only connector method (external/injected Midnight
90
115
  // wallets manage dust themselves). When the embedded wallet has no dust
91
116
  // registered yet, we surface a "Register DUST" button instead of the gauge.
92
117
  const isWaasWallet = Boolean(primaryWallet && isDynamicWaasConnector(primaryWallet.connector));
93
118
  // True once registered. balance/cap can't tell us this on load (cap reads 0
94
119
  // on current wallet-client builds), so we track it after a register call to
95
- // flip from the "Register DUST" prompt to the generating state.
120
+ // flip from the "Register DUST" prompt to the generating state. Dust is
121
+ // per-network, so a switch resets the flag — otherwise the register prompt
122
+ // stays hidden on a network where dust was never registered.
96
123
  const [dustRegistered, setDustRegistered] = useState(false);
124
+ useEffect(() => {
125
+ setDustRegistered(false);
126
+ }, [network]);
97
127
  // Returns the promise so <Button> shows its built-in spinner while the heavy
98
128
  // registerDust call runs.
99
129
  const handleRegisterDust = () => __awaiter(void 0, void 0, void 0, function* () {
@@ -114,9 +144,11 @@ const ActiveMidnightWalletBalance = ({ isLoading = false, }) => {
114
144
  logger.error('Failed to register dust', err);
115
145
  }
116
146
  });
117
- // Only show the full skeleton on the very first load; background dust polls
118
- // keep `balances` populated so the view never flashes back to a skeleton.
119
- if ((isLoading || balancesLoading) && !balances) {
147
+ // Show the full skeleton on the very first load AND while switching networks
148
+ // (so the previous network's stale value isn't shown until the new one
149
+ // arrives). Background dust polls keep `balances` for the same network, so
150
+ // they never flash the skeleton.
151
+ if (((isLoading || balancesLoading) && !balances) || isNetworkSwitching) {
120
152
  return (jsx("div", { className: 'active-midnight-balance', children: jsx(Skeleton, { className: 'active-midnight-balance__skeleton' }) }));
121
153
  }
122
154
  const dustFill = dustBalance
@@ -128,12 +128,26 @@ const MIDNIGHT_ADDRESS_TYPE_UNSHIELDED = 'midnight_unshielded';
128
128
  const MIDNIGHT_ADDRESS_TYPE_DUST = 'midnight_dust';
129
129
  const MIDNIGHT_ADDRESS_TYPE_SHIELDED = 'midnight_shielded';
130
130
  /**
131
- * Gets a Midnight address from the wallet's additional addresses by type.
131
+ * Gets a Midnight address from the wallet's additional addresses by type,
132
+ * preferring the variant tagged with the selected network.
133
+ *
134
+ * Midnight stores one address per (type, network) — e.g. unshielded has both a
135
+ * mainnet and a preview encoding of the same key. When the user switches the
136
+ * network picker we surface the address tagged with that network. Legacy wallets
137
+ * created before per-network tagging have no `network`, so we fall back to the
138
+ * first address of the requested type.
139
+ *
132
140
  * @param type - The address type to find
141
+ * @param network - The selected network tag ('mainnet' | 'preview')
133
142
  * @param wallet - The wallet containing additional addresses
134
143
  * @returns The address string or undefined if not found
135
144
  */
136
- const getMidnightAddressByType = (type, wallet) => { var _a, _b; return (_b = (_a = wallet === null || wallet === void 0 ? void 0 : wallet.additionalAddresses) === null || _a === void 0 ? void 0 : _a.find((addr) => addr.type === type)) === null || _b === void 0 ? void 0 : _b.address; };
145
+ const getMidnightAddressByType = (type, network, wallet) => {
146
+ var _a, _b, _c;
147
+ const matches = (_b = (_a = wallet === null || wallet === void 0 ? void 0 : wallet.additionalAddresses) === null || _a === void 0 ? void 0 : _a.filter((addr) => addr.type === type)) !== null && _b !== void 0 ? _b : [];
148
+ const networkMatch = matches.find((addr) => addr.network === network);
149
+ return (_c = (networkMatch !== null && networkMatch !== void 0 ? networkMatch : matches[0])) === null || _c === void 0 ? void 0 : _c.address;
150
+ };
137
151
  /**
138
152
  * Component for displaying Midnight wallet addresses.
139
153
  * Midnight supports multiple address types:
@@ -141,12 +155,12 @@ const getMidnightAddressByType = (type, wallet) => { var _a, _b; return (_b = (_
141
155
  * - Dust: For gas/fee management
142
156
  * - Shielded: For private transfers (Phase 2)
143
157
  */
144
- const ActiveMidnightWalletAddresses = ({ menuOption, wallet, }) => {
158
+ const ActiveMidnightWalletAddresses = ({ menuOption, wallet, network, }) => {
145
159
  var _a;
146
160
  const { t } = reactI18next.useTranslation();
147
- const unshieldedAddress = (_a = getMidnightAddressByType(MIDNIGHT_ADDRESS_TYPE_UNSHIELDED, wallet)) !== null && _a !== void 0 ? _a : wallet === null || wallet === void 0 ? void 0 : wallet.address;
148
- const dustAddress = getMidnightAddressByType(MIDNIGHT_ADDRESS_TYPE_DUST, wallet);
149
- const shieldedAddress = getMidnightAddressByType(MIDNIGHT_ADDRESS_TYPE_SHIELDED, wallet);
161
+ const unshieldedAddress = (_a = getMidnightAddressByType(MIDNIGHT_ADDRESS_TYPE_UNSHIELDED, network, wallet)) !== null && _a !== void 0 ? _a : wallet === null || wallet === void 0 ? void 0 : wallet.address;
162
+ const dustAddress = getMidnightAddressByType(MIDNIGHT_ADDRESS_TYPE_DUST, network, wallet);
163
+ const shieldedAddress = getMidnightAddressByType(MIDNIGHT_ADDRESS_TYPE_SHIELDED, network, wallet);
150
164
  // Collapse dust + shielded behind a toggle so the header shows only the
151
165
  // primary (unshielded) address by default — the user reveals the rest on
152
166
  // click, instead of all three being shown at once.
@@ -4,6 +4,12 @@ import { Wallet } from '../../../../../shared';
4
4
  type Props = {
5
5
  menuOption: MenuOption[];
6
6
  wallet?: Wallet;
7
+ /**
8
+ * The selected Midnight network tag ('mainnet' | 'preview'). Midnight is one
9
+ * wallet (one MPC key) that exposes a per-network address for each type, so we
10
+ * select the variant matching the network chosen in the picker.
11
+ */
12
+ network?: string;
7
13
  };
8
14
  /**
9
15
  * Component for displaying Midnight wallet addresses.
@@ -12,5 +18,5 @@ type Props = {
12
18
  * - Dust: For gas/fee management
13
19
  * - Shielded: For private transfers (Phase 2)
14
20
  */
15
- export declare const ActiveMidnightWalletAddresses: ({ menuOption, wallet, }: Props) => JSX.Element;
21
+ export declare const ActiveMidnightWalletAddresses: ({ menuOption, wallet, network, }: Props) => JSX.Element;
16
22
  export {};