> ## 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.

# Upgrading legacy embedded wallets to Dynamic WaaS

> Detect users whose embedded wallets need upgrading and move them onto Dynamic WaaS with the ready-made upgrade app — by redirect, embed, or popup — using the legacy-embedded-wallet-upgrade package.

## Prerequisites

Before this page: users who sign in to your app (see [Post-login user setup](/docs/javascript/building-ui/post-auth-user-setup)) and a Dynamic environment with WaaS enabled.

## What you'll build

Some users may still hold an earlier, legacy generation of embedded wallet that should be moved onto Dynamic WaaS. Dynamic provides a ready-made **upgrade app** that performs the upgrade — the wallet keeps the **same address**.

The `@dynamic-labs-sdk/legacy-embedded-wallet-upgrade` package wraps the whole flow so you only call functions — it derives the environment from your client, builds the app URL, validates the return URL, and handles the completion handshake. Your job is to:

1. **Detect** which users need the upgrade.
2. **Route** them to the upgrade app — by redirect, by embedding it, or by popup.
3. **React** when the upgrade finishes.

This page covers what's shared across the routes. Each route then has its own page:

* [Redirect to the app](/docs/javascript/building-ui/legacy-wallet-upgrade/redirect) — navigate away and read the outcome on return.
* [Embed the app (iframe)](/docs/javascript/building-ui/legacy-wallet-upgrade/embed) — render it inline in a sheet or modal.
* [Popup (new window)](/docs/javascript/building-ui/legacy-wallet-upgrade/popup) — open it in a separate window. **The recommended route for React Native**, where it runs in the system auth session.

If none of those fit, [going lower-level](/docs/javascript/building-ui/legacy-wallet-upgrade/lower-level) lets you build the URL and own the mounting yourself.

## Install

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

<Note>
  The React entry point (`/react`) has `react` and `@dynamic-labs-sdk/react-hooks` as **optional** peer dependencies. Non-React apps import the root and never pull React in.
</Note>

## Detect who needs the upgrade

Two synchronous helpers tell you whether the signed-in user has any wallet that needs upgrading. They read the environment and wallets from your client, so single-client apps call them with no arguments.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={"system"}
    import {
      hasWalletsRequiringUpgrade,
      getWalletsRequiringUpgrade,
    } from '@dynamic-labs-sdk/legacy-embedded-wallet-upgrade';

    function maybePromptUpgrade() {
      if (!hasWalletsRequiringUpgrade()) return;

      const wallets = getWalletsRequiringUpgrade();
      // showUpgradeBanner is your implementation.
      showUpgradeBanner(`${wallets.length} wallet(s) can be upgraded.`);
    }
    ```
  </Tab>

  <Tab title="React / React Native">
    ```tsx theme={"system"}
    import {
      useHasWalletsRequiringUpgrade,
      useWalletsRequiringUpgrade,
    } from '@dynamic-labs-sdk/legacy-embedded-wallet-upgrade/react';

    function UpgradeBanner() {
      const needsUpgrade = useHasWalletsRequiringUpgrade();
      const wallets = useWalletsRequiringUpgrade();

      if (!needsUpgrade) return null;

      return <p>{wallets.length} wallet(s) can be upgraded.</p>;
    }
    ```
  </Tab>
</Tabs>

## Choose a route

| Route                                                                 | When to use                                                                                                               |
| --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| [Redirect](/docs/javascript/building-ui/legacy-wallet-upgrade/redirect)    | Simplest — you don't mind a full-page navigation away and back.                                                           |
| [Embed (iframe)](/docs/javascript/building-ui/legacy-wallet-upgrade/embed) | You want the app inline in your own sheet/modal and never leave the page. Web only.                                       |
| [Popup](/docs/javascript/building-ui/legacy-wallet-upgrade/popup)          | A redirect is too heavy but you don't want it inline. **Use this for React Native** — it runs in the system auth session. |

## Hosting the app

By default every helper points at Dynamic's hosted upgrade app. You can run it three ways:

1. **Dynamic-hosted** — use the shared Dynamic-hosted deployment as-is (the default; pass nothing).
2. **Your own subdomain** — point a CNAME (e.g. `upgrade.yourapp.com`) at the deployment so it runs on your own domain and branding.
3. **Fork & self-host** — clone the [upgrade app repo](https://github.com/dynamic-labs-oss/waas-migration), build, and host it yourself.

For options 2 and 3, tell the package where the app lives with `baseUrl` — every entry point accepts it:

```typescript theme={"system"}
const baseUrl = 'https://upgrade.yourapp.com';

// Redirect
void startUpgradeRedirect({ baseUrl });

// Embed
const session = openUpgradeIframe({ baseUrl, container });

// Popup
const popupSession = openUpgradePopup({ baseUrl });

// Low-level
const uri = buildUpgradeUrl({ baseUrl });
const unsubscribe = subscribeToUpgradeCompletion({ baseUrl, onComplete });
```

## Required: allowlist the app's origin (CORS)

Add the origin serving the app to your environment's **allowed origins** in the Dynamic dashboard, or the app's calls will be blocked:

* **Your own subdomain / self-host** → add your origin (e.g. `https://upgrade.yourapp.com`).
* **Dynamic-hosted** → add `https://upgrade-wallet.dynamic.dev`.

If the app can't reach Dynamic, it shows an "add your domain to allowed origins" screen — a signal that the serving origin isn't allowlisted.

## Custom auth domains

If your environment authenticates through a **custom domain** (e.g. `auth.yourapp.com` in place of Dynamic's default API), the Dynamic-hosted upgrade app only authenticates against that domain once it's on Dynamic's internal allowlist. Environments that already had a custom domain configured when Dynamic v5 shipped are allowlisted — the flow works out of the box.

<Note>
  Set up a custom domain more recently and the upgrade fails to reach your API? Reach out to Dynamic to have your domain added to the allowlist. (If you fork and self-host the app, you control this list yourself.)
</Note>

## Smoother UX: skip the extra login

If you serve the app on a **subdomain of your app's domain** and your environment uses **cookie-based sessions**, an already-signed-in user lands in the app already authenticated — no second login. Otherwise the user simply signs in again with the same credential they use in your app.

<Tip>
  Tell the just-arrived user to sign in with the **same credential** they use in your app — that's how the app finds the wallet to upgrade.
</Tip>

## Handling errors

| Situation                   | What to do                                                                          |
| --------------------------- | ----------------------------------------------------------------------------------- |
| Outcome `status` is `error` | The upgrade failed or was cancelled — offer a retry.                                |
| App calls blocked           | The serving origin isn't allowlisted — add it to the environment's allowed origins. |

Route-specific errors (blocked/closed popups, etc.) are covered on each route's page.

## See also

* [Post-login user setup](/docs/javascript/building-ui/post-auth-user-setup) — detect upgrade needs alongside other post-login steps
* [Connected wallets management](/docs/javascript/building-ui/connected-wallets-management) — the upgraded wallet appears here with the same address
* [Wallet backup & recovery](/docs/javascript/building-ui/wallet-backup-recovery) — back up the wallet after upgrading
