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

# Gasless

> Swap and bridge on EVM without holding native gas tokens

Gasless lets a user trade with zero native balance: they sign an authorization off-chain, DZap submits the transaction and pays gas, then recovers the gas cost from the trade output in the source token. Gasless is EVM-only and the source token must be an ERC-20 that supports EIP-2612 permits (or use allowance/Permit2 mode). Pick Swap or Bridge below; the tabs stay in sync across Setup, Steps, End-to-end, and API usage.

<Note>
  Nothing here gives DZap open-ended access to funds. The intent signature is bound to a single `txId`, carries a deadline, and is consumed by a per-user nonce, so it cannot be replayed.
</Note>

## Setup

<Snippet file="install-sdk.mdx" />

<Tabs>
  <Tab title="Swap">
    A gasless same-chain swap on Arbitrum: 5 USDC to WETH, with 0 ETH in the wallet.

    ```ts theme={null}
    import { DZapClient, PermitTypes, Services, TxnStatus } from '@dzapio/sdk';
    import { createWalletClient, http } from 'viem';
    import { arbitrum } from 'viem/chains';
    import { privateKeyToAccount } from 'viem/accounts';

    const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
    const walletClient = createWalletClient({ account, chain: arbitrum, transport: http() });

    // An API key is recommended: the gasless and build endpoints are rate-limited per IP without one.
    const dzap = DZapClient.getInstance(process.env.DZAP_API_KEY);

    const USDC = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831';
    const WETH = '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1';
    const AMOUNT = '5000000';                             // 5 USDC (6 decimals), must clear the $1 minimum
    const fromChain = 42161;
    const toChain = 42161;                                // same chain as fromChain = swap
    ```
  </Tab>

  <Tab title="Bridge">
    A gasless cross-chain bridge: 5 USDC from Arbitrum to Base, with 0 ETH in the wallet.

    ```ts theme={null}
    import { DZapClient, PermitTypes, Services, TxnStatus } from '@dzapio/sdk';
    import { createWalletClient, http } from 'viem';
    import { arbitrum } from 'viem/chains';
    import { privateKeyToAccount } from 'viem/accounts';

    const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
    const walletClient = createWalletClient({ account, chain: arbitrum, transport: http() });

    // An API key is recommended: the gasless and build endpoints are rate-limited per IP without one.
    const dzap = DZapClient.getInstance(process.env.DZAP_API_KEY);

    const USDC_ARB = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831';
    const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
    const AMOUNT = '5000000';                             // 5 USDC (6 decimals), must clear the $1 minimum
    const fromChain = 42161;
    const toChain = 8453;                                 // Base, different chain = bridge
    ```
  </Tab>
</Tabs>

## Steps

