Skip to content
LogoLogo

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.

Before you begin

Before starting the migration:

  1. Complete the Openfort quickstart to understand the SDK structure.
  2. Obtain your API keys from the Openfort dashboard.
  3. Set up a recovery endpoint (for automatic recovery).

Feature mapping

Dynamic featureOpenfort equivalentNotes
Email authenticationAuthProvider.EMAIL_OTPOTP-based authentication
Social login (Google, Facebook)AuthProvider.GOOGLE, AuthProvider.FACEBOOKConfigure in dashboard
SMS authenticationAuthProvider.PHONEOTP-based authentication
External walletsAuthProvider.WALLETWalletConnect support
DynamicWidget modalOpenfortButtonPre-built UI component
Appearance customizationuiConfig propTheme and branding

Install Openfort dependencies

Remove Dynamic packages and install Openfort with its peer dependencies:

Update provider configuration

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

Before (Dynamic):

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):

Providers.tsx (Openfort)
import {
  AuthProvider,
  OpenfortProvider,
  getDefaultConfig,
  RecoveryMethod,
} from "@openfort/react";
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],  
    ssr: true,
  })
);
 
const queryClient = new QueryClient();
 
function Providers({ children }: { children: React.ReactNode }) {
  return (
    <WagmiProvider config={config}>
      <QueryClientProvider client={queryClient}>
        <OpenfortProvider
          publishableKey="YOUR_OPENFORT_PUBLISHABLE_KEY"
          walletConfig={{
            shieldPublishableKey: "YOUR_SHIELD_PUBLISHABLE_KEY",  
            createEncryptedSessionEndpoint: "YOUR_RECOVERY_ENDPOINT",  
          }}
          uiConfig={{
            authProviders: [
              AuthProvider.EMAIL_OTP,  
              AuthProvider.GOOGLE,
              AuthProvider.WALLET,
            ],
            walletRecovery: {
              defaultMethod: RecoveryMethod.AUTOMATIC,  
            },
          }}
        >
          {children}
        </OpenfortProvider>
      </QueryClientProvider>
    </WagmiProvider>
  );
}

Update authentication code

Replace Dynamic authentication hooks with Openfort equivalents.

Before (Dynamic):

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):

LoginButton.tsx (Openfort)
import { useUser, useSignOut, OpenfortButton } from "@openfort/react";
 
function LoginButton() {
  const { user, isAuthenticated } = useUser();  
  const { signOut } = useSignOut();
 
  if (!isAuthenticated) {
    // Use the pre-built button component
    return <OpenfortButton />;  
  }
 
  return (
    <div>
      <p>Welcome, {user?.email}</p>
      <button onClick={signOut}>Sign Out</button>
    </div>
  );
}

For email OTP authentication specifically:

Before (Dynamic):

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):

EmailLogin.tsx (Openfort)
import { useEmailOtpAuth } from "@openfort/react";  
 
function EmailLogin() {
  const {  
    requestEmailOtp,  
    signInEmailOtp,  
    isRequesting,  
    isLoading,  
    isAwaitingInput,  
  } = useEmailOtpAuth();  
  const [email, setEmail] = useState('');
  const [otp, setOtp] = useState('');
 
  const handleRequestOtp = async () => {
    await requestEmailOtp({ email });  
  };
 
  const handleSignIn = async () => {
    await signInEmailOtp({ email, otp });  
  };
 
  return (
    <div>
      <input value={email} onChange={(e) => setEmail(e.target.value)} />
      {isAwaitingInput && (
        <input value={otp} onChange={(e) => setOtp(e.target.value)} />
      )}
      {!isAwaitingInput ? (
        <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):

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):

WalletInfo.tsx (Openfort)
import { useAccount } from "wagmi";  
 
function WalletInfo() {
  const { address, isConnected } = useAccount();  
 
  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):

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):

SendTransaction.tsx (Openfort)
import { useSendTransaction, useAccount } from "wagmi";  
import { parseEther } from "viem";
 
function SendTransaction() {
  const { address } = useAccount();
  const { sendTransaction, isPending } = useSendTransaction();  
 
  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):

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):

SignMessage.tsx (Openfort)
import { useSignMessage } from "wagmi";  
 
function SignMessage() {
  const { signMessage, isPending } = useSignMessage();  
 
  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:

Hook mapping reference

Dynamic hook/methodOpenfort/Wagmi equivalent
useDynamicContext (user, handleLogOut)useUser + useSignOut
useIsLoggedInuseUser (isAuthenticated)
setShowAuthFlow(true)OpenfortButton or individual auth hooks
useEmbeddedWalletAutomatic via wallet config
primaryWallet from useDynamicContextuseAccount (wagmi)
primaryWallet.signMessage()useSignMessage (wagmi)
primaryWallet.getWalletClient()useSendTransaction (wagmi)
useUserWalletsuseAccount (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 methodDescriptionBackend required
AutomaticSeamless, backend-managed recoveryYes
PasswordUser-set password for recoveryNo
PasskeyBiometric/device authenticationNo

Chain configuration

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

config.ts
import { mainnet, polygon, arbitrum, base } from "viem/chains";
 
const config = createConfig(
  getDefaultConfig({
    appName: "Your App",
    chains: [mainnet, polygon, arbitrum, base],
  })
);

Next steps

Wallet configuration
Wallet configuration
Wallet setup
Configure your embedded wallet settings.
Gas sponsorship
Gas sponsorship
Paymaster setup
Cover gas fees for your users.
Session keys
Session keys
Delegated signing
Delegate signing with session keys.