For the complete documentation index, see llms.txt. This page is also available as Markdown.

Smart Contracts

Contract interface, data types, events, error codes, deployments, and coverage. For the REST API, see the API Reference. Compiler: Solidity 0.8.19 (optimizer enabled, 200 runs). License: BUSL-1.1

The contracts and error codes use the ZDLite identifier (the on-chain name for the product branded ZeroDelta). Contract sources carry an SPDX BUSL-1.1 header (source-available); test sources are MIT. Integration and use are permitted.

Per-(chain, token) addresses: the canonical source for token contract addresses on each chain is GET /api/v1/tokens (see Coverage and the Integration Guide). Addresses change as chains/tokens are onboarded; do not hardcode them.

Data types

struct OrderRequest {
    address owner;                // Order owner (the user); need not equal msg.sender
    address bidToken;             // Token being sold (source chain)
    uint256 bidTokenAmount;       // Amount of bidToken to sell
    address askToken;             // Token being bought (destination chain)
    uint256 askTokenAmount;       // Minimum acceptable output (slippage floor)
    address destinationReceiver;  // Recipient on the destination chain
    uint256 destinationChainId;   // Target chain ID
    uint256 deadline;             // Unix expiry (0 = no deadline)
    bytes   data;                 // Arbitrary pass-through data
}

struct Order {
    address owner;
    uint96  nonce;                // Auto-incremented, set by the contract
    address bidToken;
    uint256 bidTokenAmount;       // Actual amount escrowed (defensive accounting; current stablecoins are not fee-on-transfer)
    uint256 sourceChainId;        // Set by the contract
    address askToken;
    uint256 askTokenAmount;       // Minimum acceptable output (slippage floor)
    address destinationReceiver;
    uint256 destinationChainId;
    uint256 deadline;
    bytes   data;
}

enum OrderStatus {
    Nonexistent,
    Pending,
    Filled,
    Cancelled
}

On-chain enum vs. read-API status — two different vocabularies. The on-chain OrderStatus enum above (Nonexistent / Pending / Filled / Cancelled) is what orderStatuses(orderId) returns. The read API (GET /api/v1/transactions/{txHash}) returns its own lowercase status string, not the enum names — if you poll the API, compare against the lowercase values, not Filled / Cancelled. The full read-API set (code-verified), with terminal classification:

Status
Terminal?

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. This is the canonical set the Integration Guide poller mirrors; treat anything outside the non-terminal set (the first five) as terminal.

The order is identified by orderId, an immutable identity derived by the contract from owner, nonce, and sourceChainId (not a function of amounts). The contract computes it as keccak256(abi.encode(owner, nonce, sourceChainId)) (code-verified against the live contract).

Do not precompute or reconstruct orderId. nonce and sourceChainId are assigned by the contract at submit, so an off-chain caller cannot know them in advance, and under concurrency a precomputed id can be wrong. Obtain orderId after submission from the OrderSubmitted event in the transaction receipt, or from an eth_call simulation of submitOrder. A normal state-changing send does not return the value to an off-chain caller.

On-chain events key on orderId; the read API is queryable by source transaction hash. Persist both: orderId for the on-chain cancel/read path (getOrder, orderStatuses), the source tx hash for API status polling.


Integrator-relevant functions

getOrder and orderStatuses are hub-chain views (Ethereum, chainId 1). The hub escrow is the same contract address as the source-chain escrow (0x46A3219c357C2E50129d4839058FCFD421789aE7 on all 8 chains today); to read order state you point a provider at Ethereum and call against that address. See the cancel snippet in the Integration Guide for hubEscrow instantiation.

The contract also exposes a nonce() view, but do not use it to precompute orderId: nonce is contract-assigned and advances as orders are submitted, so a read-then-derive sequence is racy under concurrency. Always read orderId back from the OrderSubmitted event instead.

The contract also exposes administrative functions (token/adapter registration, pause, role management) that are operator-only and not part of the integration surface. The full interface is available in the OpenAPI/ABI package provided on onboarding.


Events

Event
Indexed
Emitted when

