TransactionsSend transactions

Using EIP-7702

Upgrade your user’s accounts to Smart Wallets using EIP-7702. This gives users access to all of the capabilities of Smart Wallets including gas sponsorship, batching, and more.

How it works

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:

  • Transaction APIs 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.

Prerequisites

Implementation

Required SDK version: ^v4.61.0

Use the eip7702Auth capability on the smart wallet client sendCalls action. sendCalls will automatically sign any required authorization requests to delegate the EOA to Modular Account v2.

If the signing EOA is delegated to a different smart contract, sendCalls will request to re-delegate to Modular Account v2, overriding any previous delegation.

See the sendCalls SDK reference for full descriptions of the parameters used in the following example.

1import { client, config } from "./client.ts";
2
3try {
4 const { preparedCallIds } = await client.sendCalls({
5 capabilities: {
6 paymasterService: {
7 policyId: config.policyId,
8 },
9 eip7702Auth: true,
10 },
11 calls: [
12 {
13 to: "0x0000000000000000000000000000000000000000",
14 value: "0x00",
15 data: "0x",
16 },
17 ],
18 });
19
20 console.log(preparedCallIds);
21} catch (error) {
22 console.error(error);
23}

Advanced

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:

  1. An EIP-7702 authorization signature (for delegation)
  2. 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.

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 signatureRequest describing 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:

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_prepareCalls with 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.

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.

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. To simplify usage, it is recommended to use eip7702Auth: true.

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.

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.

use-smart-embedded-wallet.ts
1import { Address, Authorization, createWalletClient, custom } from "viem";
2import { useSign7702Authorization } from "@privy-io/react-auth";
3import { AuthorizationRequest, WalletClientSigner } from "@aa-sdk/core";
4import { sepolia, alchemy } from "@account-kit/infra";
5import { useEffect, useState } from "react";
6import {
7 createSmartWalletClient,
8 SmartWalletClient,
9} from "@account-kit/wallet-client";
10import { ConnectedWallet as PrivyWallet } from "@privy-io/react-auth";
11
12/\*_ Creates an Alchemy Smart Wallet client for an embedded Privy wallet using EIP-7702. _/;
13export const useSmartEmbeddedWallet = ({
14 embeddedWallet,
15}: {
16 embeddedWallet: PrivyWallet;
17}) => {
18 const { signAuthorization } = useSign7702Authorization();
19 const [client, setClient] = useState<SmartWalletClient>();
20
21 useEffect(() => {
22 const apiKey = process.env.NEXT_PUBLIC_ALCHEMY_API_KEY;
23 if (!apiKey) {
24 throw new Error("Missing NEXT_PUBLIC_ALCHEMY_API_KEY");
25 }
26 (async () => {
27 const provider = await embeddedWallet.getEthereumProvider();
28
29 const baseSigner = new WalletClientSigner(
30 createWalletClient({
31 account: embeddedWallet.address as Address,
32 chain: sepolia,
33 transport: custom(provider),
34 }),
35 "privy",
36 );
37
38 const signer = {
39 ...baseSigner,
40 signAuthorization: async (
41 unsignedAuth: AuthorizationRequest<number>,
42 ): Promise<Authorization<number, true>> => {
43 const signature = await signAuthorization({
44 ...unsignedAuth,
45 contractAddress:
46 unsignedAuth.address ?? unsignedAuth.contractAddress,
47 });
48
49 return {
50 ...unsignedAuth,
51 ...signature,
52 };
53 },
54 };
55
56 const client = createSmartWalletClient({
57 chain: sepolia,
58 transport: alchemy({
59 apiKey,
60 }),
61 signer,
62 policyId: process.env.NEXT_PUBLIC_ALCHEMY_POLICY_ID,
63 });
64
65 setClient(client);
66 })();
67 }, [embeddedWallet, signAuthorization]);
68
69 return { client };
70};

When using the SmartWalletClient you must set the eip7702Auth capability to true, as shown in smart-wallet-demo.tsx

