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 test

Contract

// 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

  • euint64

  • externalEuint64

  • ebool

Tags

defi dex trading privacy orderbook matching

Prerequisites

Before this example, you should understand:

Next Steps

After this example, check out:


Generated with Lab-Z

Last updated