Bet without revealing. Verify without exposing. Markets without manipulation.
NullCast is the first fully confidential prediction market protocol built on Fully Homomorphic Encryption (FHE). Using Zama's FHEVM on Ethereum Sepolia, NullCast enables users to place bets on real-world and crypto outcomes while keeping individual position sizes completely private — even from the protocol itself.
Built for: Zama Developer Program Season 2 — Builder Track
User enters bet amount (plaintext)
│
▼
FHEVM SDK encrypts amount client-side
│
▼
Encrypted bet submitted on-chain (euint64)
│
▼
FHE arithmetic updates encrypted pool totals
│
▼
Aggregate odds publicly decryptable ──► UI shows live odds
Individual positions stay private ──► Only owner can decrypt
| Property | Description |
|---|---|
| Position Privacy | Individual bet amounts are encrypted euint64 values, never publicly visible |
| Live Odds | Aggregate pool totals are publicly decryptable — odds update in real-time |
| No Front-Running | Position sizes hidden, preventing MEV and whale manipulation |
| Composable | Built on ERC-7984 cUSDT, compatible with any FHEVM-native protocol |
| Permissionless | Anyone can create a market via the factory contract |
| Reputation-Gated | Optional encrypted reputation score for market participation |
nullcast/
├── contracts/ # Hardhat project — Solidity + FHEVM
│ ├── contracts/
│ │ ├── NullCastMarket.sol # Core FHE prediction market
│ │ ├── NullCastFactory.sol # Permissionless market creation
│ │ ├── LiquidityPool.sol # LP deposits + fee distribution
│ │ ├── OracleMock.sol # Sepolia demo resolution
│ │ ├── ReputationGate.sol # Encrypted reputation scores
│ │ ├── interfaces/
│ │ └── mocks/
│ ├── test/ # 79 tests (unit + integration)
│ ├── scripts/ # Deploy + seed scripts
│ └── deployments/sepolia.json # Deployed addresses
│
├── client/ # Next.js 14 frontend
│ └── src/
│ ├── app/ # App Router pages
│ ├── components/ # UI components
│ ├── hooks/ # wagmi contract hooks
│ ├── lib/ # Config, contracts, store
│ ├── constants/ # ABIs + addresses
│ └── styles/ # Design system CSS
│
└── SPEC.md # Full technical specification
| Contract | Address | Etherscan |
|---|---|---|
| MockcUSDT | 0x904793F739dA238686078dDC477CeFD2a071F9F9 |
View |
| OracleMock | 0xF01fa4F99146A938633E06DC8C9B3CE72778a846 |
View |
| ReputationGate | 0xfC448A571c0bEBf9B7f5AfA2bac89137F460DeA8 |
View |
| NullCastFactory | 0x5BEe5fae827Cebd188A44C55d89323693888c059 |
View |
| VaultFactory | 0xe3675c64a72eFc3e89105B826720Eb7E9f956f8a |
View |
Each market gets a paired LiquidityPool. Each vault is deployed by VaultFactory.
| Market | Address | Type | Category |
|---|---|---|---|
| BTC above $90k on Apr 30? | 0xF539719aDf6646D0842aCECbBB1190EeecAE8F55 |
Binary | CRYPTO |
| ETH above $2k on May 5? | 0xA04f5885f979FC925487327aA027C7781BEec5BC |
Binary | CRYPTO |
| BTC price range May 10 | 0xf2848303d93149dF0113fE8c13404A07b99924C4 |
Scalar (3 buckets) | CRYPTO |
- Node.js >= 20
- npm >= 7
- Sepolia ETH (for contract interaction)
cd contracts
npm install
cp .env.example .env # Fill in your Sepolia RPC, private key, Etherscan key
# Run tests (79 passing)
npx hardhat test
# Deploy to Sepolia
npx hardhat run scripts/deploy.ts --network sepolia
# Seed demo markets
npx hardhat run scripts/createDemoMarkets.ts --network sepolia
# Keeper: update odds (run periodically)
npx hardhat run scripts/oddsKeeper.ts --network sepolia
# Keeper: compute reputation scores (run per epoch)
npx hardhat run scripts/computeScores.ts --network sepoliacd client
npm install
# Create .env.local with:
# NEXT_PUBLIC_WALLET_CONNECT_ID=your_walletconnect_project_id
# NEXT_PUBLIC_SEPOLIA_RPC_URL=your_sepolia_rpc_url
npm run dev # http://localhost:3000ENCRYPTED (never publicly readable):
├── userPositions[address] — individual bet amounts (euint64)
├── userWinnings[address] — individual payout amounts (euint64)
├── lpShares[address] — individual LP positions (euint64)
└── reputationScore[address] — individual reputation (euint8)
PUBLICLY DECRYPTABLE (aggregate, revealed via makePubliclyDecryptable):
├── totalYesPool — sum of all YES bets (euint64)
├── totalNoPool — sum of all NO bets (euint64)
└── totalLiquidity — sum of all LP deposits (euint64)
Every encrypted value follows strict access control:
// User places a bet:
FHE.allowThis(userPositions[msg.sender]); // contract can compute
FHE.allow(userPositions[msg.sender], msg.sender); // user can decrypt
// Pool totals — anyone can request decryption:
FHE.makePubliclyDecryptable(totalYesPool);- Binary — YES/NO outcome, two pools
- Scalar — Multiple buckets (e.g., price ranges), N pools
- Categories — Markets tagged with
bytes32category (CRYPTO, MACRO, EQUITY, SPORTS, TECH). Filterable on the frontend.
createMarket() → OPEN → placeBet() → EXPIRED → resolveMarket() → RESOLVED → claimWinnings()
Reputation scores are encrypted on-chain (euint8, 0-100), computed by a protocol keeper from on-chain signals:
| Input | Weight | Source |
|---|---|---|
| Wallet age | 40pts max | Block history |
| Transaction count | 40pts max | On-chain activity |
| NullCast participation | 20pts max | Auto-tracked per bet |
Scores decay at 5 points per 7-day epoch of inactivity.
Tier system — derived from threshold checks (meetsThreshold), not the raw score:
| Tier | Threshold | Access |
|---|---|---|
| Oracle | ≥ 80 | Top-tier markets, max position sizes |
| Strategist | ≥ 60 | High-stakes markets |
| Analyst | ≥ 40 | Standard markets |
| Explorer | ≥ 20 | Basic markets |
Anyone can verify a user's tier via meetsThreshold(user, threshold) → returns encrypted boolean. The actual score is never revealed publicly — only the user can decrypt it.
Participation is tracked automatically: NullCastMarket.placeBet() calls ReputationGate.recordParticipation() on every bet.
Keeper script: npx hardhat run scripts/computeScores.ts --network sepolia computes scores for active users per epoch.
Markets have a 24-hour dispute window (~7200 blocks) after resolution:
- Oracle resolves the market
- 7200-block window opens — anyone can call
raiseDispute()with a bond - If disputed, claims are frozen pending owner review
- Owner calls
resolveDispute(upheld, newOutcome)— if upheld, outcome is reversed and a new window opens; if rejected, original outcome stands - After window closes with no dispute, winners can claim
Managers create vaults, followers deposit cUSDT:
- Manager creates a vault with a name, required reputation tier, and performance fee
- Followers deposit encrypted cUSDT into the vault
- Manager places bets from vault funds across markets
- Individual allocations are encrypted — followers see the vault's aggregate performance
- Vault closure: manager closes the vault, followers withdraw
Contracts: StrategyVault.sol + VaultFactory.sol
122 tests passing across 9 test files:
NullCastMarket: 39 tests — placeBet, odds, resolve, claim, dispute, categories, admin
NullCastFactory: 17 tests — creation, LP pool deployment, validation, admin
LiquidityPool: 8 tests — deposits, withdrawal, LP tracking
OracleMock: 7 tests — registration, resolution
ReputationGate: 13 tests — scoring, decay, threshold
StrategyVault: 17 tests — deposit, withdraw, close, access control
VaultFactory: 10 tests — create, registry, views
Integration: 2 tests — full YES/NO lifecycle with factory + oracle
Scalar: 4 tests — bucket betting, validation
| Tool | Purpose |
|---|---|
| Solidity ^0.8.24 | Contract language |
| Hardhat 2.x | Development framework |
| @fhevm/solidity 0.11 | FHE types + operations |
| @fhevm/hardhat-plugin | Local mock FHEVM for testing |
| OpenZeppelin 5.x | Pausable, Ownable, ReentrancyGuard |
| Tool | Purpose |
|---|---|
| Next.js 14 | React framework (App Router) |
| TypeScript | Type safety |
| Tailwind CSS | Utility styling |
| RainbowKit | Wallet connection |
| wagmi v2 + viem | Ethereum interaction |
| @zama-fhe/sdk | Client-side FHE encryption + user decryption |
| Zustand | State management (persisted to localStorage) |
- Mock oracle — single EOA resolution for demo (production: Chainlink/UMA)
- Gas costs — FHE operations are expensive (~500k-2M gas per bet)
- Async odds — 5-15 second delay between bet and odds update
- No dispute mechanism — resolution is final once submitted
See SPEC.md for the full technical specification including future work and production oracle design.
MIT
NullCast — Confidential Finance Track | Builder Track Deployed on Ethereum Sepolia | Powered by Zama FHEVM