# Migrate from Dynamic

This guide walks you through migrating from Dynamic to Openfort. The migration involves updating your SDK dependencies, provider configuration, authentication flows, and wallet interactions.

:::warning
This migration generates new wallet addresses for your users. Users need to transfer any assets from their Dynamic wallets to their new Openfort wallets after migration.
:::

## Before you begin

Before starting the migration:

1. Complete the [Openfort quickstart](/docs/products/embedded-wallet/react) to understand the SDK structure.
2. Obtain your [API keys](/docs/configuration/api-keys) from the Openfort dashboard.
3. Set up a [recovery endpoint](/docs/products/embedded-wallet/server/automatic-recovery-session) (for automatic recovery).

### Feature mapping

| Dynamic feature | Openfort equivalent | Notes |
|-----------------|---------------------|-------|
| Email authentication | `AuthProvider.EMAIL_OTP` | OTP-based authentication |
| Social login (Google, Facebook) | `AuthProvider.GOOGLE`, `AuthProvider.FACEBOOK` | Configure in dashboard |
| SMS authentication | `AuthProvider.PHONE` | OTP-based authentication |
| External wallets | `AuthProvider.WALLET` | WalletConnect support |
| `DynamicWidget` modal | `OpenfortButton` | Pre-built UI component |
| Appearance customization | `uiConfig` prop | Theme and branding |

::::steps

### Install Openfort dependencies

Remove Dynamic packages and install Openfort with its peer dependencies:

:::code-group

```sh [npm]
npm uninstall @dynamic-labs/sdk-react-core @dynamic-labs/ethereum @dynamic-labs/ethers-v6
npm install @openfort/react wagmi viem@^2.22.0 @tanstack/react-query
```

```sh [yarn]
yarn remove @dynamic-labs/sdk-react-core @dynamic-labs/ethereum @dynamic-labs/ethers-v6
yarn add @openfort/react wagmi viem@^2.22.0 @tanstack/react-query
```

```sh [pnpm]
pnpm remove @dynamic-labs/sdk-react-core @dynamic-labs/ethereum @dynamic-labs/ethers-v6
pnpm add @openfort/react wagmi viem@^2.22.0 @tanstack/react-query
```

:::

### Update provider configuration

Replace the Dynamic provider with Openfort's layered provider structure.

**Before (Dynamic):**

```tsx [Providers.tsx (Dynamic)]
import { DynamicContextProvider, DynamicWidget } from '@dynamic-labs/sdk-react-core';
import { EthereumWalletConnectors } from '@dynamic-labs/ethereum';

function App() {
  return (
    <DynamicContextProvider
      settings={{
        environmentId: 'YOUR_DYNAMIC_ENVIRONMENT_ID',
        walletConnectors: [EthereumWalletConnectors],
      }}
    >
      <DynamicWidget />
      <YourApp />
    </DynamicContextProvider>
  );
}
```

**After (Openfort):**

```tsx [Providers.tsx (Openfort)]
import {
  AuthProvider,
  OpenfortProvider,
  RecoveryMethod,
} from "@openfort/react";
import { getDefaultConfig, OpenfortWagmiBridge } from "@openfort/react/wagmi";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { WagmiProvider, createConfig } from "wagmi";
import { mainnet, sepolia } from "viem/chains";

const config = createConfig(
  getDefaultConfig({
    appName: "Your App Name",
    chains: [mainnet, sepolia],  // [!code highlight]
    ssr: true,
  })
);

const queryClient = new QueryClient();

function Providers({ children }: { children: React.ReactNode }) {
  return (
    <QueryClientProvider client={queryClient}>
      <WagmiProvider config={config}>
        <OpenfortWagmiBridge>
        <OpenfortProvider
          publishableKey="YOUR_OPENFORT_PUBLISHABLE_KEY"  // [!code highlight]
          walletConfig={{
            shieldPublishableKey: "YOUR_SHIELD_PUBLISHABLE_KEY",  // [!code highlight]
            createEncryptedSessionEndpoint: "YOUR_RECOVERY_ENDPOINT",  // [!code highlight]
          }}
          uiConfig={{
            authProviders: [
              AuthProvider.EMAIL_OTP,  // [!code highlight]
              AuthProvider.GOOGLE,
              AuthProvider.WALLET,
            ],
            walletRecovery: {
              defaultMethod: RecoveryMethod.AUTOMATIC,  // [!code highlight]
            },
          }}
        >
          {children}
        </OpenfortProvider>
        </OpenfortWagmiBridge>
      </WagmiProvider>
    </QueryClientProvider>
  );
}
```

### Update authentication code

Replace Dynamic authentication hooks with Openfort equivalents.

**Before (Dynamic):**

