> ## Documentation Index
> Fetch the complete documentation index at: https://www.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 [Managing EIP-7702 delegation](/docs/javascript/reference/evm/managing-7702-delegation) 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. |

### Batch calls

A single sponsored transaction can carry **more than one call** — they're executed together, atomically (all succeed or all revert). For each call, set `target` to the contract (or recipient), put the encoded function call in `data`, and use `value` for any native-token amount you want to send with that call (it's `0n` when the call moves no native token, like the ERC-20 transfers below).

The example below sends **two USDC transfers to two different addresses in one sponsored transaction**. USDC is an ERC-20 token, so each `data` is the calldata for its `transfer(address,uint256)` function, built with viem's [`encodeFunctionData`](https://viem.sh/docs/contract/encodeFunctionData) and the standard `erc20Abi` viem ships:

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

// USDC on Base — an ERC-20 contract with 6 decimals
const USDC_ADDRESS = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';

const sendTwoUsdcTransfers = async (walletAccount, recipientA, recipientB) => {
  const { transactionHash } = await sendSponsoredTransaction({
    walletAccount,
    calls: [
      {
        target: USDC_ADDRESS,
        value: 0n, // no native token — the transfer moves USDC, not ETH
        data: encodeFunctionData({
          abi: erc20Abi,
          functionName: 'transfer',
          args: [recipientA, parseUnits('5', 6)], // 5 USDC
        }),
      },
      {
        target: USDC_ADDRESS,
        value: 0n,
        data: encodeFunctionData({
          abi: erc20Abi,
          functionName: 'transfer',
          args: [recipientB, parseUnits('10', 6)], // 10 USDC
        }),
      },
    ],
  });

  console.log('Both transfers landed in one sponsored transaction:', transactionHash);
};
```

<Note>
  Use `parseUnits(amount, decimals)` for ERC-20 tokens, not `parseEther`. USDC has **6** decimals, so `parseUnits('5', 6)` is 5 USDC. `parseEther` assumes 18 decimals and would send a vastly wrong amount.
</Note>

### React example

In React, use `useGetWalletAccounts` to get the user's embedded wallet, then call `sendSponsoredTransaction` from a button handler. This example sponsors a **USDC transfer** — the same ERC-20 pattern as above, with a single call. 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 { encodeFunctionData, erc20Abi, parseUnits } from 'viem';

// USDC on Base — an ERC-20 contract with 6 decimals
const USDC_ADDRESS = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';

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: USDC_ADDRESS,
            value: 0n,
            data: encodeFunctionData({
              abi: erc20Abi,
              functionName: 'transfer',
              args: [recipientAddress, parseUnits('5', 6)], // 5 USDC
            }),
          },
        ],
      });
      setTransactionHash(transactionHash);
    } catch (err) {
      if (err instanceof SponsorTransactionError) {
        setError('Gas sponsorship failed');
      }
    }
  };

  return (
    <div>
      <button onClick={handleSend} disabled={!walletAccount}>
        Send 5 USDC (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 relay reports a terminal `failure` status
* 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)

***

## 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`     |
| Robinhood Chain  | `4663`   |

**Testnet**

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

## Going further

The quick start covers the common case. For finer control, each of these has its own reference page:

* **[sendSponsoredTransaction](/docs/javascript/reference/evm/send-sponsored-transaction)** — the full API for the primary send function: every parameter, batching, nonces, and return value.
* **[Splitting sign & send](/docs/javascript/reference/evm/splitting-sign-and-send)** — pre-sign an intent with `signSponsoredTransaction`, relay it separately, reuse a nonce for cancel-replace, and drive custom progress UI with `getEVMSponsoredTransactionStatus` / `waitForSponsoredTransaction`.
* **[Managing EIP-7702 delegation](/docs/javascript/reference/evm/managing-7702-delegation)** — check, sign, and activate the one-time delegation yourself with `is7702DelegationActive`, `sign7702Authorization`, and `activate7702Delegation`.
* **[EVM Server-Controlled Sponsorship](/docs/recipes/integrations/evm-server-controlled-sponsorship)** — move the sponsorship decision to your backend: the user signs on the client, and your server validates and relays so you control what gets sponsored, per user and per transaction.

## Related functions

* [sendSponsoredTransaction](/docs/javascript/reference/evm/send-sponsored-transaction) — full reference for the primary send function
* [Splitting sign & send](/docs/javascript/reference/evm/splitting-sign-and-send) — pre-sign, relay, and poll status for custom flows
* [Managing EIP-7702 delegation](/docs/javascript/reference/evm/managing-7702-delegation) — control the one-time delegation step yourself
* [EVM Server-Controlled Sponsorship](/docs/recipes/integrations/evm-server-controlled-sponsorship) — sign on the client, validate and relay from your backend
* [ZeroDev Gas Sponsorship Quickstart](/docs/javascript/reference/zerodev/gas-sponsorship-quickstart) — the ZeroDev / ERC-4337 alternative for smart accounts
* [SVM Gas Sponsorship](/docs/javascript/reference/solana/svm-gas-sponsorship) — the Solana equivalent
