> ## 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 from your server with Dynamic's Java SDK

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

On the server, the wallet client signs a sponsored transaction with the user's embedded-wallet shares, relays the signed intent to Dynamic's sponsorship backend, and waits for it to land on-chain. For the basic case you don't need to understand relayers or delegation. Enable it in the dashboard and call one method.

<Note>
  EVM Gas Sponsorship works only with **V3 MPC embedded wallets** (the wallets Dynamic creates for your users). It does not work with externally imported keys that you sign for directly.
</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 wallet's key shares, the `chainId`, an `rpcUrl`, and a list of `calls` (what you want the transaction to do). It signs the intent, relays it, waits for the transaction to land on-chain, and returns the transaction hash.

    ```java theme={"system"}
    import xyz.dynamic.waas.evm.SponsoredCall;
    import xyz.dynamic.waas.evm.SignedSponsoredTransaction;
    import xyz.dynamic.waas.evm.opts.SignSponsoredTransactionOpts;

    import java.util.List;

    // Load the WalletProperties + shares you persisted at creation time.
    // See /java/storage-best-practices for the recommended split.

    SignedSponsoredTransaction signed = client.signSponsoredTransaction(
        SignSponsoredTransactionOpts.builder()
            .walletProperties(walletProperties)
            .externalServerKeyShares(externalServerKeyShares)
            .chainId(8453L)  // Base
            .rpcUrl(System.getenv("BASE_RPC_URL"))
            .calls(List.of(new SponsoredCall(
                recipientAddress,  // who/what you're sending to
                "0",               // wei amount of native token to send
                "0x"               // '0x' = a plain token transfer
            )))
            .build()
    ).join();

    String txHash = client.sendSponsoredTransaction(signed).join();
    System.out.println("Sponsored transaction confirmed: " + txHash);
    ```

    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. For that to work it needs an `rpcUrl` so it can read the wallet's delegation state and EOA nonce. If you omit `rpcUrl`, either pass `autoDelegate(false)` or supply a pre-signed `authorization` (see [Managing EIP-7702 delegation yourself](#managing-eip-7702-delegation-yourself)).
</Tip>

### What goes in `calls`

Each entry in the `calls` list describes one action the transaction should perform. Most apps only need a single call.

| Field    | Type     | Description                                                                                    |
| -------- | -------- | ---------------------------------------------------------------------------------------------- |
| `target` | `String` | 0x-prefixed address you're sending to (a recipient or a contract).                             |
| `value`  | `String` | Amount of native token in wei as a decimal string. Use `"0"` when no native token should move. |
| `data`   | `String` | 0x-prefixed hex calldata. Use `"0x"` for a plain native-token transfer.                        |

### Sending an ERC-20 token (USDC)

To move an ERC-20 token instead of native gas, point `target` at the token contract, encode a `transfer` call as `data`, and set `value` to `"0"`:

```java theme={"system"}
// USDC on Base (6 decimals)
String USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";

// ERC-20 transfer(address,uint256) selector = 0xa9059cbb
// Encode: 32-byte padded address + 32-byte padded amount (10 USDC = 10_000_000)
String transferData = "0xa9059cbb"
    + String.format("%064x", new java.math.BigInteger(recipientAddress.substring(2), 16))
    + String.format("%064x", java.math.BigInteger.valueOf(10_000_000));

SignedSponsoredTransaction signed = client.signSponsoredTransaction(
    SignSponsoredTransactionOpts.builder()
        .walletProperties(walletProperties)
        .externalServerKeyShares(externalServerKeyShares)
        .chainId(8453L)
        .rpcUrl(System.getenv("BASE_RPC_URL"))
        .calls(List.of(new SponsoredCall(USDC_BASE, "0", transferData)))
        .build()
).join();

String txHash = client.sendSponsoredTransaction(signed).join();
```

### Batching multiple calls

A single sponsored transaction can carry several calls, executed in order within one on-chain transaction. This is useful for paying multiple recipients at once:

```java theme={"system"}
List<SponsoredCall> payouts = List.of(
    new SponsoredCall("0x1111111111111111111111111111111111111111", "0", transferData1),
    new SponsoredCall("0x2222222222222222222222222222222222222222", "0", transferData2)
);

SignedSponsoredTransaction signed = client.signSponsoredTransaction(
    SignSponsoredTransactionOpts.builder()
        .walletProperties(walletProperties)
        .externalServerKeyShares(externalServerKeyShares)
        .chainId(8453L)
        .rpcUrl(System.getenv("BASE_RPC_URL"))
        .calls(payouts)
        .build()
).join();

String txHash = client.sendSponsoredTransaction(signed).join();
```

### Relaying on behalf of an end user

