EIP-7702 support is currently in preview and only works on testnets that deployed support for the Ethereum Pectra upgrade, such as Ithaca Odyssey. 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 introduces a key update by decoupling the wallet address from a policy engine, enabling developers to upgrade an EOA into a smart account while keeping the same address. This means users can enjoy features like gasless transactions without switching wallets or spinning up new ones.

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 Odyssey Testnet

Step 2: Npm Install the preview packages

"@dynamic-labs/ethereum": "4.4.2-preview.0",
"@dynamic-labs/ethereum-aa": "4.4.2-preview.0",
"@dynamic-labs/sdk-react-core": "4.4.2-preview.0",

Step 3: Initialize the Dynamic Context Provider

To initialize pass the ZeroDevSmartWalletConnectors to the WalletConnectors Array.

Step 4: Kernel Interaction

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

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';
import Button from '@sendbird/uikit-react/ui/Button';


function App() {
   return (


     <DynamicContextProvider
     settings={{
       environmentId: '54a5040b-cdd2-47f4-ac72-8e37dd8db1b6',
       apiBaseUrl: 'https://app.dynamic-preprod.xyz/api/v0',
       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