> For the complete documentation index, see [llms.txt](https://docs.glacislabs.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.glacislabs.com/zero-delta/integration-guide/smart-contracts.md).

# Smart Contracts

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

```solidity
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:

<table data-search="false"><thead><tr><th>Status</th><th>Terminal?</th></tr></thead><tbody><tr><td><code>pending</code></td><td>no</td></tr><tr><td><code>in_progress</code></td><td>no</td></tr><tr><td><code>outbound_bridging</code></td><td>no</td></tr><tr><td><code>cancel_pending</code></td><td>no</td></tr><tr><td><code>awaiting_claim</code></td><td>no</td></tr><tr><td><code>delivered</code></td><td>yes (success)</td></tr><tr><td><code>expired</code></td><td>yes</td></tr><tr><td><code>cancelled</code></td><td>yes</td></tr><tr><td><code>fill_failed</code></td><td>yes</td></tr><tr><td><code>delivery_failed</code></td><td>yes</td></tr><tr><td><code>failed</code></td><td>yes</td></tr></tbody></table>

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

```solidity
// ── Source chain ──────────────────────────────────────────────────────────
// Submit a cross-chain order. Pulls bidToken from msg.sender, bridges
// to the hub chain, emits OrderSubmitted. msg.value covers the bridge fee.
// Returns (orderId, order) on-chain. Off-chain callers do NOT receive these from a
// normal send: read orderId from the OrderSubmitted event, or use eth_call to simulate.
function submitOrder(OrderRequest calldata request)
    external payable returns (bytes32 orderId, Order memory order);

// ── Hub chain: cancel / claim ────────────────────────────
function cancel(Order calldata order) external;                  // owner cancels; funds released once arrived
function claimCancellation(Order calldata order) external;       // withdraw a pre-arrival cancellation once it lands
// Roadmap (not in the current public interface): cancelFor(Order, signature) — a gasless cancel a
// relayer submits on the owner's behalf. Present in the contract but not yet exposed for integration;
// the signing spec ships when it goes live. Build the cancel path on cancel + claimCancellation today.

// ── Views ──────────────────────────────────────────────────
// getOrder / orderStatuses resolve the canonical order state on the HUB-chain escrow.
// A source-chain instance will not hold the canonical record post-bridge (returns Nonexistent).
function getOrder(bytes32 orderId) external view returns (Order memory);
function orderStatuses(bytes32 orderId) external view returns (OrderStatus);
function isSupportedToken(uint256 chainId, address token) external view returns (bool);
function getBridgeAdapter(address token, uint256 destChainId) external view returns (address);
function getEquivalentToken(uint256 destChainId, address destToken) external view returns (address);
```

`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.

<table data-search="false"><thead><tr><th>Error</th><th>Meaning</th></tr></thead><tbody><tr><td><code>ZDLite__UnsupportedToken</code></td><td>The bid or ask token is not registered on that chain.</td></tr><tr><td><code>ZDLite__BridgeAdapterNotFound</code></td><td>No adapter is registered for the <code>(token, destChain)</code> route.</td></tr><tr><td><code>ZDLite__ZeroAddress</code></td><td>A required address (<code>owner</code> or <code>destinationReceiver</code>) is the zero address; both must be real addresses.</td></tr><tr><td><code>ZDLite__ZeroAmount</code></td><td><code>bidTokenAmount</code> (or a required amount) is zero.</td></tr><tr><td><code>ZDLite__InvalidDeadline</code></td><td><code>deadline</code> is in the past (and not <code>0</code>).</td></tr><tr><td><code>ZDLite__OrderExpired</code></td><td>The order's deadline passed before it could be filled.</td></tr><tr><td><code>ZDLite__InsufficientAskToken(expected, actual)</code></td><td>A fill could not meet the user's minimum output; the fill reverts.</td></tr><tr><td><code>ZDLite__OrderNotPending</code></td><td>The order is not in <code>Pending</code> state for the requested action.</td></tr><tr><td><code>ZDLite__OrderNotArrived</code></td><td>A cancel/claim was attempted before the order arrived on the hub.</td></tr><tr><td><code>ZDLite__OrderAlreadyArrived</code></td><td>Re-delivery guard (bridge replay protection).</td></tr><tr><td><code>ZDLite__OrderAlreadyFilled</code></td><td>The order was already filled.</td></tr><tr><td><code>ZDLite__OrderNotCancellable</code></td><td>The order is not in a cancellable state.</td></tr><tr><td><code>ZDLite__UnauthorizedCaller</code></td><td>Caller is not permitted for that action (e.g. cancel by non-owner).</td></tr><tr><td><code>ZDLite__InvalidSignature</code></td><td>A signature (e.g. for an authorized cancel) did not validate.</td></tr></tbody></table>

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.
