@dynamic-labs/sdk-react-core 4.92.0 → 4.92.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,4 +1,15 @@
1
1
 
2
+ ### [4.92.1](https://github.com/dynamic-labs/dynamic-auth/compare/v4.92.0...v4.92.1) (2026-07-09)
3
+
4
+
5
+ ### Bug Fixes
6
+
7
+ * **ethereum:** fire in-app-browser redirect on sign for injected EVM wallets that use in-app-browser experience ([#11888](https://github.com/dynamic-labs/dynamic-auth/issues/11888)) ([5116d91](https://github.com/dynamic-labs/dynamic-auth/commit/5116d91ab2c71000a490d72c5d278549d4d8ae9a))
8
+ * **sdk-react-core:** show clear message for Phantom redirect signing failures (v4 backport) ([#11886](https://github.com/dynamic-labs/dynamic-auth/issues/11886)) ([60ac897](https://github.com/dynamic-labs/dynamic-auth/commit/60ac897ba81f7758ccd7a05f97b619785e1f50a7))
9
+ * **social:** use redirect strategy for step-up OAuth on mobile [V4 backport] ([#11881](https://github.com/dynamic-labs/dynamic-auth/issues/11881)) ([42a202e](https://github.com/dynamic-labs/dynamic-auth/commit/42a202e6255de8db73df02eff37c0b10b24fef1a))
10
+ * **solana:** fire in-app-browser redirect on sign and wallet switch ([#11871](https://github.com/dynamic-labs/dynamic-auth/issues/11871)) ([4e2d6b7](https://github.com/dynamic-labs/dynamic-auth/commit/4e2d6b7ac8fdef5bf7c62b8a726ecaef1c6210ec))
11
+ * **solana:** resolve Phantom/Backpack provider collision and Backpack >=0.10.x connect hangs ([#11856](https://github.com/dynamic-labs/dynamic-auth/issues/11856)) ([#11882](https://github.com/dynamic-labs/dynamic-auth/issues/11882)) ([5784aff](https://github.com/dynamic-labs/dynamic-auth/commit/5784aff9641bbc85445fb466f3ab1d6a14331e73))
12
+
2
13
  ## [4.92.0](https://github.com/dynamic-labs/dynamic-auth/compare/v4.91.6...v4.92.0) (2026-07-08)
3
14
 
4
15
 
package/package.cjs CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  Object.defineProperty(exports, '__esModule', { value: true });
5
5
 
6
- var version = "4.92.0";
6
+ var version = "4.92.1";
7
7
  var dependencies = {
8
8
  "@dynamic-labs/sdk-api-core": "0.0.1067",
9
9
  "@dynamic-labs-sdk/client": "1.18.0",
package/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  'use client'
2
- var version = "4.92.0";
2
+ var version = "4.92.1";
3
3
  var dependencies = {
4
4
  "@dynamic-labs/sdk-api-core": "0.0.1067",
5
5
  "@dynamic-labs-sdk/client": "1.18.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dynamic-labs/sdk-react-core",
3
- "version": "4.92.0",
3
+ "version": "4.92.1",
4
4
  "dependencies": {
5
5
  "@dynamic-labs/sdk-api-core": "0.0.1067",
6
6
  "@dynamic-labs-sdk/client": "1.18.0",
@@ -18,17 +18,17 @@
18
18
  "react-i18next": "13.5.0",
19
19
  "react-international-phone": "4.5.0",
20
20
  "yup": "0.32.11",
21
- "@dynamic-labs/assert-package-version": "4.92.0",
22
- "@dynamic-labs/iconic": "4.92.0",
23
- "@dynamic-labs/locale": "4.92.0",
24
- "@dynamic-labs/logger": "4.92.0",
25
- "@dynamic-labs/multi-wallet": "4.92.0",
26
- "@dynamic-labs/rpc-providers": "4.92.0",
27
- "@dynamic-labs/store": "4.92.0",
28
- "@dynamic-labs/types": "4.92.0",
29
- "@dynamic-labs/utils": "4.92.0",
30
- "@dynamic-labs/wallet-book": "4.92.0",
31
- "@dynamic-labs/wallet-connector-core": "4.92.0",
21
+ "@dynamic-labs/assert-package-version": "4.92.1",
22
+ "@dynamic-labs/iconic": "4.92.1",
23
+ "@dynamic-labs/locale": "4.92.1",
24
+ "@dynamic-labs/logger": "4.92.1",
25
+ "@dynamic-labs/multi-wallet": "4.92.1",
26
+ "@dynamic-labs/rpc-providers": "4.92.1",
27
+ "@dynamic-labs/store": "4.92.1",
28
+ "@dynamic-labs/types": "4.92.1",
29
+ "@dynamic-labs/utils": "4.92.1",
30
+ "@dynamic-labs/wallet-book": "4.92.1",
31
+ "@dynamic-labs/wallet-connector-core": "4.92.1",
32
32
  "eventemitter3": "5.0.1"
33
33
  },
34
34
  "devDependencies": {
@@ -16,6 +16,14 @@ const UNLOCK_WALLET_MESSAGE = 'Please unlock your wallet extension and try again
16
16
  const INTERNAL_ERROR_MESSAGE = 'There was an internal error. Please try again.';
17
17
  const AUTHENTICATION_FAILED_MESSAGE = 'Authentication failed. Please try again.';
18
18
  const SESSION_TIMEOUT_MESSAGE = 'Session time out. Please try again.';
19
+ const PHANTOM_SIGNING_FAILED_MESSAGE = "Phantom couldn't sign with the selected account. Switch accounts in Phantom or update the app, then try again.";
20
+ /**
21
+ * Error code used when Phantom relays an internal signing failure via its
22
+ * deep-link redirect (e.g. the active account has no usable key to sign).
23
+ * Phantom's own code for these is generic (e.g. `500`), so we normalize to
24
+ * this key to surface an actionable message instead of the default fallback.
25
+ */
26
+ const PHANTOM_SIGNING_FAILED_ERROR_CODE = 'phantom_signing_failed';
19
27
  let _errorMessageMap;
20
28
  const getErrorMessageMap = () => {
21
29
  if (!_errorMessageMap) {
@@ -32,13 +40,14 @@ const getErrorMessageMap = () => {
32
40
  4900: INTERNAL_ERROR_MESSAGE,
33
41
  5001: MESSAGE_SIGNATURE_DENIED,
34
42
  5002: MESSAGE_SIGNATURE_DENIED,
35
- account_already_linked_to_different_profile: 'This social account is already linked to a different profile.',
43
+ [PHANTOM_SIGNING_FAILED_ERROR_CODE]: PHANTOM_SIGNING_FAILED_MESSAGE,
36
44
  [utils.SdkErrorCode.CONNECTION_REJECTED]: 'Connection rejected. Please try again.',
37
- general_error: DEFAULT_ERROR_MESSAGE,
45
+ account_already_linked_to_different_profile: 'This social account is already linked to a different profile.',
38
46
  [utils.SdkErrorCode.INVALID_EMAIL_VERIFICATION]: 'The verification link is no longer valid. Please start again.',
47
+ general_error: DEFAULT_ERROR_MESSAGE,
39
48
  invalid_provider: 'This sign-in provider is not supported.',
40
- metamask_timeout: 'Whoops. Looks like something went wrong. Please try again.',
41
49
  [utils.SdkErrorCode.MISSING_NONCE]: DEFAULT_ERROR_MESSAGE,
50
+ metamask_timeout: 'Whoops. Looks like something went wrong. Please try again.',
42
51
  'missing-public-address': 'Connection cancelled. Please try again',
43
52
  no_account_linked: 'No linked account was found for this provider.',
44
53
  no_auth_code: AUTHENTICATION_FAILED_MESSAGE,
@@ -64,6 +73,12 @@ const getErrorMessageMap = () => {
64
73
  }
65
74
  return _errorMessageMap;
66
75
  };
76
+ /**
77
+ * Returns the mapped, user-facing message for a given error code, or
78
+ * `undefined` when the code is not recognized. Exported so callers can decide
79
+ * whether to normalize an unmapped code before displaying it.
80
+ */
81
+ const getMappedErrorMessage = (status) => getErrorMessageMap()[status];
67
82
  const ErrorContext = React.createContext(undefined);
68
83
  const ErrorContextProvider = ({ children }) => {
69
84
  /** The error message as a string, used as a fallback if translation is not available */
@@ -117,4 +132,6 @@ const useErrorContext = createProviderHook.createProviderHook(ErrorContext, 'Err
117
132
 
118
133
  exports.ErrorContext = ErrorContext;
119
134
  exports.ErrorContextProvider = ErrorContextProvider;
135
+ exports.PHANTOM_SIGNING_FAILED_ERROR_CODE = PHANTOM_SIGNING_FAILED_ERROR_CODE;
136
+ exports.getMappedErrorMessage = getMappedErrorMessage;
120
137
  exports.useErrorContext = useErrorContext;
@@ -1,5 +1,12 @@
1
1
  import { ReactNode } from 'react';
2
2
  import { ErrorCode } from '@dynamic-labs/utils';
3
+ /**
4
+ * Error code used when Phantom relays an internal signing failure via its
5
+ * deep-link redirect (e.g. the active account has no usable key to sign).
6
+ * Phantom's own code for these is generic (e.g. `500`), so we normalize to
7
+ * this key to surface an actionable message instead of the default fallback.
8
+ */
9
+ export declare const PHANTOM_SIGNING_FAILED_ERROR_CODE = "phantom_signing_failed";
3
10
  interface ErrorContextProps {
4
11
  error: string | undefined;
5
12
  errorCode: ErrorCode | string | undefined;
@@ -8,6 +15,12 @@ interface ErrorContextProps {
8
15
  setDefaultError: (error?: unknown) => void;
9
16
  clearError(): void;
10
17
  }
18
+ /**
19
+ * Returns the mapped, user-facing message for a given error code, or
20
+ * `undefined` when the code is not recognized. Exported so callers can decide
21
+ * whether to normalize an unmapped code before displaying it.
22
+ */
23
+ export declare const getMappedErrorMessage: (status: string) => string | undefined;
11
24
  export declare const ErrorContext: import("react").Context<ErrorContextProps | undefined>;
12
25
  export declare const ErrorContextProvider: ({ children }: {
13
26
  children: ReactNode;
@@ -12,6 +12,14 @@ const UNLOCK_WALLET_MESSAGE = 'Please unlock your wallet extension and try again
12
12
  const INTERNAL_ERROR_MESSAGE = 'There was an internal error. Please try again.';
13
13
  const AUTHENTICATION_FAILED_MESSAGE = 'Authentication failed. Please try again.';
14
14
  const SESSION_TIMEOUT_MESSAGE = 'Session time out. Please try again.';
15
+ const PHANTOM_SIGNING_FAILED_MESSAGE = "Phantom couldn't sign with the selected account. Switch accounts in Phantom or update the app, then try again.";
16
+ /**
17
+ * Error code used when Phantom relays an internal signing failure via its
18
+ * deep-link redirect (e.g. the active account has no usable key to sign).
19
+ * Phantom's own code for these is generic (e.g. `500`), so we normalize to
20
+ * this key to surface an actionable message instead of the default fallback.
21
+ */
22
+ const PHANTOM_SIGNING_FAILED_ERROR_CODE = 'phantom_signing_failed';
15
23
  let _errorMessageMap;
16
24
  const getErrorMessageMap = () => {
17
25
  if (!_errorMessageMap) {
@@ -28,13 +36,14 @@ const getErrorMessageMap = () => {
28
36
  4900: INTERNAL_ERROR_MESSAGE,
29
37
  5001: MESSAGE_SIGNATURE_DENIED,
30
38
  5002: MESSAGE_SIGNATURE_DENIED,
31
- account_already_linked_to_different_profile: 'This social account is already linked to a different profile.',
39
+ [PHANTOM_SIGNING_FAILED_ERROR_CODE]: PHANTOM_SIGNING_FAILED_MESSAGE,
32
40
  [SdkErrorCode.CONNECTION_REJECTED]: 'Connection rejected. Please try again.',
33
- general_error: DEFAULT_ERROR_MESSAGE,
41
+ account_already_linked_to_different_profile: 'This social account is already linked to a different profile.',
34
42
  [SdkErrorCode.INVALID_EMAIL_VERIFICATION]: 'The verification link is no longer valid. Please start again.',
43
+ general_error: DEFAULT_ERROR_MESSAGE,
35
44
  invalid_provider: 'This sign-in provider is not supported.',
36
- metamask_timeout: 'Whoops. Looks like something went wrong. Please try again.',
37
45
  [SdkErrorCode.MISSING_NONCE]: DEFAULT_ERROR_MESSAGE,
46
+ metamask_timeout: 'Whoops. Looks like something went wrong. Please try again.',
38
47
  'missing-public-address': 'Connection cancelled. Please try again',
39
48
  no_account_linked: 'No linked account was found for this provider.',
40
49
  no_auth_code: AUTHENTICATION_FAILED_MESSAGE,
@@ -60,6 +69,12 @@ const getErrorMessageMap = () => {
60
69
  }
61
70
  return _errorMessageMap;
62
71
  };
72
+ /**
73
+ * Returns the mapped, user-facing message for a given error code, or
74
+ * `undefined` when the code is not recognized. Exported so callers can decide
75
+ * whether to normalize an unmapped code before displaying it.
76
+ */
77
+ const getMappedErrorMessage = (status) => getErrorMessageMap()[status];
63
78
  const ErrorContext = createContext(undefined);
64
79
  const ErrorContextProvider = ({ children }) => {
65
80
  /** The error message as a string, used as a fallback if translation is not available */
@@ -111,4 +126,4 @@ const ErrorContextProvider = ({ children }) => {
111
126
  };
112
127
  const useErrorContext = createProviderHook(ErrorContext, 'ErrorContextProvider');
113
128
 
114
- export { ErrorContext, ErrorContextProvider, useErrorContext };
129
+ export { ErrorContext, ErrorContextProvider, PHANTOM_SIGNING_FAILED_ERROR_CODE, getMappedErrorMessage, useErrorContext };
@@ -379,8 +379,17 @@ const useResponseHandlers = () => {
379
379
  return;
380
380
  }
381
381
  instrumentAuthLoginFailed.instrumentAuthLoginFailed({ code: errorCode, message: errorMessage }, { authOrigin: 'phantom-redirect' });
382
- setErrorMessage(errorCode, undefined, 'phantom-redirect');
383
382
  const method = phantomRedirectConnector.consumeMethod();
383
+ // Phantom relays its own signing failures back through the redirect URL
384
+ // (e.g. the active account has no usable key to sign). These come with a
385
+ // generic, unmapped code, which would otherwise surface the default
386
+ // "Something went wrong" message. When the failed operation was a signing
387
+ // request, normalize unmapped codes to an actionable Phantom message.
388
+ const isUnmappedSigningError = method !== undefined && ErrorContext.getMappedErrorMessage(errorCode) === undefined;
389
+ const errorMessageKey = isUnmappedSigningError
390
+ ? ErrorContext.PHANTOM_SIGNING_FAILED_ERROR_CODE
391
+ : errorCode;
392
+ setErrorMessage(errorMessageKey, errorMessage, 'phantom-redirect');
384
393
  logger.logger.logVerboseTroubleshootingMessage('[PhantomRedirect handleErrorResponse] consumed method, emitting error', { errorCode, errorMessage, method });
385
394
  switch (method) {
386
395
  case 'signAndSendTransaction':
@@ -10,7 +10,7 @@ import '../DynamicContext/useDynamicContext/useDynamicContext.js';
10
10
  import { useInternalDynamicContext } from '../DynamicContext/useDynamicContext/useInternalDynamicContext/useInternalDynamicContext.js';
11
11
  import { useCaptchaContext } from '../CaptchaContext/CaptchaContext.js';
12
12
  import '@dynamic-labs/sdk-api-core';
13
- import { useErrorContext } from '../ErrorContext/ErrorContext.js';
13
+ import { useErrorContext, getMappedErrorMessage, PHANTOM_SIGNING_FAILED_ERROR_CODE } from '../ErrorContext/ErrorContext.js';
14
14
  import { useViewContext } from '../ViewContext/ViewContext.js';
15
15
  import '@dynamic-labs/iconic';
16
16
  import 'react/jsx-runtime';
@@ -375,8 +375,17 @@ const useResponseHandlers = () => {
375
375
  return;
376
376
  }
377
377
  instrumentAuthLoginFailed({ code: errorCode, message: errorMessage }, { authOrigin: 'phantom-redirect' });
378
- setErrorMessage(errorCode, undefined, 'phantom-redirect');
379
378
  const method = phantomRedirectConnector.consumeMethod();
379
+ // Phantom relays its own signing failures back through the redirect URL
380
+ // (e.g. the active account has no usable key to sign). These come with a
381
+ // generic, unmapped code, which would otherwise surface the default
382
+ // "Something went wrong" message. When the failed operation was a signing
383
+ // request, normalize unmapped codes to an actionable Phantom message.
384
+ const isUnmappedSigningError = method !== undefined && getMappedErrorMessage(errorCode) === undefined;
385
+ const errorMessageKey = isUnmappedSigningError
386
+ ? PHANTOM_SIGNING_FAILED_ERROR_CODE
387
+ : errorCode;
388
+ setErrorMessage(errorMessageKey, errorMessage, 'phantom-redirect');
380
389
  logger.logVerboseTroubleshootingMessage('[PhantomRedirect handleErrorResponse] consumed method, emitting error', { errorCode, errorMessage, method });
381
390
  switch (method) {
382
391
  case 'signAndSendTransaction':
@@ -394,7 +394,13 @@ const useSocialAuth = ({ onSettled, onError, onFarcasterUrl, }) => {
394
394
  }
395
395
  const isMobile = utils.isMobile();
396
396
  try {
397
- const effectiveStrategy = authMode === 'stepup' || forcePopup ? 'popup' : strategy;
397
+ // Step-up normally forces a popup, but on mobile browsers popups
398
+ // opened after an async gap (e.g. captcha verification) lose user
399
+ // activation and are blocked (notably iOS Safari). Fall back to the
400
+ // configured strategy (redirect) on mobile so the flow can resume.
401
+ const effectiveStrategy = (authMode === 'stepup' && !isMobile) || forcePopup
402
+ ? 'popup'
403
+ : strategy;
398
404
  const authCode = yield utils.Oauth2Service.getOauthCode({
399
405
  apiProvider: getProviderByType.getProviderByType((_k = projectSettings === null || projectSettings === void 0 ? void 0 : projectSettings.providers) !== null && _k !== void 0 ? _k : [], provider),
400
406
  getOAuthResultFromApi: () => oauth.getOAuthResult(environmentId, provider, {
@@ -390,7 +390,13 @@ const useSocialAuth = ({ onSettled, onError, onFarcasterUrl, }) => {
390
390
  }
391
391
  const isMobile$1 = isMobile();
392
392
  try {
393
- const effectiveStrategy = authMode === 'stepup' || forcePopup ? 'popup' : strategy;
393
+ // Step-up normally forces a popup, but on mobile browsers popups
394
+ // opened after an async gap (e.g. captcha verification) lose user
395
+ // activation and are blocked (notably iOS Safari). Fall back to the
396
+ // configured strategy (redirect) on mobile so the flow can resume.
397
+ const effectiveStrategy = (authMode === 'stepup' && !isMobile$1) || forcePopup
398
+ ? 'popup'
399
+ : strategy;
394
400
  const authCode = yield Oauth2Service.getOauthCode({
395
401
  apiProvider: getProviderByType((_k = projectSettings === null || projectSettings === void 0 ? void 0 : projectSettings.providers) !== null && _k !== void 0 ? _k : [], provider),
396
402
  getOAuthResultFromApi: () => getOAuthResult(environmentId, provider, {
@@ -5,6 +5,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
5
5
 
6
6
  var _tslib = require('../../../../../_virtual/_tslib.cjs');
7
7
  var React = require('react');
8
+ var utils = require('@dynamic-labs/utils');
8
9
  var walletConnectorCore = require('@dynamic-labs/wallet-connector-core');
9
10
  require('../../../context/DynamicContext/useDynamicContext/useDynamicContext.cjs');
10
11
  var useInternalDynamicContext = require('../../../context/DynamicContext/useDynamicContext/useInternalDynamicContext/useInternalDynamicContext.cjs');
@@ -49,6 +50,19 @@ const useSwitchWallet = () => {
49
50
  if (!wallet.connector.isAvailable) {
50
51
  throw new Error("Cannot make this wallet active, since this wallet doesn't have a compatible connector on this device.");
51
52
  }
53
+ // On mobile, an in-app-browser wallet that isn't injected on the current
54
+ // browser (e.g. switching to a linked Phantom on mobile web) has no
55
+ // provider to sign with. Trigger the wallet's in-app-browser redirect up
56
+ // front so the user lands inside the wallet app, instead of the switch
57
+ // silently succeeding and every later signature failing. The connector
58
+ // self-gates: this is a no-op for installed, embedded, or redirect-only
59
+ // wallets.
60
+ if (utils.isMobile()) {
61
+ const didOpenInAppBrowser = wallet.connector.openInAppBrowserIfRequired();
62
+ if (didOpenInAppBrowser) {
63
+ return;
64
+ }
65
+ }
52
66
  const eoaWallet = getEOAWallet(wallet);
53
67
  if (eoaWallet && walletConnectorCore.isTurnkeyWalletConnector(eoaWallet.connector)) {
54
68
  const sortedVcs = (_b = (_a = eoaWallet.connector) === null || _a === void 0 ? void 0 : _a.verifiedCredentials) === null || _b === void 0 ? void 0 : _b.reduce((acc, vc) => {
@@ -1,6 +1,7 @@
1
1
  'use client'
2
2
  import { __awaiter } from '../../../../../_virtual/_tslib.js';
3
3
  import { useCallback } from 'react';
4
+ import { isMobile } from '@dynamic-labs/utils';
4
5
  import { isTurnkeyWalletConnector, isEmailWalletConnector } from '@dynamic-labs/wallet-connector-core';
5
6
  import '../../../context/DynamicContext/useDynamicContext/useDynamicContext.js';
6
7
  import { useInternalDynamicContext } from '../../../context/DynamicContext/useDynamicContext/useInternalDynamicContext/useInternalDynamicContext.js';
@@ -45,6 +46,19 @@ const useSwitchWallet = () => {
45
46
  if (!wallet.connector.isAvailable) {
46
47
  throw new Error("Cannot make this wallet active, since this wallet doesn't have a compatible connector on this device.");
47
48
  }
49
+ // On mobile, an in-app-browser wallet that isn't injected on the current
50
+ // browser (e.g. switching to a linked Phantom on mobile web) has no
51
+ // provider to sign with. Trigger the wallet's in-app-browser redirect up
52
+ // front so the user lands inside the wallet app, instead of the switch
53
+ // silently succeeding and every later signature failing. The connector
54
+ // self-gates: this is a no-op for installed, embedded, or redirect-only
55
+ // wallets.
56
+ if (isMobile()) {
57
+ const didOpenInAppBrowser = wallet.connector.openInAppBrowserIfRequired();
58
+ if (didOpenInAppBrowser) {
59
+ return;
60
+ }
61
+ }
48
62
  const eoaWallet = getEOAWallet(wallet);
49
63
  if (eoaWallet && isTurnkeyWalletConnector(eoaWallet.connector)) {
50
64
  const sortedVcs = (_b = (_a = eoaWallet.connector) === null || _a === void 0 ? void 0 : _a.verifiedCredentials) === null || _b === void 0 ? void 0 : _b.reduce((acc, vc) => {