This repository implements confidential single-price auctions using Zama's FHEVM. Bidders submit encrypted prices and quantities for a batch of tokens, ensuring privacy on-chain. The final single settlement price is the price from the bid required to purchase the last token sold.
- Overview
- Global Architecture
- Core Contracts
- Workflow
- Deployment & Usage
- Important Notes & Security
- Configuration
- Development & Build
- Documentation
A Single-Price Sealed Auction is one where participants submit encrypted bids consisting of:
- The quantity of tokens (encrypted) they are willing to buy.
- The maximum price (encrypted) they are willing to pay per token.
The auction contract:
- Locks the seller’s supply of the confidential ERC20 token.
- Accepts sealed bids for a limited time (
biddingTime). - Validates bids using FHE operations against minimum price/quantity constraints.
- Computes a single clearing price that every winning bidder pays (the lowest successful price for the last token sold).
- Refunds any leftover deposit to bidders who overpaid and returns unsold tokens to the seller if demand is under-subscribed.
Note: To discourage invalid or excessively low bids, the contract can charge a penalty fee (
DEFAULT_PENALTY_FEE) for the bids having their quantity below a public minimum:minQtyor their price below a public minimum:minPrice. Such bids are called invalid. Bids are flagged by a bit which remains encryptedisValid(1 for valid and 0 for invalid), so the contract never reveals who placed an invalid bid.
flowchart TB
%% Define subgraphs for clarity
subgraph Payment
C1["ConfidentialERC20 / ConfidentialWETH<br>(ERC20 or Ether)"]
end
subgraph Asset
A1["ConfidentialERC20<br>(Tokens Sold)"]
end
%% Layer 1: Factory
SealedAuctionFactory("SealedAuctionFactory")
%% Layer 2: Auction
SealedAuction("SealedAuction<br>Main Auction Logic")
%% Layer 3: Bidding Participants
BiddingParticipants(("Bidding Participants"))
%% Layer 4: Token Deposits
C1 --> SealedAuction
A1 --> SealedAuction
%% Diagram connections
SealedAuctionFactory -->|"Creates"| SealedAuction
BiddingParticipants --> SealedAuction
-
SealedAuction
- Stores and processes encrypted bids, compares them homomorphically, determines the final single settlement price, and handles claims/refunds.
-
SealedAuctionFactory
- Deploys new
SealedAuctioncontracts with consistent parameters (penalty fees, token addresses, etc.). - Ensures the asset tokens (
supply) are locked when creating the auction.
- Deploys new
Path: contracts/SealedAuction.sol
Role: Manages a single instance of the auction, from bidding phase to final settlement.
Key Variables
endTime: Deadline after which no new bids can be placed.supply: Total tokens to be sold.assetToken: Confidential ERC20 for the asset (seller’s tokens).paymentToken: Confidential ERC20 or wrapped Ether (for bidder payments).eMinPrice,eMinQty: Encrypted minimum price & quantity.eSettlementPrice: The encrypted clearing price, decrypted later intodecryptedPrice.bids: A mapping of bid IDs to bid data (eBidPrice,eBidQty, and an encrypted booleanisValid).outcomes: A mapping of bidder addresses to allocations, total deposits, penalties, etc.
Important:
For production, uncomment the line:require(msg.sender == OFFICIAL_FACTORY, "Must be deployed via factory");to ensure only the official factory can create this contract.
Key Functions
- Constructor – Initializes the auction (supply, penalty fee, etc.).
placeBid(einput encPrice, einput encQty, bytes calldata proof)- Stores the bid, locking
price * qty + penaltyFee. Checks validity with FHE operations.
- Stores the bid, locking
finalize()- Called by the auction owner after
endTimeto move to the settlement phase.
- Called by the auction owner after
allocateBids(...),computeBidsBefore(...)- Process bids in batches to determine each bidder’s allocated tokens and the final clearing price.
claim()- Bidders withdraw their tokens and any deposit refunds.
ownerWithdraw()- Owner claims proceeds (sold tokens * settlement price + penalties) and unsold tokens (if any).
Path: contracts/SealedAuctionFactory.sol
Role: Central contract for deploying SealedAuction instances in a standardized way.
Key Variables
auctions: Addresses of all deployed auctions.defaultAssetERC20,defaultPaymentERC20,defaultWETH: Default confidential tokens.DEFAULT_MAX_BIDS_PER_ADDRESS,DEFAULT_PENALTY_FEE: Defaults for new auctions.
Key Functions
-
createAuction(...)- Validates parameters (supply, minPrice, etc.).
- Deploys a new
SealedAuction. - Transfers
supplyfrom the owner to the newly created auction. - Emits
AuctionCreated.
-
getAuctions(...),getActiveAuctions(...)- Retrieve the list of auctions, filtered by active/inactive status.
-
Initialization
- The seller (Alice) has
supplytokens of a confidential ERC20 (defaultAssetERC20). - Through a user interface or direct contract call, she calls
SealedAuctionFactory.createAuction(...)specifying:auctionOwner,supply,biddingTime(duration),minPrice,minQty, etc.
- The factory deploys
SealedAuctionand locks Alice’ssupplytokens inside it.
- The seller (Alice) has
-
Owner vs. Factory
- In production, the
SealedAuctionconstructor ensures only the factory can create auctions. - For development, you may comment out
require(msg.sender == OFFICIAL_FACTORY, ...)for direct testing.
- In production, the
-
Bidding Phase
- The auction is open for a certain time:
biddingTime. - Each bidder can place one or several sealed bids, up to
MAX_BIDS_PER_ADDRESS. - A sealed bid includes:
- Encrypted max price (
encPrice). - Encrypted max quantity (
encQty).
- Encrypted max price (
- Bidders must lock a deposit
encPrice * encQty + penaltyFee(using FHE arithmetic).
- The auction is open for a certain time:
-
Validity & Penalties
- If a bid is below the published
minPriceorminQty(both stored in encrypted form), it is flagged internally as invalid (isValid = 0) but not revealed on-chain. - A penalty may apply to invalid bids (
DEFAULT_PENALTY_FEE).
- If a bid is below the published
-
No Modification
- Once placed, a bid cannot be modified before the auction ends.
-
Settlement Phase
-
After
endTime, only the owner can callfinalize(). -
The contract computes the encrypted boolean
eDemandOverSupply, which indicates whether the total (encrypted) quantities demanded by valid bids exceedsupply. -
Then, the contract decrypts this boolean into
isDemandOverSupply:
this single boolean is the only additional information revealed by the contract. -
If under-subscribed (the easy case), i.e.,
eDemandOverSupply= 0, then the settlement price (eMarketPrice) becomes equal to the lowestencPriceamong the valid bids. -
If over-subscribed (the hard case), i.e.,
eDemandOverSupply= 1, then the settlement price becomes the lowestencPriceamong the valid bids needed to sell all tokens.If two bids, with indices
iandj, have equalencPrice, then the bid that arrived first (i.e., the one with the lower index) has priority.Let us describe the details:
- For each bid
i, the contract first determines if any token can be sold toi. This is flagged in the encrypted booleancanSell. canSellis set to 1 if the sum of the quantities requested by biddersjhigher thani(calledeCumulativeBetterBids[i], computed viacomputeBidsBefore) is lower than the total supply.- The contract homomorphically computes the encrypted quantity:
eSold, effectively sold toi:- If
canSell= 0 →eSold= 0 - Else →
min(`encQty[i]`, `eCumulativeBetterBids[i]`)
- If
- The
minfunction handles cases whereiis the last served bidder. Even ifcanSell[i]= 1 , since the bids better than i are served first, there might be too little supply left to fully satisfyencQty[i]. - Finally, the settlement price is set as the
minover the encrypted prices of all the winning bidders, i.e., those withcanSell= 1.
- For each bid
-
The contract requests an off-chain decryption of
eSettlementPrice(computeBidsBefore&allocateBids).
-
-
Revealing the Final Price
- Once the settlement price is decrypted, each winner effectively pays that same price.
- Losers or invalid bidders can claim partial refunds minus penalties.
-
Bidders (Claim)
- Call
claim()to receive the allocated quantity of tokens and to be refunded any excess deposit. - Invalid bids may forfeit part or all of their penalty fee.
- Call
-
Owner (Withdrawal)
- Calls
ownerWithdraw()to collect the final proceeds:- Settlement Price * Number of tokens sold
- Total penalty fees.
- If under-subscribed, unsold tokens are returned to the owner.
- Calls
- ConfidentialWETH Payment Flow
- Currently, the auction logic supports confidentialERC20 tokens for payment.
- We have partially prepared a flow for using ConfidentialWETH, but this functionality has not been fully tested or finalized.
- Ongoing development will focus on validating ConfidentialWETH transactions and ensuring compatibility.
- React Frontend Interface
- A basic React interface has been set up for user interaction with the contracts, but additional features and UX improvements are still in progress.
- Future updates will provide a more user-friendly bidding process, real-time status updates, and extended error-handling. We appreciate any feedback on these areas and are actively working to enhance and complete the above features.
-
** Pre Requisites
Install pnpm
cp .env.example .env
-
Install Dependencies
npm install # or yarn install npm install fhevm fhevm-contracts @openzeppelin/contracts -
Compile
npm compile
-
Tests
npm test- Note: For local testing, you can comment out:
in the
// require(msg.sender == OFFICIAL_FACTORY, "Must be deployed via factory");SealedAuctionconstructor if you want to deploy it directly without the factory.
- Note: For local testing, you can comment out:
-
Factory Enforcement
- In production, uncomment the line:
This prevents malicious actors from creating auctions with altered parameters.
require(msg.sender == OFFICIAL_FACTORY, "Must be deployed via factory");
- In production, uncomment the line:
-
Penalty Fee
- A default penalty for invalid/too-low bids helps deter spamming. Adjust
DEFAULT_PENALTY_FEEas necessary.
- A default penalty for invalid/too-low bids helps deter spamming. Adjust
-
Batch Processing
- If many bids exist, calls like
computeBidsBeforeandallocateBidscan process them in batches to avoid gas limits.
- If many bids exist, calls like
Copy .env.example to .env and update the gateway URL, ACL address, and KMS address to match your FHEVM environment.
-
Development Server
npm run dev
Access your local server at http://localhost:5173.
-
Build
npm run build
For more details on fhevmjs and the Zama FHEVM, refer to the official documentation.


