Private prediction markets with sealed-bid privacy, commit-reveal integrity, and CRE-driven AI settlement.
- What is ShadowMarket?
- Core Features
- System Architecture
- Repository Layout
- How the Market Lifecycle Works
- Smart Contract Reference
- Prerequisites
- Installation
- Environment Variables
- Quick Start (Local Dev)
- Deployment Guide (Sepolia)
- Script Reference
- CRE Workflow Notes
- Frontend Notes
- Deployed Addresses
- Chainlink Usage
- Troubleshooting
- Security Notes
- License
ShadowMarket is a prediction market protocol where users commit hidden positions first and reveal later.
This design prevents pre-expiry information leakage and front-running by splitting bidding into two phases:
- Commit: user submits hash of
(amount, side, salt). - Reveal: user reveals the clear values after the market transitions to
RESOLVING.
Market outcomes are then settled by an authorized settler (owner or CRE DON receiver path), and winners claim a proportional payout from the total pool minus protocol fee.
- Commit-reveal bidding: private bid intent before reveal phase.
- USDC-denominated pools: contract uses Sepolia USDC address constant in production contract.
- Deterministic payout math: winners share
distributablePool = totalPool - feepro-rata. - CRE-compatible receiver interface:
onReport(bytes report, bytes signature)for signed report delivery. - Owner/authorized settler controls: create markets, transition status, settle, withdraw fees.
graph TD
U[User Wallet] --> FE[Next.js Frontend]
FE --> C[ShadowMarket.sol on Sepolia]
C -->|MarketExpired event| CRE[Chainlink CRE Workflow]
CRE -->|Confidential HTTP| OR[OpenRouter / Gemini]
CRE -->|Optional search grounding| S[Serper API]
CRE -->|Report submit| C
C --> U
- Owner creates market with expiry.
- Users submit sealed bid commitments.
- Owner transitions market to
RESOLVING(emitsMarketExpired). - Users reveal amount/side/salt and transfer USDC into pool.
- Settler resolves outcome (
settleMarketoronReport). - Winners call
claimWinnings.
.
├─ contracts/
│ ├─ ShadowMarket.sol # production contract (fixed Sepolia USDC constant)
│ ├─ ShadowMarketTest.sol # test contract (injectable USDC)
│ ├─ MockUSDC.sol # local/test token
│ └─ IReceiver.sol # CRE report receiver interface
├─ test/
│ └─ ShadowMarket.test.ts # unit/integration tests for core lifecycle
├─ scripts/
│ ├─ deploy.ts # Sepolia deployment + sample markets + env updates
│ ├─ check-env.ts # env validation checks
│ ├─ interact.ts # generic CLI interactions
│ ├─ demo-setup.ts # create demo market + optional mint to demo wallets
│ ├─ manual-settle.ts # manual settlement helper
│ └─ e2e-test.ts # lifecycle E2E on Hardhat
├─ cre-workflow/
│ ├─ workflow.yaml # CRE trigger/capability definition
│ └─ src/ # settlement logic, simulation, tests
├─ frontend/
│ ├─ app/ # Next.js routes
│ ├─ components/
│ └─ lib/ # ABIs, hooks, crypto helpers
└─ deployments/
└─ sepolia.json # latest deployment metadata
ownercallscreateMarket(question, expiryTimestamp).- Market starts in
OPENstatus.
- User computes commitment hash:
keccak256(abi.encode(amount, side, salt))- User submits
submitSealedBid(marketId, commitmentHash).
ownercallssetResolvingStatus(marketId).- Emits
MarketExpired(marketId, question, expiry).
- User calls
revealBid(marketId, amount, side, salt). - Contract checks hash match and transfers USDC from user to pool.
- Authorized entity calls
settleMarket(...)or CRE callsonReport(...). - Market moves to
SETTLEDwith outcome and rationale.
- Winning users call
claimWinnings(marketId). - Contract applies fee and pays proportional share.
contracts/ShadowMarket.sol
OPEN = 0RESOLVING = 1SETTLED = 2
createMarket(string question, uint256 expiryTimestamp)submitSealedBid(uint256 marketId, bytes32 commitmentHash)setResolvingStatus(uint256 marketId)revealBid(uint256 marketId, uint256 amount, bool side, bytes32 salt)settleMarket(uint256 marketId, bool outcome, string rationale)onReport(bytes report, bytes signature)claimWinnings(uint256 marketId)
Given:
winningPoollosingPooltotalPool = winningPool + losingPoolfee = totalPool * 200 / 10000(2%)distributable = totalPool - fee
Then each winner receives:
- Node.js
>= 20 - npm
>= 8 - Sepolia RPC provider URL
- Wallet private key funded with Sepolia ETH
git clone <your-fork-or-this-repo-url>
cd ShadowMarket-main
npm installOptional full bootstrap (root + subprojects + compile/build):
npm run bootstrapThere is currently no committed .env.example, so create .env manually at repo root.
PRIVATE_KEY=0x...
SEPOLIA_RPC_URL=https://...
ETHERSCAN_API_KEY=...
# Used by script checks and/or workflow integration docs
GEMINI_API_KEY=...
CHAINLINK_CRE_DON_ID=...
CRE_DON_ADDRESS=0x...
# Needed by interact/manual scripts
SHADOWMARKET_CONTRACT_ADDRESS=0x...
# Optional for demo setup minting
DEMO_WALLET_1=0x...
DEMO_WALLET_2=0x...NEXT_PUBLIC_SHADOWMARKET_ADDRESS=0x...
NEXT_PUBLIC_CHAIN_ID=11155111
NEXT_PUBLIC_SEPOLIA_RPC=https://rpc.sepolia.org
NEXT_PUBLIC_REPO_URL=https://github.com/<you>/<repo>SHADOWMARKET_CONTRACT_ADDRESS=0x...
SEPOLIA_RPC_URL=https://...
CRE_SIGNING_KEY=0x...
# Depending on flow variant in use:
OPENROUTER_API_KEY=...
SERPER_API_KEY=...
GEMINI_API_KEY=...Validate root environment:
npm run check-envnpx hardhat compile
npx hardhat testnpx hardhat run scripts/e2e-test.tscd frontend
npm install
npm run devFrontend default URL:
http://localhost:3000
Deploy contracts and seed markets:
npm run deploy:sepoliascripts/deploy.ts performs:
- Deploy
MockUSDCon non-mainnet. - Deploy
ShadowMarket. - Set
authorizedSettlerifCRE_DON_ADDRESSis set. - Attempt to create three sample markets (skips if expiry already past).
- Write deployment file to
deployments/sepolia.json. - Update
cre-workflow/.envandfrontend/.env.localwith contract address.
Verify contract (if desired):
npx hardhat verify --network sepolia <SHADOWMARKET_ADDRESS>npm run deploy:sepolia— deploys contracts to Sepolia.npm run test:all— Hardhat tests + CRE workflow tests.npm run bootstrap— installs dependencies and builds subprojects.npm run demo:setup— creates a demo market and optional demo mints.npm run demo:settle— helper for manual settlement.npm run check-env— validates key env variables and connectivity.
npx ts-node scripts/interact.ts --command create-market --question "Will X happen?" --expiry <unix_ts>
npx ts-node scripts/interact.ts --command submit-bid --market-id 1 --commitment 0x...
npx ts-node scripts/interact.ts --command reveal-bid --market-id 1 --amount 100 --side yes --salt 0x...
npx ts-node scripts/interact.ts --command settle-market --market-id 1 --outcome yes --rationale "..."
npx ts-node scripts/interact.ts --command claim --market-id 1npx ts-node scripts/manual-settle.ts --market-id 1 --outcome yes --rationale "optional rationale"The repo contains CRE workflow assets in cre-workflow/:
workflow.yamldefinesevm_logtrigger forMarketExpired(uint256,string,uint256).- Capabilities include confidential HTTP, EVM read, and EVM write.
- Settlement logic lives in
src/settlement.tsandsrc/workflow.ts.
cd cre-workflow
npm install
npm testcd cre-workflow
npm run buildCurrent cre-workflow/src files include multiple settlement/runtime variants and may require reconciliation before production deployment to a DON. Treat this folder as an active integration workspace, not yet final production wiring.
- Next.js app is in
frontend/. - Reusable contract hooks and crypto helpers are present in
frontend/lib/for real wallet interactions.
cd frontend
npm install
npm run dev
npm run build
npm run startFrom deployments/sepolia.json:
- Network:
sepolia - ShadowMarket:
0x3881DFC77ABFc85b4aDe32D998FA2fd2229F7290 - MockUSDC:
0xF56d05d89f0373b7414D8a6FdBB6f293e7dBDeE7 - Deployer:
0x9f2EdCE3a34e42eaf8f965d4E14aDDd12Cf865f4 - Timestamp:
2026-03-01T00:18:13.056Z
Contract constant in production ShadowMarket.sol:
- Sepolia USDC:
0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238
ShadowMarket actively uses the Chainlink Confidential Runtime Environment (CRE) to trustlessly resolve prediction market outcomes via AI consensus.
When a market expires, the contract emits a MarketExpired event, which triggers the off-chain Chainlink CRE workflow. This workflow uses confidential HTTP capabilities to perform search grounding (fetching real-world data like historical crypto prices via the Serper API) and passes this context to dual LLM models (Gemini Flash & Claude via OpenRouter) to determine the outcome. Once consensus is reached, the CRE encodes a payload and securely writes the settlement report back on-chain.
You can view the specific Chainlink CRE implementation logic here:
- CRE Workflow Definition: workflow.yaml
- EVM Trigger & Execution: cre-workflow/src/workflow.ts
- AI Outcome Consensus Logic: cre-workflow/src/settlement.ts
- Never commit private keys or API keys.
- Use dedicated deployment wallets for testnet/prod.
- Review and test settle authorization paths before mainnet usage.
- Consider adding report-signature verification in
onReportfor stronger trust minimization.
MIT