# Migrate from Privy

This guide walks you through migrating from Privy 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 Privy 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

| Privy feature | Openfort equivalent | Notes |
|---------------|---------------------|-------|
| Email OTP | `AuthProvider.EMAIL_OTP` | Similar OTP flow |
| Social login (Google, Twitter) | `AuthProvider.GOOGLE`, `AuthProvider.TWITTER` | Configure in dashboard |
| External wallets | `AuthProvider.WALLET` | WalletConnect support |
| Embedded wallet auto-create | Automatic via wallet config | Configured in provider |
| Appearance customization | `uiConfig` prop | Theme and branding |

::::steps

### Install Openfort dependencies

Remove Privy packages and install Openfort with its peer dependencies:

:::code-group

```sh [npm]
npm uninstall @privy-io/react-auth
npm install @openfort/react wagmi viem@^2.22.0 @tanstack/react-query
```

```sh [yarn]
yarn remove @privy-io/react-auth
yarn add @openfort/react wagmi viem@^2.22.0 @tanstack/react-query
```

```sh [pnpm]
pnpm remove @privy-io/react-auth
pnpm add @openfort/react wagmi viem@^2.22.0 @tanstack/react-query
```

:::

### Update provider configuration

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

**Before (Privy):**

```tsx [Providers.tsx (Privy)]
import { PrivyProvider } from '@privy-io/react-auth';

function App() {
  return (
    <PrivyProvider
      appId="your-privy-app-id"
      config={{
        appearance: {
          theme: 'dark',
          accentColor: '#6366f1',
          logo: 'https://your-logo.com/logo.png',
        },
        loginMethods: ['email', 'wallet', 'google', 'twitter'],
        embeddedWallets: {
          ethereum: {
            createOnLogin: 'users-without-wallets',
          },
        },
      }}
    >
      <YourApp />
    </PrivyProvider>
  );
}
```

**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, polygon } from "viem/chains";

const config = createConfig(
  getDefaultConfig({
    appName: "Your App Name",
    chains: [mainnet, polygon],  // [!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.TWITTER,
              AuthProvider.WALLET,
            ],
            walletRecovery: {
              defaultMethod: RecoveryMethod.AUTOMATIC,  // [!code highlight]
            },
            // Appearance configuration
            theme: "auto",  // [!code highlight]
            customTheme: {  // [!code highlight]
              '--ck-accent-color': '#6366f1',  // [!code highlight]
            },  // [!code highlight]
          }}
        >
          {children}
        </OpenfortProvider>
        </OpenfortWagmiBridge>
      </WagmiProvider>
    </QueryClientProvider>
  );
}
```

### Update authentication code

Replace Privy authentication hooks with Openfort equivalents.

**Before (Privy):**

```tsx [LoginButton.tsx (Privy)]
import { usePrivy } from '@privy-io/react-auth';

function LoginButton() {
  const { ready, authenticated, user, login, logout } = usePrivy();

  if (!ready) return <div>Loading...</div>;

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

  return <button onClick={login}>Login with Privy</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 (Privy):**

```tsx [EmailLogin.tsx (Privy)]
import { useLoginWithEmail } from '@privy-io/react-auth';

function EmailLogin() {
  const { sendCode, loginWithCode, state } = useLoginWithEmail();
  const [email, setEmail] = useState('');
  const [code, setCode] = useState('');

  const handleSubmit = async () => {
    if (state.status === 'initial') {
      await sendCode({ email });
    } else if (state.status === 'awaiting-code-input') {
      await loginWithCode({ code });
    }
  };

  return (
    <div>
      <input value={email} onChange={(e) => setEmail(e.target.value)} />
      {state.status === 'awaiting-code-input' && (
        <input value={code} onChange={(e) => setCode(e.target.value)} />
      )}
      <button onClick={handleSubmit}>
        {state.status === 'initial' ? 'Send Code' : 'Verify'}
      </button>
    </div>
  );
}
```

**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 Privy wallet access with Wagmi hooks.

**Before (Privy):**

```tsx [WalletInfo.tsx (Privy)]
import { useWallets } from '@privy-io/react-auth';

function WalletInfo() {
  const { wallets } = useWallets();
  const embeddedWallet = wallets.find(w => w.walletClientType === 'privy');

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

  return <p>Wallet: {embeddedWallet.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 Privy transaction hooks with Wagmi equivalents.

**Before (Privy):**

```tsx [SendTransaction.tsx (Privy)]
import { useSendTransaction } from '@privy-io/react-auth';

function SendTransaction() {
  const { sendTransaction } = useSendTransaction();

  const send = async () => {
    await sendTransaction({
      to: '0xRecipientAddress',
      value: 100000,
    });
  };

  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 Privy signing with Wagmi hooks.

**Before (Privy):**

```tsx [SignMessage.tsx (Privy)]
import { useSignMessage } from '@privy-io/react-auth';

function SignMessage() {
  const { signMessage } = useSignMessage();

  const sign = async () => {
    const { signature } = await signMessage({ message: '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 Privy dependencies

Clean up unused Privy packages:

:::code-group

```sh [npm]
npm uninstall @privy-io/react-auth @privy-io/wagmi-connector
```

```sh [yarn]
yarn remove @privy-io/react-auth @privy-io/wagmi-connector
```

```sh [pnpm]
pnpm remove @privy-io/react-auth @privy-io/wagmi-connector
```

:::

::::

## Hook mapping reference

| Privy hook | Openfort/Wagmi equivalent |
|------------|---------------------------|
| `usePrivy` (`login`, `logout`, `authenticated`) | `useUser` + `useSignOut` + `OpenfortButton` |
| `useLoginWithEmail` | `useEmailOtpAuth` |
| `useWallets` | `useAccount` (wagmi) |
| `useSendTransaction` | `useSendTransaction` (wagmi) |
| `useSignMessage` | `useSignMessage` (wagmi) |

## Considerations

### Embedded wallet creation

Privy's `createOnLogin: 'users-without-wallets'` is the default behavior in Openfort. Openfort creates wallets automatically during authentication based on your recovery method configuration.

### Session handling

Privy 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 Privy wallet.
2. Transfer assets to their new Openfort wallet after authentication.

### Chain configuration

Both Privy 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],
  })
);
```

### Smart wallet benefits

Openfort wallets are ERC-4337 smart contract accounts. After migration, you gain access to:

* **Gas sponsorship**: Cover gas fees for your users.
* **Transaction batching**: Combine multiple operations into one transaction.
* **Session keys**: Allow specific actions without user confirmation.

Configure gas sponsorship in the [Openfort dashboard](https://dashboard.openfort.io).

### Recovery methods

Privy uses TEE-based key management. 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 |

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