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

# EVM Gas Sponsorship

> Sponsor EVM transaction fees for your users with Dynamic's built-in gas sponsorship feature.

<Info>
  EVM Gas Sponsorship is an enterprise-only feature. [Contact us](https://www.dynamic.xyz/talk-to-us) to learn more about upgrading your plan.
</Info>

Normally, a user needs to hold some of a network's native token (like ETH) to pay the "gas" fee on every transaction. **Gas sponsorship** lets your app pay those fees instead, so users can transact without ever topping up a wallet. This is one of the most common ways to remove friction for people new to crypto.

Dynamic has gas sponsorship built in. For the basic case you don't need to understand relayers, delegation, or any of the underlying mechanics — flip a switch in the dashboard and call one function.

<Note>
  EVM Gas Sponsorship works only with **V3 MPC embedded wallets** (the wallets Dynamic creates for your users). It does not work with external wallets like MetaMask.
</Note>

## Quick start

This is everything you need to sponsor a transaction. The SDK handles the underlying setup for you automatically.

<Steps>
  <Step title="Turn on gas sponsorship in the dashboard">
    1. Go to the [Dynamic Dashboard](https://app.dynamic.xyz)
    2. Navigate to **Settings** → **Embedded Wallets**
    3. Make sure the EVM chains you want to sponsor are enabled
    4. Toggle on **EVM Gas Sponsorship**
  </Step>

  <Step title="Send a sponsored transaction">
    Call `sendSponsoredTransaction` with the user's wallet and a list of `calls` (what you want the transaction to do). It signs, sends, waits for the transaction to land on-chain, and returns the transaction hash.

    ```javascript theme={"system"}
    import { sendSponsoredTransaction } from '@dynamic-labs-sdk/evm';
    import { parseEther } from 'viem';

    const sendSponsoredTx = async (walletAccount, recipientAddress) => {
      const { transactionHash } = await sendSponsoredTransaction({
        walletAccount,
        calls: [
          {
            target: recipientAddress, // who/what you're sending to
            data: '0x',               // '0x' = a plain token transfer
            value: parseEther('0.01'), // amount of native token to send
          },
        ],
      });

      console.log('Sponsored transaction confirmed:', transactionHash);
    };
    ```

    That's it — the user pays no gas, and you didn't have to think about delegation or relayers.
  </Step>
</Steps>

<Tip>
  The first time a wallet sends a sponsored transaction, the SDK does a one-time on-chain setup (EIP-7702 delegation) for you automatically. You don't need to do anything — it just works. See [Advanced](#advanced-usage) if you want to control that step yourself.
</Tip>

### What goes in `calls`

Each entry in the `calls` array describes one action the transaction should perform. Most apps only need a single call.

| Field    | Type     | Description                                                                                                      |
| -------- | -------- | ---------------------------------------------------------------------------------------------------------------- |
| `target` | `Hex`    | The address you're sending to (a recipient or a contract).                                                       |
| `data`   | `Hex`    | The action to run on the target. Use `0x` for a plain native-token transfer.                                     |
| `value`  | `bigint` | Amount of native token (in wei) to send with the call. Use `parseEther` to convert from a human-readable amount. |

### React example

In React, use `useGetWalletAccounts` to get the user's embedded wallet, then call `sendSponsoredTransaction` from a button handler. The `try/catch` shows the user a friendly message if sponsorship fails (see [Error handling](#error-handling)).

```tsx theme={"system"}
import {
  sendSponsoredTransaction,
  isEvmWalletAccount,
  SponsorTransactionError,
} from '@dynamic-labs-sdk/evm';
import { useGetWalletAccounts } from '@dynamic-labs-sdk/react-hooks';
import { useState } from 'react';
import { parseEther } from 'viem';

function SponsoredSendButton({ recipientAddress }) {
  const { data: walletAccounts = [] } = useGetWalletAccounts();
  const walletAccount = walletAccounts.find(isEvmWalletAccount);
  const [transactionHash, setTransactionHash] = useState('');
  const [error, setError] = useState('');

  const handleSend = async () => {
    if (!walletAccount) return;
    setError('');
    try {
      const { transactionHash } = await sendSponsoredTransaction({
        walletAccount,
        calls: [
          { target: recipientAddress, data: '0x', value: parseEther('0.01') },
        ],
      });
      setTransactionHash(transactionHash);
    } catch (err) {
      if (err instanceof SponsorTransactionError) {
        setError('Gas sponsorship failed');
      }
    }
  };

  return (
    <div>
      <button onClick={handleSend} disabled={!walletAccount}>
        Send (Sponsored)
      </button>
      {transactionHash && <p>Hash: {transactionHash.slice(0, 20)}...</p>}
      {error && <p style={{ color: 'red' }}>{error}</p>}
    </div>
  );
}
```

### Error handling

If sponsorship can't go through, `sendSponsoredTransaction` throws a `SponsorTransactionError`. There is **no silent fallback** — if it throws, the transaction did not happen. Wrap the call in a `try/catch` so you can show the user a message and decide what to do next.

```javascript theme={"system"}
import {
  sendSponsoredTransaction,
  SponsorTransactionError,
} from '@dynamic-labs-sdk/evm';

const sendTransaction = async (walletAccount, calls) => {
  try {
    const { transactionHash } = await sendSponsoredTransaction({
      walletAccount,
      calls,
    });
    return { success: true, transactionHash };
  } catch (error) {
    if (error instanceof SponsorTransactionError) {
      return { success: false, error: 'Gas sponsorship failed' };
    }
    return { success: false, error: error.message };
  }
};
```

A `SponsorTransactionError` is thrown when:

* The sponsorship API rejects the request (sponsorship not enabled, chain not supported, or a paymaster limit was hit)
* The transaction ended in a terminal failure status (`FAILED`, `CANCELED`, `EXPIRED`, `REJECTED`)
* The request times out after 60 seconds
* The wallet doesn't support sponsored transactions (e.g. an external wallet rather than a V3 MPC embedded wallet)

***

## Advanced usage

Everything below is optional. Reach for it only when you need more control than the quick start gives you — custom UI, pre-signing, or managing the one-time delegation step yourself.

### How it works under the hood

When you call `sendSponsoredTransaction`, the SDK:

1. Builds an EIP-712 intent describing the batch of calls and a deadline
2. Signs the intent with the user's embedded wallet (and, if delegation is needed, an EIP-7702 authorization for the wallet's EOA)
3. Relays the signed intent to Dynamic's sponsorship backend
4. Polls the relayer until the request reaches a terminal state, then returns the on-chain transaction hash

EIP-7702 delegation is what lets a relayer submit a batch of calls on the user's behalf. The user's EOA is delegated, once, to a Dynamic-operated relayer contract.

### Supported chains

Dynamic operates relayers on the following EVM chains.

**Mainnet**

| Chain            | Chain ID |
| ---------------- | -------- |
| Ethereum Mainnet | `1`      |
| Base             | `8453`   |
| Optimism         | `10`     |
| Arbitrum One     | `42161`  |
| BNB Smart Chain  | `56`     |

**Testnet**

| Chain            | Chain ID   |
| ---------------- | ---------- |
| Ethereum Sepolia | `11155111` |
| Base Sepolia     | `84532`    |

### Splitting sign and send

Reuse a pre-signed intent (for example, to send it from a different process) by calling `signSponsoredTransaction` first and passing the result to `sendSponsoredTransaction`:

```javascript theme={"system"}
import {
  signSponsoredTransaction,
  sendSponsoredTransaction,
} from '@dynamic-labs-sdk/evm';

const signedTransaction = await signSponsoredTransaction({
  walletAccount,
  calls,
});

const { transactionHash } = await sendSponsoredTransaction({ signedTransaction });
```

By default the signed intent is valid for 10 minutes. Override with `validForSeconds`:

```javascript theme={"system"}
const signedTransaction = await signSponsoredTransaction({
  walletAccount,
  calls,
  validForSeconds: 60,
});
```

### Reusing a nonce

By default each signed intent gets a fresh single-use bitmap nonce. Pass `nonce` (a `bigint`, matching the `nonce` on the signed result) to reuse one across intents — sign several intents with the same nonce so at most one can ever land on-chain (cancel-replace). The value is used as-is, with no on-chain validation:

```javascript theme={"system"}
const signedTransaction = await signSponsoredTransaction({
  walletAccount,
  calls,
  nonce: 42n,
});
```

`nonce` works the same way on `relaySponsoredTransaction` and `sendSponsoredTransaction`.

### Managing EIP-7702 delegation yourself

Before a wallet can submit a sponsored transaction, its EOA must be delegated to the Dynamic relayer contract via an EIP-7702 authorization. The quick start handles this automatically on the first transaction. These three helpers exist for when you want explicit control over the delegation step.

<Note>
  When `sendSponsoredTransaction` is called on a wallet that hasn't been delegated yet, the SDK signs an EIP-7702 authorization automatically and attaches it to the first call. You only need these helpers when you want explicit control over the delegation step.
</Note>

#### Checking delegation status

Use `is7702DelegationActive` to check whether delegation is already active on the wallet's current network. Results are cached per wallet + chain in an in-memory registry, so repeated calls are cheap:

```javascript theme={"system"}
import { is7702DelegationActive } from '@dynamic-labs-sdk/evm';

const isActive = await is7702DelegationActive({ walletAccount });
```

#### Signing an authorization

`sign7702Authorization` signs an EIP-7702 authorization for the Dynamic delegation contract using the wallet's active network (or an explicit `chainId` override). Pass the result to `signSponsoredTransaction` or `sendSponsoredTransaction` to attach it to the next sponsored call:

```javascript theme={"system"}
import {
  sign7702Authorization,
  signSponsoredTransaction,
} from '@dynamic-labs-sdk/evm';

const authorization = await sign7702Authorization({ walletAccount });

const signedTransaction = await signSponsoredTransaction({
  walletAccount,
  calls,
  authorization,
});
```

#### Activating delegation up-front

For flows that want delegation persisted on-chain before the first user-facing sponsored transaction (e.g. during onboarding), call `activate7702Delegation`. It sends an empty sponsored transaction that only carries the EIP-7702 authorization, then returns the on-chain transaction hash. Subsequent sponsored transactions no longer need to include an authorization:

```javascript theme={"system"}
import {
  activate7702Delegation,
  is7702DelegationActive,
} from '@dynamic-labs-sdk/evm';

if (!(await is7702DelegationActive({ walletAccount }))) {
  const { transactionHash } = await activate7702Delegation({ walletAccount });
  console.log('Delegation activated:', transactionHash);
}
```

You can also pass a pre-signed `authorization` to `activate7702Delegation` to reuse one obtained from `sign7702Authorization`.

### Polling the relay status yourself

For custom UI (e.g. a progress bar across `SUBMITTED` → `CONFIRMED`), call `getEVMSponsoredTransactionStatus` directly on the `requestId` returned by `relaySponsoredTransaction`:

```javascript theme={"system"}
import {
  relaySponsoredTransaction,
  getEVMSponsoredTransactionStatus,
} from '@dynamic-labs-sdk/evm';

const { requestId } = await relaySponsoredTransaction({ walletAccount, calls });

const { status, transactionHash, errorMessage } =
  await getEVMSponsoredTransactionStatus({ requestId });
```

`status` is one of `PENDING`, `SENT`, `SUBMITTED`, `CONFIRMED`, `FAILED`, `CANCELED`, `EXPIRED`, or `REJECTED`. `transactionHash` is set once the relay broadcasts the transaction.

For the common "wait until done" case, use `waitForSponsoredTransaction` — it polls every 2 seconds and resolves on `CONFIRMED` (timeout: 60s):

```javascript theme={"system"}
import { waitForSponsoredTransaction } from '@dynamic-labs-sdk/evm';

const { transactionHash } = await waitForSponsoredTransaction({ requestId });
```

### Limitations

| Limitation      | Details                                                                                       |
| --------------- | --------------------------------------------------------------------------------------------- |
| Wallet type     | Embedded wallets only (V3 MPC)                                                                |
| Mechanism       | EIP-7702 delegation to the Dynamic relayer contract                                           |
| Intent validity | 10 minutes by default; configurable via `validForSeconds`                                     |
| Polling timeout | `waitForSponsoredTransaction` resolves or throws within 60 seconds                            |
| Batching        | One signed intent per `sendSponsoredTransaction` call; each intent can contain multiple calls |

## Related functions

* [isEvmGasSponsorshipEnabled](/javascript/reference/evm/is-evm-gas-sponsorship-enabled) — check whether the dashboard toggle is on for the current project
* [EVM Gas Sponsorship Quickstart](/javascript/reference/zerodev/gas-sponsorship-quickstart) — the ZeroDev / ERC-4337 alternative for smart accounts
* [SVM Gas Sponsorship](/javascript/reference/solana/svm-gas-sponsorship) — the Solana equivalent

## Helpers

* `sign7702Authorization` — sign an EIP-7702 authorization for the Dynamic delegation contract
* `is7702DelegationActive` — check whether delegation is active for a wallet on its current network
* `activate7702Delegation` — activate delegation on-chain via a single empty sponsored transaction
