Alchemy Smart Wallets use EIP-7702 by default to give users access to all Smart Wallet capabilities including gas sponsorship, batching, and more - while keeping the same address.
EIP-7702 enables EOAs (Externally Owned Accounts) to delegate control to Smart Wallets that can execute code directly from their addresses. When using Transaction APIs:
- You can use your signer address directly as the account address - no need to call
wallet_requestAccountfirst - Transaction APIs automatically detect whether a user must first delegate via EIP-7702
- If delegation is required, Transaction APIs prepare the correct authorization payload
- Your application prompts the user to sign when required
- We combine the delegation and transaction into a single onchain submission
Once delegated, the user's account behaves as a Smart Wallet while keeping the same address and assets. Subsequent transactions only require a single user operation signature.
For implementation details, see the Send Transactions guide.
If you need to use a traditional Smart Contract Account instead of EIP-7702, you can opt out of the default 7702 behavior by calling wallet_requestAccount first.
When you call wallet_requestAccount with a signer address, it creates a dedicated Smart Contract Account address. Using this SCA address (instead of your signer address) in subsequent API calls will bypass 7702 mode.
When to use SCA mode:
- Backwards compatibility with existing Smart Contract Accounts
- Using chains that don't yet support EIP-7702
- Using a signer that doesn't support signing EIP-7702 authorizations
- Specific requirements for smart contract account architecture
import { createSmartWalletClient } from "@account-kit/wallet-client";
import { LocalAccountSigner } from "@aa-sdk/core";
import { alchemy, sepolia } from "@account-kit/infra";
const signer = LocalAccountSigner.privateKeyToAccountSigner(
process.env.PRIVATE_KEY!,
);
const client = createSmartWalletClient({
transport: alchemy({ apiKey: process.env.ALCHEMY_API_KEY! }),
chain: sepolia,
signer,
});
// Request a Smart Contract Account
const account = await client.requestAccount();
// Use the SCA address as the `from` param to bypass 7702 mode
await client.sendCalls({
from: account.address,
calls: [...],
});How Wallet APIs handle delegation and signing
When interacting with Wallet APIs, delegation is handled automatically as part of transaction preparation.
If a user has not yet delegated on the target chain, the API response will include multiple signature requests:
- An EIP-7702 authorization signature (for delegation)
- An ERC-4337 user operation signature (for the transaction itself)
Your application is responsible for:
- Prompting the user to sign each request
- Returning the signatures to Alchemy
We combine these signatures into a single UserOperation and submits it to the bundler. After delegation is completed, future requests only require a user operation signature.
EIP-7702 authorization signing
When delegation is required, Wallet APIs return a Prepared EIP-7702 Authorization object.
This includes:
- The delegation contract address
- A nonce
- The chain ID
- A
signatureRequestdescribing how the authorization must be signed
For quick testing purposes, you can simply use eth_sign to sign the signatureRequest.rawPayload.
For production usage, we recommend:
- Verifying the delegation address is trusted
- Using a dedicated EIP-7702 signing utility to compute the hash to sign
Example of signing utility:
Delegation-only (smart account upgrade) flows
You do not need to send a dummy or no-op transaction to perform delegation.
If a user needs to upgrade to a Smart Wallet:
- Call
wallet_prepareCallswith your intended calls (or an empty call set) - Wallet APIs detect that delegation is required
- The response includes the required authorization and user operation signature requests
We handle combining delegation with the user operation automatically.
Wallet compatibility considerations
Some wallets restrict or block signing EIP-7702 authorizations for security reasons.
In particular:
- MetaMask only allows delegation to its own contract via its UI
- MetaMask does not support signing arbitrary EIP-7702 authorization payloads
For MetaMask users, you may need to rely on wallet-native features such as ERC-5792 batching instead of direct EIP-7702 delegation flows.
Ensure your users’ wallets support EIP-7702 authorization signing before enabling this flow.
EIP-7702 delegations
EIP-7702 delegation is now the default mode for Alchemy Smart Wallets. When you use your signer address directly with wallet_prepareCalls or other Transaction APIs, 7702 mode is automatically enabled.
The eip7702Auth capability supports the interface defined in ERC-7902.
Currently, Wallet APIs only support delegation to the following contract:
0x69007702764179f14F51cdce752f4f775d74E139 (Modular Account v2)
All other delegation addresses will be rejected.
Once delegated, an account remains delegated until the delegation is replaced or removed.
To reset an account back to a pure EOA, delegate to
0x0000000000000000000000000000000000000000
as defined in EIP-7702.
Undelegating (returning to a pure EOA)
To remove a delegation and return an account to a pure EOA, you must re-delegate to the zero address:
0x0000000000000000000000000000000000000000.
Bundlers cannot relay undelegations, so you must submit this transaction directly from the account (with enough native gas token to cover fees). If you are re-delegating to another 4337 account, the bundler can relay the delegation.
To undelegate:
- Fund the account with a native gas token.
- Sign an EIP-7702 authorization delegating to
address(0)withcurrentNonce + 1. - Send an empty transaction (using
currentNonce) that includes the authorization.
We recommend using Viem to sign the EIP-7702 authorization (with executor: self):
Viem signAuthorization.
Third party signers
If you have an existing custom signer or another third-party embedded wallet provider, you can upgrade your embedded EOAs to smart wallets by connecting your existing signer. This will allow you to use EIP-7702 to get features like gas sponsorship, batching, and more.
Requirement: Your signer or embedded wallet provider must support signing EIP-7702 authorizations in order to delegate to a smart account.
To bring your own signer, you create a SmartAccountSigner that implements signAuthorization. See the details for the interface requirements here.
For example, you can upgrade an existing Privy embedded EOA by extending a Wallet Client to use the sign7702Authorization action exposed by Privy.
See full example code here.
The bulk of the logic happens in use-embedded-smart-wallet.ts.
In this react hook, a Privy embedded wallet is wrapped in a WalletClientSigner that supports signAuthorization.
This signer is then passed to createSmartWalletClient to construct a client for sending transactions.
import { Address, Authorization, createWalletClient, custom } from "viem";
import { useSign7702Authorization } from "@privy-io/react-auth";
import { AuthorizationRequest, WalletClientSigner } from "@aa-sdk/core";
import { sepolia, alchemy } from "@account-kit/infra";
import { useEffect, useState } from "react";
import {
createSmartWalletClient,
SmartWalletClient,
} from "@account-kit/wallet-client";
import { ConnectedWallet as PrivyWallet } from "@privy-io/react-auth";
/\*_ Creates an Alchemy Smart Wallet client for an embedded Privy wallet using EIP-7702. _/;
export const useSmartEmbeddedWallet = ({
embeddedWallet,
}: {
embeddedWallet: PrivyWallet;
}) => {
const { signAuthorization } = useSign7702Authorization();
const [client, setClient] = useState<SmartWalletClient>();
useEffect(() => {
const apiKey = process.env.NEXT_PUBLIC_ALCHEMY_API_KEY;
if (!apiKey) {
throw new Error("Missing NEXT_PUBLIC_ALCHEMY_API_KEY");
}
(async () => {
const provider = await embeddedWallet.getEthereumProvider();
const baseSigner = new WalletClientSigner(
createWalletClient({
account: embeddedWallet.address as Address,
chain: sepolia,
transport: custom(provider),
}),
"privy",
);
const signer = {
...baseSigner,
signAuthorization: async (
unsignedAuth: AuthorizationRequest<number>,
): Promise<Authorization<number, true>> => {
const signature = await signAuthorization({
...unsignedAuth,
contractAddress:
unsignedAuth.address ?? unsignedAuth.contractAddress,
});
return {
...unsignedAuth,
...signature,
};
},
};
const client = createSmartWalletClient({
chain: sepolia,
transport: alchemy({
apiKey,
}),
signer,
policyId: process.env.NEXT_PUBLIC_ALCHEMY_POLICY_ID,
});
setClient(client);
})();
}, [embeddedWallet, signAuthorization]);
return { client };
};When using the SmartWalletClient you must set the eip7702Auth capability to true, as shown in smart-wallet-demo.tsx
import { ConnectedWallet as PrivyWallet } from "@privy-io/react-auth";
import { useSmartEmbeddedWallet } from "../hooks/use-smart-embedded-wallet";
import { useCallback, useState } from "react";
import type { Address, Hex } from "viem";
export const SmartWalletDemo = ({
embeddedWallet,
}: {
embeddedWallet: PrivyWallet;
}) => {
const { client } = useSmartEmbeddedWallet({ embeddedWallet });
const [status, setStatus] = useState<
| { status: "idle" | "error" | "sending" }
| { status: "success"; txHash: Hex }
>({ status: "idle" });
const delegateAndSend = useCallback(async () => {
if (!client) {
return;
}
setStatus({ status: "sending" });
try {
const {
id: callId,
} = await client.sendCalls({
capabilities: {
eip7702Auth: true,
},
from: embeddedWallet.address as Address,
calls: [
{
to: "0x0000000000000000000000000000000000000000",
data: "0x",
},
],
});
if (!callId) {
throw new Error("Missing call id");
}
const { receipts } = await client.waitForCallsStatus({ id: callId });
if (!receipts?.length) {
throw new Error("Missing transaction receipts");
}
const [receipt] = receipts;
if (receipt?.status !== "success") {
throw new Error("Transaction failed");
}
setStatus({ status: "success", txHash: receipt.transactionHash });
} catch (err) {
console.error("Transaction failed:", err);
setStatus({ status: "error" });
}
}, [client, embeddedWallet]);
return (
<div className="flex flex-col gap-4">
<div>
<h2 className="text-lg font-semibold text-gray-900">
Embedded EOA Address
</h2>
<p className="text-gray-600 font-mono break-all">
{embeddedWallet.address}
</p>
</div>
<button
onClick={delegateAndSend}
disabled={!client || status.status === "sending"}
className={`w-full py-3 px-4 rounded-lg font-semibold text-white transition-colors ${
status.status === "sending"
? "bg-indigo-400 cursor-not-allowed"
: "bg-indigo-600 hover:bg-indigo-700"
}`}
>
{status.status === "sending"
? "Sending..."
: "Upgrade & Send Sponsored Transaction"}
</button>
{status.status === "success" && (
<section className="bg-green-50 rounded-xl shadow-lg p-6 border border-green-200">
<h2 className="text-lg font-semibold text-green-900 mb-4">
Congrats! Sponsored transaction successful!
</h2>
<p className="text-green-700 mb-4">
You've successfully upgraded your EOA to a smart account and sent
your first sponsored transaction.{" "}
<a
href="https://www.alchemy.com/docs/wallets/transactions/using-eip-7702"
className="text-indigo-600 hover:underline"
target="_blank"
rel="noopener noreferrer"
>
Keep building
</a>
.
</p>
<p className="text-green-700">
<strong>Transaction Hash:</strong>{" "}
<span className="font-mono break-all">{status.txHash}</span>
</p>
</section>
)}
{status.status === "error" && (
<section className="bg-red-50 rounded-xl shadow-lg p-6 border border-red-200">
<h2 className="text-lg font-semibold text-red-900 mb-4">
Transaction Failed
</h2>
<p className="text-red-700">
There was an error sending your sponsored transaction. Please try
again.
</p>
</section>
)}
</div>
);
};Build more: