> ## Documentation Index
> Fetch the complete documentation index at: https://docs.li.fi/llms.txt
> Use this file to discover all available pages before exploring further.

# Gasless Transactions

> Let users swap and bridge without holding the source chain's native token, with LI.FI relaying the signed payload and paying the gas.

Gasless execution lets a user swap or bridge without holding the source chain's native token. Instead of sending a transaction, the user signs an EIP-712 payload. LI.FI relays that payload on-chain and pays the gas, then charges the relay cost as a fee in the input token and itemizes it in the quote.

This guide covers only what gasless adds on top of a standard integration. For the concepts it builds on, see [requesting routes and quotes](/introduction/user-flows-and-examples/requesting-route-fetching-quote), [quote versus route](/introduction/user-flows-and-examples/difference-between-quote-and-route), [status tracking](/introduction/user-flows-and-examples/status-tracking), and the [API reference](/api-reference/introduction) for authentication and rate limits.

***

## How it works

<Steps>
  <Step title="Get a signable step" icon="file-signature">
    Add `gasless=true` to a standard quote request, or set `options.gasless: true` on a routes request and post the chosen step for transaction data. Either path returns a step carrying an EIP-712 payload, an expiry, and a relay-fee entry in the cost estimate.
  </Step>

  <Step title="Sign" icon="pen">
    The user signs the typed data with `eth_signTypedData_v4`. No transaction leaves the user's account and no native token is required.
  </Step>

  <Step title="Relay" icon="paper-plane">
    Post the signed step to `POST /v1/advanced/relay`. LI.FI verifies the signature, validates the payload against the original quote, and broadcasts from a relayer account. The response carries a task ID.
  </Step>

  <Step title="Track" icon="magnifying-glass">
    Poll `GET /v1/status?taskId=...` until the transfer reaches `DONE` or `FAILED`.
  </Step>
</Steps>

The signed payload is a batch of calls executed through the user's own account under EIP-7702 delegation: the relay fee transfer, a token approval for ERC-20 input, and the swap or bridge call. The relayer can only submit the batch exactly as signed, and the batch executes all or nothing. If any call reverts, everything reverts, including the fee transfer.

***

## Requirements

