Smart Contracts

ConfidentialDealRoom

The core contract that stores encrypted deal handles, manages deal state, and coordinates Nox ACL viewer access.

Arbitrum Sepolia

ConfidentialDealRoom

0x809Ad39e12360C3073B2AB2913D238287DB540c5

Function

createDeal

createDeal(bytes32,bytes,bytes32,bytes) returns (uint256)
ParamTypeDescription
callerFounderExpected caller role: Founder
Events

DealCreated

Errors

Unauthorized, InvalidDealState, NoxValidateInputProofFailed, NoxAddViewerFailed

Function

submitScore

submitScore(uint256,bytes32,bytes)
ParamTypeDescription
callerFounderExpected caller role: Founder
Events

ScoreSubmitted

Errors

Unauthorized, InvalidDealState, NoxValidateInputProofFailed, NoxAddViewerFailed

Function

counterDeal

counterDeal(uint256,bytes32,bytes)
ParamTypeDescription
callerInvestorExpected caller role: Investor
Events

DealCountered

Errors

Unauthorized, InvalidDealState, NoxValidateInputProofFailed, NoxAddViewerFailed

Function

acceptDeal

acceptDeal(uint256)
ParamTypeDescription
callerFounderExpected caller role: Founder
Events

DealAccepted

Errors

Unauthorized, InvalidDealState, NoxValidateInputProofFailed, NoxAddViewerFailed

Function

closeDeal

closeDeal(uint256)
ParamTypeDescription
callerInvestorExpected caller role: Investor
Events

DealClosed

Errors

Unauthorized, InvalidDealState, NoxValidateInputProofFailed, NoxAddViewerFailed

Function

rejectDeal

rejectDeal(uint256)
ParamTypeDescription
callerFounderExpected caller role: Founder
Events

DealRejected

Errors

Unauthorized, InvalidDealState, NoxValidateInputProofFailed, NoxAddViewerFailed

Solidity source

contracts/ConfidentialDealRoom.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {INoxCompute} from "@iexec-nox/nox-protocol-contracts/contracts/interfaces/INoxCompute.sol";
import {TEEType} from "@iexec-nox/nox-protocol-contracts/contracts/shared/TypeUtils.sol";
import {euint256} from "@iexec-nox/nox-protocol-contracts/contracts/sdk/Nox.sol";
import {IERC7984} from "@iexec-nox/nox-confidential-contracts/contracts/interfaces/IERC7984.sol";