```tsx [LoginButton.tsx (Dynamic)]
import { useDynamicContext, useIsLoggedIn } from '@dynamic-labs/sdk-react-core';

function LoginButton() {
  const { user, setShowAuthFlow, handleLogOut } = useDynamicContext();
  const isLoggedIn = useIsLoggedIn();

  if (isLoggedIn) {
    return (
      <div>
        <p>Welcome, {user?.email}</p>
        <button onClick={handleLogOut}>Logout</button>
      </div>
    );
  }

  return (
    <button onClick={() => setShowAuthFlow(true)}>
      Login with Dynamic
    </button>
  );
}
```

**After (Openfort):**

```tsx [LoginButton.tsx (Openfort)]
import { useUser, useSignOut, OpenfortButton } from "@openfort/react";

function LoginButton() {
  const { user, isLoading, isAuthenticated } = useUser();  // [!code highlight]
  const { signOut } = useSignOut();

  if (isLoading) return <div>Loading...</div>;  // [!code highlight]

  if (isAuthenticated) {
    return (
      <div>
        <p>Welcome, {user?.email}</p>
        <button onClick={signOut}>Sign Out</button>
      </div>
    );
  }

  // Use the pre-built button component  // [!code highlight]
  return <OpenfortButton />;  // [!code highlight]
}
```

For email OTP authentication specifically:

**Before (Dynamic):**

```tsx [EmailLogin.tsx (Dynamic)]
import { useDynamicContext } from '@dynamic-labs/sdk-react-core';

function EmailLogin() {
  const { setShowAuthFlow } = useDynamicContext();

  // Dynamic handles email OTP through the DynamicWidget modal
  return (
    <button onClick={() => setShowAuthFlow(true)}>
      Sign in with Email
    </button>
  );
}
```

**After (Openfort):**

```tsx [EmailLogin.tsx (Openfort)]
import { useEmailOtpAuth } from "@openfort/react";  // [!code highlight]
import { useState } from "react";

function EmailLogin() {
  const {  // [!code highlight]
    requestEmailOtp,  // [!code highlight]
    signInEmailOtp,  // [!code highlight]
    isRequesting,  // [!code highlight]
    isLoading,  // [!code highlight]
  } = useEmailOtpAuth();  // [!code highlight]
  const [email, setEmail] = useState('');
  const [otp, setOtp] = useState('');
  const [otpSent, setOtpSent] = useState(false);  // [!code highlight]

  const handleRequestOtp = async () => {
    await requestEmailOtp({ email });  // [!code highlight]
    setOtpSent(true);  // [!code highlight]
  };

  const handleSignIn = async () => {
    await signInEmailOtp({ email, otp });  // [!code highlight]
  };

  return (
    <div>
      <input value={email} onChange={(e) => setEmail(e.target.value)} />
      {otpSent && (
        <input value={otp} onChange={(e) => setOtp(e.target.value)} />
      )}
      {!otpSent ? (
        <button onClick={handleRequestOtp} disabled={isRequesting}>
          {isRequesting ? 'Sending...' : 'Send OTP'}
        </button>
      ) : (
        <button onClick={handleSignIn} disabled={isLoading}>
          {isLoading ? 'Verifying...' : 'Verify'}
        </button>
      )}
    </div>
  );
}
```

### Update wallet access code

Replace Dynamic wallet access with Wagmi hooks.

**Before (Dynamic):**

```tsx [WalletInfo.tsx (Dynamic)]
import { useDynamicContext } from '@dynamic-labs/sdk-react-core';

function WalletInfo() {
  const { primaryWallet } = useDynamicContext();

  if (!primaryWallet) return <p>No wallet</p>;

  return <p>Wallet: {primaryWallet.address}</p>;
}
```

**After (Openfort):**

```tsx [WalletInfo.tsx (Openfort)]
import { useAccount } from "wagmi";  // [!code highlight]

function WalletInfo() {
  const { address, isConnected } = useAccount();  // [!code highlight]

  if (!isConnected) return <p>No wallet</p>;

  return <p>Wallet: {address}</p>;  // Smart wallet address
}
```

### Update transaction code

Replace Dynamic transaction code with Wagmi hooks.

**Before (Dynamic):**

```tsx [SendTransaction.tsx (Dynamic)]
import { useDynamicContext } from '@dynamic-labs/sdk-react-core';
import { isEthereumWallet } from '@dynamic-labs/ethereum';

function SendTransaction() {
  const { primaryWallet } = useDynamicContext();

  const send = async () => {
    if (!primaryWallet || !isEthereumWallet(primaryWallet)) return;

    const walletClient = await primaryWallet.getWalletClient();
    const tx = await walletClient.sendTransaction({
      to: '0xRecipientAddress',
      value: BigInt(1000000000000000),
    });
    console.log('Transaction:', tx);
  };

  return <button onClick={send}>Send Transaction</button>;
}
```

**After (Openfort):**

