Integration Guide
Integrate the clearing house. One quote, one contract call.
Guide for engineering teams integrating ZeroDelta. Today the clearing house settles stablecoins across 8 chains; this is the surface you integrate now. A base integration is four calls (quote, approve, submitOrder, poll) with no proprietary SDK to learn. The same four calls clear stablecoins today and tokenized assets as coverage expands. Pre-req reading: Product Overview and Architecture.
Jump to reference
Quote routes, check status, and read supported coverage.
See submitOrder, cancellation, and on-chain behavior.
Before you start
The on-chain contracts and API use the internal name ZDLite (you will see it in error codes like
ZDLite__UnsupportedTokenand in theZDLITE_ABIpackage); ZeroDelta is the product. The two names refer to the same thing.
These come from the Glacis team during onboarding, not from this doc. Line them up first:
API key (
x-apikeyheader) and the base URL for your environment (dev/staging/production).The ABI/OpenAPI package (
ZDLITE_ABI, full request/response schemas).The environment scope your key is granted (mainnet small-amount and/or onboarding-provisioned staging; see Testing without mainnet funds).
Confirmed chain IDs, token addresses, and fee model for your routes (the live
/chains,/tokens,/routesendpoints are canonical).
What ZeroDelta is, and isn't
Is:
Single-call cross-chain stablecoin clearing.
Audited contracts on every supported chain.
A quote-and-submit interface with deterministic order tracking.
Isn't:
A bridge for arbitrary ERC-20s (supported stablecoins only).
An atomic same-block swap (settlement spans cross-chain bridge windows).
A public/permissionless solver network (today).
A replacement for general-purpose bridges.
Quick start
A minimal integration is four steps: quote → approve → submitOrder → poll status.
You interact with one smart-contract function (submitOrder) on the source chain and the ZeroDelta API for quoting and tracking. Base URL and an API key are provided during onboarding (see Operational details).
Two things to know before you write the submit code.
submitOrderis a state-changing transaction, soorderIdis not returned to your off-chain call; you parse it from theOrderSubmittedevent (shown in step 2). Andmsg.valuemust carry the bridge fee from the quote (gasCostSource), denominated in the source chain's native token (see step 2 for buffer and revert behavior).
1. Request a quote
Refetch the quote roughly every 10 seconds in any live interface to keep pricing current.
The /quote request requires { fromChainId, toChainId, fromToken, toToken, fromAmount } and accepts an optional owner — when you pass it, the response includes the source escrowAddress and executionChainId (used in the approve + submit steps). The response also carries finalAmount (what the receiver gets on the destination chain), fee and ETA fields, and the prepared orderRequest struct. A full annotated response is shown under Quote example.
You must set
owneranddestinationReceiveronorderRequestbefore submitting./quoteaccepts an optionalowner(passing it makes the response includeescrowAddress+executionChainId) but it does not acceptdestinationReceiver, and the preparedorderRequestreturns bothowneranddestinationReceiverasnullregardless. Fill both client-side with real, non-zero addresses before callingsubmitOrder; the contract reverts withZDLite__ZeroAddresson a zeroownerordestinationReceiver(see step 2).
Approve and submit
submitOrder pulls the bid token from the caller, so the user approves the escrow first (standard ERC-20 approve). msg.value must cover the bridge adapter's native messaging fee, returned in the quote as gasCostSource.
On msg.value / gasCostSource:
Unit.
gasCostSourceis the source chain's native messaging fee in wei, not the asset being moved. Always pass{ value: gasCostSource }from the quote.Per rail.
gasCostSourceis0for CCTP-source orders (USDC) and nonzero for LayerZero-source orders (USDT/USDe). The worked example below is a USDC source, so itsgasCostSourceis0; a USDT or USDe source returns a real native fee.Buffer (LayerZero-source only). When
gasCostSourceis nonzero, send it as quoted or slightly above; because the messaging fee can move between quote and inclusion, a small buffer (for example +10-20%) reduces the chance of an underpayment revert on volatile-gas chains. CCTP-source orders carry no source messaging fee, so no buffer is needed.Underpayment. If
msg.valueis below what the bridge adapter requires,submitOrderreverts and no order is created. There is noZDLite__error for this case: the revert comes from the bridge adapter itself and is adapter-specific (CCTP vs LayerZero), so do not match it by aZDLite__name. SendgasCostSource(plus the buffer above) to avoid it.Overpayment. Send
gasCostSourceas quoted (plus the small buffer above) and avoid large overpayments.
OrderRequest.ownerdoes not have to equalmsg.sender. An integrator can submit on a user's behalf: pull the bid token from the user through your own flow, then submit withownerset to the user's address. Set bothowneranddestinationReceiverto the correct, non-zero addresses before you submit — the contract reverts (ZDLite__ZeroAddress) if either is the zero address.
Track status
Poll the read API by source transaction hash (about every 10 seconds) until the order reaches a terminal state. Immediately after broadcast, before the source tx is mined and indexed, the API has not seen the hash yet and returns 404. Treat 404 as "not indexed yet" and keep polling; start with a short initial backoff (about 5–10 seconds) to let the source tx mine and the indexer catch up.
Status vocabulary — read this before you write the terminal check. The read API returns a lowercase status string, not the on-chain enum names (Filled / Cancelled); comparing status === 'Filled' never matches. The full, code-verified set:
pending
no
in_progress
no
outbound_bridging
no
cancel_pending
no
awaiting_claim
no
delivered
yes (success)
expired
yes
cancelled
yes
fill_failed
yes
delivery_failed
yes
failed
yes
Happy path: pending → in_progress → outbound_bridging → delivered (outbound_bridging appears only when the destination chain differs from the hub). The poller above stops only on a terminal status, so a newly added status can never trap the loop.
A response looks like:
statusis the lowercase lifecycle string in the table above.deliveredis the success terminal. Keep polling while the status is in the non-terminal set; stop on any terminal status.currentStepand thesteps[]array (stepName∈submitted/arrived/filled/delivered,stepStatus∈completed/pending/failed/recovered) give finer-grained progress.The API is keyed by source transaction hash (
txHash), not byorderId. Persist your source tx hash for API status polling; persistorderId(read from theOrderSubmittedevent) for the on-chain cancel/read path.
The API is keyed by source transaction hash. The same lifecycle can be read on-chain by orderId: getOrder(orderId) returns the full Order struct, and orderStatuses(orderId) returns the on-chain status enum (Pending / Filled / Cancelled — a different vocabulary from the API string; see the Reference). To obtain orderId from a submit, read it from the OrderSubmitted event in the transaction receipt.
Supported coverage
Three stablecoins (USDC, USDT, USDe) across 8 chains. The 8 chains and their IDs are fixed; per-(chain, token) availability is not uniform — not every token is enabled on every chain. Query GET /api/v1/routes for the live, authoritative per-cell set before you build a route picker.
Bridge rail by token (the same regardless of chain):
USDC
Circle CCTP V2 (burn-and-mint, native 1:1)
USDT
LayerZero V2 OFT
USDe
LayerZero V2 OFT
Chains and IDs (code-verified against deployments.json; confirm against the live /chains API):
Ethereum
1
Connected & Hub (execution chain)
Optimism
10
Connected
Arbitrum
42161
Connected
Base
8453
Connected
Ink
57073
Connected
Unichain
130
Connected
Plasma
9745
Connected
Sonic
146
Connected
Do not assume all 24 (chain × token) cells are enabled. Token coverage differs by chain (for example, not every chain carries all three stablecoins).
GET /api/v1/routesreturns exactly which(fromChain, fromToken) → (toChain, toToken)pairs are live; a quote for an unsupported cell returns422/ZDLite__UnsupportedTokenon submit. Treat/routesas the source of truth and gate your UI on it.
Per-(chain, token) contract addresses are returned by GET /api/v1/tokens; treat that endpoint as the canonical source rather than hardcoding addresses. A representative /tokens response is shown under API reference.
New chains, tokens, and routes are onboarded on partner request. Always treat GET /api/v1/chains, /tokens, and /routes as the live source of truth for what is currently enabled.
Smart contract interface
The escrow contract is deployed on every supported chain. On source chains it handles submitOrder and bridging to the hub. Integrators only need submitOrder on the source chain; full interface is in the Reference.
submitOrder
submitOrderTransfers the bid token from the caller, bridges it to the hub chain, and emits OrderSubmitted. If the source chain is already the hub, the order is stored directly without bridging. The function is payable; always include { value: gasCostSource } from the quote. Off-chain callers read orderId from the OrderSubmitted event (or an eth_call simulation); a normal send does not return values to the caller.
Requirements:
Caller must approve
bidTokento the escrow before calling.msg.valuemust cover the bridge adapter's native messaging fee.bidTokenandaskTokenmust be registered/supported on their respective chains.A bridge adapter must exist for the
(bidToken, hubChainId)pair.deadlinemust be0(no expiry) or a future timestamp.The contract must not be paused.
See Reference for the OrderRequest / Order structs, events, and error codes.
Cancellation
If an order cannot be filled (for example, the market moves beyond the quoted minimum), the user reclaims funds on the hub chain:
cancel(Order)— direct cancel by the order owner; funds are released immediately once the order has arrived on the hub.claimCancellation(Order)— withdraw funds for an order cancelled before it arrived, once it lands.
The base cancel path is cancel + claimCancellation; both are in the public interface today.
Roadmap (not yet exposed): a gasless
cancelFor(Order, signature)— a cancel a relayer submits on the owner's behalf using the owner's signature — exists in the contract but is not part of the current public integration surface, and the production frontend treats it as not-live. Build your cancel flow oncancel+claimCancellation; thecancelForsigning spec (EIP-712 domain + typed-data struct) ships when it goes live, so do not build against it yet.
Which to call: check the status steps (step 3). If the arrived step is completed (the order has reached the hub), use cancel. If you cancelled while the order was still pre-arrival, call claimCancellation once it lands on the hub.
Both take the full populated Order struct, which the contract assigns at submit (it sets nonce and sourceChainId). You already have this struct from the OrderSubmitted event (it carries the full Order tuple), so persist it at submit time; otherwise read it back from the hub chain (Ethereum, chainId 1), where the canonical record lives after the order bridges — query the hub escrow, not the source-chain one:
getOrder(orderId) is a view on the hub-chain escrow; it returns the canonical Order (see the Reference). Calling it on a source-chain instance returns Nonexistent after the order bridges, so switch your provider to Ethereum first. Because nonce and sourceChainId are contract-assigned, do not reconstruct the struct by hand; always read it back.
Solver model
Solvers are operated by Glacis today, vetted and approved to fill orders. Liquidity on the other side is sourced from vetted private market makers under Glacis operation. There is nothing for an integrator to run or operate; you submit orders and the network fills them. An open, third-party solver network is on the roadmap. If you want to run a solver as an independent party, contact the Glacis team.
Risk model
ZeroDelta's contracts have been audited by Halborn. The operational risks an integrator should be aware of:
Upgradeable contracts
The contracts are currently upgradeable under admin roles held by Glacis (a disclosed operational risk). Compromise of admin keys could put escrowed funds at risk.
We intend to move admin roles toward multisig control, with progressive decentralization of governance as a forward direction.
Quote drift
Quotes are estimates; if the market moves significantly mid-flight, a solver may be unable to fill at the quoted minimum.
The order stays Pending; the user cancels and reclaims funds on the hub chain.
Bridge / relayer dependence
Cross-chain settlement relies on third-party messaging infrastructure (Circle CCTP for USDC, LayerZero for USDT/USDe). Outages can delay a cross-chain leg.
These are widely used, production cross-chain providers; a stuck leg completes once the provider recovers.
Token-level pauses
A stablecoin or its bridge implementation with pause features can halt a transfer mid-flight.
Limited to assets with such features; surfaced in transaction status.
FAQ
Do I need to run a solver or hold inventory? No. You quote and submit; the Glacis-operated solver network fills.
Can I submit on behalf of my users? Yes. Set OrderRequest.owner to the user's address and submit from your own contract or wallet (msg.sender need not equal owner).
What happens if the order can't be filled? It stays Pending; nothing auto-refunds. The user (or you on their behalf) calls cancel and reclaims funds on the hub chain. With deadline = 0 (the default) there is no expiry; set a deadline for unattended flows so an order has a defined failure point. See Cancellation.
Is it atomic / instant? No. Settlement spans cross-chain bridge windows. The quote's estimatedDuration (seconds) is the expected time for that route; actual finality depends on the chains and assets involved and is bounded by the underlying bridge (Circle CCTP V2 for USDC, LayerZero V2 OFT for USDT/USDe). ZeroDelta optimizes for certainty at size, not sub-minute UX.
Which stablecoins and chains are supported? USDC, USDT, USDe across 8 chains today; query /chains, /tokens, and /routes for the live set.
How long does integration take? The surface is small: four calls (quote, approve, submitOrder, poll) and no proprietary SDK to learn, so once you have the prerequisites (see Before you start) the build is short. The gating step is onboarding (key, base URL, ABI package, environment scope), not the code.
Can I get more chains/tokens/routes? Yes, on request. Contact the Glacis team.
Operational details
Environments: separate dev/staging/production base URLs, provided on onboarding. See Testing without mainnet funds.
Auth & rate limits:
x-apikeyheader; per-key rate limits apply to pricing endpoints. See Errors and limits for429/Retry-Afterand retry guidance.Versioning: additive within
/api/v1/; breaking changes ship under a new version with advance notice (see Auth and environments).Support & incidents: support and status channels provided on onboarding.
Audit
Halborn has audited the ZeroDelta (ZDLite) smart contracts. All important findings have been addressed. The report is available on request.
Roadmap
Next-version capabilities include deeper network settlement that tightens pricing as orderflow grows, an open solver network, RWA support, and the partner capabilities described in the Product Overview. The internal technical reference is available under NDA. To start an integration or discuss your use case, contact the Glacis team.
Last updated

