> ## 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 dashboard 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.

<Info>
  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.
</Info>

## 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`)<br />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).
</Tip>

## Before you start (dashboard)

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

<Steps>
  <Step title="Enable native EVM gas sponsorship">
    In your environment, go to **Sponsor Gas → Dynamic Native Sponsorship** and
    turn on **Sponsor Network Fees (EVM)**. After changing the dashboard, 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()`.
  </Step>

  <Step title="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.
    </Warning>
  </Step>
</Steps>

## Migrate your code

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

<Tabs>
  <Tab title="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.

    <Steps>
      <Step title="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.
        </Warning>
      </Step>

      <Step title="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.
        </Warning>
      </Step>

      <Step title="Remove the ZeroDev connector">
        Once your sends go through native sponsorship, remove
        `ZeroDevSmartWalletConnectors` from your `walletConnectors`, and disable
        ZeroDev in the dashboard.

        ```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 dashboard **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
          dashboard while connector still in code) during the rollout window.
        </Warning>
      </Step>

      <Step title="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
        ```
      </Step>
    </Steps>
  </Tab>

  <Tab title="React Native">
    Using `@dynamic-labs/client`. Today you have `.extend(ZeroDevExtension())`
    from `@dynamic-labs/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.

    <Steps>
      <Step title="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
        ```
      </Step>

      <Step title="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`.
      </Step>

      <Step title="Remove the ZeroDev extension">
        Once your sends go through native sponsorship, drop
        `.extend(ZeroDevExtension())` and the `@dynamic-labs/zerodev-extension`
        import, and disable ZeroDev in the dashboard.

        ```diff theme={"system"}
        - import { ZeroDevExtension } from '@dynamic-labs/zerodev-extension';

          const client = createClient({ environmentId /* ... */ })
            .extend(ReactNativeExtension({ /* ... */ }))
            .extend(ViemExtension())
        -   .extend(ZeroDevExtension())
            .extend(EthereumGaslessExtension());
        ```

        <Warning>
          Order matters. Disable ZeroDev in the dashboard **before** removing the
          extension from code — and don't disable it in the dashboard while the
          extension is still wired up. Either mismatch can throw during the
          rollout window.
        </Warning>
      </Step>

      <Step title="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
        ```
      </Step>
    </Steps>
  </Tab>
</Tabs>

## 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.
</Tip>
