Skip to main content

Overview

Gasless execution lets a user trade when their native balance is zero. Instead of the user sending and paying for the transaction, they sign an authorization off-chain and DZap submits the transaction on their behalf, recovering the gas cost from the trade output in the source token.

No native token needed

A user holding only USDC on Arbitrum can trade it with 0 ETH in their wallet.

Trade the full balance

Nothing has to be held back to pay for gas, so 100% of a token can be swapped or bridged.

Same routes, same rates

Gasless uses the normal DZap aggregation. Only the fee line changes.

Works across chains

Available for both same-chain swaps and cross-chain bridges on supported EVM networks.

How a gasless trade works

1

Authorize the token

The user permits the DZap router to move the source token — by signing an EIP-2612 permit, by a one-time ERC-20 allowance, or by a one-time Permit2 approval.
2

Get a gasless quote

Quotes requested with gasless: true return routes whose destAmount is already net of the relayer’s gas. The fee itself is itemized at build time, not quote time — see Fees.
3

Sign the intent

The user signs an EIP-712 “user intent” that binds their address, the exact trade data, a nonce and a deadline. This signature is what authorizes DZap to execute — and only that trade.
4

DZap executes and settles the fee

DZap submits the transaction and pays gas. The gas equivalent is deducted from the output in the source token.
Nothing in this flow 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.

Fees

The gasless fee is charged in the source token and is deducted from what the user receives. Both the quote and the build return it, in different fields and at different accuracy: 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:
Fee entry
amount is in the source token’s smallest unit; amountUSD is the same fee priced in dollars. included: true means it is already reflected in the quoted output — do not subtract it again.
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.
For a multi-token trade, every srcToken in data[] must be gasless-listed — one unlisted source token makes the whole request ineligible. Check each against the list before requesting a gasless quote. The $1 minimum is applied per pair, not to the basket total.

Minimum trade size

The fee has to be covered by the trade itself, so the API enforces a $1 minimum per pair for gasless. Below it, the pair returns an explicit error instead of routes:
Branch on that status/message rather than inferring the floor from an empty route list — the threshold is server-side and can move with gas prices.

Gasless or regular?

Prerequisites

Gasless is enabled per chain and per token, and the constraint applies to the source side. Every srcToken in data[] must be on the gasless token list — if one is not, the request is not eligible for gasless. Destination tokens are unconstrained: you can trade into any token DZap routes to on the destination chain.In practice that means one constrained token selector, not two — filter the “from” list against the gasless list and leave the “to” list alone.Check the list before offering gasless. Do not infer support from the quote: /v1/quotes and /v1/buildTx still return gasless: true routes for an unlisted source token, so a failed lookup against the list is your only reliable pre-flight signal.
Native tokens (ETH, MATIC, …) cannot be traded gaslessly: moving native value requires the user to send the transaction, which is exactly what gasless avoids. The gasless list contains only ERC-20 tokens.
Gasless runs on DZap’s v2 EVM router. Solana, Bitcoin, Sui, Aptos, Ton and HyperLiquid trades are not gasless.
See Fees. Below it the pair returns a status: "error" with an explicit message.
The gasless and build endpoints are rate-limited per IP without a key, and the limits are low enough to hit while iterating — /v1/buildTx allows 5 requests per 60s unauthenticated. A 429 body is an error object, not a token map, so code that assumes the happy shape renders “no gasless tokens” and looks like an unsupported chain.Send your key as x-api-key and branch on 429 explicitly. See Rate Limits.

Step 1 — Check gasless support

Two endpoints expose the gasless support matrix.
GET /v1/token/gasless returns tokens grouped by chain ID, then keyed by checksummed token address. GET /v1/token/gasless/{chainId} returns just the inner map for one chain.
Response (trimmed)
The permit field tells you which authorization modes the token supports and therefore which gasless flow to use. balance and price are not populated by this endpoint — use getAllTokens / getTokenDetails for those.

With the SDK

Keys in the response are checksummed, and the lookup is a plain object index — a lowercased or uppercased key simply misses, which reads as “token not gasless”.Do not rely on the SDK’s formatToken to normalize here. It is a formatter, not a validator: it checksums lowercase input correctly, but returns uppercase input unchanged and non-address input unchanged, and never throws. Use viem’s getAddress(), or formatToken(addr.toLowerCase()) if you are already importing it.

Step 2 — Choose an authorization mode

DZap can only move the source token if it is authorized to. There are three ways to do that, and the choice determines whether the user needs gas now or not at all.
“Needs gas” applies only to the one-time setup transaction. Once the allowance or Permit2 approval exists, every subsequent gasless trade costs the user zero native token.
Why sign() and approve() take different enums. sign() takes a PermitTypes value — a signature scheme. approve() and getAllowance() take an ApprovalModes value — an on-chain approval target (Default → the router, anything else → Permit2).ApprovalModes deliberately has no EIP2612Permit member: an EIP-2612 permit is a signature and never approves anything on-chain, so there is nothing for approve() to do. The asymmetry is intentional, not a typo.

Step 3 — Get a gasless quote

