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

# Migrate from Fireblocks

> Move an existing Fireblocks Non-Custodial Wallet (NCW) into a Dynamic embedded wallet in a single call.

Migration takes over the key material from a user's Fireblocks Non-Custodial Wallet (NCW) and provisions Dynamic [embedded wallets](/overview/wallets/embedded-wallets/mpc/overview) for it. It is a single cross-chain operation: one call migrates the device across every supported chain and preserves the wallet addresses the user already had.

<Note>
  **Before you start.** This is for apps already running a Fireblocks NCW integration. The user must be **signed in to Dynamic** — migration creates the embedded wallet for the current Dynamic user, and your Fireblocks wallet is left untouched. You supply the existing device's `deviceId`, `password`, and `storage` from your NCW integration, plus a `jwt` from your own auth. Migration currently supports **EVM** and **Solana**; other chains come back as a per-chain failure (see [Result](#result)).
</Note>

## Prerequisites

<Note>
  Fireblocks migration is behind a feature flag. [Contact support](https://www.dynamic.xyz/contact) to enable it for your environment.
</Note>

Before users can migrate, set up the migration backend — see [Migrate from Fireblocks](/overview/wallets/embedded-wallets/mpc/migrate-from-fireblocks) for the full setup. In short:

* **Dashboard** — configure the migration backend base URL and environment (`sandbox` / `production`) in your [environment settings](https://app.dynamic.xyz/dashboard/embedded-wallets/dynamic).
* **Backend** — expose `POST /api/devices/{deviceId}/rpc` that relays NCW messages to Fireblocks, and enable CORS for the Dynamic iframe origin `https://app.dynamicauth.com`.

<Frame>
  <img src="https://mintcdn.com/dynamic-docs-testing/toNPEsthPMpi15t9/images/mpc/fireblocks-migration-settings.png?fit=max&auto=format&n=toNPEsthPMpi15t9&q=85&s=640e08a5350bc4f0fcb14fa22a6c87e5" alt="Fireblocks migration settings in the Dynamic dashboard" width="3374" height="432" data-path="images/mpc/fireblocks-migration-settings.png" />
</Frame>

## Migrate a wallet

`migrateFromFireblocks(options)` is available from the `useDynamicWaas` hook.

**Parameters:**

* `deviceId`: string — The Fireblocks NCW device id being migrated.
* `jwt`: string — Your application's auth token for the user (for example, from social login). It's forwarded as the bearer token to your backend's RPC endpoint, which authenticates the request. This is not a Fireblocks credential — your Fireblocks API auth stays on the backend.
* `password`: string — The password that encrypted the NCW device storage. Required.
* `storage`: `Record<string, string>` — The device's persisted Fireblocks NCW entries (every `NCW-*` key), **verbatim — the encrypted values exactly as stored**. See [Building the `storage` map](#building-the-storage-map).
* `expectedAddresses`: `Record<string, string>` — Required. The wallet address Fireblocks held per chain name (for example, `{ EVM: "0x…" }`). **Its keys are the chains migration runs on** — only those chains are migrated — and each imported Dynamic address is verified against the value, so a wrong derivation fails loudly instead of producing an unusable wallet.

```tsx theme={"system"}
import { useDynamicWaas } from "@dynamic-labs/sdk-react-core";

const { migrateFromFireblocks } = useDynamicWaas();

const result = await migrateFromFireblocks({
  deviceId,
  jwt,
  password,
  storage,
});

if (!result) return; // no embedded wallet chain enabled for this environment

const failed = result.wallets.filter((w) => !w.result.ok);
```

The call returns once every chain has been processed. It resolves to `undefined` if no embedded wallet chain is enabled for the environment; otherwise inspect `result.wallets` for the per-chain outcome (see [Result](#result)).

## Building the `storage` map

`storage` must contain the device's persisted Fireblocks NCW entries (every `NCW-*` key) **verbatim — the encrypted values exactly as stored**. The migration iframe re-hydrates this map and decrypts the secure entries itself using the same `password` and `deviceId`, so the values must stay as ciphertext.

Build it in your application, where your Fireblocks NCW integration persisted the device. The snippet below reads the default browser storage; if you configured a custom NCW storage provider, read the device's entries from there instead.

```ts theme={"system"}
import { BrowserLocalStorageProvider } from "@fireblocks/ncw-js-sdk";

const buildStorage = async (): Promise<Record<string, string>> => {
  const provider = new BrowserLocalStorageProvider();
  // getAllKeys() returns logical keys with the `NCW-` prefix stripped.
  const keys = await provider.getAllKeys();
  const storage: Record<string, string> = {};
  for (const key of keys) {
    // The base provider returns the raw stored value (ciphertext for secure entries).
    const value = await provider.get(key);
    // Re-add the `NCW-` prefix so the map matches what was persisted.
    if (value !== null) storage[`NCW-${key}`] = value;
  }
  return storage;
};
```

<Warning>
  Enumerate the **base** `BrowserLocalStorageProvider` so secure entries stay encrypted. Do **not** read entries through a secure-storage provider (`ISecureStorageProvider`, e.g. `PasswordEncryptedLocalStorage`) — its `get()` **decrypts** to plaintext, which will not round-trip through the takeover.
</Warning>

## Result

On success the call resolves to a `MigrateFromFireblocksResponse`:

```ts theme={"system"}
type MigrateFromFireblocksResponse = {
  wallets: MigratedWalletResult[];
  migration: { fireblocksDeviceId: string };
};

type MigratedWalletResult = {
  chainName: string;
  result:
    // Migrated on this run.
    | { ok: true; account: CreateWalletAccountResponse; fireblocksAlgorithm: string; fireblocksKeyId: string }
    // Already migrated on a prior run — the wallet exists, nothing to do.
    | { ok: true; alreadyMigrated: true }
    // This chain failed; other chains may still have succeeded.
    | { ok: false; errorCode: string };
};
```

Migration is per chain, so inspect `wallets` to see which chains migrated. `account` is the created Dynamic embedded wallet account; `fireblocksAlgorithm` and `fireblocksKeyId` identify the source Fireblocks key, useful for logging and reconciliation.

Re-running migration is safe: a chain already imported on a prior run comes back as `{ ok: true, alreadyMigrated: true }`. A `{ ok: false }` entry means that chain failed while others may have succeeded — surface it and let the user retry. Possible `errorCode` values:

| `errorCode`                   | Meaning                                                                                       |
| ----------------------------- | --------------------------------------------------------------------------------------------- |
| `MIGRATION_UNSUPPORTED_CHAIN` | The chain can't be migrated from a Fireblocks NCW wallet (e.g. Aleo, Midnight).               |
| `MIGRATION_UNVERIFIED_CHAIN`  | The chain isn't supported for migration yet.                                                  |
| `MIGRATION_NO_KEY_FOR_ALGO`   | The device has no key for that chain's signing algorithm.                                     |
| `MIGRATION_IMPORT_*`          | The key import into Dynamic failed (transient backend or address-check issue). Safe to retry. |

Treat any other `ok: false` as a failure to surface and retry.

<Warning>
  The `password` must match the password that originally encrypted the Fireblocks NCW device storage. A wrong password fails the takeover; it does not create a usable wallet.
</Warning>

## Related

* [Importing & Exporting Embedded Wallets](/react/wallets/embedded-wallets/mpc/import-export)
* [Password Encryption](/react/wallets/embedded-wallets/mpc/password-encryption)
* [Recovery](/react/wallets/embedded-wallets/mpc/recovery)
