Upgrade Your EOA with EIP-7702

EIP-7702 introduces a straightforward way for Externally Owned Accounts (EOAs) to use smart contract account features. This is a major milestone in Ethereum’s account abstraction journey, enabling existing EOAs to benefit from functionality previously exclusive to smart accounts – such as batching actions, sponsoring transactions, and more.

With Account Kit, you can support this upgrade path directly into your app or wallet. Users will seamlessly access advanced features like transaction batching, session keys, and gas sponsorship, while maintaining their existing EOA address and without having to migrate assets.

Below we will guide you through when to use EIP-7702 and how to leverage Account Kit to upgrade your existing embedded EOAs to a smart contract account, specifically to Modular Account v2.

When to Use EIP-7702?

EIP-7702 is particularly useful in the following scenarios:

  1. You have an app using embedded wallets with EOAs
  2. You have a wallet using EOAs

You can upgrade your users’ EOAs directly to a smart account at their current address. This means you don’t have to move assets.

See our full recommendations here.

Gain Smart Account Features

By upgrading to a smart account, you immediately unlock features such as:

  • Batching actions: Execute multiple operations in a single transaction.
  • Gas sponsorship: Let your users pay zero gas by using sponsored transactions, or use custom paymaster contracts that allow you to pay gas in other tokens.
  • Custom session keys: Provide temporary keys to interact with your app without exposing your primary signer, allowing you to skip transaction confirmations.

EIP-7702 Compatible Accounts

Account Kit provides an EIP-7702 compatible smart account, Modular Account v2, that you can upgrade existing EOAs to. This ERC-4337 compatible account is the most gas efficient on the market, fully audited, and supports ecosystem modules for advanced functionality.

Signer Requirements

To use EIP-7702, you need a signer that can generate an authorization signature. Typically, this is implemented as a method called signAuthorization. Regardless of which signer you choose, you’ll need to create an instance of a SmartAccountSigner that you can pass to the client.

Out-of-the-Box Support in Account Kit

Alchemy Signer

Alchemy Signer supports signing EIP-7702 authorizations. This means you can create new embedded EOAs with social login that are upgradable to smart accounts seamlessly for your users. See this guide for using simple React Hooks.

If you are a developer without existing embedded wallet users, considering using smart accounts from the start without EIP-7702. Decide what’s best for you!

Local Signer

If you have locally managed private keys (e.g. you are a self-custodial wallet), you can use a local account signer to upgrade your EOAs to smart accounts.

Note: viem requires that you generate a Private Key account to use signAuthorization, meaning you cannot use Mnemonic accounts or Hierarchical Deterministic (HD) accounts yet.

import { 
class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>

Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.

LocalAccountSigner
} from "@aa-sdk/core";
const
const privateKey: "0x..."
privateKey
= "0x..."; // Private key
const
const signer: LocalAccountSigner<{ address: Address; nonceManager?: NonceManager | undefined; sign: (parameters: { hash: Hash; }) => Promise<Hex>; ... 6 more ...; type: "local"; }>
signer
=
class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>

Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.

LocalAccountSigner
.
LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>.privateKeyToAccountSigner(key: Hex): LocalAccountSigner<PrivateKeyAccount>

Creates a LocalAccountSigner instance using the provided private key.

privateKeyToAccountSigner
(
const privateKey: "0x..."
privateKey
);

Bring a third party wallet or signer

If you have an existing custom signer or another third-party embedded wallet provider, you can upgrade your embedded EOAs to our smart wallets by connecting your exisitng signer. This will allow you to use EIP-7702 to get features like gas sponsorship, batching, and more.

Requirements Your signer or embedded wallet provider must support signAuthorization in order to be compatible with EIP-7702 delegations. To bring your own signer, you’ll create a SmartAccountSigner interface 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 include the signAuthorization method exposed by Privy. See a full example repo here.

7702-third-party-example.ts
1import {
2 AuthorizationRequest,
3 SmartAccountSigner,
4 WalletClientSigner,
5} from "@aa-sdk/core";
6import { createWalletClient, custom } from "viem";
7import { sepolia } from "viem/chains";
8import { SignedAuthorization } from "viem/experimental";
9import { useSignAuthorization, useWallets } from "@privy-io/react-auth";
10
11const { wallets } = useWallets();
12const embeddedWallet = wallets.find((x) => x.walletClientType === "privy"); // or any other EIP-1193 provider
13const { signAuthorization } = useSignAuthorization();
14
15const baseSigner = new WalletClientSigner(
16 createWalletClient({
17 account: embeddedWallet!.address as Hex,
18 chain: sepolia
19 transport: custom(await embeddedWallet!.getEthereumProvider()),
20 }),
21 "privy"
22);
23
24const signer: SmartAccountSigner = {
25 ...baseSigner,
26 signAuthorization: async (
27 unsignedAuth: AuthorizationRequest<number>
28 ): Promise<SignedAuthorization<number>> => {
29 const contractAddress = unsignedAuth.contractAddress ?? unsignedAuth.address;
30
31 const signature = await signAuthorization({
32 ...unsignedAuth,
33 contractAddress,
34 });
35
36 return {
37 ...unsignedAuth,
38 ...{
39 r: signature.r!,
40 s: signature.s!,
41 v: signature.v!,
42 },
43 address: contractAddress,
44 };
45 },
46};