| Requirement   | Detail                                                                                                                                                                                                      |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Source chain  | An EVM chain on which LI.FI currently relays the account's installed delegate. Availability is determined at quote time. The destination can be any chain [LI.FI supports](/introduction/chains).           |
| `fromAddress` | Required on every gasless request. The account's on-chain state decides how the payload is built.                                                                                                           |
| Account type  | An EOA already delegated via EIP-7702 to a delegate contract LI.FI relays for, currently [Calibur](https://github.com/Uniswap/calibur). Smart contract wallets that sign through ERC-1271 aren't supported. |

<Warning>
  An undelegated EOA can't currently be served. `/v1/quote` can return a generic no-quote error for this case. To retrieve the specific account-and-chain reason, request routes and inspect `unavailableRoutes.filteredOut[].reason`.
</Warning>

***

## Step 1: Get a signable step

Both standard quoting flows work unchanged. Gasless is a request flag.

Add `gasless=true` to a standard quote request, and the response is the signable step:

<CodeGroup>
  ```bash curl theme={"system"}
  curl --request GET \
    --url 'https://li.quest/v1/quote?fromChain=42161&toChain=42161&fromToken=0xaf88d065e77c8cC2239327C5EDb3A432268e5831&toToken=0x82aF49447D8a07e3bd95BD0d56f35241523fBab1&fromAmount=50000000&fromAddress=0xYOUR_USER_ADDRESS&integrator=YOUR_INTEGRATOR_NAME&gasless=true' \
    --header 'x-lifi-api-key: YOUR_API_KEY'
  ```

  ```ts TypeScript theme={"system"}
  const params = new URLSearchParams({
    fromChain: '42161',                                            // Arbitrum
    toChain: '42161',
    fromToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',       // USDC
    toToken: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1',         // WETH
    fromAmount: '50000000',                                        // 50 USDC (6 decimals)
    fromAddress: '0xYOUR_USER_ADDRESS',                            // required for gasless
    integrator: 'YOUR_INTEGRATOR_NAME',
    gasless: 'true',
  })

  const step = await fetch(`https://li.quest/v1/quote?${params}`, {
    headers: { 'x-lifi-api-key': API_KEY },
  }).then((res) => res.json())
  ```
</CodeGroup>

To pick from several routes instead, request routes with `options.gasless: true` in the standard `/v1/advanced/routes` body. Those routes already price in the relay fee but don't yet carry the signable payload. Post the chosen route's step, unmodified, to `/v1/advanced/stepTransaction`, and the response comes back in the same shape as the single-call quote.

<Note>
  Gasless isn't available on `/v1/quote/toAmount` or combined with `executionType=message` or `executionType=all`; those requests are rejected. `/v1/quote/contractCalls` does not support gasless execution and ignores the `gasless` field, so the request proceeds as a normal contract-call quote. Reverse quoting is rejected because its convergence loop only solves for percentage-based fees and can't solve for a flat, gas-denominated relay fee.
</Note>

### The signable step

A gasless step is a standard LI.FI step with two extra properties, plus the relay-fee entry in the cost estimate:

```json theme={"system"}
{
  "id": "9d5cbeeb-...",
  "type": "lifi",
  "tool": "sushiswap",
  "action": { /* as usual */ },
  "estimate": {
    "feeCosts": [
      { "name": "LIFI Gasless Relay Fee", "included": true /* ... */ }
    ]
    // ...
  },

  // The payload the user signs.
  "typedData": [
    {
      "primaryType": "SignedBatchedCall",
      "domain": {
        "name": "Calibur",
        "version": "1.0.0",
        "chainId": 42161,
        "verifyingContract": "0xYOUR_USER_ADDRESS",  // the user's own account
        "salt": "0x..."
      },
      "types": { /* EIP712Domain, SignedBatchedCall, BatchedCall, Call */ },
      "message": {
        "batchedCall": {
          "calls": [
            { "to": "0x...", "value": "0", "data": "0x..." },  // relay fee transfer
            { "to": "0x...", "value": "0", "data": "0x..." },  // approval (ERC-20 input only)
            { "to": "0x...", "value": "0", "data": "0x..." }   // the swap or bridge call
          ],
          "revertOnFailure": true
        },
        "nonce": "...",
        "keyHash": "0x00...00",
        "executor": "0x...",
        "deadline": 1765432100
      }
    }
  ],

  // Unix seconds after which the signature is no longer accepted.
  "expiresAt": 1765432100
}
```

* **`typedData`** is ready for `eth_signTypedData_v4`. Under EIP-7702 delegation the account executing the batch is the user's own address, which is why `domain.verifyingContract` is that address. The delegate implementation is bound through `domain.salt`.
* **`expiresAt`** is Unix seconds and equals the payload's `deadline`. The validity window is short, in minutes rather than hours. After expiry, request a fresh quote. Nothing is charged.
* **`transactionRequest`** isn't used here. Execution happens through the signed typed data and the relay endpoint.

<Warning>
  Submit the step to the relay exactly as received. LI.FI verifies it against a record stored at quote time, so any change to the calls, amounts, recipient, or deadline is rejected.
</Warning>

***

## Step 2: Sign the payload

Sign with standard EIP-712 typed-data signing. When passing the payload to a wallet over JSON-RPC, use it exactly as returned, because the types object includes the `EIP712Domain` entry that wallets validate.

<CodeGroup>
  ```ts viem theme={"system"}
  const [payload] = step.typedData

  const signature = await walletClient.signTypedData({
    account,
    domain: payload.domain,
    types: payload.types,
    primaryType: payload.primaryType,
    message: payload.message,
  })
  ```

  ```ts ethers v6 theme={"system"}
  const [payload] = step.typedData

  // ethers derives the domain type itself and rejects an explicit EIP712Domain entry.
  const { EIP712Domain, ...types } = payload.types

  const signature = await signer.signTypedData(payload.domain, types, payload.message)
  ```
</CodeGroup>

***

## Step 3: Submit the relay

Post the step exactly as received, with the signature added to the typed-data entry:

```ts TypeScript theme={"system"}
const relayBody = {
  ...step,
  typedData: [{ ...step.typedData[0], signature }],
}

const response = await fetch('https://li.quest/v1/advanced/relay', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-lifi-api-key': API_KEY,
  },
  body: JSON.stringify(relayBody),
})
```

On acceptance the endpoint responds `200`:

```json theme={"system"}
{
  "status": "ok",
  "data": {
    "taskId": "0x2f8a...c41d"
  }
}
```

Acceptance means LI.FI has verified the signed execution, recorded it durably, and the downstream executor accepted it for submission. Persist the returned task ID and use the status endpoint after acceptance. If the request fails before that acknowledgement, treat the outcome as ambiguous and follow the retry guidance below rather than assuming the payload was or was not accepted.

### Relay errors

Rejections use the standard LI.FI error response: the same body shape, the same numeric [error codes](/api-reference/error-codes), and the same HTTP statuses as every other endpoint. The status and code identify the resolution. The message explains which check failed, for your logs and for support.

| HTTP | `code` | Meaning                                                                                                                                                    | Resolution                                                                                                                                                                                                                                               |
| ---- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400  | 1011   | The signed payload can never execute. Verification failed on the signature, payload integrity, account state, or a dry run. Nothing was submitted.         | Request a fresh quote and have the user sign it. Resubmitting fails the same way.                                                                                                                                                                        |
| 404  | 1003   | The signed step can't be matched to a live quote-integrity record because its binding is missing, expired, or was consumed before acceptance.              | Request a fresh quote.                                                                                                                                                                                                                                   |
| 409  | 1007   | The nonce for this signed quote no longer matches on-chain state, or gas prices moved and the quoted fee no longer covers execution.                       | Request a fresh quote after state stabilizes, unless this follows an ambiguous `424`; in that case reconcile the original execution before submitting a replacement.                                                                                     |
| 422  | 1004   | The relay can't serve this request. Gasless relaying is switched off, or the account needs a capability the relay doesn't support.                         | Don't retry. Contact LI.FI.                                                                                                                                                                                                                              |
| 424  | 1008   | The relay couldn't confirm whether acceptance completed. The failure can happen before a durable record exists or after downstream submission has started. | Treat the outcome as ambiguous. Do not immediately sign or submit a different execution. Retry the exact request with backoff. A subsequent `404` means re-quote; a `409` can mean the original execution advanced, so reconcile it before replacing it. |

Failures reported before downstream submission do not broadcast a transaction or charge the user. A `424` is different: it can represent an ambiguous downstream outcome and must not be treated as proof that nothing was submitted.

***

## Step 4: Track the execution

Status tracking works as described in the [status tracking guide](/introduction/user-flows-and-examples/status-tracking), with two gasless specifics. The execution is addressable by task ID from the moment the relay is accepted, before any transaction exists. Once a transaction hash appears in the response, the same execution also resolves by hash.

```bash curl theme={"system"}
curl --request GET \
  --url 'https://li.quest/v1/status?taskId=0x2f8a...c41d' \
  --header 'x-lifi-api-key: YOUR_API_KEY'
