Skip to main content

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 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 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 pendingsubmittedsuccess (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.
  • V3 MPC embedded wallets enabled in Settings > Embedded Wallets in the Dynamic dashboard.
  • EVM Gas Sponsorship enabled for your environment. Contact us to turn it on.

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 install @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.
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 useWalletAccounts from @dynamic-labs-sdk/react-hooks to reactively get the EVM embedded wallet:
import {
  sendSponsoredTransaction,
  isEvmWalletAccount,
  SponsorTransactionError,
} from '@dynamic-labs-sdk/evm';
import { useWalletAccounts } from '@dynamic-labs-sdk/react-hooks';
import { useState } from 'react';
import { parseEther } from 'viem';

function SendGaslessButton({ recipient }: { recipient: `0x${string}` }) {
  const walletAccounts = useWalletAccounts();
  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:
FieldTypeDescription
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.
valuebigintNative token amount (in wei) to send with the call.
For contract calls, encode data with encodeFunctionData from viem:
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:
OptionDefaultDescription
autoDelegatetrueWhen true, the SDK signs an EIP-7702 authorization if the wallet is not already delegated. Set to false if you manage delegation yourself.
authorizationPre-signed EIP-7702 authorization. Takes priority over autoDelegate.
validForSeconds600How long the signed intent stays valid before the relayer rejects it.

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

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

LimitationDetails
Wallet typeV3 MPC embedded wallets only. External wallets and legacy embedded wallets are not supported.
ChainEVM networks where Dynamic has enabled a relayer. Use isEvmGasSponsorshipEnabled to gate UI per environment.
Batch atomicityCalls execute atomically inside one sponsored transaction — the whole batch reverts if any call fails.
Intent lifetimeThe signed intent expires after validForSeconds (default 600). Sign close to the time you relay.