Skip to main content
Migration takes over the key material from a user’s Fireblocks Non-Custodial Wallet (NCW) and provisions Dynamic embedded wallets 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.
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).

Prerequisites

Fireblocks migration is behind a feature flag. Contact support to enable it for your environment.
Before users can migrate, set up the migration backend — see Migrate from Fireblocks for the full setup. In short:
  • Dashboard — configure the migration backend base URL and environment (sandbox / production) in your environment settings.
  • 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.
Fireblocks migration settings in the Dynamic dashboard

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.
  • 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.
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).

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.
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;
};
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.

Result

On success the call resolves to a MigrateFromFireblocksResponse:
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:
errorCodeMeaning
MIGRATION_UNSUPPORTED_CHAINThe chain can’t be migrated from a Fireblocks NCW wallet (e.g. Aleo, Midnight).
MIGRATION_UNVERIFIED_CHAINThe chain isn’t supported for migration yet.
MIGRATION_NO_KEY_FOR_ALGOThe 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.
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.
Last modified on June 25, 2026