> ## 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 gas sponsorship feature for embedded wallets.

<Card title="Recommended: JavaScript SDK with React Hooks" icon="react" color="#4779FE">
  For new React apps, we recommend the JavaScript SDK with React Hooks (`@dynamic-labs-sdk/react-hooks`) instead of the legacy React SDK documented here. The JS SDK comes with many benefits such as a much smaller bundle size and other optimizations. Use the [React quickstart (JavaScript SDK)](/javascript/reference/react-quickstart) to get started.
</Card>

EVM Gas Sponsorship lets your users send EVM transactions without paying gas. Dynamic relays the transaction on the user's behalf and uses EIP-7702 to execute a batch of calls directly from the user's embedded wallet.

<Note>
  EVM Gas Sponsorship is available exclusively for **V3 MPC embedded wallets**. [Contact us](https://www.dynamic.xyz/talk-to-us) to enable it for your project.
</Note>

## How it works

A sponsored transaction is a batch of `{ target, data, value }` calls that Dynamic submits on-chain on the user's behalf:

1. The user's embedded wallet signs an EIP-712 intent that authorizes the calls, binds them to a relayer address, and sets a deadline.
2. The first time a wallet is sponsored, the SDK also signs an EIP-7702 authorization to delegate the EOA to Dynamic's gasless delegation contract. After activation, the delegation persists on-chain and is reused on subsequent transactions.
3. Dynamic's relayer submits the transaction. The lifecycle is `pending` → `submitted` → `success` (or `failure`). The on-chain transaction hash is available once the relay reports `submitted`.

## Prerequisites

Before you start, make sure you have:

* A React app set up with `DynamicContextProvider` and `EthereumWalletConnectors`. See the [React quickstart](/react/reference/quickstart).
* V3 MPC embedded wallets enabled in **Settings > Embedded Wallets** in the [Dynamic dashboard](https://app.dynamic.xyz).
* EVM Gas Sponsorship enabled for your environment. [Contact us](https://www.dynamic.xyz/talk-to-us) to turn it on.
* The wallet's active network is one of the [supported chains](#supported-chains) below.

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

## Setup

EVM gas sponsorship is exposed through `@dynamic-labs-sdk/evm`. Install it alongside the React-hooks package that lets you reactively read wallet accounts:

<Tabs>
  <Tab title="npm">
    ```bash theme={"system"}
    npm install @dynamic-labs-sdk/evm @dynamic-labs-sdk/react-hooks
    ```
  </Tab>

  <Tab title="yarn">
    ```bash theme={"system"}
    yarn add @dynamic-labs-sdk/evm @dynamic-labs-sdk/react-hooks
    ```
  </Tab>

  <Tab title="pnpm">
    ```bash theme={"system"}
    pnpm add @dynamic-labs-sdk/evm @dynamic-labs-sdk/react-hooks
    ```
  </Tab>

  <Tab title="bun">
    ```bash theme={"system"}
    bun add @dynamic-labs-sdk/evm @dynamic-labs-sdk/react-hooks
    ```
  </Tab>
</Tabs>

No additional provider is needed — these packages share state with `DynamicContextProvider`.

## Check if sponsorship is enabled

`isEvmGasSponsorshipEnabled` returns synchronously based on the project settings already loaded by the SDK. Use it to gate UI before showing a "Send gasless" button.

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

const canSponsor = isEvmGasSponsorshipEnabled();
```

## Send a sponsored transaction

`sendSponsoredTransaction` signs the intent, hands it to the relayer, and resolves once the transaction is included on-chain. The `calls` array supports batches — every entry runs atomically.

Use `useGetWalletAccounts` from `@dynamic-labs-sdk/react-hooks` to reactively get the EVM embedded wallet:

```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 SendGaslessButton({ recipient }: { recipient: `0x${string}` }) {
  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: recipient,
            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>Tx: {transactionHash.slice(0, 20)}...</p>}
      {error && <p style={{ color: 'red' }}>{error}</p>}
    </div>
  );
}
```

### Call shape

Each entry in `calls` describes a single call inside the sponsored batch:

| Field    | Type                | Description                                                 |
| -------- | ------------------- | ----------------------------------------------------------- |
| `target` | `` `0x${string}` `` | Contract or recipient address to execute the call against.  |
| `data`   | `` `0x${string}` `` | Hex-encoded calldata. Use `0x` for a plain native transfer. |
| `value`  | `bigint`            | Native token amount (in wei) to send with the call.         |

For contract calls, encode `data` with `encodeFunctionData` from viem:

```tsx theme={"system"}
import { encodeFunctionData, parseUnits } from 'viem';

const ERC20_TRANSFER_ABI = [
  {
    inputs: [
      { name: 'to', type: 'address' },
      { name: 'amount', type: 'uint256' },
    ],
    name: 'transfer',
    outputs: [{ name: '', type: 'bool' }],
    stateMutability: 'nonpayable',
    type: 'function',
  },
] as const;

const transferCall = {
  target: '0xTokenAddress...',
  data: encodeFunctionData({
    abi: ERC20_TRANSFER_ABI,
    functionName: 'transfer',
    args: ['0xRecipient...', parseUnits('1', 6)],
  }),
  value: 0n,
};
```

### Options

Pass these alongside `walletAccount` and `calls`:

| Option            | Default | Description                                                                                                                                                                                                                                         |
| ----------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `autoDelegate`    | `true`  | When `true`, the SDK signs an EIP-7702 authorization if the wallet is not already delegated. Set to `false` if you manage delegation yourself.                                                                                                      |
| `authorization`   | —       | Pre-signed EIP-7702 authorization. Takes priority over `autoDelegate`.                                                                                                                                                                              |
| `validForSeconds` | `600`   | How long the signed intent stays valid before the relayer rejects it.                                                                                                                                                                               |
| `nonce`           | —       | Reuse a specific bitmap nonce (`bigint`, as returned on the signed intent) instead of letting the SDK generate one. Sign several intents with the same nonce so at most one can land on-chain (cancel-replace). Used as-is, with no on-chain check. |

## Split signing and relaying

For retry, batching, or custom UI flows you can split the steps:

* `signSponsoredTransaction` returns the signed intent without contacting the relayer.
* `relaySponsoredTransaction` sends a signed intent (or signs and sends in one step) and returns a `requestId`.
* `waitForSponsoredTransaction` polls a `requestId` until the relay reports inclusion on-chain.
* `getEVMSponsoredTransactionStatus` does a one-shot status read for custom polling.

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

// Sign now, relay later
const signedTransaction = await signSponsoredTransaction({
  walletAccount,
  calls,
});

const { requestId } = await relaySponsoredTransaction({
  signedTransaction,
});

// Block until inclusion
const { transactionHash } = await waitForSponsoredTransaction({ requestId });

// Or read the current status without polling
const status = await getEVMSponsoredTransactionStatus({ requestId });
```

`status.status` is one of `pending`, `submitted`, `success`, or `failure`. `status.transactionHash` is populated once the relay reports `submitted`; `status.errorMessage` is populated on `failure`.

## EIP-7702 delegation

The first sponsored transaction from a given wallet activates EIP-7702 delegation to Dynamic's gasless contract. By default `sendSponsoredTransaction` and `relaySponsoredTransaction` handle this for you via `autoDelegate: true`. If you want to manage delegation explicitly — for example, to surface an "Enable gasless" button before the first transaction — use these functions directly.

### Check delegation status

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

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

Results are cached per wallet and chain.

### Sign a 7702 authorization

`sign7702Authorization` returns a signed authorization without broadcasting anything. Pass it to a later `sendSponsoredTransaction`, `relaySponsoredTransaction`, or `activate7702Delegation` call.

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

const authorization = await sign7702Authorization({
  walletAccount,
  // Defaults to the wallet's active network chain ID.
  // chainId: 1,
});
```

### Activate delegation explicitly

`activate7702Delegation` sends a sponsored transaction whose only purpose is to activate the delegation on-chain. After it resolves, subsequent sponsored transactions skip the authorization step.

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

// Sign now, activate later
const authorization = await sign7702Authorization({ walletAccount });

const { transactionHash } = await activate7702Delegation({
  walletAccount,
  authorization,
});

// Or have the SDK sign and activate in one call
await activate7702Delegation({ walletAccount });
```

## Error handling

Sponsorship failures throw a `SponsorTransactionError` — there is no silent fallback. The error is thrown when:

* The relay returns a terminal failure or polling times out.
* The sponsorship API rejects the request.
* The wallet provider does not support sponsored transactions (e.g. external wallets).

Wrap calls in `try`/`catch` and surface a useful message to the user:

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

try {
  const { transactionHash } = await sendSponsoredTransaction({
    walletAccount,
    calls,
  });
} catch (error) {
  if (error instanceof SponsorTransactionError) {
    // Show a user-facing fallback or retry UI.
  }
}
```

## Limitations

| Limitation      | Details                                                                                                        |
| --------------- | -------------------------------------------------------------------------------------------------------------- |
| Wallet type     | V3 MPC embedded wallets only. External wallets and legacy embedded wallets are not supported.                  |
| Chain           | EVM networks where Dynamic has enabled a relayer. Use `isEvmGasSponsorshipEnabled` to gate UI per environment. |
| Batch atomicity | Calls execute atomically inside one sponsored transaction — the whole batch reverts if any call fails.         |
| Intent lifetime | The signed intent expires after `validForSeconds` (default 600). Sign close to the time you relay.             |