Pass gasless: true to the quote request. Quotes are returned keyed by token pair ({srcChainId}_{srcToken}-{destChainId}_{destToken}), which getTokensPairKey builds for you.
route.fee.protocolFee is an estimate, fine for previewing while the user is still choosing. Re-read fees.executorFee from the build before the confirmation screen — that is the number that will actually be charged. route.fee.gasFee is always empty on gasless routes. See Fees.
An empty quoteRates does not mean “not gasless”. A source token that is not on the gasless list still returns gasless: true routes here — the API does not gate quotes on the list. The two cases you can actually distinguish are:
  • pair.status === 'error' with a message — below the $1 minimum.
  • Token absent from the gasless list (step 1) — check this yourself before offering gasless.
In both cases, fall back to trade() and let the user pay gas normally.
The $1 minimum applies per pair. In a multi-token request one pair can fail this check while the others quote normally — check status on each pair, not just the first.

Step 4 — Execute

tradeGasless() does the whole tail of the flow in one call: it builds the gasless transaction, collects the intent signature from the wallet, and submits it to DZap for execution. The mode is selected by what you pass in.
No on-chain setup. The user signs the token permit, then the intent — two wallet prompts, zero gas.
sign() returns the tokens array with permitData filled in per token. Check permit.eip2612.supported from step 1 first — signing an EIP-2612 permit for a token that does not implement it throws.

Driving a progress UI

txnStatusCallback receives TxnStatus values. Of the eight members, these are the ones a gasless trade emits, and they map directly onto what you should show: In EIP-2612 mode pendingWalletConfirmation fires twice — once for the token permit and once for the intent. Do not treat the second one as a stuck state.

Reusing a build response

tradeGasless() builds the transaction internally. If you already built it — to show a confirmation screen with the fee, for instance — pass it back as txnData to skip the rebuild:
A gasless build resolves to GaslessBaseParamsResponse:
status, gasless and onlySwapData are literal types, so a gasless build narrows cleanly — no cast needed. fees.executorFee is the authoritative fee described in Fees. What invalidates a build. The intent signature the build is bound to expires after SignatureExpiryInSecs1800 seconds (30 minutes), exported from the SDK. Rebuild if:
  • the deadline has passed, or
  • the user’s router nonce was consumed by another trade in the meantime, or
  • you are switching authorization mode — adding permitData after building changes the request, so the earlier build no longer matches. Rebuild on that path.
Reusing a build inside the window, with the same request and an unconsumed nonce, is safe.

Step 5 — Track the transaction

tradeGasless() resolves with the hash of the transaction DZap submitted. Track it exactly like a regular trade:
STATUS_RESPONSE is exported — compare against its members rather than hand-written strings. status.status is one of exactly five values:
There is no SUCCESS member. The completed state is COMPLETEDif (status.status === 'SUCCESS') is a branch that never fires.PARTIAL and REFUNDED are the states cross-chain bridges actually land in when the destination leg fails, so give them their own UI rather than folding them into a generic failure.
Do not confuse STATUS_RESPONSE with the separately exported STATUS constant — that one is an unrelated enum with lowercase members (pending, success, …) and will never match a trade status.
For cross-chain gasless bridges the status covers both legs. Poll until the status leaves PENDING.

Complete example

Every block above depends on identifiers from earlier steps. This one is self-contained — copy it whole. It covers the EIP-2612 path, the one that works for a wallet with zero native balance.
Prefer a running app? DZapIO/examples is a Vite + wagmi app with a working gasless trade wired to a real wallet:
  • src/components/trade-gasless-viem/ — the gasless trade component, using a viem WalletClient as the signer.
  • src/lib/trade.tsexecuteGaslessTradeWithGasFallback(), which falls back to a regular trade when gasless is unavailable.
  • vite.config.ts — the vite-plugin-node-polyfills setup the SDK needs in a browser bundle.

Advanced — the REST flow

Use this if you are not using the SDK. It is the same sequence tradeGasless() performs.
1

POST /v1/quotes

Body as in step 3, with "gasless": true. Pick a route.
2

POST /v1/buildTx

Body: the trade request with "gasless": true, the chosen protocol, sender, refundee, recipient. The gasless response is not raw calldata — it is the set of hashes you must sign:
fees.executorFee is the authoritative gasless fee — show this on the confirmation screen rather than the quote’s estimate.
3

Sign the user intent (EIP-712)

Read the user’s current nonce from the router. The router address for a chain comes from GET /v1/chainscontracts.router:
Domain
Primary type — chosen by what the build response contains:txId is bytes32, user is address, everything else is uint256/bytes32 as named. deadline is a unix timestamp. The SDK uses SignatureExpiryInSecs1800 seconds (30 minutes) from signing. Match it unless you have a reason not to; a shorter deadline is safer, a longer one widens the replay window.For the Permit2 mode you sign a PermitBatchWitnessTransferFrom instead, with the same fields carried as the witness, against the Permit2 domain.
4

POST /v1/gasless/executeTx

EIP-2612 or allowance mode:
Permit2 mode:
The response carries status and the txnHash DZap submitted. In allowance mode the per-token permit is the default empty permit — ABI-encoded (uint8 mode, bytes data) with mode = 0 (EIP-2612) and data = 0x.

Error handling

Reference

Endpoints

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 for the unauthenticated caps.

SDK methods

Types

Last modified on July 28, 2026