By default the transaction is relayed as the authenticated service user. To relay on behalf of one of your end users (the wallet's owner), pass their `userId`:

```java theme={"system"}
String txHash = client.sendSponsoredTransaction(signed, endUserId,
    EvmGaslessConstants.EVM_GASLESS_DEFAULT_POLL_INTERVAL_MS,
    EvmGaslessConstants.EVM_GASLESS_DEFAULT_TIMEOUT_MS
).join();
```

`userId` works the same way on `relaySponsoredTransaction`.

***

## Advanced usage

Everything below is optional. Reach for it only when you need more control than the quick start gives you: pre-signing, custom polling, or managing the one-time delegation step yourself.

### How it works under the hood

When you call `signSponsoredTransaction` followed by `sendSponsoredTransaction`, the SDK:

1. Builds an EIP-712 intent describing the batch of calls and a deadline
2. Signs the intent with the user's embedded-wallet shares (and, if delegation is needed, an EIP-7702 authorization for the wallet's EOA)
3. Relays the signed intent to Dynamic's sponsorship backend and gets back a `requestId`
4. Polls the relayer until the request reaches a terminal state, then returns the on-chain transaction hash

EIP-7702 delegation is what lets a relayer submit a batch of calls on the user's behalf. The user's EOA is delegated, once, to a Dynamic-operated relayer contract (`0x0000Fb7702036ff9f76044a501ac1aA74cbab16b`).

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

### Splitting sign and relay

Reuse a pre-signed intent (for example, to sign in one process and relay from another) by calling `signSponsoredTransaction` first and passing the result to `sendSponsoredTransaction`:

```java theme={"system"}
SignedSponsoredTransaction signed = client.signSponsoredTransaction(
    SignSponsoredTransactionOpts.builder()
        .walletProperties(walletProperties)
        .externalServerKeyShares(externalServerKeyShares)
        .chainId(8453L)
        .rpcUrl(System.getenv("BASE_RPC_URL"))
        .calls(calls)
        .build()
).join();

// The signed intent is JSON-serializable — persist or transfer it here.

String txHash = client.sendSponsoredTransaction(signed).join();
```

`SignedSponsoredTransaction` is a Java record containing `walletAddress`, `calls`, `chainId`, `deadline`, `nonce`, `relayer`, `signature`, and an optional `authorization`. It can be serialized to JSON for cross-process transport.

By default the signed intent is valid for 10 minutes. Override with `validForSeconds`:

```java theme={"system"}
SignedSponsoredTransaction signed = client.signSponsoredTransaction(
    SignSponsoredTransactionOpts.builder()
        .walletProperties(walletProperties)
        .externalServerKeyShares(externalServerKeyShares)
        .chainId(8453L)
        .rpcUrl(System.getenv("BASE_RPC_URL"))
        .calls(calls)
        .validForSeconds(60)
        .build()
).join();
```

### Reusing a nonce

By default each signed intent gets a fresh single-use bitmap nonce. Pass `nonce` (a `BigInteger`) to reuse one across intents: sign several intents with the same nonce so at most one can ever land on-chain (cancel-replace):

```java theme={"system"}
import java.math.BigInteger;

SignedSponsoredTransaction signed = client.signSponsoredTransaction(
    SignSponsoredTransactionOpts.builder()
        .walletProperties(walletProperties)
        .externalServerKeyShares(externalServerKeyShares)
        .chainId(8453L)
        .rpcUrl(System.getenv("BASE_RPC_URL"))
        .calls(calls)
        .nonce(BigInteger.valueOf(42))
        .build()
).join();
```

### Managing EIP-7702 delegation yourself

Before a wallet can submit a sponsored transaction, its EOA must be delegated to the Dynamic relayer contract via an EIP-7702 authorization. The quick start handles this automatically on the first transaction (when you provide an `rpcUrl`). These helpers exist for when you want explicit control over the delegation step.

#### Checking delegation status

Use `is7702DelegationActive` to check whether delegation is already active for a wallet on a given chain. It reads the wallet's on-chain code, so it requires an `rpcUrl`:

```java theme={"system"}
boolean isActive = client.is7702DelegationActive(
    walletProperties.accountAddress(),
    8453L,
    System.getenv("BASE_RPC_URL")
).join();
```

#### Signing an authorization

`sign7702Authorization` signs an EIP-7702 authorization for the Dynamic delegation contract. Pass the result as `authorization` to `signSponsoredTransaction` to attach it to the next sponsored call. This is useful when you want to skip auto-delegation (e.g. you've set `autoDelegate(false)`, or you're signing without an `rpcUrl`):