contract ConfidentialDealRoom {
    enum DealStatus { Open, Countered, Accepted, Closed, Rejected }

    struct Deal {
        address founder;
        address investor;
        bytes32 encryptedValuation;
        bytes32 encryptedEquityPct;
        bytes32 encryptedRiskScore;
        bytes32 encryptedFundAmount;
        DealStatus status;
        uint256 createdAt;
    }

    error DealNotFound(uint256 dealId);
    error Unauthorized();
    error InvalidDealState(DealStatus current);
    error FounderCannotCounter();
    error MissingFundAmount();
    error NoxValidateInputProofFailed(bytes data);
    error NoxAddViewerFailed(string step, bytes data);
    error NoxAllowFailed(string step, bytes data);

    event DealCreated(uint256 indexed dealId, address indexed founder, bytes32 valuationHandle, bytes32 equityHandle);
    event ScoreSubmitted(uint256 indexed dealId, bytes32 scoreHandle);
    event DealCountered(uint256 indexed dealId, address indexed investor, bytes32 fundAmountHandle);
    event DealAccepted(uint256 indexed dealId);
    event DealClosed(uint256 indexed dealId, bytes32 transferredAmountHandle);
    event DealRejected(uint256 indexed dealId);

    INoxCompute public immutable nox;
    IERC7984 public immutable confidentialToken;
    mapping(uint256 dealId => Deal) public deals;
    uint256 public dealCount;

    constructor(address noxAddress, address confidentialTokenAddress) {
        nox = INoxCompute(noxAddress);
        confidentialToken = IERC7984(confidentialTokenAddress);
    }

    function createDeal(bytes32 valuationHandle, bytes calldata valuationProof, bytes32 equityHandle, bytes calldata equityProof) external returns (uint256 dealId) {
        nox.validateInputProof(valuationHandle, msg.sender, valuationProof, TEEType.Uint256);
        nox.validateInputProof(equityHandle, msg.sender, equityProof, TEEType.Uint256);
        dealId = dealCount;
        dealCount = dealCount + 1;
        deals[dealId] = Deal(msg.sender, address(0), valuationHandle, equityHandle, bytes32(0), bytes32(0), DealStatus.Open, block.timestamp);
        try nox.allow(valuationHandle, address(this)) {} catch (bytes memory reason) { revert NoxAllowFailed("valuation->dealRoom", reason); }
        try nox.allow(equityHandle, address(this)) {} catch (bytes memory reason) { revert NoxAllowFailed("equity->dealRoom", reason); }
        nox.addViewer(valuationHandle, msg.sender);
        nox.addViewer(equityHandle, msg.sender);
        emit DealCreated(dealId, msg.sender, valuationHandle, equityHandle);
    }

    function submitScore(uint256 dealId, bytes32 scoreHandle, bytes calldata scoreProof) external {
        Deal storage deal = _requireDeal(dealId);
        if (msg.sender != deal.founder) revert Unauthorized();
        if (deal.status == DealStatus.Closed || deal.status == DealStatus.Rejected) revert InvalidDealState(deal.status);
        nox.validateInputProof(scoreHandle, msg.sender, scoreProof, TEEType.Uint256);
        deal.encryptedRiskScore = scoreHandle;
        try nox.allow(scoreHandle, address(this)) {} catch (bytes memory reason) { revert NoxAllowFailed("score->dealRoom", reason); }
        nox.addViewer(scoreHandle, deal.founder);
        if (deal.investor != address(0)) nox.addViewer(scoreHandle, deal.investor);
        emit ScoreSubmitted(dealId, scoreHandle);
    }

    function counterDeal(uint256 dealId, bytes32 fundAmountHandle, bytes calldata fundAmountProof) external {
        Deal storage deal = _requireDeal(dealId);
        if (deal.status != DealStatus.Open) revert InvalidDealState(deal.status);
        if (msg.sender == deal.founder) revert FounderCannotCounter();
        try nox.validateInputProof(fundAmountHandle, msg.sender, fundAmountProof, TEEType.Uint256) {} catch (bytes memory reason) { revert NoxValidateInputProofFailed(reason); }
        deal.investor = msg.sender;
        deal.encryptedFundAmount = fundAmountHandle;
        deal.status = DealStatus.Countered;
        try nox.allow(fundAmountHandle, address(this)) {} catch (bytes memory reason) { revert NoxAllowFailed("fundAmount->dealRoom", reason); }
        try nox.addViewer(fundAmountHandle, msg.sender) {} catch (bytes memory reason) { revert NoxAddViewerFailed("fundAmount->investor", reason); }
        try nox.addViewer(fundAmountHandle, deal.founder) {} catch (bytes memory reason) { revert NoxAddViewerFailed("fundAmount->founder", reason); }
        try nox.addViewer(deal.encryptedValuation, msg.sender) {} catch (bytes memory reason) { revert NoxAddViewerFailed("valuation->investor", reason); }
        try nox.addViewer(deal.encryptedEquityPct, msg.sender) {} catch (bytes memory reason) { revert NoxAddViewerFailed("equity->investor", reason); }
        if (deal.encryptedRiskScore != bytes32(0)) {
            try nox.addViewer(deal.encryptedRiskScore, msg.sender) {} catch (bytes memory reason) { revert NoxAddViewerFailed("score->investor", reason); }
        }
        emit DealCountered(dealId, msg.sender, fundAmountHandle);
    }

    function acceptDeal(uint256 dealId) external {
        Deal storage deal = _requireDeal(dealId);
        if (msg.sender != deal.founder) revert Unauthorized();
        if (deal.status != DealStatus.Countered) revert InvalidDealState(deal.status);
        deal.status = DealStatus.Accepted;
        emit DealAccepted(dealId);
    }

    function closeDeal(uint256 dealId) external {
        Deal storage deal = _requireDeal(dealId);
        if (msg.sender != deal.investor) revert Unauthorized();
        if (deal.status != DealStatus.Accepted) revert InvalidDealState(deal.status);
        if (deal.encryptedFundAmount == bytes32(0)) revert MissingFundAmount();
        nox.allow(deal.encryptedFundAmount, address(confidentialToken));
        euint256 transferred = confidentialToken.confidentialTransferFrom(deal.investor, deal.founder, euint256.wrap(deal.encryptedFundAmount));
        deal.status = DealStatus.Closed;
        emit DealClosed(dealId, euint256.unwrap(transferred));
    }

    function rejectDeal(uint256 dealId) external {
        Deal storage deal = _requireDeal(dealId);
        if (msg.sender != deal.founder) revert Unauthorized();
        if (deal.status != DealStatus.Open && deal.status != DealStatus.Countered) revert InvalidDealState(deal.status);
        deal.status = DealStatus.Rejected;
        emit DealRejected(dealId);
    }

    function _requireDeal(uint256 dealId) internal view returns (Deal storage deal) {
        if (dealId >= dealCount) revert DealNotFound(dealId);
        deal = deals[dealId];
    }
}