Z Confidential Prize Pool

No-loss prize savings · Zama FHE · gasless

Win the yield.
Never risk the principal.
Keep every number private.

A confidential PoolTogether. Deposit, and your amount is encrypted before it ever leaves your device. Each round, one depositor wins the pooled yield — picked onchain by fully-homomorphic randomness, weighted by deposit size. Only the winner ever learns the outcome. Withdraw your principal anytime, in full.

🔒Deposits, balances, winnings, odds, and the winner are all ciphertext onchain — never plaintext.

Prize Ticket № 0x0c29 euint64 · sealed
🔒 Your pool balance 0x9af3·71c2·e8d4·0a5f
Odds 🔒 sealed Principal always yours

Why it's different

Public prize pool. Blind to everyone.

🔒

Encrypted by default

Every deposit is an ERC-7984 confidential transfer. Balances, the pooled total, each player's share and odds live onchain only as euint64 handles — decryptable by their owner alone.

🎲

The winner is drawn onchain

No offchain RNG, no oracle, no operator discretion. FHE.randEuint64 draws a ticket inside the EVM and settles it against encrypted balances — deposit-weighted, verifiable, blind.

🛟

No loss, no gas

You can only win. Principal is withdrawable in full at any time, clamped in encrypted math so a withdrawal never reveals a balance. Every action is gasless via an ERC-4337 smart account.

One round, end to end

How a round works

The full cycle runs onchain on Sepolia — every step a single gasless transaction from your smart account.

STEP 01

Faucet

Mint test USDC to your smart account to play with.

mint(account, 1,000e6)

STEP 02

Deposit

Approve → wrap to cUSDC → confidential transfer into the pool. The amount is encrypted client-side first.

confidentialTransferAndCall(pool, enc, proof)

STEP 03

Reveal

Decrypt your own balance and winnings with an EIP-712 signature. Nobody else can.

userDecrypt(handle)

STEP 04

Draw

The keeper triggers the round. A winner is chosen onchain over the encrypted balances.

draw() → FHE.randEuint64()

STEP 05

Claim

The winner — and only the winner — sees a non-zero prize and claims it confidentially.

claim() → confidentialTransfer

STEP 06

Withdraw

Take back your principal anytime, in full. Clamped in FHE so the amount never leaks.

withdraw(enc, proof) · no loss

Confidentiality design

What stays private — and what doesn't

Confidentiality is a design, not a slogan. Here is exactly what the chain can and cannot see, stated plainly.

CIPHERTEXT Hidden from everyone

  • Your deposit amount — encrypted on your device before it's sent.
  • Your balance & winningseuint64 handles, only you hold the key.
  • The pooled total — never a plaintext number onchain.
  • Each player's odds & share — derived from encrypted balances.
  • Who won — the prize lands as ciphertext; only the winner can decrypt it.

PUBLIC Visible by design

  • The set of depositors — addresses that joined are onchain.
  • The depositor count — the draw loops over it, so its length is observable.
  • Timing — when deposits, draws and withdrawals happen.
  • That a draw occurred — the event fires; the outcome inside it does not.

Leakage, acknowledged: membership and timing are the deliberate, documented trade-off — the minimum a permissionless onchain pool must expose to function. Every monetary quantity and the winner selection remain encrypted end to end.

The moat

A fair draw nobody can see

Picking a deposit-weighted winner over encrypted balances — without ever decrypting them — is the hard part. Here's the whole trick.

// deposit-weighted, exact, no encrypted division euint128 r = FHE.asEuint128(FHE.randEuint64()); euint128 ticket = FHE.mul(r, total128); // r · Σdeposits // walk encrypted prefix sums; first to cross wins prefix = FHE.add(prefix, bal128); threshold = FHE.mul(prefix, 2^64); crossed = FHE.lt(ticket, threshold); isWinner = FHE.and(crossed, notPrev); // award lands as ciphertext — only the winner decrypts it winnings[i] = FHE.add(winnings[i], FHE.select(isWinner, prize, 0));
01

Randomness stays in the EVM

FHE.randEuint64() produces an encrypted random value onchain — never generated offchain, never revealed.

02

Weighted by deposit, exactly

A ticket r · total is tested against each player's encrypted prefix sum scaled by 2⁶⁴ — bigger deposits span more of the range, with no encrypted division and no rounding.

03

Exactly one winner, revealed to one

The first crossing wins; euint128 math avoids overflow. The prize is added as ciphertext, so the pool itself can't tell who won.

Built on

The stack

Zama FHEVM · @fhevm/solidity ERC-7984 confidential tokens euint64 / euint128 encrypted math ERC-4337 smart accounts (Safe · EntryPoint v0.7) Pimlico paymaster — gasless EIP-712 user-decryption Sepolia testnet MV3 Chrome wallet · React + Vite

See it live

Try it yourself

The contract is deployed and running on Sepolia right now. Load the wallet extension and run a full round.

Deposit asset
cUSDC — confidential ERC-7984 wrapper of test USDC
Network
Sepolia (chainId 11155111) · gas sponsored

Run a round

  1. Open chrome://extensions and turn on Developer mode.
  2. Load unpacked → select the extension's build/ folder.
  3. Open the wallet → Prize Pool tab.
  4. Faucet → Deposit → Enable private view → Reveal → Draw → Claim → Withdraw.