> ## Documentation Index
> Fetch the complete documentation index at: https://www.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Upgrade SDK from V4 to V5

<Card title="Recommended: JavaScript SDK with React Hooks" icon="react" color="#4779FE">
  For new React apps, we recommend the JavaScript SDK with React Hooks (`@dynamic-labs-sdk/react-hooks`) instead of the legacy React SDK documented here. The JS SDK comes with many benefits such as a much smaller bundle size and other optimizations. Use the [React quickstart (JavaScript SDK)](/javascript/reference/react-quickstart) to get started.
</Card>

Upgrade to V5 to use the latest React SDK APIs and Dynamic's current embedded wallet infrastructure. V3 embedded wallets use TSS-MPC for stronger key security, sub-second signing, flexible recovery options, and broader chain support. [Learn more about V3 embedded wallets](/overview/wallets/embedded-wallets/mpc/overview).

<Note>
  **SDK and wallet version numbers overlap.** The React SDK and embedded wallets have independent version histories. In this guide, **SDK V5** means React SDK major version 5, while **wallet V1/V2/V3** means embedded wallet generations. React SDK V5 creates V3 wallets, while existing V1/V2 wallets are supported only through the migration flow.
</Note>

## Check whether wallet migration is required

Follow the [Dynamic CLI getting started guide](/cli/getting-started) to install the CLI, authenticate, and select the environment you want to check. Then run:

```bash theme={"system"}
dyn wallets list-active-embedded-versions --output json | jq '{
  migrationRequired: any(.[]; . == "V1" or . == "V2"),
  legacyVersions: [.[] | select(. == "V1" or . == "V2")],
  legacyVersionCount: ([.[] | select(. == "V1" or . == "V2")] | length)
}'
```

If `migrationRequired` is `true`, add the migration flow described below. `legacyVersions` lists the legacy version types in use, and `legacyVersionCount` counts those version types. This endpoint reports which versions are active, not the number of wallets or users on each version. If `migrationRequired` is `false`, you can skip the migration section.

To inspect an individual user's wallet versions, open [Authenticated Users](https://app.dynamic.xyz/dashboard/users/authenticated) in the Dynamic dashboard, select the user, and open the **Account** tab. To see the wallet version configured for new users, open [Embedded Wallets](https://app.dynamic.xyz/dashboard/embedded-wallets/dynamic).

## Upgrade checklist