The gasless path is quote, sign a permit, execute, and confirm. `tradeGasless()` does the whole tail in one call: it builds the transaction, collects the intent signature from the wallet, and submits it to DZap. These steps use the **EIP-2612 permit** mode, the one that works for a wallet with zero native balance. Allowance and Permit2 are in [Authorization modes](#authorization-modes).

<Tabs>
  <Tab title="Swap">
    <Steps>
      <Step title="Quote">
        Pass `gasless: true`. The returned `destAmount` is already net of the relayer's gas.

        ```ts theme={null}
        const quotes = await dzap.getTradeQuotes({
          fromChain,
          account: account.address,
          gasless: true,
          data: [{
            srcToken: USDC,
            destToken: WETH,
            amount: AMOUNT,
            srcDecimals: 6,
            destDecimals: 18,
            toChain,               // same chain as fromChain = swap
            slippage: 1,           // 1 = 1%
          }],
        });

        const pair = quotes[Object.keys(quotes)[0]];
        if (pair.status === 'error') throw new Error(pair.message); // e.g. below the $1 minimum

        const source = pair.recommendedSource ?? pair.bestReturnSource;
        const route = pair.quoteRates![source];
        console.log('you receive:', route.destAmount);                // net of the gasless fee
        console.log('estimated fee:', route.fee.protocolFee[0]?.amountUSD);
        ```

        <Note>
          Confirm the source token supports EIP-2612 permits before offering the zero-gas path: quotes don't check this. See [Fees & limits](#fees-and-limits) for the `$1` per-pair minimum and the [End-to-end](#end-to-end) tab for the pre-flight check.
        </Note>
      </Step>

      <Step title="Sign the permit">
        Sign an EIP-2612 permit for the source token. This is off-chain, so it costs zero gas.

        ```ts theme={null}
        const permit = await dzap.sign({
          chainId: fromChain,
          sender: account.address,
          signer: walletClient,
          service: Services.trade,
          permitType: PermitTypes.EIP2612Permit,
          tokens: [{ address: USDC, amount: AMOUNT }],
        });

        if (permit.status !== TxnStatus.success || !('tokens' in permit)) {
          throw new Error(`permit signing failed: ${permit.code}`);
        }
        const permitData = permit.tokens[0].permitData;
        ```

        <Note>
          Signing an EIP-2612 permit for a token that does not implement it throws. If `permit.eip2612.supported` is false for your token, use allowance or Permit2 mode from [Authorization modes](#authorization-modes).
        </Note>
      </Step>

      <Step title="Execute">
        Feed the permit back in as `permitData` and call `tradeGasless()`. It builds the transaction, prompts the user to sign the intent, and submits it to DZap.

        ```ts theme={null}
        const request = {
          fromChain,
          sender: account.address,
          refundee: account.address,     // receives funds if the trade is refunded
          gasless: true,
          data: [{
            srcToken: USDC,
            destToken: WETH,
            amount: AMOUNT,
            srcDecimals: 6,
            destDecimals: 18,
            toChain,
            protocol: source,            // provider ID from the quote
            recipient: account.address,
            slippage: 1,
            additionalInfo: route.additionalInfo,
            permitData,
          }],
        };

        const result = await dzap.tradeGasless({
          request,
          signer: walletClient,
          txnStatusCallback: (status) => console.log('gasless status:', status),
        });
        if (result.status !== TxnStatus.success) throw new Error(result.errorMsg);
        console.log('submitted by DZap:', result.txnHash);
        ```

        <Note>
          DZap pays gas and deducts the equivalent from the output in the source token. Call `buildTradeTxn({ ...request })` first only to preview the authoritative `fees.executorFee`. Full request shape: [Execute trade](/sdk/trade/execute-trade).
        </Note>
      </Step>

      <Step title="Status">
        `tradeGasless()` resolves with the hash DZap submitted. Track it like a regular trade.

        ```ts theme={null}
        const status = await dzap.getTradeTxnStatus({ txHash: result.txnHash!, chainId: fromChain });
        console.log(status.status);   // COMPLETED once settled
        ```

        <Note>
          `status.gasless` is `true`. Same-chain gasless swaps settle fast; terminal states are `COMPLETED`, `FAILED`, `PARTIAL`, and `REFUNDED`. See [Track trade status](/sdk/trade/status-tracking).
        </Note>
      </Step>
    </Steps>
  </Tab>

  <Tab title="Bridge">
    <Steps>
      <Step title="Quote">
        Pass `gasless: true` and set `toChain` to a different chain. The returned `destAmount` is already net of the relayer's gas.

        ```ts theme={null}
        const quotes = await dzap.getTradeQuotes({
          fromChain,
          account: account.address,
          gasless: true,
          data: [{
            srcToken: USDC_ARB,
            destToken: USDC_BASE,
            amount: AMOUNT,
            srcDecimals: 6,
            destDecimals: 6,
            toChain,               // Base, different chain = bridge
            slippage: 1,           // 1 = 1%
          }],
        });

        const pair = quotes[Object.keys(quotes)[0]];
        if (pair.status === 'error') throw new Error(pair.message); // e.g. below the $1 minimum

        const source = pair.recommendedSource ?? pair.bestReturnSource;
        const route = pair.quoteRates![source];
        console.log('you receive:', route.destAmount);                // net of the gasless fee
        console.log('estimated fee:', route.fee.protocolFee[0]?.amountUSD);
        ```

        <Note>
          Gasless applies to the source chain only: the source token must support EIP-2612 permits on `fromChain` (or use allowance/Permit2 mode). See [Fees & limits](#fees-and-limits) for the `$1` per-pair minimum.
        </Note>
      </Step>

      <Step title="Sign the permit">
        Sign an EIP-2612 permit for the source token on the source chain. This is off-chain, so it costs zero gas.

        ```ts theme={null}
        const permit = await dzap.sign({
          chainId: fromChain,
          sender: account.address,
          signer: walletClient,
          service: Services.trade,
          permitType: PermitTypes.EIP2612Permit,
          tokens: [{ address: USDC_ARB, amount: AMOUNT }],
        });

        if (permit.status !== TxnStatus.success || !('tokens' in permit)) {
          throw new Error(`permit signing failed: ${permit.code}`);
        }
        const permitData = permit.tokens[0].permitData;
        ```

        <Note>
          Signing an EIP-2612 permit for a token that does not implement it throws. If `permit.eip2612.supported` is false for your token, use allowance or Permit2 mode from [Authorization modes](#authorization-modes).
        </Note>
      </Step>

      <Step title="Execute">
        Feed the permit back in as `permitData` and call `tradeGasless()`. It builds the transaction, prompts the user to sign the intent, and submits it to DZap.

        ```ts theme={null}
        const request = {
          fromChain,
          sender: account.address,
          refundee: account.address,     // funds return here if the destination leg fails
          gasless: true,
          data: [{
            srcToken: USDC_ARB,
            destToken: USDC_BASE,
            amount: AMOUNT,
            srcDecimals: 6,
            destDecimals: 6,
            toChain,
            protocol: source,            // provider ID from the quote
            recipient: account.address,  // can be a different address on Base
            slippage: 1,
            additionalInfo: route.additionalInfo,
            permitData,
          }],
        };

        const result = await dzap.tradeGasless({
          request,
          signer: walletClient,
          txnStatusCallback: (status) => console.log('gasless status:', status),
        });
        if (result.status !== TxnStatus.success) throw new Error(result.errorMsg);
        console.log('submitted by DZap:', result.txnHash);
        ```

        <Note>
          DZap pays gas on the source chain and deducts the equivalent from the source token. Use a `refundee` you control on the source chain. Full request shape: [Execute trade](/sdk/trade/execute-trade).
        </Note>
      </Step>

      <Step title="Status">
        Cross-chain settlement is asynchronous, so poll until the status reaches a terminal state.

        ```ts theme={null}
        const status = await dzap.getTradeTxnStatus({ txHash: result.txnHash!, chainId: fromChain });
        console.log(status.status);   // PENDING to COMPLETED (or FAILED / PARTIAL / REFUNDED)
        ```

        <Note>
          For gasless bridges the status covers both legs. Poll until it leaves `PENDING`; the [End-to-end](#end-to-end) tab shows a ready-made loop. See [Track trade status](/sdk/trade/status-tracking).
        </Note>
      </Step>
    </Steps>
  </Tab>
</Tabs>

## End-to-end

<Tabs>
  <Tab title="Swap">
    ```ts theme={null}
    import { DZapClient, PermitTypes, Services, TxnStatus, checkEIP2612PermitSupport } from '@dzapio/sdk';
    import { createWalletClient, http } from 'viem';
    import { arbitrum } from 'viem/chains';
    import { privateKeyToAccount } from 'viem/accounts';

    const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
    const walletClient = createWalletClient({ account, chain: arbitrum, transport: http() });
    const dzap = DZapClient.getInstance(process.env.DZAP_API_KEY);

    const USDC = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831';
    const WETH = '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1';
    const AMOUNT = '5000000'; // 5 USDC, must clear the $1 minimum
    const fromChain = 42161;
    const toChain = 42161;

    // 1. Confirm the source token supports EIP-2612 permits (the zero-gas path needs it)
    const { supportsPermit } = await checkEIP2612PermitSupport({ chainId: fromChain, address: USDC, owner: account.address });
    if (!supportsPermit) {
      throw new Error('token has no EIP-2612 permit; use allowance/Permit2 mode, or trade() normally');
    }

    // 2. Quote (gasless)
    const quotes = await dzap.getTradeQuotes({
      fromChain,
      account: account.address,
      gasless: true,
      data: [{ srcToken: USDC, destToken: WETH, amount: AMOUNT, srcDecimals: 6, destDecimals: 18, toChain, slippage: 1 }],
    });
    const pair = quotes[Object.keys(quotes)[0]];
    if (pair.status === 'error') throw new Error(pair.message);
    const source = pair.recommendedSource ?? pair.bestReturnSource;
    const route = pair.quoteRates![source];

    // 3. Sign the EIP-2612 permit (off-chain, no gas)
    const permit = await dzap.sign({
      chainId: fromChain,
      sender: account.address,
      signer: walletClient,
      service: Services.trade,
      permitType: PermitTypes.EIP2612Permit,
      tokens: [{ address: USDC, amount: AMOUNT }],
    });
    if (permit.status !== TxnStatus.success || !('tokens' in permit)) throw new Error(`permit failed: ${permit.code}`);

    // 4. Execute (builds + signs the intent + submits)
    const result = await dzap.tradeGasless({
      request: {
        fromChain,
        sender: account.address,
        refundee: account.address,
        gasless: true,
        data: [{ srcToken: USDC, destToken: WETH, amount: AMOUNT, srcDecimals: 6, destDecimals: 18, toChain, protocol: source, recipient: account.address, slippage: 1, additionalInfo: route.additionalInfo, permitData: permit.tokens[0].permitData }],
      },
      signer: walletClient,
    });
    if (result.status !== TxnStatus.success) throw new Error(result.errorMsg);

    // 5. Status
    const status = await dzap.getTradeTxnStatus({ txHash: result.txnHash!, chainId: fromChain });
    console.log('settled:', status.status);
    ```
  </Tab>

  <Tab title="Bridge">
    ```ts theme={null}
    import { DZapClient, PermitTypes, Services, TxnStatus, checkEIP2612PermitSupport } from '@dzapio/sdk';
    import { createWalletClient, http } from 'viem';
    import { arbitrum } from 'viem/chains';
    import { privateKeyToAccount } from 'viem/accounts';

    const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
    const walletClient = createWalletClient({ account, chain: arbitrum, transport: http() });
    const dzap = DZapClient.getInstance(process.env.DZAP_API_KEY);

    const USDC_ARB = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831';
    const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
    const AMOUNT = '5000000'; // 5 USDC, must clear the $1 minimum
    const fromChain = 42161;
    const toChain = 8453; // Base

    // 1. Confirm the source token supports EIP-2612 permits (the zero-gas path needs it)
    const { supportsPermit } = await checkEIP2612PermitSupport({ chainId: fromChain, address: USDC_ARB, owner: account.address });
    if (!supportsPermit) {
      throw new Error('token has no EIP-2612 permit; use allowance/Permit2 mode, or trade() normally');
    }

    // 2. Quote (gasless)
    const quotes = await dzap.getTradeQuotes({
      fromChain,
      account: account.address,
      gasless: true,
      data: [{ srcToken: USDC_ARB, destToken: USDC_BASE, amount: AMOUNT, srcDecimals: 6, destDecimals: 6, toChain, slippage: 1 }],
    });
    const pair = quotes[Object.keys(quotes)[0]];
    if (pair.status === 'error') throw new Error(pair.message);
    const source = pair.recommendedSource ?? pair.bestReturnSource;
    const route = pair.quoteRates![source];

    // 3. Sign the EIP-2612 permit (off-chain, no gas)
    const permit = await dzap.sign({
      chainId: fromChain,
      sender: account.address,
      signer: walletClient,
      service: Services.trade,
      permitType: PermitTypes.EIP2612Permit,
      tokens: [{ address: USDC_ARB, amount: AMOUNT }],
    });
    if (permit.status !== TxnStatus.success || !('tokens' in permit)) throw new Error(`permit failed: ${permit.code}`);

    // 4. Execute (builds + signs the intent + submits)
    const result = await dzap.tradeGasless({
      request: {
        fromChain,
        sender: account.address,
        refundee: account.address,
        gasless: true,
        data: [{ srcToken: USDC_ARB, destToken: USDC_BASE, amount: AMOUNT, srcDecimals: 6, destDecimals: 6, toChain, protocol: source, recipient: account.address, slippage: 1, additionalInfo: route.additionalInfo, permitData: permit.tokens[0].permitData }],
      },
      signer: walletClient,
    });
    if (result.status !== TxnStatus.success) throw new Error(result.errorMsg);

    // 5. Poll until settled (cross-chain is async)
    for (let i = 0; i < 60; i++) {
      const status = await dzap.getTradeTxnStatus({ txHash: result.txnHash!, chainId: fromChain });
      if (['COMPLETED', 'PARTIAL', 'FAILED', 'REFUNDED'].includes(status.status)) {
        console.log('settled:', status.status);
        break;
      }
      await new Promise((r) => setTimeout(r, 5000));
    }
    ```
  </Tab>
</Tabs>

## API usage

The REST flow is the same sequence `tradeGasless()` performs. Unlike a regular trade, the gasless build returns a set of hashes you must sign as an EIP-712 intent, so the sign step is client-side code, not a curl.

<Tabs>
  <Tab title="Swap">
    <Steps>
      <Step title="Quote">
        Send `gasless: true`. Pick a route's `recommendedSource`.

        ```bash theme={null}
        curl -X POST https://api.dzap.io/v1/quotes \
          -H "Content-Type: application/json" \
          -H "x-api-key: $DZAP_API_KEY" \
          -d '{
            "fromChain": 42161,
            "gasless": true,
            "data": [{
              "amount": "5000000",
              "srcToken": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
              "destToken": "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
              "srcDecimals": 6,
              "destDecimals": 18,
              "toChain": 42161,
              "slippage": 1
            }],
            "account": "0xUser"
          }'
        ```
      </Step>

      <Step title="Build">
        The gasless build returns the hashes to sign, not raw calldata. `fees.executorFee` is the authoritative fee.

        ```bash theme={null}
        curl -X POST https://api.dzap.io/v1/buildTx \
          -H "Content-Type: application/json" \
          -H "x-api-key: $DZAP_API_KEY" \
          -d '{
            "sender": "0xUser",
            "refundee": "0xUser",
            "fromChain": 42161,
            "gasless": true,
            "data": [{
              "amount": "5000000",
              "srcToken": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
              "destToken": "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
              "srcDecimals": 6,
              "destDecimals": 18,
              "toChain": 42161,
              "protocol": "<recommendedSource from quote>",
              "recipient": "0xUser",
              "slippage": 1
            }]
          }'
        ```

        ```json Build response (trimmed) theme={null}
        {
          "status": "success",
          "txId": "0x...",
          "gasless": true,
          "transaction": {
            "txType": "swap",
            "executorFeesHash": "0x...",
            "swapDataHash": "0x...",
            "value": "0"
          },
          "fees": { "executorFee": [{ "symbol": "USDC", "amount": "76476", "amountUSD": "0.07656", "included": true }] }
        }
        ```
      </Step>

      <Step title="Sign the intent (EIP-712)">
        Read the user's nonce from the router, then sign a `SignedGasLessSwapData` intent over the hashes from the build response.

        ```ts theme={null}
        import { createPublicClient, http, keccak256, toBytes } from 'viem';
        import { arbitrum } from 'viem/chains';

        // Router address for the chain: GET /v1/chains -> contracts.router
        const chains = await fetch('https://api.dzap.io/v1/chains').then((r) => r.json());
        const router = chains.find((c) => c.chainId === 42161).contracts.router;

        const nonce = await createPublicClient({ chain: arbitrum, transport: http() }).readContract({
          address: router,
          abi: [{ name: 'getNonce', type: 'function', stateMutability: 'view', inputs: [{ name: 'user', type: 'address' }], outputs: [{ type: 'uint256' }] }],
          functionName: 'getNonce',
          args: [account.address],
        });

        const deadline = BigInt(Math.floor(Date.now() / 1000) + 1800); // 30 min, matches SignatureExpiryInSecs

        const signature = await walletClient.signTypedData({
          account,
          domain: {
            name: 'DZapVerifier',
            version: '1',
            chainId: 42161,
            verifyingContract: router,
            salt: keccak256(toBytes('DZap-v0.1')),
          },
          types: {
            SignedGasLessSwapData: [
              { name: 'txId', type: 'bytes32' },
              { name: 'user', type: 'address' },
              { name: 'nonce', type: 'uint256' },
              { name: 'deadline', type: 'uint256' },
              { name: 'executorFeesHash', type: 'bytes32' },
              { name: 'swapDataHash', type: 'bytes32' },
            ],
          },
          primaryType: 'SignedGasLessSwapData',
          // txId, executorFeesHash, swapDataHash come from the build response
          message: { txId, user: account.address, nonce, deadline, executorFeesHash, swapDataHash },
        });
        ```
      </Step>

      <Step title="Execute">
        Submit the signed intent. The `permit` object carries the token permit plus the intent signature, nonce, and deadline.

        ```bash theme={null}
        curl -X POST https://api.dzap.io/v1/gasless/executeTx \
          -H "Content-Type: application/json" \
          -H "x-api-key: $DZAP_API_KEY" \
          -d '{
            "chainId": 42161,
            "txId": "0x...",
            "permit": {
              "permitData": [{ "token": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "amount": "5000000", "permit": "0x..." }],
              "gaslessIntentSignature": "0x...",
              "gaslessIntentNonce": "3",
              "gaslessIntentDeadline": "1753600000"
            }
          }'
        ```

        The response carries `status` and the `txnHash` DZap submitted.
      </Step>

      <Step title="Status">
        ```bash theme={null}
        curl "https://api.dzap.io/v1/status?txHash=0x...&chainId=42161"
        ```
      </Step>
    </Steps>
  </Tab>

  <Tab title="Bridge">
    <Steps>
      <Step title="Quote">
        Send `gasless: true` with a different `toChain`. Pick a route's `recommendedSource`.

        ```bash theme={null}
        curl -X POST https://api.dzap.io/v1/quotes \
          -H "Content-Type: application/json" \
          -H "x-api-key: $DZAP_API_KEY" \
          -d '{
            "fromChain": 42161,
            "gasless": true,
            "data": [{
              "amount": "5000000",
              "srcToken": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
              "destToken": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
              "srcDecimals": 6,
              "destDecimals": 6,
              "toChain": 8453,
              "slippage": 1
            }],
            "account": "0xUser"
          }'
        ```
      </Step>

      <Step title="Build">
        The gasless build returns the hashes to sign, not raw calldata. For a bridge, the intent carries `adapterDataHash` instead of `swapDataHash`.

        ```bash theme={null}
        curl -X POST https://api.dzap.io/v1/buildTx \
          -H "Content-Type: application/json" \
          -H "x-api-key: $DZAP_API_KEY" \
          -d '{
            "sender": "0xUser",
            "refundee": "0xUser",
            "fromChain": 42161,
            "gasless": true,
            "data": [{
              "amount": "5000000",
              "srcToken": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
              "destToken": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
              "srcDecimals": 6,
              "destDecimals": 6,
              "toChain": 8453,
              "protocol": "<recommendedSource from quote>",
              "recipient": "0xUser",
              "slippage": 1
            }]
          }'
        ```

        ```json Build response (trimmed) theme={null}
        {
          "status": "success",
          "txId": "0x...",
          "gasless": true,
          "transaction": {
            "txType": "bridge",
            "executorFeesHash": "0x...",
            "adapterDataHash": "0x...",
            "value": "0"
          },
          "fees": { "executorFee": [{ "symbol": "USDC", "amount": "76476", "amountUSD": "0.07656", "included": true }] }
        }
        ```
      </Step>

      <Step title="Sign the intent (EIP-712)">
        Read the user's nonce from the router, then sign a `SignedGasLessBridgeData` intent over the hashes from the build response.

        ```ts theme={null}
        import { createPublicClient, http, keccak256, toBytes } from 'viem';
        import { arbitrum } from 'viem/chains';

        // Router address for the source chain: GET /v1/chains -> contracts.router
        const chains = await fetch('https://api.dzap.io/v1/chains').then((r) => r.json());
        const router = chains.find((c) => c.chainId === 42161).contracts.router;

        const nonce = await createPublicClient({ chain: arbitrum, transport: http() }).readContract({
          address: router,
          abi: [{ name: 'getNonce', type: 'function', stateMutability: 'view', inputs: [{ name: 'user', type: 'address' }], outputs: [{ type: 'uint256' }] }],
          functionName: 'getNonce',
          args: [account.address],
        });

        const deadline = BigInt(Math.floor(Date.now() / 1000) + 1800); // 30 min, matches SignatureExpiryInSecs

        const signature = await walletClient.signTypedData({
          account,
          domain: {
            name: 'DZapVerifier',
            version: '1',
            chainId: 42161,
            verifyingContract: router,
            salt: keccak256(toBytes('DZap-v0.1')),
          },
          types: {
            SignedGasLessBridgeData: [
              { name: 'txId', type: 'bytes32' },
              { name: 'user', type: 'address' },
              { name: 'nonce', type: 'uint256' },
              { name: 'deadline', type: 'uint256' },
              { name: 'executorFeesHash', type: 'bytes32' },
              { name: 'adapterDataHash', type: 'bytes32' },
            ],
          },
          primaryType: 'SignedGasLessBridgeData',
          // txId, executorFeesHash, adapterDataHash come from the build response
          message: { txId, user: account.address, nonce, deadline, executorFeesHash, adapterDataHash },
        });
        ```
      </Step>

      <Step title="Execute">
        Submit the signed intent. The `permit` object carries the token permit plus the intent signature, nonce, and deadline.

        ```bash theme={null}
        curl -X POST https://api.dzap.io/v1/gasless/executeTx \
          -H "Content-Type: application/json" \
          -H "x-api-key: $DZAP_API_KEY" \
          -d '{
            "chainId": 42161,
            "txId": "0x...",
            "permit": {
              "permitData": [{ "token": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "amount": "5000000", "permit": "0x..." }],
              "gaslessIntentSignature": "0x...",
              "gaslessIntentNonce": "3",
              "gaslessIntentDeadline": "1753600000"
            }
          }'
        ```

        The response carries `status` and the `txnHash` DZap submitted.
      </Step>

      <Step title="Status">
        Cross-chain settlement is asynchronous, so poll until the status leaves `PENDING`.

        ```bash theme={null}
        curl "https://api.dzap.io/v1/status?txHash=0x...&chainId=42161"
        ```
      </Step>
    </Steps>
  </Tab>
</Tabs>

## Fees and limits

The gasless fee is charged in the **source token** and deducted from what the user receives. The quote returns an estimate; the build returns the authoritative number.

| Response           | Field              | Accuracy                                                  |
| ------------------ | ------------------ | --------------------------------------------------------- |
| `POST /v1/quotes`  | `fee.protocolFee`  | **Estimate.** Good to preview, may move before build.     |
| `POST /v1/buildTx` | `fees.executorFee` | **Authoritative.** This is what will actually be charged. |

Show the quote's `protocolFee` while the user is still choosing, and re-read `executorFee` from the build for the confirmation screen. Both carry the same shape:

```json Fee entry theme={null}
{
  "address": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
  "chainId": 42161,
  "symbol": "USDC",
  "decimals": 6,
  "amount": "76476",
  "amountUSD": "0.07656",
  "included": true
}
```

`amount` is in the source token's smallest unit; `amountUSD` is the same fee in dollars. `included: true` means it is already reflected in the quoted output, so do not subtract it again.

<Warning>
  Do not read the gasless fee from `fee.gasFee`. On a gasless quote that array is always empty: the estimate lives in `fee.protocolFee`, and the final number in the build's `fees.executorFee`.
</Warning>

**Minimum trade size.** The fee has to be covered by the trade, so the API enforces a **\$1 minimum per pair**. Below it, the pair returns an explicit error instead of routes. Branch on `status`/`message` rather than inferring the floor from an empty route list, the threshold is server-side and moves with gas prices.

```json theme={null}
{ "status": "error", "message": "Minimum $1 per pair required for gasless" }
```

**Source token must support EIP-2612 permits** for the zero-gas path. Quotes and builds don't enforce this, so verify it yourself before offering gasless:

```ts theme={null}
import { checkEIP2612PermitSupport } from '@dzapio/sdk';

const { supportsPermit } = await checkEIP2612PermitSupport({ chainId: 42161, address: srcToken, owner: userAddress });
```

<Note>
  If `supportsPermit` is false, the token can't use the EIP-2612 path, so fall back to allowance or Permit2 mode ([Authorization modes](#authorization-modes)). In a multi-token trade **every** `srcToken` must qualify, and the gasless fee is split across tokens proportionally to their USD value.
</Note>

**Gasless or regular?**

| Situation                                        | Recommendation                                                     |
| ------------------------------------------------ | ------------------------------------------------------------------ |
| User has no native token                         | **Gasless**, it is the only way to trade.                          |
| User wants to trade their entire token balance   | **Gasless**, no gas reserve needed.                                |
| User already holds native token and trades often | **Regular**, cheaper in aggregate.                                 |
| Small trade (a few dollars)                      | **Regular**, the gas fee dominates and gasless may be unavailable. |

## Authorization modes

DZap can only move the source token if it is authorized to. The steps above use **EIP-2612 permit**, the only mode that works for a wallet with zero native balance. The other two need one on-chain approval first, after which every gasless trade is a single intent signature.

| Mode                 | One-time on-chain tx?     | Per trade                      | Use when                                                                        |
| -------------------- | ------------------------- | ------------------------------ | ------------------------------------------------------------------------------- |
| **EIP-2612 permit**  | No                        | 2 signatures (permit + intent) | The token supports `permit` and the wallet may have zero native balance.        |
| **ERC-20 allowance** | Yes, approve the router   | 1 signature (intent)           | An allowance to the router already exists, or the user can afford one approval. |
| **Permit2**          | Yes, approve Permit2 once | 1 batch signature (intent)     | Trading many tokens or repeatedly; one approval covers all future trades.       |

For **allowance** mode, run `getAllowance` / `approve` with `ApprovalModes.Default` once, then call `tradeGasless()` with no `permitData`. For **Permit2**, approve with `ApprovalModes.PermitBatchWitnessTransferFrom`, then call `tradeGasless()` with `hasPermit2ApprovalForAllTokens: true`. Details: [Check allowance](/sdk/approvals/check-allowance) and [Approval mechanisms](/sdk/approval-mechanisms).

<Note>
  `sign()` takes a `PermitTypes` value (a signature scheme). `approve()` and `getAllowance()` take an `ApprovalModes` value (an on-chain approval target: `Default` is the router, anything else is Permit2). `ApprovalModes` has no `EIP2612Permit` member because an EIP-2612 permit is a signature and never approves anything on-chain.
</Note>

## Reference

### Endpoints

| Method | Path                    | Purpose                                                     |
| ------ | ----------------------- | ----------------------------------------------------------- |
| `POST` | `/v1/quotes`            | Quotes; send `gasless: true`                                |
| `POST` | `/v1/buildTx`           | Gasless build; send `gasless: true`, returns hashes to sign |
| `POST` | `/v1/gasless/executeTx` | Submit the signed intent for execution                      |
| `GET`  | `/v1/status`            | Status of the executed transaction                          |
| `GET`  | `/v1/chains`            | Chain config, including `contracts.router`                  |

Base URLs: `https://api.dzap.io` (production), `https://staging.dzap.io` (staging). Send your API key as the `x-api-key` header, see [Rate Limits](/api/rate-limits) for the unauthenticated caps.

### SDK methods

| Method                                                            | Returns                                                                  |
| ----------------------------------------------------------------- | ------------------------------------------------------------------------ |
| `checkEIP2612PermitSupport({ chainId, address, owner })`          | `{ supportsPermit: boolean }`, whether a token supports EIP-2612 permits |
| `getTradeQuotes({ gasless: true, ... })`                          | Quotes with gasless routes; fee estimate in `fee.protocolFee`            |
| `sign({ permitType: PermitTypes.EIP2612Permit, ... })`            | Tokens with `permitData` populated                                       |
| `tradeGasless({ request, signer, txnData?, txnStatusCallback? })` | `{ status, code, txnHash }`, builds + signs the intent + submits         |
| `getTradeTxnStatus({ txHash, chainId })`                          | Execution status, with `gasless: true`                                   |
