## Recommended: JavaScript SDK with React Hooks

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)](/content/docs/javascript/reference/react-quickstart/index.html) to get started.

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.

EVM Gas Sponsorship is available exclusively for **V3 MPC embedded wallets**. [Contact us](/content/talk-to-us/index.html) to enable it for your project.

## 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](/content/docs/react/reference/quickstart/index.html).
- 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](/content/talk-to-us/index.html) to turn it on.
- The wallet’s active network is one of the [supported chains](/content/docs/react/smart-wallets/evm-gas-sponsorship#supported-chains/index.html) 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` |
| Robinhood Chain | `4663` |

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

- npm

```bash
npm install @dynamic-labs-sdk/evm @dynamic-labs-sdk/react-hooks
```

- yarn

```bash
yarn add @dynamic-labs-sdk/evm @dynamic-labs-sdk/react-hooks
```

- pnpm

```bash
pnpm add @dynamic-labs-sdk/evm @dynamic-labs-sdk/react-hooks
```

- bun

```bash
bun add @dynamic-labs-sdk/evm @dynamic-labs-sdk/react-hooks
```

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.

```javascript
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:

```javascript
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:

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

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

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

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

```javascript
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:

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