Traders are used to limit orders and order books on CEX. But on DeFi exchanges with AMM, this is unavailable—only market orders with slippage. We build an order book DEX that provides CEX-like liquidity without sacrificing decentralization. Our experience: 10+ years in blockchain and over 50 projects, including integrations with AMM and L2.
Problems we solve
- Gas costs of on-chain storage: each order is a transaction, each match is a transaction; at trading frequency, this is economically unviable—solution: off-chain matching engine + on-chain settlement.
- Front-running and MEV: in the public mempool, orders are visible to all; bots can front-run your transaction—we use EIP-712 signatures with non-standard nonces, integration with Flashbots, and transaction batching.
- Liquidity at launch: an empty order book scares traders—solutions: integration with AMM pools as fallback, RFQ service for institutional orders, market maker program with reduced fees.
Which order book model to choose?
Fully on-chain order book: order book stored and matched directly in a smart contract. Each order is a transaction. Gas cost high: placing, canceling, filling—all paid by trader. Latency ~12 seconds (Ethereum block). Front-running inevitable. Suitable only for auctions and batch settlement.
Off-chain order book + on-chain settlement: dominant model. Matching—off-chain (fast, free). Only the final trade is settled on-chain. Examples: dYdX v3 (Starkware), Serum (Solana). Off-chain matching outperforms fully on-chain by 10-100x in gas cost and 10x in speed.
Hybrid: off-chain orders + on-chain matching: orders signed off-chain (EIP-712), stored in off-chain order book, but matching executed on-chain at settlement. Example: 0x Protocol, Hashflow. Maker pays no gas for placement—only for execution.
| Criteria | On-chain | Off-chain + on-chain settlement | Hybrid |
|---|---|---|---|
| Gas cost | High | Low | Medium |
| Latency | ~12 sec | <1 sec | <1 sec |
| Complexity | Low | Medium | High |
| Applicability | Auctions | Professional trading | Market making |
Smart contracts for settlement
EIP-712 order signatures:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract OrderBookSettlement {
bytes32 public constant ORDER_TYPEHASH = keccak256(
"Order(address maker,address taker,address makerToken,address takerToken,"
"uint256 makerAmount,uint256 takerAmount,uint256 nonce,uint256 expiry)"
);
struct Order {
address maker;
address taker; // address(0) = any taker
address makerToken;
address takerToken;
uint256 makerAmount;
uint256 takerAmount;
uint256 nonce;
uint256 expiry;
}
mapping(address => mapping(uint256 => bool)) public usedNonces;
mapping(bytes32 => uint256) public filledAmounts; // partial fills
function fillOrder(
Order calldata order,
bytes calldata signature,
uint256 takerFillAmount // for partial fills
) external {
require(block.timestamp < order.expiry, "ORDER_EXPIRED");
require(
order.taker == address(0) || order.taker == msg.sender,
"INVALID_TAKER"
);
bytes32 orderHash = getOrderHash(order);
require(
filledAmounts[orderHash] + takerFillAmount <= order.takerAmount,
"OVERFILL"
);
// Verify EIP-712 signature
address recovered = recoverSigner(orderHash, signature);
require(recovered == order.maker, "INVALID_SIGNATURE");
// Calculate maker amount proportional to partial fill
uint256 makerFillAmount = (order.makerAmount * takerFillAmount) / order.takerAmount;
filledAmounts[orderHash] += takerFillAmount;
// Atomic swap
IERC20(order.takerToken).transferFrom(msg.sender, order.maker, takerFillAmount);
IERC20(order.makerToken).transferFrom(order.maker, msg.sender, makerFillAmount);
emit OrderFilled(orderHash, order.maker, msg.sender, makerFillAmount, takerFillAmount);
}
}
The EIP-712 specification defines the signature structure. Key security aspects: filledAmounts for partial fills, usedNonces against replay, mandatory expiry. For standard approve, a separate transaction is needed—Permit2 by Uniswap Labs solves this with a single signature.
How does the off-chain matching engine work?
This is a high-performance service similar to CEX matching, but with specifics:
- Orders are signed messages, not on-chain.
- Partial fills tracked off-chain and on-chain (mapping filledAmounts).
- Order cancellation: on-chain nonce or off-chain cancel with maker confirmation.
from dataclasses import dataclass
from decimal import Decimal
from typing import Optional
import asyncio
@dataclass
class SignedOrder:
maker: str
taker_token: str
maker_token: str
taker_amount: Decimal
maker_amount: Decimal
nonce: int
expiry: int
signature: str
@property
def price(self) -> Decimal:
"""Price in units of maker_token per taker_token"""
return self.maker_amount / self.taker_amount
@property
def is_expired(self) -> bool:
import time
return time.time() > self.expiry
class OffChainOrderBook:
def __init__(self, pair: str):
self.pair = pair
self.bids: list[SignedOrder] = [] # buy orders, sorted by price DESC
self.asks: list[SignedOrder] = [] # sell orders, sorted by price ASC
self._settlement_queue = asyncio.Queue()
async def add_order(self, order: SignedOrder, side: str):
if side == 'bid':
self.bids.append(order)
self.bids.sort(key=lambda x: x.price, reverse=True)
else:
self.asks.append(order)
self.asks.sort(key=lambda x: x.price)
await self.try_match()
async def try_match(self):
while self.bids and self.asks:
best_bid = self.bids[0]
best_ask = self.asks[0]
if best_bid.is_expired:
self.bids.pop(0)
continue
if best_ask.is_expired:
self.asks.pop(0)
continue
if best_bid.price >= best_ask.price:
# Match found
fill_amount = min(best_bid.taker_amount, best_ask.taker_amount)
await self._settlement_queue.put({
'bid': best_bid,
'ask': best_ask,
'fill_amount': fill_amount
})
# Update or remove filled orders
best_bid.taker_amount -= fill_amount
best_ask.taker_amount -= fill_amount
if best_bid.taker_amount == 0:
self.bids.pop(0)
if best_ask.taker_amount == 0:
self.asks.pop(0)
else:
break
How much does batching save?
A batch of 10 fills in one transaction saves ~70% gas compared to 10 separate ones. The fillOrderBatch function makes this possible.
function fillOrderBatch(
Order[] calldata orders,
bytes[] calldata signatures,
uint256[] calldata fillAmounts
) external {
require(orders.length == signatures.length, "LENGTH_MISMATCH");
for (uint256 i = 0; i < orders.length; i++) {
fillOrder(orders[i], signatures[i], fillAmounts[i]);
}
}
What do L2 and appchain offer?
Deploy: dYdX v4 on Cosmos appchain gives zero fees for traders and <1ms latency. Arbitrum / zkSync L2 reduce gas cost by 10-100x—on-chain orders become economical for volumes >$100. Starkware with validity proofs scales to 10,000+ trades/sec.
How to ensure liquidity at launch?
- Integration with AMM: if no market maker, the router directs to an AMM pool (e.g., Uniswap V3).
- Market maker program: reduced fees, credit line, priority matching.
- RFQ: institutional traders request quotes directly from registered MMs.
Comparison with AMM
| Criteria | Order Book DEX | AMM DEX |
|---|---|---|
| Capital efficiency | High (no idle liquidity) | Low (V2) / High (V3) |
| UX for traders | Familiar, limit orders | Simpler, market only |
| Market making | Requires professionals | Accessible to all LPs |
| Front-running risk | High (without protection) | Medium (sandwich) |
| Latency | Depends on architecture | One block |
| Complexity | High | Medium |
Development process
- Analytics: choose blockchain, L2, matching model (on-chain/off-chain/hybrid).
- Design: architecture of smart contracts, off-chain services, API.
- Implementation: develop settlement contracts, matching engine, wallet integration.
- Testing: unit tests, integration tests, fuzzing (Echidna), testnet simulation.
- Audit: automated (Slither, Mythril) and manual code review.
- Deployment: phased rollout with monitoring.
What is included in the work
- Architecture and API documentation
- Access to source code of contracts and matching engine
- Deployment and monitoring instructions
- Team training on administration
- Support for one month after deployment
Timelines: MVP in 2–3 months, a full system with L2 and RFQ from 6 months. Cost is calculated individually. Contact us for a consultation and get an estimate within 2 days. Order development to accelerate time-to-market.