```tsx [SendTransaction.tsx (Openfort)]
import { useSendTransaction, useAccount } from "wagmi";  // [!code highlight]
import { parseEther } from "viem";

function SendTransaction() {
  const { address } = useAccount();
  const { sendTransaction, isPending } = useSendTransaction();  // [!code highlight]

  const send = () => {
    sendTransaction({
      to: '0xRecipientAddress',
      value: parseEther('0.001'),
    });
  };

  return (
    <button onClick={send} disabled={isPending || !address}>
      {isPending ? 'Sending...' : 'Send Transaction'}
    </button>
  );
}
```

### Update message signing

Replace Dynamic signing with Wagmi hooks.

**Before (Dynamic):**

```tsx [SignMessage.tsx (Dynamic)]
import { useDynamicContext } from '@dynamic-labs/sdk-react-core';

function SignMessage() {
  const { primaryWallet } = useDynamicContext();

  const sign = async () => {
    if (!primaryWallet) return;
    const signature = await primaryWallet.signMessage('Hello World');
    console.log('Signature:', signature);
  };

  return <button onClick={sign}>Sign Message</button>;
}
```

**After (Openfort):**

```tsx [SignMessage.tsx (Openfort)]
import { useSignMessage } from "wagmi";  // [!code highlight]

function SignMessage() {
  const { signMessage, isPending } = useSignMessage();  // [!code highlight]

  const sign = () => {
    signMessage({ message: 'Hello World' });
  };

  return (
    <button onClick={sign} disabled={isPending}>
      {isPending ? 'Signing...' : 'Sign Message'}
    </button>
  );
}
```

### Remove Dynamic dependencies

Clean up unused Dynamic packages:

:::code-group

```sh [npm]
npm uninstall @dynamic-labs/sdk-react-core @dynamic-labs/ethereum @dynamic-labs/ethers-v6
```

```sh [yarn]
yarn remove @dynamic-labs/sdk-react-core @dynamic-labs/ethereum @dynamic-labs/ethers-v6
```

```sh [pnpm]
pnpm remove @dynamic-labs/sdk-react-core @dynamic-labs/ethereum @dynamic-labs/ethers-v6
```

:::

::::

## Hook mapping reference

| Dynamic hook/method | Openfort/Wagmi equivalent |
|---------------------|---------------------------|
| `useDynamicContext` (`user`, `handleLogOut`) | `useUser` + `useSignOut` |
| `useIsLoggedIn` | `useUser` (`isAuthenticated`) |
| `setShowAuthFlow(true)` | `OpenfortButton` or individual auth hooks |
| `useEmbeddedWallet` | Automatic via wallet config |
| `primaryWallet` from `useDynamicContext` | `useAccount` (wagmi) |
| `primaryWallet.signMessage()` | `useSignMessage` (wagmi) |
| `primaryWallet.getWalletClient()` | `useSendTransaction` (wagmi) |
| `useUserWallets` | `useAccount` (wagmi) |

## Considerations

### Embedded wallet creation

Dynamic creates embedded wallets through the `DynamicWidget` modal or via `useEmbeddedWallet`. Openfort creates wallets automatically during authentication based on your recovery method configuration.

### Session handling

Dynamic sessions are invalidated during migration. Users need to re-authenticate with Openfort.

### Wallet addresses

Users receive new smart wallet addresses. Communicate this change to users before migration so they can:

1. Export any assets from their Dynamic wallet.
2. Transfer assets to their new Openfort wallet after authentication.

### Key management differences

Dynamic uses TSS-MPC (Threshold Signature Scheme Multi-Party Computation) with key shares distributed between the user's device and a server in a Trusted Execution Environment. Openfort provides three recovery options:

| Recovery method | Description | Backend required |
|-----------------|-------------|------------------|
| Automatic | Seamless, backend-managed recovery | Yes |
| Password | User-set password for recovery | No |
| Passkey | Biometric/device authentication | No |

### Chain configuration

Both Dynamic and Openfort support multiple chains. Update your chain configuration in the Wagmi config:

```tsx [config.ts]
import { getDefaultConfig } from "@openfort/react/wagmi";
import { mainnet, polygon, arbitrum, base } from "viem/chains";
import { createConfig } from "wagmi";

const config = createConfig(
  getDefaultConfig({
    appName: "Your App",
    chains: [mainnet, polygon, arbitrum, base],
  })
);
```

## Next steps

<HoverCardLayout>
  <HoverCardLink description="Configure your embedded wallet settings." href="/products/embedded-wallet/react/wallet" title="Wallet configuration" subtitle="Wallet setup" icon={Wallet} color="#3B82F6" />

  <HoverCardLink description="Cover gas fees for your users." href="/configuration/gas-sponsorship" title="Gas sponsorship" subtitle="Paymaster setup" icon={Fuel} color="#10B981" />

  <HoverCardLink description="Delegate signing with session keys." href="/products/embedded-wallet/react/wallet/actions/session-keys" title="Session keys" subtitle="Delegated signing" icon={Key} color="#8B5CF6" />
</HoverCardLayout>
