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. Users simply sign an authorization payload to delegate, then their account can function as a smart wallet with access to all of the user’s assets. Wallet APIs will prompt the user to sign this authorization when the delegation is missing on the chain they’re interacting with.

Prerequisites

Implementation

Required SDK version: ^v4.61.0

To use Smart Wallets in EIP-7702 mode, you must specify the mode when constructing the smart account client with useSmartAccountClient. This will allow the authorized EOA to act as a smart wallet with the same address as the EOA. The useSendCalls hook will sign the required delegation payload the first time that a user interacts with a new chain. This payload is then relayed on chain with the user’s first transaction.

Prerequisites:

See the useSendCalls SDK reference for parameter descriptions.

sendCalls.tsx
1import { useSendCalls, useSmartAccountClient } from "@account-kit/react";
2
3export default function SendCalls() {
4 const { client } = useSmartAccountClient({
5 accountParams: {
6 mode: "7702",
7 },
8 });
9 const { sendCallsAsync } = useSendCalls({ client });
10
11 const handleSend = async () => {
12 if (!client) {
13 throw new Error("Smart account client not connected");
14 }
15
16 try {
17 const { ids } = await sendCallsAsync({
18 calls: [
19 {
20 to: "0x0000000000000000000000000000000000000000",
21 value: "0x00",
22 data: "0x",
23 },
24 ],
25 });
26
27 console.log("Transaction sent with ID:", ids[0]);
28 } catch (error) {
29 console.error(error);
30 }
31 };
32
33 return <button onClick={handleSend}>Click to Send</button>;
34}

Advanced

EIP-7702 is particularly useful when you have existing wallets that are being used as EOAs, but want access to smart wallet capabilities while allowing users to keep their existing address and avoid transferring their assets. With EIP-7702, you can upgrade your users’ EOAs directly to a Smart Wallet at their current address.

See a full list of recommendations here.

The EIP-7702 capability works with the prepare calls function in a similar way to its usage in send calls. However, you are required to handle the authorization signature request in your code’s logic.

When a delegation is required the response to prepare calls will return an array of signature requests:

  1. An authorization signature request
  2. A user operation signature request

Both of these requests need to be signed and included in the sendPreparedCalls request. sendPreparedCalls takes an array of signed calls for this purpose. These signatures are combined into a single transaction that is relayed onchain. Once the delegation is signed, as long as the user doesn’t delegate to another account, subsequent requests will only contain the user operation signature request.

Signing an authorization signature request requires logic specific to the wallet being used. Examples:

Most external wallet implementations, including browser wallets, will block requests to sign authorizations. This is done for security reasons. To use EIP-7702 ensure that your user’s wallets support signing authorizations.

The eip7702Auth capability supports the interface defined in ERC-7902. However, it currently only supports a single delegation address: 0x69007702764179f14F51cdce752f4f775d74E139. All other addresses will be rejected. It’s recommended to use eip7702Auth: true to avoid the extra complexity.

Once your account is delegated, it will remain delegated until the delegation is replaced or removed. We currently only support relaying delegations for Modular Account v2. If you wish to replace or remove this delegation, you’ll need to relay the delegation yourself or use a third party service. Viem has a guide for this here.

To reset an account back to a pure EOA with no delegation, delegate the account 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 our 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’ll 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: