Alchemy Logo

How EIP-7702 Works

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_requestAccount first
  • 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:

  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.

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

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_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.

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:

  1. Fund the account with a native gas token.
  2. Sign an EIP-7702 authorization delegating to address(0) with currentNonce + 1.
  3. 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.

use-smart-embedded-wallet.ts
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

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:

Was this page helpful?