* [ ] Update every `@dynamic-labs/*` package to the latest V5 version.
* [ ] Replace removed APIs (see [Removed APIs](#removed-apis)).
* [ ] If the environment includes V1/V2 wallets, add a migration path (see [Migrate existing embedded wallets](#migrate-existing-embedded-wallets)).

## Update your packages

Update all Dynamic packages to V5 at the same time. Mixing V4 and V5 packages is not supported. See the [latest version on npm](https://www.npmjs.com/package/@dynamic-labs/sdk-react-core?activeTab=versions).

## Breaking changes

### Embedded wallets require V3

V5 does not use V1/V2 wallets for normal wallet operations. Users can still access a legacy wallet through the migration flow, where they export its private key and import it into V3. Importing the same key preserves the wallet address and access to its funds.

In React, after the SDK detects a V1/V2 wallet without a migration UI, it throws this developer-facing `Error`:

```text theme={"system"}
A legacy embedded wallet (V1/V2) was detected, but `LegacyWalletMigrationProvider` from `@dynamic-labs/legacy-embedded-wallet-migration` is not mounted. Legacy wallets are no longer supported natively by `@dynamic-labs/sdk-react-core` — mount the migration provider so the user can complete the migration to a V3 WaaS wallet before performing any further actions.
```

Mounting `LegacyWalletMigrationProvider` satisfies this check. See [Migrate existing embedded wallets](#migrate-existing-embedded-wallets).

### Legacy embedded wallet connectors removed from chain aggregators

`EthereumWalletConnectors` and `SolanaWalletConnectors` no longer bundle the legacy embedded wallet connectors. New embedded wallets don't need them. If you need to support migration from legacy embedded wallets, import the legacy connectors separately and add them alongside your other connectors:

```typescript theme={"system"}
import { LegacyEVMEmbeddedWalletConnectors } from '@dynamic-labs/embedded-wallet-evm';
import { EthereumWalletConnectors } from '@dynamic-labs/ethereum';
import { LegacySolanaEmbeddedWalletConnectors } from '@dynamic-labs/embedded-wallet-solana';
import { SolanaWalletConnectors } from '@dynamic-labs/solana';

const walletConnectors = [
  EthereumWalletConnectors,
  LegacyEVMEmbeddedWalletConnectors,
  SolanaWalletConnectors,
  LegacySolanaEmbeddedWalletConnectors,
];
```

Add only the connectors for the chains you support. Pair them with `LegacyWalletMigrationProvider` so users can upgrade — see [Migrate existing embedded wallets](#migrate-existing-embedded-wallets).

### `@dynamic-labs/eclipse` removed

The `@dynamic-labs/eclipse` package has been removed. Use `@dynamic-labs/solana` for Eclipse and other Solana-based chains.

### Removed APIs

| Removed                                         | Replacement                                                                  |
| ----------------------------------------------- | ---------------------------------------------------------------------------- |
| `useUpgradeEmbeddedWallet`                      | Use the migration flow from `@dynamic-labs/legacy-embedded-wallet-migration` |
| `useUpgradeToDynamicWaasFlow`                   | Use the migration flow from `@dynamic-labs/legacy-embedded-wallet-migration` |
| `useEmbeddedWallet().revealWalletKey`           | `useLegacyWalletExport().revealWalletKey` (from the migration package)       |
| `useFunding`                                    | `useOnramp` (was already deprecated in V4)                                   |
| `handleUnlinkWallet` (from `useDynamicContext`) | `removeWallet` (from `useDynamicContext`)                                    |

## Migrate existing embedded wallets

<Note>
  This section only applies if you have users on legacy (V1/V2) embedded wallets. If you don't, there is nothing to do here — you can skip it.
</Note>

Users on legacy V1/V2 embedded wallets must upgrade to a V3 wallet. The upgrade always follows the same shape: the user exports the legacy wallet's private key, and that key is imported into a new V3 wallet. The private key is only ever handled client-side.

<Note>
  If you use Account Abstraction, the migration flow automatically resolves the legacy EOA signer behind an ERC-4337 smart wallet or ERC-7702 account. Importing the same key preserves the signer's address, so no separate smart wallet migration is required.
</Note>

Choose one of two paths:

* **With the built-in UI** — Dynamic renders a migration modal that walks the user through export and upgrade. This is the simplest option and the default.
* **Without the UI (headless)** — you build your own UI and drive the upgrade programmatically. Use this when you need full control over the experience.

All migration APIs live in the `@dynamic-labs/legacy-embedded-wallet-migration` package:

```bash theme={"system"}
npm install @dynamic-labs/legacy-embedded-wallet-migration
```

### With the built-in UI

<Tabs>
  <Tab title="React">
    Mount `LegacyWalletMigrationProvider` inside your `DynamicContextProvider`. When a legacy wallet is detected, the migration modal opens automatically and handles the export and upgrade end-to-end.

    ```tsx theme={"system"}
    import { DynamicContextProvider } from '@dynamic-labs/sdk-react-core';
    import { LegacyWalletMigrationProvider } from '@dynamic-labs/legacy-embedded-wallet-migration';

    const App = () => (
      <DynamicContextProvider settings={{ environmentId: 'YOUR_ENVIRONMENT_ID' }}>
        <LegacyWalletMigrationProvider>
          <YourApp />
        </LegacyWalletMigrationProvider>
      </DynamicContextProvider>
    );
    ```

    To open the flow manually (for example, from a "Migrate wallet" button) instead of relying on auto-open, use `useLegacyWalletMigration`:

    ```tsx theme={"system"}
    import { LegacyWalletMigrationProvider, useLegacyWalletMigration } from '@dynamic-labs/legacy-embedded-wallet-migration';

    const MigrateButton = () => {
      const { startUpgrade } = useLegacyWalletMigration();
      return <button onClick={() => startUpgrade()}>Migrate wallet</button>;
    };
    ```
  </Tab>

  <Tab title="React Native">
    The migration modal is built into the SDK and opens automatically when a legacy wallet is detected. No extra components are required — auto-open is on by default:

    ```tsx theme={"system"}
    import { createClient } from '@dynamic-labs/client';
    import { ReactNativeExtension } from '@dynamic-labs/react-native-extension';

    export const dynamicClient = createClient({
      environmentId: 'YOUR_ENVIRONMENT_ID',
      // legacyWalletMigrationModal: { autoOpen: true } is the default
    }).extend(ReactNativeExtension());
    ```

    The modal renders inside the SDK's webview and walks the user through exporting the key and upgrading to V3.
  </Tab>
</Tabs>

### Without the UI (headless)

Disable the built-in modal, then drive the upgrade with your own UI. Choose this option when you need to integrate migration into an existing flow, match custom UI or accessibility requirements, or control when each step appears. You must build and maintain the loading, error, and completion states yourself, so use the built-in UI unless you need that control.

The steps are the same on both platforms:

1. Detect the legacy wallets that need migration.
2. Trigger the private key export for the user.
3. Pass the exported key to the upgrade call.

<Warning>
  The export UI displays the private key inside a cross-origin iframe. Application code cannot read the key while it remains in the iframe. In a headless flow, the user copies the key into an application-controlled input, so your application can access it at that point. Keep the value in memory only long enough to call the upgrade API. Do not log it, persist it, send it to your backend, or include it in analytics or error reports. Clear the input and application state after each upgrade attempt.
</Warning>

<Tabs>
  <Tab title="React">
    Mount `LegacyWalletMigrationProvider` with `autoOpen={false}` (this still satisfies the legacy-wallet enforcement check), then use the migration hooks to build your own UI.

    ```tsx theme={"system"}
    import {
      LegacyWalletMigrationProvider,
      useLegacyWalletsToMigrate,
      useLegacyWalletExport,
      useUpgradeToDynamicWaas,
    } from '@dynamic-labs/legacy-embedded-wallet-migration';

    const MigrationUI = () => {
      const walletsToMigrate = useLegacyWalletsToMigrate();
      const { revealWalletKey } = useLegacyWalletExport();
      const { upgradeToDynamicWaas } = useUpgradeToDynamicWaas();

      return (
        <>
          {walletsToMigrate.map((wallet, index) => (
            <div key={wallet.id}>
              {/* Step 1: export the legacy key into a container you own */}
              <button
                onClick={() =>
                  revealWalletKey({
                    wallet,
                    type: 'privateKey',
                    htmlContainerId: `export-container-${index}`,
                  })
                }
              >
                Export key
              </button>
              <div id={`export-container-${index}`} />

              {/* Step 2: pass the exported key back to upgrade the wallet */}
              <button
                onClick={() => upgradeToDynamicWaas({ privateKey: 'PASTED_KEY', wallet })}
              >
                Upgrade to Dynamic WaaS
              </button>
            </div>
          ))}
        </>
      );
    };

    const App = () => (
      <LegacyWalletMigrationProvider autoOpen={false}>
        <MigrationUI />
      </LegacyWalletMigrationProvider>
    );
    ```

    <Note>
      Mounting `LegacyWalletMigrationProvider` with `autoOpen={false}` satisfies the legacy-wallet enforcement check for you, and the migration hooks work whether or not you render the built-in modal.
    </Note>
  </Tab>

  <Tab title="React Native">
    Set `legacyWalletMigrationModal: { autoOpen: false }` when creating the client, then read the legacy wallets from the client and drive the upgrade yourself.

    ```tsx theme={"system"}
    import { createClient } from '@dynamic-labs/client';
    import { ReactNativeExtension } from '@dynamic-labs/react-native-extension';

    export const dynamicClient = createClient({
      environmentId: 'YOUR_ENVIRONMENT_ID',
      legacyWalletMigrationModal: { autoOpen: false },
    }).extend(ReactNativeExtension());
    ```

    ```tsx theme={"system"}
    import { useReactiveClient } from '@dynamic-labs/react-hooks';
    import { dynamicClient } from './client';

    const MigrationScreen = () => {
      const client = useReactiveClient(dynamicClient);
      const walletsToMigrate = client.wallets.embedded.legacyWalletsToMigrate;

      // Step 1: export the legacy key. This opens a sheet where the user copies
      // their private key.
      const exportKey = () =>
        client.ui.wallets.revealEmbeddedWalletKey({ type: 'private-key' });

      // Step 2: pass the exported key back to upgrade the wallet.
      const upgrade = (privateKey: string) =>
        client.wallets.embedded.upgradeToDynamicWaas({ privateKey });

      return null; // render your own UI using walletsToMigrate, exportKey, upgrade
    };
    ```

    Each entry in `legacyWalletsToMigrate` has `id`, `address`, and `chain`, so you can list the wallets that still need migration.
  </Tab>
</Tabs>

## New features

### Legacy embedded wallet migration package

`@dynamic-labs/legacy-embedded-wallet-migration` is a new package that owns the entire legacy-to-V3 upgrade flow. It provides the built-in migration modal (`LegacyWalletMigrationProvider`) and the headless building blocks (`useLegacyWalletsToMigrate`, `useHasLegacyWalletsToMigrate`, `useLegacyWalletExport`, `useUpgradeToDynamicWaas`) so you can offer either experience.
