
Alchemy is sunsetting its in-house AccountKit signer. This guide walks you through replacing @account-kit/react with Openfort embedded wallets while keeping a familiar React + wagmi developer experience.
Note: This migration generates new wallet addresses for your users. Ask users to transfer any assets from their Alchemy smart accounts to their new Openfort wallets after migration.
How to Migrate from Alchemy to Openfort?
To migrate from Alchemy Account Kit to Openfort, install the @openfort/react package and its peer dependencies, replace AlchemyAccountProvider with the layered OpenfortProvider + OpenfortWagmiBridge + WagmiProvider stack, and switch your authentication and transaction code from Account Kit hooks (useAuthenticate, useSendUserOperation) to the Openfort headless hooks plus standard wagmi hooks. The Openfort smart account is bonded to the wagmi connector, so once the provider is in place your existing useSendTransaction / useSignMessage / useAccount calls keep working.
Concept mapping
Alchemy Account Kit and Openfort solve the same problem with slightly different vocabulary:
| Alchemy concept | Openfort equivalent |
|---|---|
| Account Kit signer (TEE) | OpenSigner |
| Light Account / Modular Account v1 / MAv2 / Simple Account | Any smart wallet support |
| EIP-7702 mode (signer EOA = account address) | Supported via use7702Authorization |
@alchemy/wallet-apis (createSmartWalletClient, sendCalls) | wagmi useSendTransaction / useSendCalls (smart account is bonded to the wagmi connector) |
| Session keys via Wallet APIs | useGrantPermissions / useRevokePermissions |
Feature mapping
| Alchemy feature | Openfort equivalent | Notes |
|---|---|---|
| Email OTP (Account Kit) | useEmailOtpAuth | One-time code flow |
| Email magic link | useEmailAuth | Email + verification |
| Social login (Google, X, Apple, Discord, Facebook) | useOAuth | Configure providers in dashboard |
| Passkey | usePasskey + RecoveryMethod.PASSKEY | WebAuthn-based |
| External wallets / SIWE | useConnect (wagmi) | WalletConnect support |
| Embedded wallet auto-create | Automatic via walletConfig | Configured in OpenfortProvider |
| Session keys | useGrantPermissions | ERC-7715-aligned |
1. Install Openfort dependencies
Remove the Alchemy Account Kit packages and install Openfort with its peer dependencies:
_10yarn remove @account-kit/react @account-kit/infra @account-kit/core_10yarn add @openfort/react wagmi viem@^2.22.0 @tanstack/react-query
Note: ensure your versions of
wagmi,viem,@tanstack/react-query, andreactmatch those required by@openfort/react.
If you also use @alchemy/wallet-apis for transactions, you can remove it after migrating to wagmi's useSendTransaction (Openfort smart accounts are bonded to the wagmi connector, so user operations are sent through the same hook).
2. Update provider configuration
Replace the Alchemy provider with Openfort's layered provider structure.
Before (Alchemy):
_22import { AlchemyAccountProvider } from "@account-kit/react";_22import { alchemy, sepolia } from "@account-kit/infra";_22import { createConfig } from "@account-kit/react";_22import { QueryClient, QueryClientProvider } from "@tanstack/react-query";_22_22const config = createConfig({_22 transport: alchemy({ apiKey: "YOUR_ALCHEMY_API_KEY" }),_22 chain: sepolia,_22 ssr: true,_22});_22_22const queryClient = new QueryClient();_22_22function Providers({ children }: { children: React.ReactNode }) {_22 return (_22 <QueryClientProvider client={queryClient}>_22 <AlchemyAccountProvider config={config} queryClient={queryClient}>_22 {children}_22 </AlchemyAccountProvider>_22 </QueryClientProvider>_22 );_22}
After (Openfort):
_38import { OpenfortProvider, RecoveryMethod } from "@openfort/react";_38import { getDefaultConfig, OpenfortWagmiBridge } from "@openfort/react/wagmi";_38import { QueryClient, QueryClientProvider } from "@tanstack/react-query";_38import { WagmiProvider, createConfig } from "wagmi";_38import { mainnet, sepolia } from "viem/chains";_38_38const config = createConfig(_38 getDefaultConfig({_38 appName: "Your App Name",_38 chains: [mainnet, sepolia],_38 ssr: true,_38 })_38);_38_38const queryClient = new QueryClient();_38_38function Providers({ children }: { children: React.ReactNode }) {_38 return (_38 <QueryClientProvider client={queryClient}>_38 <WagmiProvider config={config}>_38 <OpenfortWagmiBridge>_38 <OpenfortProvider_38 publishableKey="YOUR_OPENFORT_PUBLISHABLE_KEY"_38 walletConfig={{_38 shieldPublishableKey: "YOUR_SHIELD_PUBLISHABLE_KEY",_38 createEncryptedSessionEndpoint: "YOUR_RECOVERY_ENDPOINT",_38 }}_38 walletRecovery={{_38 defaultMethod: RecoveryMethod.AUTOMATIC,_38 }}_38 >_38 {children}_38 </OpenfortProvider>_38 </OpenfortWagmiBridge>_38 </WagmiProvider>_38 </QueryClientProvider>_38 );_38}
3. Update authentication code
Replace Alchemy authentication hooks with Openfort's headless equivalents. Auth state is read with useUser, sign-out with useSignOut, and individual auth methods are exposed as dedicated hooks (useEmailOtpAuth, useEmailAuth, useOAuth, usePhoneOtpAuth, useGuestAuth).
Before (Alchemy):
_28import { useAuthenticate, useSignerStatus, useUser, useLogout } from "@account-kit/react";_28import { useState } from "react";_28_28function EmailLogin() {_28 const { authenticate, isPending } = useAuthenticate();_28 const { isConnected } = useSignerStatus();_28 const { logout } = useLogout();_28 const user = useUser();_28 const [email, setEmail] = useState("");_28_28 if (isConnected) {_28 return (_28 <div>_28 <p>Welcome, {user?.email}</p>_28 <button onClick={() => logout()}>Logout</button>_28 </div>_28 );_28 }_28_28 return (_28 <div>_28 <input value={email} onChange={(e) => setEmail(e.target.value)} />_28 <button onClick={() => authenticate({ type: "email", email })} disabled={isPending}>_28 {isPending ? "Sending..." : "Send magic link"}_28 </button>_28 </div>_28 );_28}
After (Openfort):
_45import { useUser, useSignOut, useEmailOtpAuth } from "@openfort/react";_45import { useState } from "react";_45_45function EmailLogin() {_45 const { user, isAuthenticated } = useUser();_45 const { signOut } = useSignOut();_45 const { requestEmailOtp, signInEmailOtp, isRequesting, isLoading } = useEmailOtpAuth();_45 const [email, setEmail] = useState("");_45 const [otp, setOtp] = useState("");_45 const [otpSent, setOtpSent] = useState(false);_45_45 if (isAuthenticated) {_45 return (_45 <div>_45 <p>Welcome, {user?.email}</p>_45 <button onClick={signOut}>Sign Out</button>_45 </div>_45 );_45 }_45_45 const handleRequestOtp = async () => {_45 await requestEmailOtp({ email });_45 setOtpSent(true);_45 };_45_45 const handleSignIn = async () => {_45 await signInEmailOtp({ email, otp });_45 };_45_45 return (_45 <div>_45 <input value={email} onChange={(e) => setEmail(e.target.value)} />_45 {otpSent && <input value={otp} onChange={(e) => setOtp(e.target.value)} />}_45 {!otpSent ? (_45 <button onClick={handleRequestOtp} disabled={isRequesting}>_45 {isRequesting ? "Sending..." : "Send OTP"}_45 </button>_45 ) : (_45 <button onClick={handleSignIn} disabled={isLoading}>_45 {isLoading ? "Verifying..." : "Verify"}_45 </button>_45 )}_45 </div>_45 );_45}
Alchemy's email auth defaults to magic links; Openfort's useEmailOtpAuth uses one-time codes. If you want a magic-link flow instead, use useEmailAuth.
For OAuth (Google, X, Apple, Discord, Facebook) use useOAuth. For phone-number OTP use usePhoneOtpAuth. For guest sign-in use useGuestAuth.
4. Update wallet access code
Alchemy exposes the smart account through useSmartAccountClient. With Openfort, the smart account is bonded to the wagmi connector, so you read it through useAccount.
Before (Alchemy):
_10import { useSmartAccountClient } from "@account-kit/react";_10_10function WalletInfo() {_10 const { client, address, isLoadingClient } = useSmartAccountClient({});_10_10 if (isLoadingClient) return <p>Loading wallet...</p>;_10 if (!client) return <p>No wallet</p>;_10_10 return <p>Wallet: {address}</p>;_10}
After (Openfort):
_10import { useAccount } from "wagmi";_10_10function WalletInfo() {_10 const { address, isConnected } = useAccount();_10_10 if (!isConnected) return <p>No wallet</p>;_10 return <p>Wallet: {address}</p>;_10}
5. Update transaction code
Alchemy uses useSendUserOperation (or the lower-level client.sendCalls from @alchemy/wallet-apis) to send user operations. With Openfort the smart account is exposed through the wagmi connector, so you use the standard useSendTransaction.
Before (Alchemy):
_22import { useSendUserOperation, useSmartAccountClient } from "@account-kit/react";_22import { zeroAddress } from "viem";_22_22function SendTransaction() {_22 const { client } = useSmartAccountClient({});_22 const { sendUserOperation, isSendingUserOperation } = useSendUserOperation({_22 client,_22 waitForTxn: true,_22 });_22_22 const send = () => {_22 sendUserOperation({_22 uo: { target: zeroAddress, data: "0x", value: BigInt(0) },_22 });_22 };_22_22 return (_22 <button onClick={send} disabled={isSendingUserOperation || !client}>_22 {isSendingUserOperation ? "Sending..." : "Send Transaction"}_22 </button>_22 );_22}
After (Openfort):
_20import { useSendTransaction, useAccount } from "wagmi";_20import { parseEther } from "viem";_20_20function SendTransaction() {_20 const { address } = useAccount();_20 const { sendTransaction, isPending } = useSendTransaction();_20_20 const send = () => {_20 sendTransaction({_20 to: "0xRecipientAddress",_20 value: parseEther("0.001"),_20 });_20 };_20_20 return (_20 <button onClick={send} disabled={isPending || !address}>_20 {isPending ? "Sending..." : "Send Transaction"}_20 </button>_20 );_20}
For batched calls, use wagmi's EIP-5792 useSendCalls hook in place of Alchemy's client.sendCalls.
6. Update message signing
Alchemy's useSignMessage is replaced with wagmi's useSignMessage. The connector signs with the Openfort embedded wallet under the hood.
Before (Alchemy):
_17import { useSignMessage, useSmartAccountClient } from "@account-kit/react";_17_17function SignMessage() {_17 const { client } = useSmartAccountClient({});_17 const { signMessage, isSigningMessage } = useSignMessage({ client });_17_17 const sign = async () => {_17 const signature = await signMessage({ message: "Hello World" });_17 console.log("Signature:", signature);_17 };_17_17 return (_17 <button onClick={sign} disabled={isSigningMessage || !client}>_17 {isSigningMessage ? "Signing..." : "Sign Message"}_17 </button>_17 );_17}
After (Openfort):
_15import { useSignMessage } from "wagmi";_15_15function SignMessage() {_15 const { signMessage, isPending } = useSignMessage();_15_15 const sign = () => {_15 signMessage({ message: "Hello World" });_15 };_15_15 return (_15 <button onClick={sign} disabled={isPending}>_15 {isPending ? "Signing..." : "Sign Message"}_15 </button>_15 );_15}
7. Remove Alchemy dependencies
Once everything compiles and your sign-in/sign-out, signing, and transaction flows pass smoke tests, clean up the Alchemy packages:
_10yarn remove @account-kit/react @account-kit/infra @account-kit/core @alchemy/wallet-apis
Hook mapping reference
| Alchemy hook | Openfort / wagmi equivalent |
|---|---|
useSignerStatus().isConnected | useUser().isAuthenticated |
useAuthenticate() | useEmailOtpAuth() / useEmailAuth() / useOAuth() / usePhoneOtpAuth() / useGuestAuth() |
useUser() | useUser().user |
useLogout() | useSignOut().signOut() |
useSmartAccountClient() | useAccount() (wagmi) |
useSendUserOperation() | useSendTransaction() (wagmi) |
useSignMessage() (Account Kit) | useSignMessage() (wagmi) |
useExportAccount() | Export private key (Openfort signer API) |
| Account Kit signer (TEE) | Openfort iframe + Shield |
| Session keys via Wallet APIs | useGrantPermissions() / useRevokePermissions() |
Considerations
Embedded wallet creation
Alchemy creates a smart account on first authentication. Openfort does the same — wallets are provisioned automatically based on your walletRecovery configuration in OpenfortProvider.
Session handling
Alchemy sessions are not transferable. Users will need to re-authenticate with Openfort after the migration is deployed.
Wallet addresses
Users receive new smart wallet addresses. Alchemy accounts (Light Account, Modular Account v1/v2) are not redeployed at the same address by Openfort. Communicate this change to users before migration so they can:
- Export any assets from their Alchemy smart account.
- Transfer assets to their new Openfort wallet after authentication.
If your application relies on a specific Alchemy account type (e.g. MAv2-only features) that has no clean Openfort equivalent, scope the migration to the flows that map cleanly first and book a call with the Openfort team for guidance on the rest.
EIP-7702 vs ERC-4337
Alchemy ships EIP-7702 mode as the default, where the signer EOA delegates to a smart account at the same address. Openfort supports EIP-7702 via use7702Authorization. For pure ERC-4337 (assets in a separate smart account), Openfort's wagmi connector handles user operation construction so existing wagmi calls continue to work.
Chain configuration
Both Alchemy and Openfort support multiple chains. Update your chain configuration in the wagmi config:
_10import { getDefaultConfig } from "@openfort/react/wagmi";_10import { mainnet, polygon, arbitrum, base } from "viem/chains";_10import { createConfig } from "wagmi";_10_10const config = createConfig(_10 getDefaultConfig({_10 appName: "Your App",_10 chains: [mainnet, polygon, arbitrum, base],_10 })_10);
Recovery methods
Alchemy stores keys in a TEE behind email/social/passkey auth. Openfort uses an iframe-based signer plus Shield and offers 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 |
Concepts that don't map 1:1
A few Alchemy concepts have no direct Openfort equivalent. Call these out explicitly to your team during planning:
- Account type choice (Light Account, Modular Account v1, MAv2, Simple Account) — Openfort uses a single ERC-4337 smart account type. If you depend on MAv1- or Light-Account-specific behaviour, you will need to adapt your contracts or business logic.
@alchemy/wallet-apisclient.requestAccount({ creationHint })— Openfort provisions the smart account during authentication; there is no separate "request account" step before sending calls.- In-place key migration via TEE-to-TEE export — Openfort does not import private keys from Alchemy's TEE. Plan for new wallet addresses and an asset transfer step rather than an end-to-end key migration.
Test your application
- Run your app and verify sign-in, sign-out, wallet access, message signing, and transaction flows.
- Confirm there are no remaining
@account-kit/*or@alchemy/wallet-apisimports in your codebase. - Walk through the migration with a real test user before promoting the change to production.
Next steps
The full Openfort embedded-wallet React reference, including session keys and recovery methods, is in the Openfort docs. If you depend on Alchemy features that do not have a clean Openfort equivalent, book a migration call.