```java theme={"system"}
import xyz.dynamic.waas.evm.Eip7702Authorization;
import xyz.dynamic.waas.evm.opts.Sign7702AuthorizationOpts;

Eip7702Authorization authorization = client.sign7702Authorization(
    Sign7702AuthorizationOpts.builder()
        .walletProperties(walletProperties)
        .externalServerKeyShares(externalServerKeyShares)
        .chainId(8453L)
        .rpcUrl(System.getenv("BASE_RPC_URL"))
        .build()
).join();

SignedSponsoredTransaction signed = client.signSponsoredTransaction(
    SignSponsoredTransactionOpts.builder()
        .walletProperties(walletProperties)
        .externalServerKeyShares(externalServerKeyShares)
        .chainId(8453L)
        .calls(calls)
        .authorization(authorization)
        .autoDelegate(false)
        .build()
).join();

String txHash = client.sendSponsoredTransaction(signed).join();
```

When `nonce` is omitted, `sign7702Authorization` fetches the EOA nonce from the chain via `rpcUrl`; pass an explicit `nonce` to sign without an RPC.

### Polling the relay status yourself

For custom progress reporting, call `relaySponsoredTransaction` (which returns a `requestId` immediately) and poll `getSponsoredTransactionStatus` yourself:

```java theme={"system"}
import xyz.dynamic.waas.evm.SponsorTransactionResult;
import xyz.dynamic.waas.evm.SponsoredTransactionStatusResult;

SponsorTransactionResult relay = client.relaySponsoredTransaction(signed).join();
String requestId = relay.requestId();

SponsoredTransactionStatusResult status =
    client.getSponsoredTransactionStatus(requestId).join();
```

`status` is one of:

| Status      | Meaning                                                  |
| ----------- | -------------------------------------------------------- |
| `pending`   | Accepted by the relayer, not yet broadcast to the chain. |
| `submitted` | Broadcast to the chain, waiting for confirmation.        |
| `success`   | Finalized successfully on-chain.                         |
| `failure`   | Terminal failure (`errorMessage` is populated).          |

`transactionHash` is set once the relay broadcasts the transaction.

For the common "wait until done" case, use `waitForSponsoredTransaction`, which polls every 2 seconds and resolves on `success` (timeout: 60 seconds):

```java theme={"system"}
import xyz.dynamic.waas.evm.opts.WaitForSponsoredTransactionOpts;
import java.time.Duration;

String txHash = client.waitForSponsoredTransaction(
    requestId,
    WaitForSponsoredTransactionOpts.builder().build()
).join();
```

Override the cadence with `pollInterval` and `timeout`:

```java theme={"system"}
String txHash = client.waitForSponsoredTransaction(
    requestId,
    WaitForSponsoredTransactionOpts.builder()
        .pollInterval(Duration.ofSeconds(1))
        .timeout(Duration.ofSeconds(120))
        .build()
).join();
```

### Error handling

These methods throw a standard `CompletionException` when sponsorship can't go through. There is **no silent fallback**. Wrap the call in a `try/catch` so you can surface a message and decide what to do next.

```java theme={"system"}
import java.util.concurrent.CompletionException;

try {
    String txHash = client.sendSponsoredTransaction(signed).join();
    System.out.println("Confirmed: " + txHash);
} catch (CompletionException ce) {
    // e.g. "EVM sponsored transaction relay failed" or
    //      "EVM sponsored transaction timed out waiting for terminal status"
    System.err.println("Sponsorship failed: " + ce.getCause().getMessage());
}
```

An error is thrown when:

* The sponsorship API rejects the request (sponsorship not enabled, chain not supported, or no relayer available)
* The relay reaches a terminal `failure` status
* `waitForSponsoredTransaction` times out (default 60 seconds) before reaching a terminal status
* `autoDelegate` is on but no `rpcUrl` (and no pre-signed `authorization`) was supplied

### Limitations

| Limitation      | Details                                                                                       |
| --------------- | --------------------------------------------------------------------------------------------- |
| Wallet type     | Embedded wallets only (V3 MPC)                                                                |
| Mechanism       | EIP-7702 delegation to the Dynamic relayer contract                                           |
| Intent validity | 10 minutes by default; configurable via `validForSeconds`                                     |
| Polling timeout | `waitForSponsoredTransaction` resolves or throws within 60 seconds by default                 |
| Batching        | One signed intent per `sendSponsoredTransaction` call; each intent can contain multiple calls |

## Related

* [Send EVM transactions](/java/evm/send-transaction) - Sign and broadcast a regular (non-sponsored) EVM transaction
* [Sign typed data (EIP-712)](/java/evm/sign-typed-data) - Sign EIP-712 typed data
* [Storage Best Practices](/java/storage-best-practices) - Where to cache WalletProperties vs. vault shares
* [Delegated access](/java/evm/delegated-access) - Sign on behalf of users with their permission
