7702

xZeroDev

Introduction

EIP-7702 is an Ethereum update that allows externally owned accounts (EOAs) to upgrade into smart accounts. In practical terms, this means that EOA wallets can now enjoy the benefits of account abstraction, such as gas sponsorship, transaction batching, transaction automation, and even chain abstraction.

This guide assumes that you are building a dapp with embedded wallets powered by Privy. If you are using another embedded wallet, check here.


In this guide, we will walk you through:

Upgrading EOAs to smart accounts with Privy

Privy Account StatusLogged Out

Setting up Privy with 7702

Install Dependencies

npm i @turnkey/sdk-react @turnkey/viem wagmi @zerodev/ecdsa-validator @zerodev/sdk @tanstack/react-query

Setup the Turnkey context with your credentials. Initialise the 7702 client as follows.

import { useTurnkey } from "@turnkey/sdk-react";
import { createAccount } from "@turnkey/viem";

const kernelVersion = KERNEL_V3_3;
const kernelAddresses = KernelVersionToAddressesMap[kernelVersion];

// creating kernel clients with turnkey
const { turnkey, authIframeClient, getActiveClient } = useTurnkey();

const session = await turnkey?.getSession();
const turnkeyActiveAuthClient = await getActiveClient();
await authIframeClient.injectCredentialBundle(session!.token);
const suborgId = session?.organizationId;
const userResponse = await authIframeClient!.getUser({
  organizationId: suborgId!,
  userId: session.userId!,
});
const walletsResponse = await authIframeClient!.getWallets({
  organizationId: suborgId!,
});

let selectedWalletId = null;
let selectedAccount = null;

// Default to the first wallet if available
if (walletsResponse.wallets.length > 0) {
  selectedWalletId = walletsResponse.wallets[0].walletId;

  const accountsResponse = await authIframeClient!.getWalletAccounts({
    organizationId: suborgId!,
    walletId: selectedWalletId,
  });

  if (accountsResponse.accounts.length > 0) {
    selectedAccount = accountsResponse.accounts.filter(
      (account) => account.addressFormat === "ADDRESS_FORMAT_ETHEREUM",
    )?.[0];
  }
}

const viemAccount = await createAccount({
  client: turnkeyActiveClient,
  organizationId: suborgId!,
  signWith: selectedAccount?.address,
  ethereumAddress: selectedAccount?.address,
});

const viemWalletClient = createWalletClient({
  account: viemAccount as Account,
  chain: baseSepolia,
  transport: http(),
});

const authorization = await viemWalletClient.signAuthorization({
  chainId: baseSepolia.id,
  nonce: 0,
  address: kernelAddresses.accountImplementationAddress,
});

const kernelAccount = await create7702KernelAccount(baseSepoliaPublicClient, {
  signer: viemWalletClient,
  entryPoint,
  kernelVersion,
  eip7702Auth: authorization,
});

const kernelAccountClient = create7702KernelAccountClient({
  account: kernelAccount,
  chain: baseSepolia,
  bundlerTransport: http(baseSepoliaBundlerRpc),
  paymaster: baseSepoliaPaymasterClient,
  client: baseSepoliaPublicClient,
});

1. Gas Sponsorship

To sponsor gas, create a gas policy such as "Sponsor All"on the ZeroDev dashboard.

Then, set up a paymaster client with the paymaster RPC from the dashboard.

const paymasterClient = createZeroDevPaymasterClient({
  chain: baseSepolia,
  transport: http(baseSepoliaPaymasterRpc),
});

const kernelAccount = await create7702KernelAccount(...);

const kernelAccountClient = create7702KernelAccountClient({
  paymaster: baseSepoliaPaymasterClient,

  // ...
  bundlerTransport: http(baseSepoliaBundlerRpc),
  account: kernelAccount,
  chain: baseSepolia,
  client: baseSepoliaPublicClient,
});
Create 7702 Account with privy to try out the examples!

Sponsor a Transaction

Mint ZDEV Token

Balance: 0

2. Batching

When you need to send multiple transactions, you can batch them together to save on gas fees, latency, and the number of times a user needs to sign.

Each call in the example below that would have been a separate transaction is batched together and sent as a single user operation.

await kernelAccountClient.sendUserOperation({
  calls: [
    {
      to: ZERODEV_TOKEN_ADDRESS,
      value: BigInt(0),
      data: encodeFunctionData({
        abi: ZERODEV_TOKEN_ABI,
        functionName: "mint",
        args: [kernelAccountClient.account.address, amount],
      }),
    },
    {
      to: ZERODEV_TOKEN_ADDRESS,
      value: BigInt(0),
      data: encodeFunctionData({
        abi: ZERODEV_TOKEN_ABI,
        functionName: "transfer",
        args: [toAddress, amount],
      }),
    },
  ],
}
Create 7702 Account with privy to try out the examples!

Batching Multiple Transactions

1. Mint ZDEV
2. Transfer Token

Balance: 0

3. Permissions

With ZeroDev smart accounts, you can create temporary keys (session keys) with specific permissions.

With session keys, you can sign transactions without asking for further user confirmations, therefore enabling "1-click trading." You can also automate transactions on the server side, to enable use cases like subscription.

Thanks to permissions, the user can rest assured that their funds are safe, since the session keys can only do what they were explicitly given permissions to do.

In this example, we will create a session key that's allowed to transfer no more than 10 tokens.

const _sessionPrivateKey = generatePrivateKey();
const sessionAccount = privateKeyToAccount(_sessionPrivateKey as Address;

const sessionKeySigner = await toECDSASigner({
  signer: sessionAccount,
});

const permissionPlugin = await toPermissionValidator(publicClient, {
  entryPoint,
  kernelVersion,
  signer: sessionKeySigner,
  policies: [callPolicy],
});

const masterEcdsaValidator = await signerToEcdsaValidator(publicClient, {
  signer: walletClient,
  entryPoint,
  kernelVersion,
});

const sessionKeyKernelAccount = await createKernelAccount(publicClient, {
  entryPoint,
  plugins: {
    sudo: masterEcdsaValidator,
    regular: permissionPlugin,
  },
  kernelVersion: kernelVersion,
  address: masterKernelAccount.address,
});

const kernelPaymaster = createZeroDevPaymasterClient(...);

const kernelClient = createKernelAccountClient({
  account: sessionKeyKernelAccount,
  chain: baseSepolia,
  bundlerTransport: http(baseSepoliaBundlerRpc),
  paymaster: {
    getPaymasterData(userOperation) {
      return kernelPaymaster.sponsorUserOperation({ userOperation });
    },
  },
});
Create 7702 Account with privy to try out the examples!

Permissions Example

1. Create a session key
2. Transfer ZDEV

This transaction will be rejected if the amount is more than 10 ZDEV.

Balance: 0