How to Migrate from Alchemy AccountKit

By Joan Alavedra, Co-Founder at Openfort10 min read
How to Migrate from Alchemy AccountKit

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 conceptOpenfort equivalent
Account Kit signer (TEE)OpenSigner
Light Account / Modular Account v1 / MAv2 / Simple AccountAny 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 APIsuseGrantPermissions / useRevokePermissions

Feature mapping

Alchemy featureOpenfort equivalentNotes
Email OTP (Account Kit)useEmailOtpAuthOne-time code flow
Email magic linkuseEmailAuthEmail + verification
Social login (Google, X, Apple, Discord, Facebook)useOAuthConfigure providers in dashboard
PasskeyusePasskey + RecoveryMethod.PASSKEYWebAuthn-based
External wallets / SIWEuseConnect (wagmi)WalletConnect support
Embedded wallet auto-createAutomatic via walletConfigConfigured in OpenfortProvider
Session keysuseGrantPermissionsERC-7715-aligned

1. Install Openfort dependencies

Remove the Alchemy Account Kit packages and install Openfort with its peer dependencies:


_10
yarn remove @account-kit/react @account-kit/infra @account-kit/core
_10
yarn add @openfort/react wagmi viem@^2.22.0 @tanstack/react-query

Note: ensure your versions of wagmi, viem, @tanstack/react-query, and react match 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):


_22
import { AlchemyAccountProvider } from "@account-kit/react";
_22
import { alchemy, sepolia } from "@account-kit/infra";
_22
import { createConfig } from "@account-kit/react";
_22
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
_22
_22
const config = createConfig({
_22
transport: alchemy({ apiKey: "YOUR_ALCHEMY_API_KEY" }),
_22
chain: sepolia,
_22
ssr: true,
_22
});
_22
_22
const queryClient = new QueryClient();
_22
_22
function 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):


_38
import { OpenfortProvider, RecoveryMethod } from "@openfort/react";
_38
import { getDefaultConfig, OpenfortWagmiBridge } from "@openfort/react/wagmi";
_38
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
_38
import { WagmiProvider, createConfig } from "wagmi";
_38
import { mainnet, sepolia } from "viem/chains";
_38
_38
const config = createConfig(
_38
getDefaultConfig({
_38
appName: "Your App Name",
_38
chains: [mainnet, sepolia],
_38
ssr: true,
_38
})
_38
);
_38
_38
const queryClient = new QueryClient();
_38
_38
function 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):


_28
import { useAuthenticate, useSignerStatus, useUser, useLogout } from "@account-kit/react";
_28
import { useState } from "react";
_28
_28
function 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):


_45
import { useUser, useSignOut, useEmailOtpAuth } from "@openfort/react";
_45
import { useState } from "react";
_45
_45
function 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):


_10
import { useSmartAccountClient } from "@account-kit/react";
_10
_10
function 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):


_10
import { useAccount } from "wagmi";
_10
_10
function 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):


_22
import { useSendUserOperation, useSmartAccountClient } from "@account-kit/react";
_22
import { zeroAddress } from "viem";
_22
_22
function 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):


_20
import { useSendTransaction, useAccount } from "wagmi";
_20
import { parseEther } from "viem";
_20
_20
function 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):


_17
import { useSignMessage, useSmartAccountClient } from "@account-kit/react";
_17
_17
function 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):


_15
import { useSignMessage } from "wagmi";
_15
_15
function 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:


_10
yarn remove @account-kit/react @account-kit/infra @account-kit/core @alchemy/wallet-apis

Hook mapping reference

Alchemy hookOpenfort / wagmi equivalent
useSignerStatus().isConnecteduseUser().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 APIsuseGrantPermissions() / 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:

  1. Export any assets from their Alchemy smart account.
  2. 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:


_10
import { getDefaultConfig } from "@openfort/react/wagmi";
_10
import { mainnet, polygon, arbitrum, base } from "viem/chains";
_10
import { createConfig } from "wagmi";
_10
_10
const 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 methodDescriptionBackend required
AutomaticSeamless, backend-managed recoveryYes
PasswordUser-set password for recoveryNo
PasskeyBiometric / device authenticationNo

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-apis client.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-apis imports 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.

Share this article

Related Articles

  1. Agent Permissions: The Need for Scoped Access, Not Private Keys

    Agent permissions let an AI agent sign transactions inside guardrails — spending caps, contract allowlists, time windows — without ever holding a private key.

  2. Best Privacy Apps in 2026

    A curated lineup of consumer-facing privacy apps worth knowing in 2026, with a short feature spotlight on each.

  3. Best Stablecoin Apps in 2026

    A curated lineup of consumer-facing stablecoin apps worth knowing in 2026, with a short feature spotlight on each.