smart-wallet-demo.tsx
1import { ConnectedWallet as PrivyWallet } from "@privy-io/react-auth";
2import { useSmartEmbeddedWallet } from "../hooks/use-smart-embedded-wallet";
3import { useCallback, useState } from "react";
4import type { Address, Hex } from "viem";
5
6export const SmartWalletDemo = ({
7 embeddedWallet,
8}: {
9 embeddedWallet: PrivyWallet;
10}) => {
11 const { client } = useSmartEmbeddedWallet({ embeddedWallet });
12
13 const [status, setStatus] = useState<
14 | { status: "idle" | "error" | "sending" }
15 | { status: "success"; txHash: Hex }
16 >({ status: "idle" });
17
18 const delegateAndSend = useCallback(async () => {
19 if (!client) {
20 return;
21 }
22
23 setStatus({ status: "sending" });
24 try {
25 const {
26 preparedCallIds: [callId],
27 } = await client.sendCalls({
28 capabilities: {
29 eip7702Auth: true,
30 },
31 from: embeddedWallet.address as Address,
32 calls: [
33 {
34 to: "0x0000000000000000000000000000000000000000",
35 data: "0x",
36 },
37 ],
38 });
39 if (!callId) {
40 throw new Error("Missing call id");
41 }
42
43 const { receipts } = await client.waitForCallsStatus({ id: callId });
44 if (!receipts?.length) {
45 throw new Error("Missing transaction receipts");
46 }
47 const [receipt] = receipts;
48 if (receipt?.status !== "success") {
49 throw new Error("Transaction failed");
50 }
51 setStatus({ status: "success", txHash: receipt.transactionHash });
52 } catch (err) {
53 console.error("Transaction failed:", err);
54 setStatus({ status: "error" });
55 }
56 }, [client, embeddedWallet]);
57
58 return (
59 <div className="flex flex-col gap-4">
60 <div>
61 <h2 className="text-lg font-semibold text-gray-900">
62 Embedded EOA Address
63 </h2>
64 <p className="text-gray-600 font-mono break-all">
65 {embeddedWallet.address}
66 </p>
67 </div>
68 <button
69 onClick={delegateAndSend}
70 disabled={!client || status.status === "sending"}
71 className={`w-full py-3 px-4 rounded-lg font-semibold text-white transition-colors ${
72 status.status === "sending"
73 ? "bg-indigo-400 cursor-not-allowed"
74 : "bg-indigo-600 hover:bg-indigo-700"
75 }`}
76 >
77 {status.status === "sending"
78 ? "Sending..."
79 : "Upgrade & Send Sponsored Transaction"}
80 </button>
81 {status.status === "success" && (
82 <section className="bg-green-50 rounded-xl shadow-lg p-6 border border-green-200">
83 <h2 className="text-lg font-semibold text-green-900 mb-4">
84 Congrats! Sponsored transaction successful!
85 </h2>
86 <p className="text-green-700 mb-4">
87 You've successfully upgraded your EOA to a smart account and sent
88 your first sponsored transaction.{" "}
89 <a
90 href="https://www.alchemy.com/docs/wallets/transactions/using-eip-7702"
91 className="text-indigo-600 hover:underline"
92 target="_blank"
93 rel="noopener noreferrer"
94 >
95 Keep building
96 </a>
97 .
98 </p>
99 <p className="text-green-700">
100 <strong>Transaction Hash:</strong>{" "}
101 <span className="font-mono break-all">{status.txHash}</span>
102 </p>
103 </section>
104 )}
105 {status.status === "error" && (
106 <section className="bg-red-50 rounded-xl shadow-lg p-6 border border-red-200">
107 <h2 className="text-lg font-semibold text-red-900 mb-4">
108 Transaction Failed
109 </h2>
110 <p className="text-red-700">
111 There was an error sending your sponsored transaction. Please try
112 again.
113 </p>
114 </section>
115 )}
116 </div>
117 );
118};

Next steps

Build more: