> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrating from ZeroDev to native EVM gas sponsorship

> Move your embedded-wallet users off ZeroDev (EIP-7702) gasless onto Dynamic's native EVM gas sponsorship, with the exact code and console changes for React and React Native.

Dynamic sponsors EVM gas **natively** for embedded wallets. If you previously
enabled gasless transactions through the **ZeroDev** integration (EIP-7702), this
guide moves you to native sponsorship and removes ZeroDev.

> [!NOTE]
>
> This guide covers migrating from **ZeroDev EIP-7702** gasless to native
> sponsorship. Both keep the user on their **embedded EOA at the same address** —
> what changes is the connector/extension, the send API, and the on-chain 7702
> delegation target. Migrating from ERC-4337 smart-contract accounts is not
> covered here.

## What changes

| | ZeroDev (EIP-7702) | Native gas sponsorship |
| - | - | - |
| In code | `ZeroDevSmartWalletConnectors` (React) / `ZeroDevExtension` (React Native) | removed |
| Sponsored send | via ZeroDev | React: `sendSponsoredTransaction` (`@dynamic-labs-sdk/evm`) React Native: `client.ethereumGasless.send` |
| 7702 delegate (on-chain) | ZeroDev Kernel implementation | Dynamic/Fireblocks: `0x0000Fb7702036ff9f76044a501ac1aA74cbab16b` |

The user's **address does not change** — both paths delegate the same embedded
EOA via EIP-7702. The delegation *target* changes, which is how you verify the
migration on-chain.

> [!TIP]
>
> Migrations can touch users, addresses, and assets. When in doubt,
> [talk to us](https://www.dynamic.xyz/talk-to-us).

## Before you start (console)

These apply to both React and React Native and can't be done in code.

**1. Enable native EVM gas sponsorship**

In your environment, go to **Sponsor Gas → Dynamic Native Sponsorship** and
turn on **Sponsor Network Fees (EVM)**, then **Sponsor on testnets** or
**Sponsor on mainnets**. Mainnet sponsorship requires the Enterprise plan or
Dynamic enabling it for your environment. After changing the Dynamic Console, reload
so the SDK picks it up. From the SDK you can gate your UI on the enabled
check — React: `isEvmGasSponsorshipEnabled()` from `@dynamic-labs-sdk/evm`;
React Native: `await client.ethereumGasless.isEnabled()`.

**2. If your app is headless, disable the transaction confirmation UI**

If you render Dynamic's UI components, skip this. If your app is **headless**
(you don't render `DynamicWidget` / the Dynamic views), turn **off** the
embedded-wallet transaction confirmation UI for the environment.

> [!WARNING]
>
> With the confirmation UI enabled but no Dynamic UI rendered, sends wait on
> a confirmation modal that never appears and hang indefinitely.

## Migrate your code

Keep ZeroDev wired up until native sponsored sends work — remove it last.

> [tab] React
>
> Using `@dynamic-labs/sdk-react-core`. Today you have
> `ZeroDevSmartWalletConnectors` from `@dynamic-labs/ethereum-aa` in your
> `walletConnectors`, and send via
> `primaryWallet.connector.getAccountAbstractionProvider({ withSponsorship: true })`.
>
> Install `@dynamic-labs-sdk/evm` and `@dynamic-labs-sdk/client`, pinned to the
> exact `@dynamic-labs-sdk/client` version your `@dynamic-labs/sdk-react-core`
> already resolves (check
> `node_modules/@dynamic-labs/sdk-react-core/node_modules/@dynamic-labs-sdk/client/package.json`,
> or your lockfile) so the package manager keeps a single client instance.
>
> **1. Register the EVM wallet provider**
>
> `@dynamic-labs/sdk-react-core` builds the underlying
> `@dynamic-labs-sdk/client` but drives embedded wallets through the legacy
> connector system — it never registers a new-SDK wallet provider. Register
> the EVM WaaS provider yourself, once, after the SDK loads, by calling
> `addEvmExtension()` from `@dynamic-labs-sdk/evm`. It binds to the client
> `DynamicContextProvider` already created.
>
> ```tsx theme={"system"}
> import { useEffect } from 'react';
> import { useDynamicContext } from '@dynamic-labs/sdk-react-core';
> import { addEvmExtension } from '@dynamic-labs-sdk/evm';
>
> export const RegisterEvmGasless = () => {
>   const { sdkHasLoaded } = useDynamicContext();
>
>   useEffect(() => {
>     // Registers the EVM WaaS wallet provider on the provider's default client.
>     if (sdkHasLoaded) addEvmExtension();
>   }, [sdkHasLoaded]);
>
>   return null;
> };
> ```
>
> > [!WARNING]
> >
> > Without this, sponsored sends throw
> > `No wallet provider found with key: dynamicwaasevm:embeddedWallet` — the
> > wallet account exists, but the provider that signs for it was never
> > registered.
>
> **2. Send sponsored transactions**
>
> Use `sendSponsoredTransaction` from `@dynamic-labs-sdk/evm` with the EVM
> wallet account. Read the account imperatively with `getWalletAccounts()`
> from `@dynamic-labs-sdk/client` — it resolves the same client the provider
> registered.
>
> ```tsx theme={"system"}
> import { getWalletAccounts } from '@dynamic-labs-sdk/client';
> import {
>   sendSponsoredTransaction,
>   type EvmWalletAccount,
> } from '@dynamic-labs-sdk/evm';
>
> export const sendSponsored = async ({
>   to,
>   value = 0n,
>   data = '0x',
> }: {
>   to: `0x${string}`;
>   value?: bigint;
>   data?: `0x${string}`;
> }) => {
>   const walletAccount = getWalletAccounts().find(
>     (account): account is EvmWalletAccount => account.chain === 'EVM',
>   );
>   if (!walletAccount) throw new Error('No EVM wallet account');
>
>   const { transactionHash } = await sendSponsoredTransaction({
>     walletAccount,
>     calls: [{ target: to, value, data }],
>   });
>
>   return transactionHash;
> };
> ```
>
> EIP-7702 delegation is activated automatically on the first sponsored send.
>
> > [!WARNING]
> >
> > Don't reach for `useGetWalletAccounts()` from
> > `@dynamic-labs-sdk/react-hooks` in a `sdk-react-core` app: those hooks
> > read the client from the new-SDK `<DynamicProvider>` context, which
> > `<DynamicContextProvider>` never mounts, so they throw
> > `MissingProviderError`. Use the imperative functions above.
>
> **3. Remove the ZeroDev connector**
>
> Once your sends go through native sponsorship, remove
> `ZeroDevSmartWalletConnectors` from your `walletConnectors`, and disable
> ZeroDev in the Dynamic Console.
>
> ```diff theme={"system"}
> - import { ZeroDevSmartWalletConnectors } from '@dynamic-labs/ethereum-aa';
>
>   <DynamicContextProvider
>     settings={{
>       environmentId,
> -     walletConnectors: [EthereumWalletConnectors, ZeroDevSmartWalletConnectors],
> +     walletConnectors: [EthereumWalletConnectors],
>     }}
>   >
> ```
>
> > [!WARNING]
> >
> > Order matters. Ship the SDK version that tolerates connector removal and
> > disable ZeroDev in the Dynamic Console **before** deleting the connector from
> > code — otherwise clients can hit a `ConnectorSetupError` (connector
> > removed while still enabled) or a missing-project-id error (disabled in
> > console while connector still in code) during the rollout window.
>
> **4. Verify on-chain**
>
> Confirm a sponsored transaction succeeds from the embedded EOA with no
> funds, then check the account's EIP-7702 delegation now points at
> Dynamic's implementation rather than the ZeroDev Kernel:
>
> ```ts theme={"system"}
> import { is7702DelegationActive } from '@dynamic-labs-sdk/evm';
>
> // walletAccount from getWalletAccounts() — see the send step.
> const delegated = await is7702DelegationActive({ walletAccount });
> // Resolves true once delegated to the Dynamic/Fireblocks implementation:
> // 0x0000Fb7702036ff9f76044a501ac1aA74cbab16b
> ```

> [tab] React Native
>
> Using `@dynamic-labs/legacy-client`. Today you have `.extend(ZeroDevExtension())`
> from `@dynamic-labs/legacy-zerodev-extension` in your `createClient().extend(...)`
> chain, and send via
> `client.zeroDev.createKernelClient({ wallet }).sendTransaction(...)`.
>
> Install `@dynamic-labs/ethereum-gasless-extension`, matching your other
> `@dynamic-labs/*` versions.
>
> **1. Register the gasless extension**
>
> Add `EthereumGaslessExtension` to your client builder, alongside the
> existing `ZeroDevExtension` during the migration window. It exposes
> `client.ethereumGasless.*`.
>
> ```ts theme={"system"}
> import { EthereumGaslessExtension } from '@dynamic-labs/ethereum-gasless-extension';
>
> const client = createClient({ environmentId /* ... */ })
>   .extend(ReactNativeExtension({ /* ... */ }))
>   .extend(ViemExtension())
>   .extend(ZeroDevExtension())          // keep during migration
>   .extend(EthereumGaslessExtension()); // add native gasless
> ```
>
> **2. Send sponsored transactions**
>
> Use `client.ethereumGasless.send` with the embedded `wallet` you already
> pass to ZeroDev today. Note the call-shape change: ZeroDev takes
> `{ to, value, data }`; native gasless takes `{ target, value, data }`.
>
> ```ts theme={"system"}
> // Before — ZeroDev kernel client:
> //   const kc = await client.zeroDev.createKernelClient({ wallet });
> //   const txHash = await kc.sendTransaction({ calls: [{ to, value, data: '0x' }] });
>
> // After — Dynamic native gas sponsorship:
> const { transactionHash } = await client.ethereumGasless.send({
>   wallet,
>   calls: [{ target: to, value, data: '0x' }],
> });
> ```
>
> `value` stays a normal `bigint` — the extension serializes it across the
> `messageTransport` boundary for you. EIP-7702 delegation is activated
> automatically on the first sponsored send. For retry/batch UX, split the
> steps with `client.ethereumGasless.sign` / `relay` / `waitFor` /
> `getStatus`.
>
> **3. Remove the ZeroDev extension**
>
> Once your sends go through native sponsorship, drop
> `.extend(ZeroDevExtension())` and the `@dynamic-labs/legacy-zerodev-extension`
> import, and disable ZeroDev in the Dynamic Console.
>
> ```diff theme={"system"}
> - import { ZeroDevExtension } from '@dynamic-labs/legacy-zerodev-extension';
>
>   const client = createClient({ environmentId /* ... */ })
>     .extend(ReactNativeExtension({ /* ... */ }))
>     .extend(ViemExtension())
> -   .extend(ZeroDevExtension())
>     .extend(EthereumGaslessExtension());
> ```
>
> > [!WARNING]
> >
> > Order matters. Disable ZeroDev in the Dynamic Console **before** removing the
> > extension from code — and don't disable it in the Dynamic Console while the
> > extension is still wired up. Either mismatch can throw during the
> > rollout window.
>
> **4. Verify on-chain**
>
> Confirm a sponsored transaction succeeds from the embedded EOA with no
> funds, then check the wallet's EIP-7702 delegation now points at Dynamic's
> implementation rather than the ZeroDev Kernel:
>
> ```ts theme={"system"}
> const delegated = await client.ethereumGasless.is7702DelegationActive({
>   wallet,
> });
> // Resolves true once delegated to the Dynamic/Fireblocks implementation:
> // 0x0000Fb7702036ff9f76044a501ac1aA74cbab16b
> ```

## Gotchas

* **The user's address doesn't change.** Both ZeroDev and native sponsorship
  delegate the same embedded EOA via EIP-7702 — only the delegation target moves.
  Once a wallet has re-delegated to Dynamic on its first native send, don't route
  that user back through a ZeroDev send.
* **Headless apps must disable the confirmation UI**, or sends hang on an
  unrendered modal.
* **React:** register the EVM provider with `addEvmExtension()` once after the SDK
  loads, or sends throw `No wallet provider found with key:
  dynamicwaasevm:embeddedWallet`. Don't use the `@dynamic-labs-sdk/react-hooks`
  hooks in a `sdk-react-core` app — they need the new-SDK `<DynamicProvider>` and
  throw `MissingProviderError`. Read the account with `getWalletAccounts()`.

> [!TIP]
>
> Prefer to automate the code changes? Use the ZeroDev → native gasless
> migration skill with your AI coding assistant.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.