EIP-7702 support currently only works on testnets that deployed support for the Ethereum Pectra upgrade, such as Ithaca Odyssey and ABC from Gelato. Please keep this in mind when building with EIP-7702.

There are a few open items that we will address in the near future:

  • The backend still persists a regular AA account address (instead of EOA), and currently the SDK UI just overrides it.
  • Confirmation UI experience for 7702.
  • Persist the authorization code for longer periods.
  • Abstract the logic to work with regular Wallet Client transactions without the need to use the Kernel.

Our goal is to make this integration as seamless as possible. Please provide us with any feedback, and we will work closely with you to implement these needs.

EIP-7702 allows EOAs to upgrade to smart accounts and therefore leverage AA features such as gas sponsorship, transaction batching, session keys (automation), and chain abstraction. With Dynamic’s EIP-7702 integration, all Dynamic wallets, including both EOA wallets and AA wallets, can gain access to the aforementioned features.

Features

  • Gasless transctions and policies (defined within the Zerodev dashboard)
  • Batch transactions
  • Rhinestone EIP-7579 support (coming soon)

Setup for 7702 with a ZeroDev Contract

Step 1: Configure the network

  • Enable the 7702 compatible network i.e. Odyssey Testnet on Dynamic’s Chains
  • Follow the regular ZeroDev setup instructions but choose the 7702 compatible network along the way.
  • During the Zerodev configuration in the dashboard, make sure you select 7702 for wallet options.

Step 2: Npm install the packages

npm i @dynamic-labs/ethereum @dynamic-labs/ethereum-aa @dynamic-labs/sdk-react-core

Step 3: Initialize the Dynamic Context Provider

To initialize pass the ZeroDevSmartWalletConnectors to the WalletConnectors Array along with the EthereumWalletConnectors.

<DynamicContextProvider
settings={{
  environmentId: 'your-environment-id',
  walletConnectors: [EthereumWalletConnectors, ZeroDevSmartWalletConnectors],
}}
>

Step 4: Kernel Interaction

Currently to interact with the wallet, you will need to interact directly with the kernel.

const connector = primaryWallet?.connector;

const params = const kernelClient = connector.getAccountAbstractionProvider({
     withSponsorship: true,
   }
);

const userOpHash = await kernelClient.sendUserOperation({
       callData: await kernelClient.account.encodeCalls([
         {
           data: '0x',
           to: zeroAddress,
           value: BigInt(0),
         },
         {
           data: '0x',
           to: zeroAddress,
           value: BigInt(0),
         },
       ]),
     });

Example

import { EthereumWalletConnectors, } from '@dynamic-labs/ethereum';
import { DynamicContextProvider, DynamicWidget, useDynamicContext} from '@dynamic-labs/sdk-react-core';
import { ZeroDevSmartWalletConnectors, isZeroDevConnector } from '@dynamic-labs/ethereum-aa';
import { zeroAddress } from 'viem';
import { useState } from 'react';

function App() {
   return (
     <DynamicContextProvider
     settings={{
       environmentId: '54a5040b-cdd2-47f4-ac72-8e37dd8db1b6',
       walletConnectors: [
         EthereumWalletConnectors,
         ZeroDevSmartWalletConnectors
       ]
     }}
     >
       <DynamicWidget />
       <Sign7702Transaction/>
       </DynamicContextProvider>
     )
   }


function Sign7702Transaction() {
 const { primaryWallet, user } = useDynamicContext();

 const [error, setError] = useState(null);
 const [txHash, setTxHash] = useState(null);
 const [isSendingTransaction, setIsSendingTransaction] = useState(false);

 if (!primaryWallet) {
   return null;
 }

 const handleSendTransaction = async () => {
   const connector = primaryWallet?.connector;

   if (!connector) {
     setError('No connector found');
     return;
   }

   if (!isZeroDevConnector(connector)) {
     setError('Connector is not a ZeroDev connector');
     return;
   }

   const params = {
     withSponsorship: true,
   };
   const kernelClient = connector.getAccountAbstractionProvider(params);

   if (!kernelClient) {
     setError('No kernel client found');
     return;
   }

   try {
     setIsSendingTransaction(true);
     const userOpHash = await kernelClient.sendUserOperation({
       callData: await kernelClient.account.encodeCalls([
         {
           data: '0x',
           to: zeroAddress,
           value: BigInt(0),
         },
         {
           data: '0x',
           to: zeroAddress,
           value: BigInt(0),
         },
       ]),
     });

     const { receipt } = await kernelClient.waitForUserOperationReceipt({
       hash: userOpHash,
     });


     setTxHash(receipt.transactionHash);
     setError(null);
   } catch (err) {
     setError((err).message || 'Error sending transaction');
   } finally {
     setIsSendingTransaction(false);
   }
 };

 return (
   <>
     <div className='grid gap-12'>
       {primaryWallet && (
         <div className='grid gap-4'>
           <Button
             onClick={handleSendTransaction}
             disabled={!primaryWallet || isSendingTransaction}
             loading={isSendingTransaction}
             className='w-full'
           >
             Send Transaction
           </Button>

           {txHash && (
             <div className='p-6 bg-gray-50 rounded-lg mt-6'>
                 Transaction Hash:
               <a
                 href={`https://odyssey-explorer.ithaca.xyz/tx/${txHash}`}
                 target='_blank'
                 rel='noopener noreferrer'
                 className='block bg-gray-100 p-3 rounded hover:bg-gray-200 transition-colors text-blue-600 underline flex items-center gap-2'
               >
                 {`${txHash.slice(0, 6)}...${txHash.slice(-4)}`}
                 <span className='text-gray-500 text-sm'>
                   (View on Explorer)
                 </span>
               </a>
             </div>
           )}
         </div>
       )}

       {error && (
         <Typography variant='paragraph-3' className='text-red-500 mt-6'>
           Error: {error}
         </Typography>
       )}
     </div>
   </>
 );
};

export default App