Pool public, position private.
FHE Market keeps the AMM math gas-affordable by leaving pool reserves in cleartext, while every user's share balance lives as a Zama euint64 ciphertext. Only the holder — not the relayer, not the contract, not the indexer — can read the value.
Overview
FHE Market is a non-custodial, confidential prediction-market protocol. Each market asks a binary question. Users buy YES or NO shares against a Polymarket-style FPMM. Winning shares redeem 1:1 for cUSDT at resolution.
The twist: per-user share balances are encrypted on-chain. Outsiders can see the pool, the price, and the outcome — they cannot see your YES/NO position, your cumulative exposure, or how a particular wallet leans. Only you, holding the right EIP-712 signature, can decrypt your own balance.
What's encrypted
The bet amount on a single transaction reveals at the executeBuy step (necessary to update reserves), but your cumulative position across many bets stays opaque — an observer cannot link multiple actions to a single position size without breaking FHE.
One on-chain Faucet.drip(you, 5) bundles USDTMock.mint × 5 + approve + cUSDT.wrap in a single user-signed tx. No relayer round-trip; encrypted balance ready for an EIP-712 reveal in your bet panel.
How it works
Each market is a standalone PredictionMarket contract using a Fixed-Product Market Maker (FPMM). Two cleartext reserves — yesReserve and noReserve — sit on the constant-product curve k = yesReserve · noReserve. Buying YES removes YES from the pool, pushing implied probability up; selling does the reverse. Prices stay between $0.00 and $1.00 and sum to one.
- Factory deploys a market and seeds it with cUSDT pulled from the treasury.
- Trader runs
setOperatoron cUSDT, then submitsbuyIntent. - Zama relayer publicly decrypts the transferred ciphertext (~10–60s on Sepolia).
executeBuyruns CPMM math, mints encrypted shares, forwards fee.- At resolve time, factory submits the outcome.
- Winners trigger
claimIntent→ relayer decrypt →executeClaim.
Buy flow · Intent → Execute
Buy, sell, and claim split into intent + execute. The intent locks an encrypted snapshot and marks it publiclyDecryptable; the execute step verifies the relayer's cleartext via FHE.checkSignatures and then settles. Latency between the two is the relayer round-trip, ~10–60s on Sepolia.
Frontend encrypts your trade size client-side. buyIntent calls cUSDT.confidentialTransferFrom, snapshots the actually-transferred ciphertext, marks it publicly decryptable.
const ct = await fhe.encryptU64(amount, market, user);
await market.buyIntent(side, ct.handle, ct.proof, minSharesOut);Frontend reads pendingBuyHandle(user), calls relayer.publicDecrypt(handle). Relayer returns cleartext + KMS proof.
const r = await fhe.publicDecrypt([handle]);
// { clearValues, decryptionProof }Anyone calls executeBuy(user, cleartext, proof). Contract verifies via FHE.checkSignatures, runs CPMM math, mints encrypted shares, forwards the protocol fee.
await market.executeBuy(user, cleartext, proof);Sells branchlessly clamp the user's input to their encrypted balance via FHE.le + FHE.selectin the intent step — selling more than you own can't leak comparison results because the clamp happens entirely in ciphertext. The clamped value is what gets revealed.
Decryption flows
Two paths take cleartext off-chain.
User signs an EIP-712 typed message; relayer returns a re-encryption keyed to the signer. Decryption happens client-side. Powers the Reveal button on every market page.
Used at executeBuy, executeSell, executeClaim. Cleartext + KMS proof submit on-chain; FHE.checkSignatures verifies before settlement.
Trading
- Connect wallet on Sepolia, open any market, enter cUSDT amount or shares.
- First interaction signs
cUSDT.setOperator(market, until)— a 24h permission. - Minimum bet is
0.01 cUSDTso fees never round to zero. - Each action is two wallet pops (intent + execute) plus a relayer wait between.
Fees
Flat platform fee of 1.50% on both buy and sell. Forwarded on-chain to Treasury via cUSDT.confidentialTransfer. No deposit/withdraw fees, no spread.
Capped on-chain by MAX_FEE_BPS = 500 (5%), owner-tunable below that.
Resolution
- YES / NO: winning shares redeem 1 cUSDT each via the 2-tx claim flow.
- CANCELLED: all positions refund — both YES and NO 1:1.
- After all winners claim, factory sweeps residual cUSDT back to treasury.
Contracts (Sepolia)
Live, Etherscan-verified addresses. Market addresses come from MarketFactory.getAllMarkets().
Stack & versions
Contracts
@fhevm/solidity 0.11.1
@fhevm/hardhat-plugin 0.4.2
@openzeppelin/contracts ^5.1.0
hardhat ^2.28.4
solc 0.8.27 + cancun + viaIR
Frontend
next 16.2.4
wagmi ^2.19
viem ^2.48
@rainbow-me/rainbowkit ^2.2
@zama-fhe/relayer-sdk 0.4.1Browser bundle requires Cross-Origin-Opener-Policy: same-origin + Cross-Origin-Embedder-Policy: require-corp for SharedArrayBuffer (configured in next.config.ts).
FAQ
Encrypting reserves would force FHE.mul/FHE.div on two ciphertexts per trade — gas explodes by orders of magnitude. Keeping reserves public is the practical FHEVM trade-off.
CPMM math needs cleartext numbers. The relayer's public-decryption oracle has to sign the value off-chain (~10–60s). Intent locks the snapshot; execute settles after the signed cleartext lands.
Per-tx amounts leak at execute-time. Cumulative position, total YES vs NO ratio, and final share count don't.
Hit the in-app Faucet — one tx, 5 cUSDT into your wallet.
Support
Source + issues: github.com/joymadhu49/fhe-market. Twitter: @zx_joy_.
FHE Market is provided "as is" on Sepolia for research and evaluation. Trading involves risk of loss; do not deposit funds you cannot afford to lose.