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

EVM Gas Sponsorship lets your users send EVM transactions without paying gas. Dynamic relays the transaction on their behalf and uses EIP-7702 delegation to execute batched calls 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 and binds them to a relayer address and 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 and reports a `pending` → `submitted` → `success` (or `failure`) lifecycle. The on-chain transaction hash is available once the relay reports `submitted`.

## Prerequisites

Before you start, make sure you have:

* A React Native app set up with the [React Native quickstart](/react-native/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

### Install the extension

<Tabs>
  <Tab title="expo">
    ```bash theme={"system"}
    npx expo install @dynamic-labs/ethereum-gasless-extension
    ```
  </Tab>

  <Tab title="npm">
    ```bash theme={"system"}
    npm install @dynamic-labs/ethereum-gasless-extension
    ```
  </Tab>

  <Tab title="yarn">
    ```bash theme={"system"}
    yarn add @dynamic-labs/ethereum-gasless-extension
    ```
  </Tab>

  <Tab title="pnpm">
    ```bash theme={"system"}
    pnpm add @dynamic-labs/ethereum-gasless-extension
    ```
  </Tab>

  <Tab title="bun">
    ```bash theme={"system"}
    bun add @dynamic-labs/ethereum-gasless-extension
    ```
  </Tab>
</Tabs>

### Extend the Dynamic client

Add `EthereumGaslessExtension()` to the extension chain on your existing `createClient()` call. This adds an `ethereumGasless` namespace to the client.

```typescript client.ts theme={"system"}
import { createClient } from '@dynamic-labs/client';
import { ReactNativeExtension } from '@dynamic-labs/react-native-extension';
import { ViemExtension } from '@dynamic-labs/viem-extension';
import { EthereumGaslessExtension } from '@dynamic-labs/ethereum-gasless-extension';

export const dynamicClient = createClient({
  environmentId: 'YOUR_ENVIRONMENT_ID',
  appName: 'My App',
  appOrigin: 'https://YOUR-DOMAIN.com',
})
  .extend(ReactNativeExtension())
  .extend(ViemExtension())
  .extend(EthereumGaslessExtension());
```

## Check if sponsorship is enabled

Call `isEnabled` to confirm that sponsorship is turned on for your environment. Use this to gate UI before showing a "Send gasless" button.

```typescript theme={"system"}
import { dynamicClient } from './client';

const enabled = await dynamicClient.ethereumGasless.isEnabled();
```

## Send a sponsored transaction

`send` 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.

```tsx theme={"system"}
import { useReactiveClient } from '@dynamic-labs/react-hooks';
import { parseEther } from 'viem';

import { dynamicClient } from './client';

function SendGasless() {
  const { wallets } = useReactiveClient(dynamicClient);
  const wallet = wallets.primary;

  const send = async () => {
    if (!wallet || wallet.chain !== 'EVM') return;

    const { transactionHash } = await dynamicClient.ethereumGasless.send({
      wallet,
      calls: [
        {
          target: '0xRecipient...',
          data: '0x',
          value: parseEther('0.01'),
        },
      ],
    });

    console.log('Transaction hash:', transactionHash);
  };

  return <Button title="Send (gasless)" onPress={send} disabled={!wallet} />;
}
```

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

```typescript 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 `wallet` 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:

* `sign` returns the signed intent without contacting the relayer.
* `relay` sends a signed intent (or signs and sends in one step) and returns a `requestId`.
* `waitFor` polls a `requestId` until it resolves to an on-chain transaction.
* `getStatus` does a one-shot status read for custom polling.

```typescript theme={"system"}
const wallet = dynamicClient.wallets.primary;

// Sign now, relay later
const signed = await dynamicClient.ethereumGasless.sign({ wallet, calls });

const { requestId } = await dynamicClient.ethereumGasless.relay({
  wallet,
  calls,
});

const { transactionHash } = await dynamicClient.ethereumGasless.waitFor({
  requestId,
});

// Or read the current status without polling
const status = await dynamicClient.ethereumGasless.getStatus({ 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 `send` and `relay` 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 methods directly.

### Check delegation status

```typescript theme={"system"}
const wallet = dynamicClient.wallets.primary;

const isDelegated =
  await dynamicClient.ethereumGasless.is7702DelegationActive({ wallet });
```

Results are cached per wallet and chain.

### Sign a 7702 authorization

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

```typescript theme={"system"}
const wallet = dynamicClient.wallets.primary;

const authorization =
  await dynamicClient.ethereumGasless.sign7702Authorization({
    wallet,
    // 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.

```typescript theme={"system"}
const wallet = dynamicClient.wallets.primary;

// Sign now, activate later
const authorization =
  await dynamicClient.ethereumGasless.sign7702Authorization({ wallet });

const { transactionHash } =
  await dynamicClient.ethereumGasless.activate7702Delegation({
    wallet,
    authorization,
  });

// Or have the SDK sign and activate in one call
await dynamicClient.ethereumGasless.activate7702Delegation({ wallet });
```

## Error handling

Sponsorship failures throw — there is no silent fallback. Wrap calls in `try`/`catch` and surface a useful message to the user:

```typescript theme={"system"}
try {
  const { transactionHash } = await dynamicClient.ethereumGasless.send({
    wallet,
    calls,
  });
} catch (error) {
  // The relay returned a terminal failure, polling timed out, or the API errored.
  console.error('Sponsored transaction failed:', error);
}
```

Common failure causes:

* EVM Gas Sponsorship is not enabled for the environment.
* The wallet is not a V3 MPC embedded wallet.
* The wallet's active network is not supported by the relayer.
* The signed intent expired (`validForSeconds` elapsed before the relayer ran).
* A terminal on-chain failure on the relayed transaction.

## 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 `isEnabled` and `is7702DelegationActive` to gate UI per chain. |
| Batch size      | 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.                   |