OrderSubmitted(bytes32 orderId, Order order)

orderId

An order is submitted on the source chain.

OrderArrived(bytes32 orderId, Order order)

orderId

An order arrives on the hub chain (or same-chain submit).

OrderFilled(bytes32 orderId, Order order)

orderId

An order is filled.

OrderCancelled(bytes32 orderId, Order order)

orderId

A cancel returns funds.

Pre-arrival cancellations and claims emit their own events (OrderPreCancelled, EscrowClaimed); see the ABI package for the complete list.


Error codes

The errors integrators encounter most often:

All names below are code-verified against the deployed contract source. Match by selector against these exact names.

Error
Meaning

ZDLite__UnsupportedToken

The bid or ask token is not registered on that chain.

ZDLite__BridgeAdapterNotFound

No adapter is registered for the (token, destChain) route.

ZDLite__ZeroAddress

A required address (owner or destinationReceiver) is the zero address; both must be real addresses.

ZDLite__ZeroAmount

bidTokenAmount (or a required amount) is zero.

ZDLite__InvalidDeadline

deadline is in the past (and not 0).

ZDLite__OrderExpired

The order's deadline passed before it could be filled.

ZDLite__InsufficientAskToken(expected, actual)

A fill could not meet the user's minimum output; the fill reverts.

ZDLite__OrderNotPending

The order is not in Pending state for the requested action.

ZDLite__OrderNotArrived

A cancel/claim was attempted before the order arrived on the hub.

ZDLite__OrderAlreadyArrived

Re-delivery guard (bridge replay protection).

ZDLite__OrderAlreadyFilled

The order was already filled.

ZDLite__OrderNotCancellable

The order is not in a cancellable state.

ZDLite__UnauthorizedCaller

Caller is not permitted for that action (e.g. cancel by non-owner).

ZDLite__InvalidSignature

A signature (e.g. for an authorized cancel) did not validate.

These are the common subset an integrator hits; the complete error set ships in the ABI package.

On underpaid msg.value: there is no ZDLite__ error for an insufficient bridge fee. If msg.value does not cover the bridge adapter's native messaging fee, submitOrder reverts with the bridge adapter's own error, which is adapter-specific (CCTP vs LayerZero) — do not match it by a ZDLite__ name. Send gasCostSource from the quote (plus a small buffer). See the Integration Guide.

On a paused contract: pause uses OpenZeppelin PausableUpgradeable (whenNotPaused), so a submission against a paused contract reverts with OpenZeppelin's EnforcedPause selector (older deployments: the "Pausable: paused" revert string), not a ZDLite__ error.


Deployments

The escrow is deployed at the same address across all 8 supported chains today. Ethereum is the hub execution chain.

  • Escrow (current, all chains): 0x46A3219c357C2E50129d4839058FCFD421789aE7

  • Hub: Ethereum (chainId 1).

This address is the same across chains today but may differ per chain as coverage expands — do not hardcode it. GET /api/v1/chains is canonical: per chain it returns the escrow address, the bridge adapter addresses, the roles-contract address, and whether the chain is the hub. That makes the submitOrder requirement "a bridge adapter must exist for (bidToken, hubChainId)" checkable from the API before you submit. Keep the chain set in sync with the Integration Guide coverage matrix.


Coverage

  • Stablecoins: USDC (Circle CCTP V2), USDT (LayerZero V2 OFT), USDe (LayerZero V2 OFT).

  • Chains: Ethereum (hub), Optimism, Arbitrum, Base, Ink, Unichain, Plasma, Sonic. Chain IDs and the per-cell bridge matrix are in the Integration Guide.

  • Per-(chain, token) addresses: retrieved from GET /api/v1/tokens (e.g. ?chainId=42161), which returns symbol, chainId, address, and decimals per token. This is the canonical source; addresses are not hardcoded here because they change as coverage expands.

  • New chains, tokens, and routes are onboarded on partner request. The live /chains, /tokens, and /routes endpoints are the source of truth.


Audit

Halborn has audited the ZeroDelta (ZDLite) smart contracts; all important findings addressed. Report available on request.

Last updated