Using EIP-7702

Once your signer is configured and you have a SmartAccountSigner instance with signAuthorization functionality, you can use EIP-7702 and upgrade EOAs to smart accounts! Our SDK will handle all of the complexity of delegating your account to a smart account and signing transactions correctly.

  • The smart account client will automatically detect if your account is not yet delegated with 7702 when you send a user operation.
  • If it is not yet delegated, it will request an authorization signature and include that in the submitted user operation.
  • The Alchemy ERC-4337 bundler will automatically include the EIP-7702 authorization with the first user op that your account performs.

Note This will delegate to the 7702 compatible smart contract account, Modular Account v2. Learn more about MAv2 here.

import { 
function createModularAccountV2Client<TChain extends Chain = Chain, TSigner extends SmartAccountSigner = SmartAccountSigner<any>>(args: CreateModularAccountV2AlchemyClientParams<AlchemyTransport, TChain, TSigner>): Promise<ModularAccountV2Client<TSigner, TChain, AlchemyTransport>> (+1 overload)
createModularAccountV2Client
} from "@account-kit/smart-contracts";
import {
const sepolia: Chain
sepolia
,
function alchemy(config: AlchemyTransportConfig): AlchemyTransport

Creates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.

alchemy
} from "@account-kit/infra";
import {
import signer
signer
} from "./signer.js";
// Constructing the smart account client const
const smartAccountClient: { [x: string]: unknown; account: ModularAccountV2<any>; batch?: { multicall?: boolean | Prettify<MulticallBatchOptions> | undefined; } | undefined; ... 83 more ...; extend: <const client extends { ...; } & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>; }
smartAccountClient
= await
createModularAccountV2Client<Chain, any>(args: CreateModularAccountV2AlchemyClientParams<AlchemyTransport, Chain, any>): Promise<...> (+1 overload)
createModularAccountV2Client
({
mode?: "7702" | "default" | undefined
mode
: "7702",
transport: AlchemyTransport

The RPC transport

transport
:
function alchemy(config: AlchemyTransportConfig): AlchemyTransport

Creates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.

alchemy
({
apiKey: string
apiKey
: "your-api-key" }), // Get your API key: https://dashboard.alchemy.com/apps
chain: { blockExplorers?: { [key: string]: ChainBlockExplorer; default: ChainBlockExplorer; } | undefined; ... 7 more ...; testnet?: boolean | undefined; } & ChainConfig<...>

Chain for the client.

chain
:
const sepolia: Chain
sepolia
,
signer: any
signer
,
policyId?: string | string[] | undefined
policyId
: "policy-id", // Sponsor gas with your sponsorship policy id: https://dashboard.alchemy.com/gas-manager
}); // Sending a user operation const
const uoHash: SendUserOperationResult<keyof EntryPointRegistryBase<unknown>>
uoHash
= await
const smartAccountClient: { [x: string]: unknown; account: ModularAccountV2<any>; batch?: { multicall?: boolean | Prettify<MulticallBatchOptions> | undefined; } | undefined; ... 83 more ...; extend: <const client extends { ...; } & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>; }
smartAccountClient
.
sendUserOperation: (args: SendUserOperationParameters<ModularAccountV2<any>, UserOperationContext | undefined, keyof EntryPointRegistryBase<unknown>>) => Promise<...>
sendUserOperation
({
uo: UserOperationCallData | BatchUserOperationCallData
uo
: {
target: `0x${string}`
target
:
any
zeroAddress
,
value?: bigint | undefined
value
: 0n,
data: `0x${string}`
data
: "0x",
}, }); const
const txnHash: `0x${string}`
txnHash
=
await
const smartAccountClient: { [x: string]: unknown; account: ModularAccountV2<any>; batch?: { multicall?: boolean | Prettify<MulticallBatchOptions> | undefined; } | undefined; ... 83 more ...; extend: <const client extends { ...; } & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>; }
smartAccountClient
.
waitForUserOperationTransaction: (args: WaitForUserOperationTxParameters) => Promise<Hex>
waitForUserOperationTransaction
(
const uoHash: SendUserOperationResult<keyof EntryPointRegistryBase<unknown>>
uoHash
);
import { 
class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>

Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.

LocalAccountSigner
} from "@aa-sdk/core";
const
const privateKey: "0x..."
privateKey
= "0x...";
export const
const signer: LocalAccountSigner<{ address: Address; nonceManager?: NonceManager | undefined; sign: (parameters: { hash: Hash; }) => Promise<Hex>; ... 6 more ...; type: "local"; }>
signer
=
class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>

Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.

LocalAccountSigner
.
LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>.privateKeyToAccountSigner(key: Hex): LocalAccountSigner<PrivateKeyAccount>

Creates a LocalAccountSigner instance using the provided private key.

privateKeyToAccountSigner
(
const privateKey: "0x..."
privateKey
);

Congrats! You’ve now upgrading an existing embedded EOA to a smart account and sent your first sponsored transaction.

Now that you have an EOA successfully upgraded and a smart account clinet, you can use advanced features like batching transactions and more. Explore what else you can do with smart accounts here.

When can I use it?

Today! EIP-7702 is live on Ethereum Mainnet, Sepolia, and more. Account Kit will support EIP-7702 integration on more chains as they roll out EIP-7702 support.

Check out the UI Demo to see a live preview of these features in action!