```

Two states are specific to gasless:

| `status`  | `substatus`            | Meaning                                                                                                                     |
| --------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `PENDING` | none                   | Accepted. No transaction broadcast yet.                                                                                     |
| `FAILED`  | `EXPIRED`              | The signature deadline passed before broadcast. Nothing was spent or charged. Request a fresh quote.                        |
| `FAILED`  | `UNKNOWN_FAILED_ERROR` | The execution failed and `substatusMessage` carries the reason. A failed batch reverts atomically, so no fee was collected. |

Everything else follows the standard status semantics. The response attributes the transfer to the signing user's address rather than the relayer, so your accounting behaves the same as it does for self-submitted transactions.

***

## Fees

The relay fee covers the gas LI.FI spends executing on the user's behalf. It's a flat, gas-denominated amount derived from the chain's gas profile and the live gas price at quote time, then converted into the input token. The same value appears at `/v1/quote`, `/v1/advanced/routes`, and `/v1/advanced/stepTransaction`.

It arrives in the cost estimate under the stable name `LIFI Gasless Relay Fee` with `included: true`, which means it's deducted from `fromAmount` before routing and the quoted output already reflects it. The user's total spend is exactly `fromAmount`.

On-chain, the fee transfer is the first call of the signed batch. The signature covers it, so the relayer can't alter it, and it's atomic with the swap, so it can't be collected unless the swap executes. Rejected relays, expired signatures, and failed executions charge nothing.

<Note>
  The fee is fixed in the signed quote, but relay-time checks still reject it with `409` if gas prices move beyond the accepted tolerance. Request a fresh quote when that happens.
</Note>

***

## When gasless can't be served

On `/v1/quote`, a refusal comes back as the standard not-found error. On `/v1/advanced/routes` it appears in `unavailableRoutes.filteredOut`, where `reason` is a human-readable sentence explaining the specific refusal, following the same convention as every other route filter. Show it to the user or write it to your logs. Don't match on it programmatically.

Gasless requests are refused when:

* the account is an undelegated EOA, or delegates to a contract LI.FI doesn't relay for;
* the account is a smart contract wallet;
* the request carried no `fromAddress`;
* the route charges a fixed native-token fee on top of the input amount, which a gasless user can't fund. Other tools may still serve the same trade;
* the relay fee would not leave enough of the input amount behind, which is what puts a floor under trade size.

***

## Operational notes

<AccordionGroup>
  <Accordion title="Treat each signed quote as a separate execution" icon="list-ol">
    Distinct quotes use independent keyed nonces and can coexist for the same account. A `409` nonce conflict means the nonce for that signed quote no longer matches on-chain state, for example because the same execution was already consumed. Request a fresh quote rather than re-signing or modifying the old payload.
  </Accordion>

  <Accordion title="Quote at signing time" icon="clock">
    The signature deadline is short. Request the quote when the user is ready to sign, and relay straight after signing. Stale-fee and expired-quote rejections are routine, and neither costs anything, so refresh and retry.
  </Accordion>

  <Accordion title="Don't cache quotes" icon="ban">
    Every quote embeds account state and a short deadline. Treat it as an ephemeral signing artifact, honor `expiresAt`, and request it only when the user is ready to sign.
  </Accordion>

  <Accordion title="Retry the same payload, but handle state changes" icon="rotate">
    After a timeout or `424`, retry only the exact signed request with backoff; never create a replacement execution immediately. A retry can resume an existing pre-broadcast record, return `404` when quote integrity was consumed before acceptance, or return `409` after nonce or fee state changed. Re-quote after `404`; after `409`, first reconcile whether the original execution advanced. After a successful acknowledgement, persist the task ID and track status instead of replaying the payload.
  </Accordion>
</AccordionGroup>

***

## Next steps

<CardGroup cols={2}>
  <Card title="Gas Fronting" icon="gas-pump" href="/guides/gas-subsidy" horizontal>
    Deliver native gas alongside a bridged asset so users can transact on arrival.
  </Card>

  <Card title="Status tracking" icon="magnifying-glass" href="/introduction/user-flows-and-examples/status-tracking" horizontal>
    The full status and substatus vocabulary for a transfer.
  </Card>

  <Card title="Error codes" icon="triangle-exclamation" href="/api-reference/error-codes" horizontal>
    Every numeric code the API returns and what it means.
  </Card>

  <Card title="API reference" icon="code" href="/api-reference/introduction" horizontal>
    Authentication, rate limits, and the full endpoint surface.
  </Card>
</CardGroup>
