@dynamic-labs/sdk-react-core 5.0.0-rc.4 → 5.0.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.
package/CHANGELOG.md CHANGED
@@ -1,4 +1,77 @@
1
1
 
2
+ ## [5.0.0](https://github.com/dynamic-labs/dynamic-auth/compare/v5.0.0-rc.4...v5.0.0) (2026-07-29)
3
+
4
+ ## 🚀 Dynamic SDK v5 is live
5
+
6
+ This release has been a long time coming. We've cleaned up the architecture, cut support for v1 and v2 embedded wallets from core, shipped a migration path for legacy wallets, and made the SDK story a lot cleaner for both web and mobile devs.
7
+
8
+ ### 🔥 Embedded Wallet clean up
9
+
10
+ V1/V2 embedded wallets support has been removed from `@dynamic-labs/sdk-react-core`. Dynamic WaaS (V3) is now the one and only embedded wallet type, all new embedded wallets are V3.
11
+
12
+ ### 📦 New: `@dynamic-labs/legacy-embedded-wallet-migration`
13
+
14
+ For users still on V1/V2 embedded wallets, we've got you covered with a dedicated migration package. It owns all the logic for upgrading to V3. This includes recovery flow, session keys, private key export, and the migration UI.
15
+
16
+ Two paths available:
17
+
18
+ - **UI-based** (recommended): Mount `LegacyWalletMigrationProvider` and the upgrade modal opens automatically when a legacy wallet is detected at login. It handles the full flow end-to-end.
19
+ - **Headless**: Call `upgradeToDynamicWaas()` directly if you want a custom migration UX.
20
+
21
+ ### 📱 JS SDK for web and React Native are the new default
22
+
23
+ Going forward, we're encouraging all developers building for web and React Native to use the JS SDK. It's the direction we're investing in. Ideally v5 is our final version of the old React SDK. To be clear: **no new features will be added to the old SDKs, including v5. All new feature development is happening in the JS SDK.**
24
+
25
+ ### ✏️ Legacy package renames
26
+
27
+ The old packages have been renamed to make their status explicit. Update your `package.json` accordingly:
28
+
29
+ ```
30
+ @dynamic-labs/client → @dynamic-labs/legacy-client
31
+ @dynamic-labs/react-hooks → @dynamic-labs/legacy-react-hooks
32
+ @dynamic-labs/react-native-extension → @dynamic-labs/legacy-react-native-extension
33
+ @dynamic-labs/web-extension → @dynamic-labs/legacy-web-extension
34
+ @dynamic-labs/solana-extension → @dynamic-labs/legacy-solana-extension
35
+ @dynamic-labs/sui-extension → @dynamic-labs/legacy-sui-extension
36
+ @dynamic-labs/viem-extension → @dynamic-labs/legacy-viem-extension
37
+ @dynamic-labs/zerodev-extension → @dynamic-labs/legacy-zerodev-extension
38
+ ```
39
+
40
+ ### :book: Full migration guide
41
+
42
+ Everything you need to upgrade from v4 → v5: [Upgrade SDK from V4 to V5 - Dynamic](https://www.dynamic.xyz/docs/react/reference/upgrade/v5)
43
+
44
+ Huge props to everyone who contributed to this.  :tada:
45
+
46
+ ------------------------------------------
47
+
48
+ ### ⚠ BREAKING CHANGES
49
+
50
+ * **waas:** move the client key-share store onto the core storage API (#12096)
51
+ * **react-native-extension:** store sensitive information with react-native-keychain (#12092)
52
+
53
+ ### Features
54
+
55
+ * **bitcoin:** add sendBitcoinWithConfirmation to show confirm modal on BTC sends ([#12025](https://github.com/dynamic-labs/dynamic-auth/issues/12025)) ([5f992f5](https://github.com/dynamic-labs/dynamic-auth/commit/5f992f5fdaf805f07870ab9248ea2fe06a2fd3bf))
56
+ * **waas:** delegate key shares with initial signer rules [DYNT-1675] ([#12028](https://github.com/dynamic-labs/dynamic-auth/issues/12028)) ([6b1ffc5](https://github.com/dynamic-labs/dynamic-auth/commit/6b1ffc5ee7070d639916a65b6a0aa6ada0167cb8)), closes [dynamic-labs/dynamic-waas-sdk#1500](https://github.com/dynamic-labs/dynamic-waas-sdk/issues/1500) [#10754](https://github.com/dynamic-labs/dynamic-auth/issues/10754) [#10772](https://github.com/dynamic-labs/dynamic-auth/issues/10772) [#646](https://github.com/dynamic-labs/dynamic-auth/issues/646) [#649](https://github.com/dynamic-labs/dynamic-auth/issues/649) [#1500](https://github.com/dynamic-labs/dynamic-auth/issues/1500) [dynamic-labs/dynamic-waas-sdk#1500](https://github.com/dynamic-labs/dynamic-waas-sdk/issues/1500)
57
+ * **waas:** expose rawSign sha256/blake2b in SignMessageContext ([#12075](https://github.com/dynamic-labs/dynamic-auth/issues/12075)) ([20e3d2f](https://github.com/dynamic-labs/dynamic-auth/commit/20e3d2f40d18aa0bdb77e9b00fa4a5dc5dd6e9de))
58
+
59
+
60
+ ### Bug Fixes
61
+
62
+ * **bitcoin:** resolve the public key of the active wallet, not the first ([#12091](https://github.com/dynamic-labs/dynamic-auth/issues/12091)) ([ebad70c](https://github.com/dynamic-labs/dynamic-auth/commit/ebad70c37ab9a771ad7051ca8f3c68d52c1d32fa)), closes [#11970](https://github.com/dynamic-labs/dynamic-auth/issues/11970) [#11970](https://github.com/dynamic-labs/dynamic-auth/issues/11970) [#11970](https://github.com/dynamic-labs/dynamic-auth/issues/11970)
63
+ * fallback to local wallet balance when token balances API returns no tokens ([#12059](https://github.com/dynamic-labs/dynamic-auth/issues/12059)) ([26c9022](https://github.com/dynamic-labs/dynamic-auth/commit/26c9022da3371b0da0498ec641157aa9fed356aa))
64
+ * security updates ([#12114](https://github.com/dynamic-labs/dynamic-auth/issues/12114)) ([a4f9bc1](https://github.com/dynamic-labs/dynamic-auth/commit/a4f9bc19870156903b76996c55ecd46a3417f004)) ([#12050](https://github.com/dynamic-labs/dynamic-auth/issues/12050)) ([8eeec92](https://github.com/dynamic-labs/dynamic-auth/commit/8eeec923d0f2f688ade60440e1f92bb1fd646201)) ([#12088](https://github.com/dynamic-labs/dynamic-auth/issues/12088)) ([f11f1f3](https://github.com/dynamic-labs/dynamic-auth/commit/f11f1f384201fa31f58753c0854eaf17f420fb09))
65
+ * replace deprecated polygon-rpc.com RPC with polygon.drpc.org ([#12032](https://github.com/dynamic-labs/dynamic-auth/issues/12032)) ([e1ece53](https://github.com/dynamic-labs/dynamic-auth/commit/e1ece538aedf6127048ca64daf9f9f3cf3a7026e))
66
+ * **sui:** stop using Sui Foundation fullnode URLs in WaaS and extension connectors ([#12108](https://github.com/dynamic-labs/dynamic-auth/issues/12108)) ([b23c85b](https://github.com/dynamic-labs/dynamic-auth/commit/b23c85b7c06f3147705a026ba23f5e8ae1f03b68))
67
+ * **wallet-book:** use Backpack universal link for iOS in-app browser ([#12053](https://github.com/dynamic-labs/dynamic-auth/issues/12053)) ([afba6b1](https://github.com/dynamic-labs/dynamic-auth/commit/afba6b192e4be50aaeca9f5fad88b44808a034a0))
68
+ * **wallet-connect:** recover WalletConnect IndexedDB store when object store is missing ([#12048](https://github.com/dynamic-labs/dynamic-auth/issues/12048)) ([96ff80e](https://github.com/dynamic-labs/dynamic-auth/commit/96ff80e40b28740de15b1702b85f954100fd5f25))
69
+
70
+
71
+ * **react-native-extension:** store sensitive information with react-native-keychain ([#12092](https://github.com/dynamic-labs/dynamic-auth/issues/12092)) ([10c0f2f](https://github.com/dynamic-labs/dynamic-auth/commit/10c0f2f6c281e300e4a0455aaaa0d452808dcb87))
72
+ * **waas:** move the client key-share store onto the core storage API ([#12096](https://github.com/dynamic-labs/dynamic-auth/issues/12096)) ([778f993](https://github.com/dynamic-labs/dynamic-auth/commit/778f993c5528ec03a7f1446dcb81a041a22db89b)), closes [#12094](https://github.com/dynamic-labs/dynamic-auth/issues/12094) [#12094](https://github.com/dynamic-labs/dynamic-auth/issues/12094) [#12092](https://github.com/dynamic-labs/dynamic-auth/issues/12092) [#12094](https://github.com/dynamic-labs/dynamic-auth/issues/12094)
73
+
74
+
2
75
  ## [5.0.0-rc.4](https://github.com/dynamic-labs/dynamic-auth/compare/v5.0.0-rc.3...v5.0.0-rc.4) (2026-07-20)
3
76
 
4
77
 
package/package.cjs CHANGED
@@ -3,11 +3,11 @@
3
3
 
4
4
  Object.defineProperty(exports, '__esModule', { value: true });
5
5
 
6
- var version = "5.0.0-rc.4";
6
+ var version = "5.0.0";
7
7
  var dependencies = {
8
- "@dynamic-labs/sdk-api-core": "0.0.1082",
8
+ "@dynamic-labs/sdk-api-core": "0.0.1093",
9
9
  "@dynamic-labs-sdk/client": "1.20.0",
10
- "@dynamic-labs-wallet/browser-wallet-client": "1.0.74",
10
+ "@dynamic-labs-wallet/browser-wallet-client": "1.0.79",
11
11
  "@dynamic-labs-wallet/forward-mpc-client": "1.0.1",
12
12
  "@hcaptcha/react-hcaptcha": "1.4.4",
13
13
  "@marsidev/react-turnstile": "1.5.2",
package/package.js CHANGED
@@ -1,9 +1,9 @@
1
1
  'use client'
2
- var version = "5.0.0-rc.4";
2
+ var version = "5.0.0";
3
3
  var dependencies = {
4
- "@dynamic-labs/sdk-api-core": "0.0.1082",
4
+ "@dynamic-labs/sdk-api-core": "0.0.1093",
5
5
  "@dynamic-labs-sdk/client": "1.20.0",
6
- "@dynamic-labs-wallet/browser-wallet-client": "1.0.74",
6
+ "@dynamic-labs-wallet/browser-wallet-client": "1.0.79",
7
7
  "@dynamic-labs-wallet/forward-mpc-client": "1.0.1",
8
8
  "@hcaptcha/react-hcaptcha": "1.4.4",
9
9
  "@marsidev/react-turnstile": "1.5.2",
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@dynamic-labs/sdk-react-core",
3
- "version": "5.0.0-rc.4",
3
+ "version": "5.0.0",
4
4
  "dependencies": {
5
- "@dynamic-labs/sdk-api-core": "0.0.1082",
5
+ "@dynamic-labs/sdk-api-core": "0.0.1093",
6
6
  "@dynamic-labs-sdk/client": "1.20.0",
7
- "@dynamic-labs-wallet/browser-wallet-client": "1.0.74",
7
+ "@dynamic-labs-wallet/browser-wallet-client": "1.0.79",
8
8
  "@dynamic-labs-wallet/forward-mpc-client": "1.0.1",
9
9
  "@hcaptcha/react-hcaptcha": "1.4.4",
10
10
  "@marsidev/react-turnstile": "1.5.2",
@@ -18,17 +18,18 @@
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": "5.0.0-rc.4",
22
- "@dynamic-labs/iconic": "5.0.0-rc.4",
23
- "@dynamic-labs/locale": "5.0.0-rc.4",
24
- "@dynamic-labs/logger": "5.0.0-rc.4",
25
- "@dynamic-labs/multi-wallet": "5.0.0-rc.4",
26
- "@dynamic-labs/rpc-providers": "5.0.0-rc.4",
27
- "@dynamic-labs/store": "5.0.0-rc.4",
28
- "@dynamic-labs/types": "5.0.0-rc.4",
29
- "@dynamic-labs/utils": "5.0.0-rc.4",
30
- "@dynamic-labs/wallet-book": "5.0.0-rc.4",
31
- "@dynamic-labs/wallet-connector-core": "5.0.0-rc.4",
21
+ "@dynamic-labs/assert-package-version": "5.0.0",
22
+ "@dynamic-labs/iconic": "5.0.0",
23
+ "@dynamic-labs/legacy-client": "5.0.0",
24
+ "@dynamic-labs/locale": "5.0.0",
25
+ "@dynamic-labs/logger": "5.0.0",
26
+ "@dynamic-labs/multi-wallet": "5.0.0",
27
+ "@dynamic-labs/rpc-providers": "5.0.0",
28
+ "@dynamic-labs/store": "5.0.0",
29
+ "@dynamic-labs/types": "5.0.0",
30
+ "@dynamic-labs/utils": "5.0.0",
31
+ "@dynamic-labs/wallet-book": "5.0.0",
32
+ "@dynamic-labs/wallet-connector-core": "5.0.0",
32
33
  "eventemitter3": "5.0.1"
33
34
  },
34
35
  "devDependencies": {
@@ -10,12 +10,15 @@ var isSSR = require('../../../../shared/utils/functions/isSSR/isSSR.cjs');
10
10
  /**
11
11
  * Registers the storage service for the SDK.
12
12
  * When on server side rendering, the storage service is not available, so we need to register a dummy storage service.
13
+ *
14
+ * Every platform registers the same web storages. A native mobile host runs the
15
+ * SDK inside a webview that has its own `localStorage`/`sessionStorage`, and its
16
+ * secure values are routed by the client's storage adapter rather than through
17
+ * this registry.
13
18
  * @param storagePostfix - The postfix for the storage services.
14
19
  */
15
20
  const useRegisterStorageService = ({ storagePostfix, }) => {
16
21
  const lastPostfix = React.useRef(storagePostfix);
17
- if (!isSSR.isSSR() && utils.PlatformService.isNativeMobile)
18
- return;
19
22
  const storageForSSR = {
20
23
  clear: () => undefined,
21
24
  getItem: () => null,
@@ -4,6 +4,11 @@ type UseRegisterStorageServiceProps = {
4
4
  /**
5
5
  * Registers the storage service for the SDK.
6
6
  * When on server side rendering, the storage service is not available, so we need to register a dummy storage service.
7
+ *
8
+ * Every platform registers the same web storages. A native mobile host runs the
9
+ * SDK inside a webview that has its own `localStorage`/`sessionStorage`, and its
10
+ * secure values are routed by the client's storage adapter rather than through
11
+ * this registry.
7
12
  * @param storagePostfix - The postfix for the storage services.
8
13
  */
9
14
  export declare const useRegisterStorageService: ({ storagePostfix, }: UseRegisterStorageServiceProps) => void;
@@ -1,17 +1,20 @@
1
1
  'use client'
2
2
  import { useRef } from 'react';
3
- import { PlatformService, StorageService, applyPostfixToStorageService, createStorageService } from '@dynamic-labs/utils';
3
+ import { StorageService, applyPostfixToStorageService, createStorageService } from '@dynamic-labs/utils';
4
4
  import { isSSR } from '../../../../shared/utils/functions/isSSR/isSSR.js';
5
5
 
6
6
  /**
7
7
  * Registers the storage service for the SDK.
8
8
  * When on server side rendering, the storage service is not available, so we need to register a dummy storage service.
9
+ *
10
+ * Every platform registers the same web storages. A native mobile host runs the
11
+ * SDK inside a webview that has its own `localStorage`/`sessionStorage`, and its
12
+ * secure values are routed by the client's storage adapter rather than through
13
+ * this registry.
9
14
  * @param storagePostfix - The postfix for the storage services.
10
15
  */
11
16
  const useRegisterStorageService = ({ storagePostfix, }) => {
12
17
  const lastPostfix = useRef(storagePostfix);
13
- if (!isSSR() && PlatformService.isNativeMobile)
14
- return;
15
18
  const storageForSSR = {
16
19
  clear: () => undefined,
17
20
  getItem: () => null,
@@ -23,7 +23,7 @@ const useStepUpMfa = ({ stateActions }) => {
23
23
  ? error.message
24
24
  : 'Failed to authenticate passkey for step-up authentication';
25
25
  setError(errorMessage);
26
- return null;
26
+ throw error;
27
27
  }
28
28
  }), [setError, setLoading, setSuccess]);
29
29
  const verifyTotpMfa = React.useCallback((_b) => _tslib.__awaiter(void 0, [_b], void 0, function* ({ code, deviceId, requestedScopes, }) {
@@ -42,7 +42,7 @@ const useStepUpMfa = ({ stateActions }) => {
42
42
  ? error.message
43
43
  : 'Failed to authenticate TOTP device for step-up authentication';
44
44
  setError(errorMessage);
45
- return null;
45
+ throw error;
46
46
  }
47
47
  }), [setError, setLoading, setSuccess]);
48
48
  const verifyRecoveryCode = React.useCallback((_c) => _tslib.__awaiter(void 0, [_c], void 0, function* ({ code, requestedScopes, }) {
@@ -60,7 +60,7 @@ const useStepUpMfa = ({ stateActions }) => {
60
60
  ? error.message
61
61
  : 'Failed to authenticate recovery code for step-up authentication';
62
62
  setError(errorMessage);
63
- return null;
63
+ throw error;
64
64
  }
65
65
  }), [setError, setLoading, setSuccess]);
66
66
  return { verifyPasskeyMfa, verifyRecoveryCode, verifyTotpMfa };
@@ -19,7 +19,7 @@ const useStepUpMfa = ({ stateActions }) => {
19
19
  ? error.message
20
20
  : 'Failed to authenticate passkey for step-up authentication';
21
21
  setError(errorMessage);
22
- return null;
22
+ throw error;
23
23
  }
24
24
  }), [setError, setLoading, setSuccess]);
25
25
  const verifyTotpMfa = useCallback((_b) => __awaiter(void 0, [_b], void 0, function* ({ code, deviceId, requestedScopes, }) {
@@ -38,7 +38,7 @@ const useStepUpMfa = ({ stateActions }) => {
38
38
  ? error.message
39
39
  : 'Failed to authenticate TOTP device for step-up authentication';
40
40
  setError(errorMessage);
41
- return null;
41
+ throw error;
42
42
  }
43
43
  }), [setError, setLoading, setSuccess]);
44
44
  const verifyRecoveryCode = useCallback((_c) => __awaiter(void 0, [_c], void 0, function* ({ code, requestedScopes, }) {
@@ -56,7 +56,7 @@ const useStepUpMfa = ({ stateActions }) => {
56
56
  ? error.message
57
57
  : 'Failed to authenticate recovery code for step-up authentication';
58
58
  setError(errorMessage);
59
- return null;
59
+ throw error;
60
60
  }
61
61
  }), [setError, setLoading, setSuccess]);
62
62
  return { verifyPasskeyMfa, verifyRecoveryCode, verifyTotpMfa };
@@ -44,6 +44,14 @@ var useWalletDelegation = require('../useWalletDelegation/useWalletDelegation.cj
44
44
  require('../useWalletDelegation/DelegationError.cjs');
45
45
  var instrumentWalletCreation = require('./instrumentWalletCreation.cjs');
46
46
 
47
+ const DEFAULT_SDK_LOAD_STATE = {
48
+ attempt: 0,
49
+ error: null,
50
+ maxAttempts: 0,
51
+ nextRetryAt: null,
52
+ phase: null,
53
+ status: 'idle',
54
+ };
47
55
  // Validate if all required conditions are met for wallet creation
48
56
  const useWalletCreationValidation = () => {
49
57
  const user = useOnboardingCompleteUser.useOnboardingCompleteUser();
@@ -160,6 +168,71 @@ const useSyncDynamicWaas = () => {
160
168
  const hasWaasInitializedRef = React.useRef(false);
161
169
  const sessionIdRef = React.useRef(undefined);
162
170
  const initializingWaasRef = React.useRef(false);
171
+ const sdkLoadStateCacheRef = React.useRef(DEFAULT_SDK_LOAD_STATE);
172
+ // Track the SDK webview load state so WaaS init can be gated on whether the
173
+ // webview/iframe is ready, and can be retried after a recovery. Using
174
+ // useSyncExternalStore instead of useEffect/useState avoids the race between
175
+ // initial state read and subscription, handles server/client snapshots, and
176
+ // gives React the standard external-store integration for concurrent renders.
177
+ const client$1 = client.getDynamicClient();
178
+ const sdkModule = client$1.sdk;
179
+ const getSdkLoadState = React.useCallback(() => {
180
+ if (!sdkModule) {
181
+ return DEFAULT_SDK_LOAD_STATE;
182
+ }
183
+ const next = sdkModule.loadState;
184
+ const prev = sdkLoadStateCacheRef.current;
185
+ // The production store allocates a new object on every read, but
186
+ // useSyncExternalStore requires referential stability between store events.
187
+ // Return the cached snapshot unless a field actually changed.
188
+ if (next.status === prev.status &&
189
+ next.phase === prev.phase &&
190
+ next.attempt === prev.attempt &&
191
+ next.maxAttempts === prev.maxAttempts &&
192
+ next.nextRetryAt === prev.nextRetryAt &&
193
+ next.error === prev.error) {
194
+ return prev;
195
+ }
196
+ sdkLoadStateCacheRef.current = next;
197
+ return next;
198
+ }, [sdkModule]);
199
+ const subscribeToSdkLoadState = React.useCallback((onStoreChange) => {
200
+ if (!sdkModule) {
201
+ return () => { };
202
+ }
203
+ const handleLoadStateChanged = () => {
204
+ onStoreChange();
205
+ };
206
+ sdkModule.on('loadStateChanged', handleLoadStateChanged);
207
+ return () => {
208
+ sdkModule.off('loadStateChanged', handleLoadStateChanged);
209
+ };
210
+ }, [sdkModule]);
211
+ const sdkLoadState = React.useSyncExternalStore(subscribeToSdkLoadState, getSdkLoadState, getSdkLoadState);
212
+ const isSdkReadyForWaas = React.useCallback(() => {
213
+ // No SDK webview load controller is present (e.g. web platform using the
214
+ // standard React SDK client). WaaS uses its own iframe lifecycle there, so
215
+ // do not block initialization based on a non-existent webview load state.
216
+ if (!sdkModule || sdkLoadState.maxAttempts === 0) {
217
+ return true;
218
+ }
219
+ // The webview/iframe is loaded and ready. This covers platforms (RN) whose
220
+ // load controller reaches the ready state.
221
+ if (sdkLoadState.status === 'ready') {
222
+ return true;
223
+ }
224
+ return false;
225
+ }, [sdkLoadState, sdkModule]);
226
+ React.useEffect(() => {
227
+ if (sdkLoadState.status === 'failed') {
228
+ // Mark WaaS as not initialized so a retry can re-run it once the webview
229
+ // recovers. Do not reset initializingWaasRef here: if an initializeWaas
230
+ // call is already in flight, the existing guard should remain until its
231
+ // .finally clears it, otherwise a quick failure-then-recovery could start
232
+ // a duplicate initialization.
233
+ hasWaasInitializedRef.current = false;
234
+ }
235
+ }, [sdkLoadState.status]);
163
236
  useDynamicEvents.useInternalDynamicEvents('logout', () => {
164
237
  triggeredCreate.current = false;
165
238
  triggeredSettingsProcessing.current = false;
@@ -226,7 +299,7 @@ const useSyncDynamicWaas = () => {
226
299
  initializingWaasRef.current = false;
227
300
  clearRecoveredWallets();
228
301
  }
229
- if (shouldInitializeWaas && (user === null || user === void 0 ? void 0 : user.sessionId)) {
302
+ if (shouldInitializeWaas && (user === null || user === void 0 ? void 0 : user.sessionId) && isSdkReadyForWaas()) {
230
303
  const { sessionId, id: userId } = user;
231
304
  const shouldInitialize = !initializingWaasRef.current &&
232
305
  (!hasWaasInitializedRef.current || sessionIdRef.current !== sessionId);
@@ -277,7 +350,14 @@ const useSyncDynamicWaas = () => {
277
350
  });
278
351
  }
279
352
  }
280
- }, [initializeWaas, shouldInitializeWaas, user]);
353
+ }, [
354
+ initializeWaas,
355
+ shouldInitializeWaas,
356
+ user,
357
+ isSdkReadyForWaas,
358
+ clearRecoveredWallets,
359
+ environmentId,
360
+ ]);
281
361
  React.useEffect(() => {
282
362
  if (shouldCreateWallets()) {
283
363
  handleWalletCreation();
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
  import { __awaiter } from '../../../../../_virtual/_tslib.js';
3
- import { useRef, useCallback, useEffect } from 'react';
3
+ import { useRef, useCallback, useSyncExternalStore, useEffect } from 'react';
4
4
  import { getDynamicClient } from '../../../client/client.js';
5
5
  import '@dynamic-labs-sdk/client/core';
6
6
  import '@dynamic-labs/sdk-api-core';
@@ -40,6 +40,14 @@ import { useWalletDelegation } from '../useWalletDelegation/useWalletDelegation.
40
40
  import '../useWalletDelegation/DelegationError.js';
41
41
  import { instrumentWalletCreation } from './instrumentWalletCreation.js';
42
42
 
43
+ const DEFAULT_SDK_LOAD_STATE = {
44
+ attempt: 0,
45
+ error: null,
46
+ maxAttempts: 0,
47
+ nextRetryAt: null,
48
+ phase: null,
49
+ status: 'idle',
50
+ };
43
51
  // Validate if all required conditions are met for wallet creation
44
52
  const useWalletCreationValidation = () => {
45
53
  const user = useOnboardingCompleteUser();
@@ -156,6 +164,71 @@ const useSyncDynamicWaas = () => {
156
164
  const hasWaasInitializedRef = useRef(false);
157
165
  const sessionIdRef = useRef(undefined);
158
166
  const initializingWaasRef = useRef(false);
167
+ const sdkLoadStateCacheRef = useRef(DEFAULT_SDK_LOAD_STATE);
168
+ // Track the SDK webview load state so WaaS init can be gated on whether the
169
+ // webview/iframe is ready, and can be retried after a recovery. Using
170
+ // useSyncExternalStore instead of useEffect/useState avoids the race between
171
+ // initial state read and subscription, handles server/client snapshots, and
172
+ // gives React the standard external-store integration for concurrent renders.
173
+ const client = getDynamicClient();
174
+ const sdkModule = client.sdk;
175
+ const getSdkLoadState = useCallback(() => {
176
+ if (!sdkModule) {
177
+ return DEFAULT_SDK_LOAD_STATE;
178
+ }
179
+ const next = sdkModule.loadState;
180
+ const prev = sdkLoadStateCacheRef.current;
181
+ // The production store allocates a new object on every read, but
182
+ // useSyncExternalStore requires referential stability between store events.
183
+ // Return the cached snapshot unless a field actually changed.
184
+ if (next.status === prev.status &&
185
+ next.phase === prev.phase &&
186
+ next.attempt === prev.attempt &&
187
+ next.maxAttempts === prev.maxAttempts &&
188
+ next.nextRetryAt === prev.nextRetryAt &&
189
+ next.error === prev.error) {
190
+ return prev;
191
+ }
192
+ sdkLoadStateCacheRef.current = next;
193
+ return next;
194
+ }, [sdkModule]);
195
+ const subscribeToSdkLoadState = useCallback((onStoreChange) => {
196
+ if (!sdkModule) {
197
+ return () => { };
198
+ }
199
+ const handleLoadStateChanged = () => {
200
+ onStoreChange();
201
+ };
202
+ sdkModule.on('loadStateChanged', handleLoadStateChanged);
203
+ return () => {
204
+ sdkModule.off('loadStateChanged', handleLoadStateChanged);
205
+ };
206
+ }, [sdkModule]);
207
+ const sdkLoadState = useSyncExternalStore(subscribeToSdkLoadState, getSdkLoadState, getSdkLoadState);
208
+ const isSdkReadyForWaas = useCallback(() => {
209
+ // No SDK webview load controller is present (e.g. web platform using the
210
+ // standard React SDK client). WaaS uses its own iframe lifecycle there, so
211
+ // do not block initialization based on a non-existent webview load state.
212
+ if (!sdkModule || sdkLoadState.maxAttempts === 0) {
213
+ return true;
214
+ }
215
+ // The webview/iframe is loaded and ready. This covers platforms (RN) whose
216
+ // load controller reaches the ready state.
217
+ if (sdkLoadState.status === 'ready') {
218
+ return true;
219
+ }
220
+ return false;
221
+ }, [sdkLoadState, sdkModule]);
222
+ useEffect(() => {
223
+ if (sdkLoadState.status === 'failed') {
224
+ // Mark WaaS as not initialized so a retry can re-run it once the webview
225
+ // recovers. Do not reset initializingWaasRef here: if an initializeWaas
226
+ // call is already in flight, the existing guard should remain until its
227
+ // .finally clears it, otherwise a quick failure-then-recovery could start
228
+ // a duplicate initialization.
229
+ hasWaasInitializedRef.current = false;
230
+ }
231
+ }, [sdkLoadState.status]);
159
232
  useInternalDynamicEvents('logout', () => {
160
233
  triggeredCreate.current = false;
161
234
  triggeredSettingsProcessing.current = false;
@@ -222,7 +295,7 @@ const useSyncDynamicWaas = () => {
222
295
  initializingWaasRef.current = false;
223
296
  clearRecoveredWallets();
224
297
  }
225
- if (shouldInitializeWaas && (user === null || user === void 0 ? void 0 : user.sessionId)) {
298
+ if (shouldInitializeWaas && (user === null || user === void 0 ? void 0 : user.sessionId) && isSdkReadyForWaas()) {
226
299
  const { sessionId, id: userId } = user;
227
300
  const shouldInitialize = !initializingWaasRef.current &&
228
301
  (!hasWaasInitializedRef.current || sessionIdRef.current !== sessionId);
@@ -273,7 +346,14 @@ const useSyncDynamicWaas = () => {
273
346
  });
274
347
  }
275
348
  }
276
- }, [initializeWaas, shouldInitializeWaas, user]);
349
+ }, [
350
+ initializeWaas,
351
+ shouldInitializeWaas,
352
+ user,
353
+ isSdkReadyForWaas,
354
+ clearRecoveredWallets,
355
+ environmentId,
356
+ ]);
277
357
  useEffect(() => {
278
358
  if (shouldCreateWallets()) {
279
359
  handleWalletCreation();
@@ -161,7 +161,9 @@ const useWalletDelegation = () => {
161
161
  .filter((wallet) => wallet !== null)
162
162
  .filter((wallet) => wallet.chain === sdkApiCore.ChainEnum.Evm || wallet.chain === sdkApiCore.ChainEnum.Sol);
163
163
  }, [user === null || user === void 0 ? void 0 : user.verifiedCredentials, userWallets]);
164
- const delegateKeyShares = React.useCallback((wallets, password) => _tslib.__awaiter(void 0, void 0, void 0, function* () {
164
+ const delegateKeyShares = React.useCallback((wallets, password,
165
+ // Seeds the delegated signer's policy layer with rules at delegation time.
166
+ initialSignerRules) => _tslib.__awaiter(void 0, void 0, void 0, function* () {
165
167
  if (!delegatedAccessEnabled) {
166
168
  throw new Error('Wallet delegation is not enabled for this project environment. Please check the project settings.');
167
169
  }
@@ -185,7 +187,7 @@ const useWalletDelegation = () => {
185
187
  continue;
186
188
  }
187
189
  try {
188
- yield walletConnector.delegateKeyShares(Object.assign({ accountAddress: wallet.accountAddress }, (password && { password })));
190
+ yield walletConnector.delegateKeyShares(Object.assign(Object.assign({ accountAddress: wallet.accountAddress }, (password && { password })), (initialSignerRules && { initialSignerRules })));
189
191
  successfulWallets.push(wallet);
190
192
  }
191
193
  catch (error) {
@@ -1,4 +1,4 @@
1
- import { ChainEnum } from '@dynamic-labs/sdk-api-core';
1
+ import { ChainEnum, WaasPolicyRule } from '@dynamic-labs/sdk-api-core';
2
2
  import { Wallet } from '../../../shared';
3
3
  export type WalletDelegationStatus = 'delegated' | 'denied' | 'pending';
4
4
  export type WalletWithStatus = Wallet & {
@@ -27,7 +27,7 @@ export declare const useWalletDelegation: () => {
27
27
  readonly delegateKeyShares: (wallets?: {
28
28
  chainName: ChainEnum;
29
29
  accountAddress: string;
30
- }[], password?: string) => Promise<void>;
30
+ }[], password?: string, initialSignerRules?: WaasPolicyRule[]) => Promise<void>;
31
31
  readonly delegatedAccessEnabled: boolean | undefined;
32
32
  readonly denyWalletDelegation: (walletId: string) => Promise<void>;
33
33
  readonly dismissDelegationPrompt: (walletId?: string) => void;
@@ -157,7 +157,9 @@ const useWalletDelegation = () => {
157
157
  .filter((wallet) => wallet !== null)
158
158
  .filter((wallet) => wallet.chain === ChainEnum.Evm || wallet.chain === ChainEnum.Sol);
159
159
  }, [user === null || user === void 0 ? void 0 : user.verifiedCredentials, userWallets]);
160
- const delegateKeyShares = useCallback((wallets, password) => __awaiter(void 0, void 0, void 0, function* () {
160
+ const delegateKeyShares = useCallback((wallets, password,
161
+ // Seeds the delegated signer's policy layer with rules at delegation time.
162
+ initialSignerRules) => __awaiter(void 0, void 0, void 0, function* () {
161
163
  if (!delegatedAccessEnabled) {
162
164
  throw new Error('Wallet delegation is not enabled for this project environment. Please check the project settings.');
163
165
  }
@@ -181,7 +183,7 @@ const useWalletDelegation = () => {
181
183
  continue;
182
184
  }
183
185
  try {
184
- yield walletConnector.delegateKeyShares(Object.assign({ accountAddress: wallet.accountAddress }, (password && { password })));
186
+ yield walletConnector.delegateKeyShares(Object.assign(Object.assign({ accountAddress: wallet.accountAddress }, (password && { password })), (initialSignerRules && { initialSignerRules })));
185
187
  successfulWallets.push(wallet);
186
188
  }
187
189
  catch (error) {
@@ -460,13 +460,18 @@ const ActiveWalletBalance = ({ isLoading = false, }) => {
460
460
  (_c = projectSettings === null || projectSettings === void 0 ? void 0 : projectSettings.sdk.featureFlags) === null || _c === void 0 ? void 0 : _c.connectOnlyMultiAsset,
461
461
  multiAsset,
462
462
  ]);
463
+ const hasTokenBalances = React.useMemo(() => filteredTokenBalances.length > 0, [filteredTokenBalances]);
463
464
  const isTooManyRequests = errorTokenBalances === 'Too many requests fetching balances';
464
465
  const showMultiAsset = !isTooManyRequests && enableMultiAsset;
465
466
  const currencyFormatter = new Intl.NumberFormat('en-US', {
466
467
  currency: 'USD',
467
468
  style: 'currency',
468
469
  });
469
- const canShowFiat = React.useMemo(() => !testnet && showFiat && showMultiAsset && !errorTokenBalances, [testnet, showFiat, showMultiAsset, errorTokenBalances]);
470
+ const canShowFiat = React.useMemo(() => !testnet &&
471
+ showFiat &&
472
+ showMultiAsset &&
473
+ !errorTokenBalances &&
474
+ hasTokenBalances, [testnet, showFiat, showMultiAsset, errorTokenBalances, hasTokenBalances]);
470
475
  const handleScroll = () => {
471
476
  if (contentRef.current) {
472
477
  setHasShadow(contentRef.current.scrollTop > 0);
@@ -456,13 +456,18 @@ const ActiveWalletBalance = ({ isLoading = false, }) => {
456
456
  (_c = projectSettings === null || projectSettings === void 0 ? void 0 : projectSettings.sdk.featureFlags) === null || _c === void 0 ? void 0 : _c.connectOnlyMultiAsset,
457
457
  multiAsset,
458
458
  ]);
459
+ const hasTokenBalances = useMemo(() => filteredTokenBalances.length > 0, [filteredTokenBalances]);
459
460
  const isTooManyRequests = errorTokenBalances === 'Too many requests fetching balances';
460
461
  const showMultiAsset = !isTooManyRequests && enableMultiAsset;
461
462
  const currencyFormatter = new Intl.NumberFormat('en-US', {
462
463
  currency: 'USD',
463
464
  style: 'currency',
464
465
  });
465
- const canShowFiat = useMemo(() => !testnet && showFiat && showMultiAsset && !errorTokenBalances, [testnet, showFiat, showMultiAsset, errorTokenBalances]);
466
+ const canShowFiat = useMemo(() => !testnet &&
467
+ showFiat &&
468
+ showMultiAsset &&
469
+ !errorTokenBalances &&
470
+ hasTokenBalances, [testnet, showFiat, showMultiAsset, errorTokenBalances, hasTokenBalances]);
466
471
  const handleScroll = () => {
467
472
  if (contentRef.current) {
468
473
  setHasShadow(contentRef.current.scrollTop > 0);