Dark Pool DEX
🔴 Advanced | 🚀 Advanced
Private DEX order book - orders matched without revealing prices
Overview
A dark pool exchange where buy and sell orders are matched without revealing individual prices. Uses encrypted price comparisons (FHE.lte/gte) to find matches. Matched prices remain private - only the trade execution is visible. Demonstrates complex order matching with encrypted values.
Quick Start
# Create new project from this template
npx labz create dark-pool my-project
# Navigate and install
cd my-project
npm install
# Run tests
npx hardhat testContract
// SPDX-License-Identifier: BSD-3-Clause-Clear
pragma solidity ^0.8.24;
import { FHE, euint64, euint8, ebool, eaddress, externalEuint64, externalEuint8, externalEbool, externalEaddress } from "@fhevm/solidity/lib/FHE.sol";
import { ZamaEthereumConfig } from "@fhevm/solidity/config/ZamaConfig.sol";
/// @title DarkPool - Private DEX Order Book
/// @notice A dark pool where orders are matched without revealing prices
/// @dev Uses FHE for encrypted order matching with async public decryption
contract DarkPool is ZamaEthereumConfig {
// ============ Enums ============
enum OrderSide { Buy, Sell }
enum OrderStatus { Active, Matching, Filled, Cancelled }
// ============ Structs ============
struct Order {
address trader;
OrderSide side;
euint64 encryptedPrice;
euint64 encryptedAmount;
euint64 filledAmount;
OrderStatus status;
uint256 timestamp;
}
struct PendingMatch {
uint256 buyOrderId;
uint256 sellOrderId;
ebool canMatch;
bool isResolved;
}
// ============ Errors ============
error OrderNotFound(uint256 orderId);
error OrderNotActive(uint256 orderId);
error NotOrderOwner();
error NoMatchFound();
error InvalidOrder();
error MatchNotPending();
error MatchAlreadyResolved();
error InvalidDecryptionProof();
// ============ Events ============
event OrderPlaced(uint256 indexed orderId, address indexed trader, OrderSide side);
event OrderCancelled(uint256 indexed orderId);
event MatchRequested(uint256 indexed matchId, uint256 buyOrderId, uint256 sellOrderId);
event MatchReadyForReveal(uint256 indexed matchId);
event MatchRevealed(uint256 indexed matchId, bool canMatch);
event TradeExecuted(uint256 indexed buyOrderId, uint256 indexed sellOrderId, address buyer, address seller);
// ============ State Variables ============
mapping(uint256 => Order) private _orders;
mapping(uint256 => PendingMatch) private _pendingMatches;
uint256 private _orderCount;
uint256 private _matchCount;
uint256[] private _activeBuyOrders;
uint256[] private _activeSellOrders;
uint256 public minOrderAmount;
// ============ Modifiers ============
// ============ Constructor ============
constructor(uint256 _minOrderAmount) {
minOrderAmount = _minOrderAmount;
}
// ============ External Functions ============
/// @notice Place a buy order with encrypted price and amount
function placeBuyOrder(
externalEuint64 encryptedPrice,
bytes calldata priceProof,
externalEuint64 encryptedAmount,
bytes calldata amountProof
) external returns (uint256 orderId) {
euint64 price = FHE.fromExternal(encryptedPrice, priceProof);
euint64 amount = FHE.fromExternal(encryptedAmount, amountProof);
orderId = ++_orderCount;
_orders[orderId] = Order({
trader: msg.sender,
side: OrderSide.Buy,
encryptedPrice: price,
encryptedAmount: amount,
filledAmount: FHE.asEuint64(0),
status: OrderStatus.Active,
timestamp: block.timestamp
});
FHE.allowThis(_orders[orderId].encryptedPrice);
FHE.allowThis(_orders[orderId].encryptedAmount);
FHE.allowThis(_orders[orderId].filledAmount);
FHE.allow(_orders[orderId].encryptedPrice, msg.sender);
FHE.allow(_orders[orderId].encryptedAmount, msg.sender);
_activeBuyOrders.push(orderId);
emit OrderPlaced(orderId, msg.sender, OrderSide.Buy);
}
/// @notice Place a sell order with encrypted price and amount
function placeSellOrder(
externalEuint64 encryptedPrice,
bytes calldata priceProof,
externalEuint64 encryptedAmount,
bytes calldata amountProof
) external returns (uint256 orderId) {
euint64 price = FHE.fromExternal(encryptedPrice, priceProof);
euint64 amount = FHE.fromExternal(encryptedAmount, amountProof);
orderId = ++_orderCount;
_orders[orderId] = Order({
trader: msg.sender,
side: OrderSide.Sell,
encryptedPrice: price,
encryptedAmount: amount,
filledAmount: FHE.asEuint64(0),
status: OrderStatus.Active,
timestamp: block.timestamp
});
FHE.allowThis(_orders[orderId].encryptedPrice);
FHE.allowThis(_orders[orderId].encryptedAmount);
FHE.allowThis(_orders[orderId].filledAmount);
FHE.allow(_orders[orderId].encryptedPrice, msg.sender);
FHE.allow(_orders[orderId].encryptedAmount, msg.sender);
_activeSellOrders.push(orderId);
emit OrderPlaced(orderId, msg.sender, OrderSide.Sell);
}
/// @notice Cancel an active order
function cancelOrder(uint256 orderId) external {
Order storage order = _orders[orderId];
if (order.trader == address(0)) revert OrderNotFound(orderId);
if (order.trader != msg.sender) revert NotOrderOwner();
if (order.status != OrderStatus.Active) revert OrderNotActive(orderId);
order.status = OrderStatus.Cancelled;
emit OrderCancelled(orderId);
}
/// @notice Request matching check between buy and sell orders
/// @dev Step 1: Creates encrypted comparison, marks for decryption
function requestMatch(uint256 buyOrderId, uint256 sellOrderId) external returns (uint256 matchId) {
Order storage buyOrder = _orders[buyOrderId];
Order storage sellOrder = _orders[sellOrderId];
if (buyOrder.trader == address(0)) revert OrderNotFound(buyOrderId);
if (sellOrder.trader == address(0)) revert OrderNotFound(sellOrderId);
if (buyOrder.status != OrderStatus.Active) revert OrderNotActive(buyOrderId);
if (sellOrder.status != OrderStatus.Active) revert OrderNotActive(sellOrderId);
if (buyOrder.side != OrderSide.Buy) revert InvalidOrder();
if (sellOrder.side != OrderSide.Sell) revert InvalidOrder();
// Encrypted comparison: buyPrice >= sellPrice
ebool canMatch = FHE.ge(buyOrder.encryptedPrice, sellOrder.encryptedPrice);
matchId = ++_matchCount;
_pendingMatches[matchId] = PendingMatch({
buyOrderId: buyOrderId,
sellOrderId: sellOrderId,
canMatch: canMatch,
isResolved: false
});
// Mark orders as pending match
buyOrder.status = OrderStatus.Matching;
sellOrder.status = OrderStatus.Matching;
FHE.allowThis(canMatch);
// Mark for public decryption
FHE.makePubliclyDecryptable(canMatch);
emit MatchRequested(matchId, buyOrderId, sellOrderId);
emit MatchReadyForReveal(matchId);
}
/// @notice Get the encrypted match result handle for off-chain decryption
function getMatchHandle(uint256 matchId) external view returns (ebool) {
PendingMatch storage pm = _pendingMatches[matchId];
if (pm.buyOrderId == 0) revert MatchNotPending();
return pm.canMatch;
}
/// @notice Finalize match with decryption proof
/// @dev Step 3: Verify decryption and execute trade if matched
function finalizeMatch(
uint256 matchId,
bool canMatchResult,
bytes calldata decryptionProof
) external {
PendingMatch storage pm = _pendingMatches[matchId];
if (pm.buyOrderId == 0) revert MatchNotPending();
if (pm.isResolved) revert MatchAlreadyResolved();
// Verify decryption proof
bytes32[] memory cts = new bytes32[](1);
cts[0] = ebool.unwrap(pm.canMatch);
bytes memory cleartexts = abi.encode(canMatchResult);
// This reverts if proof is invalid
FHE.checkSignatures(cts, cleartexts, decryptionProof);
pm.isResolved = true;
Order storage buyOrder = _orders[pm.buyOrderId];
Order storage sellOrder = _orders[pm.sellOrderId];
if (canMatchResult) {
// Execute trade
_executeMatch(pm.buyOrderId, pm.sellOrderId);
emit MatchRevealed(matchId, true);
} else {
// No match - revert orders to active
buyOrder.status = OrderStatus.Active;
sellOrder.status = OrderStatus.Active;
emit MatchRevealed(matchId, false);
}
}
// ============ View Functions ============
function getOrderCount() external view returns (uint256) {
return _orderCount;
}
function getMatchCount() external view returns (uint256) {
return _matchCount;
}
function getActiveBuyOrderCount() external view returns (uint256) {
return _activeBuyOrders.length;
}
function getActiveSellOrderCount() external view returns (uint256) {
return _activeSellOrders.length;
}
function getOrderInfo(uint256 orderId) external view returns (
address trader,
OrderSide side,
OrderStatus status,
uint256 timestamp
) {
Order storage order = _orders[orderId];
return (order.trader, order.side, order.status, order.timestamp);
}
function getMatchInfo(uint256 matchId) external view returns (
uint256 buyOrderId,
uint256 sellOrderId,
bool isResolved
) {
PendingMatch storage pm = _pendingMatches[matchId];
return (pm.buyOrderId, pm.sellOrderId, pm.isResolved);
}
function getOrderPrice(uint256 orderId) external view returns (euint64) {
return _orders[orderId].encryptedPrice;
}
function getOrderAmount(uint256 orderId) external view returns (euint64) {
return _orders[orderId].encryptedAmount;
}
// ============ Internal Functions ============
function _executeMatch(uint256 buyOrderId, uint256 sellOrderId) internal {
Order storage buyOrder = _orders[buyOrderId];
Order storage sellOrder = _orders[sellOrderId];
// Calculate trade amount: min(buyAmount - buyFilled, sellAmount - sellFilled)
euint64 buyRemaining = FHE.sub(buyOrder.encryptedAmount, buyOrder.filledAmount);
euint64 sellRemaining = FHE.sub(sellOrder.encryptedAmount, sellOrder.filledAmount);
euint64 tradeAmount = FHE.min(buyRemaining, sellRemaining);
// Update filled amounts
buyOrder.filledAmount = FHE.add(buyOrder.filledAmount, tradeAmount);
sellOrder.filledAmount = FHE.add(sellOrder.filledAmount, tradeAmount);
FHE.allowThis(buyOrder.filledAmount);
FHE.allowThis(sellOrder.filledAmount);
FHE.allowThis(tradeAmount);
// Mark as filled (simplified - full implementation would check remaining)
buyOrder.status = OrderStatus.Filled;
sellOrder.status = OrderStatus.Filled;
emit TradeExecuted(buyOrderId, sellOrderId, buyOrder.trader, sellOrder.trader);
}
}
FHE Operations Used
FHE.lt()FHE.gt()FHE.lte()FHE.gte()FHE.min()FHE.max()FHE.eq()FHE.select()FHE.sub()FHE.add()FHE.allowThis()FHE.allow()FHE.fromExternal()
FHE Types Used
euint64externalEuint64ebool
Tags
defi dex trading privacy orderbook matching
Related Examples
Prerequisites
Before this example, you should understand:
Next Steps
After this example, check out:
Generated with Lab-Z
Last updated
