Confidential payment streaming for DAOs, built on Zama's fhEVM.
StreamVeil lets an organization stream tokens to a recipient continuously (salary, grants, vesting) where every sensitive number β the deposit, the perβsecond rate, the amount claimed β stays encrypted endβtoβend using Fully Homomorphic Encryption. The chain enforces the accounting without ever seeing a plaintext amount. Only the sender, the recipient, and an optional auditor can decrypt their own figures, clientβside.
Schedule is public, money is private.
This is a fromβscratch fhEVM port of the StackStream Clarity protocol, rebuilt for Solidity + Zama and submitted to the Zama Builder Track.
- Live dApp: streamveil-kappa.vercel.app β connect a Sepolia wallet and use the built-in faucet (no real funds needed).
- Demo video: Watch the 2.5-minute walkthrough β β encrypting amounts in the browser, the opaque ciphertext on-chain, live accrual, claims, and auditor reveal.
Both contracts are deployed to the Sepolia testnet and verified on Etherscan β click to read the source:
| Contract | Address |
|---|---|
ConfidentialToken (svUSD, ERCβ7984) |
0xf98404FF4e1824AB64b244894c66c49cAD048461 |
| StreamVeil (protocol) | 0x4bb78Acf2696e660100048B728e817850d94f754 |
Run the dApp locally (cd frontend && npm install && npm run dev), connect a Sepolia wallet, and use the builtβin faucet β no real funds needed. Full walkthrough: docs/USERGUIDE.md.
| Doc | What's inside |
|---|---|
| docs/PROBLEM.md | Why onβchain payroll leaks compensation data, and what a real fix must do |
| docs/SOLUTION.md | The approach + the full technology stack (where Zama fhEVM, ERCβ7984 & the relayer SDK are used) |
| docs/IMPACT.md | The gap StreamVeil bridges and what it makes newly possible |
| docs/USERGUIDE.md | Stepβbyβstep walkthrough that doubles as an endβtoβend test checklist |
| docs/EVIDENCE.md | Verifiable onβchain proof β the transaction hash for each function, with what it demonstrates |
| Capability | Confidential? |
|---|---|
| Create a stream funded by an encrypted deposit | β amount encrypted |
| Continuous byβtheβsecond accrual | β rate encrypted |
| Recipient claims accrued funds anytime | β amounts encrypted |
| Pause / resume (resume extends the end date) | β |
| Cancel with automatic confidential refund of the unstreamed remainder | β |
| Topβup an existing stream | β |
| DAO / organization registry | metadata public |
| Auditor selective reveal (programmable compliance) | β optβin per stream |
The whole asset is a real ERCβ7984 confidential fungible token (OpenZeppelin), so balances and transfers are encrypted too β not just StreamVeil's internal bookkeeping.
βββββββββββββββββββββββββββββββββββββββββββββββ
Browser (Next.js) β Sepolia β
βββββββββββββββββββββ β ββββββββββββββββββ βββββββββββββββββ β
β @zama-fhe/relayer βencrypt β StreamVeil.sol β pull β ConfidentialTokenβ
β -sdk (WASM) ββββββΌβββΆβ (euint64 acct) βββββββΆβ (ERC-7984) β β
β β’ createEncrypted β β β β’ org registry β pay β β’ faucet/mint β β
β Input β handle β β β β’ create/pause β βββββββββββββββββ β
β β’ userDecrypt ββββββΌββββ /cancel/claimβ β
β (EIP-712 + ACL) βdecrypt β β’ FHE.allow ACLβ βββββββββββββββββ β
βββββββββββββββββββββ β βββββββββ¬ββββββββββ β Zama FHE β β
β β ciphertext ops β coprocessor + β β
β ββββββββββββββββββΆβ KMS / Gateway β β
ββββββββββββββββββββββββββββββββββββββββββββββββββ
Two contracts:
ConfidentialToken.solβ an OpenZeppelin ERCβ7984 (ConfidentialFungibleToken) used as the streamed asset, plus a publicfaucet()so the demo is selfβserve. Balances/transfers areeuint64.StreamVeil.solβ the protocol. Holds deposited tokens, tracks encrypteddeposit / rate / claimedper stream, and a plaintextstart / stop / pauseschedule. Pulls funds via ERCβ7984 operatorconfidentialTransferFromand pays out viaconfidentialTransfer, always using the returned actuallyβtransferred ciphertext to keep invariants exact.
Frontend: Next.js 16 (App Router) Β· TypeScript Β· Tailwind v4 Β· Zustand Β· TanStack Query Β· @zama-fhe/relayer-sdk for inβbrowser encryption and user decryption.
These are the design calls that make StreamVeil work well under FHE:
-
Privacy boundary: encrypt the money, not the clock. Amounts (
deposit,rate,claimed) areeuint64; the schedule (timestamps, status) is plaintext. This keeps all branching/time logic in ordinary Solidity (cheap, no FHE branches) and confines FHE to arithmetic. The right tradeoff for payroll: you hide salaries, not the fact that payday is monthly. -
Temporal accrual with timestamps. Continuous streaming is approximated with
block.timestamp. We banksecondsStreamedat each checkpoint plus a livelastCheckpoint, soaccrued = min(rate * activeSeconds, deposit). Pause simply banks the active seconds (no FHE write needed); resume shiftsstopTimeforward by the paused gap so the full deposit still streams, just later. -
Precision / no overflow.
rate = deposit / durationvia FHE scalar division (FHE.div(euint64, uint64)). Integer truncation guaranteesrate * duration β€ deposit, so theFHE.min(..., deposit)cap is exact and never overpays; any dust is refundable to the sender on cancel. Usingeuint64(noteuint256) matches ERCβ7984 and keeps gas/HCU sane. -
Permission system (ACL). After every state change, each encrypted field is granted with
FHE.allowThis+FHE.allow(field, sender)+FHE.allow(field, recipient)(+ auditor if set). Crossβcontract transfers useFHE.allowTransient(amount, token)so the token can operate on the ciphertext for exactly one transaction. Decryption happens clientβside via the relayer SDK (EIPβ712 signature βuserDecrypt); the contract never learns plaintext and there is no onβchain oracle roundβtrip in the hot path. -
Real confidential asset, not play money. We integrate OpenZeppelin's audited ERCβ7984 rather than faking balances. Deposits are pulled with the operator model (
setOperatorβconfidentialTransferFrom) and the returned transferred handle (capped at the payer's balance) is what we book β so underβfunding degrades gracefully instead of reverting on a value the contract can't see.
- Node β₯ 20
- A wallet (MetaMask) + some Sepolia ETH for the live demo
npm install
npm run compile
npm test # 7 passing β full flow with real FHE encrypt/decrypt in mock modeRun the scripted endβtoβend demo against a local node:
npx hardhat node # terminal 1
npm run demo:localhost # terminal 2 β faucet β stream β claim β auditor revealcp .env.example .env # set MNEMONIC (+ optionally a dedicated SEPOLIA_RPC_URL)
# Find the deployer address (first account from your mnemonic) and fund it
# with a little Sepolia ETH from a faucet before deploying:
npm run accounts:sepolia
# Deploy both contracts. This also writes the addresses + ABIs into
# deployments/<chainId>.json AND frontend/src/abi/deployment.json:
npm run deploy:sepoliaThe contracts inherit ZamaEthereumConfig, which wires the live Zama coprocessor
on Sepolia automatically β no extra coprocessor setup is required.
cd frontend
npm install
cp .env.local.example .env.local # already targets Sepolia (chainId 11155111)
npm run dev # http://localhost:3000
# production build
npm run build # webpack β reliable on Vercel/CI (default)
npm run build:turbopack # optional: Next 16's Turbopack builderIf you previously ran a localhost deploy, restart
npm run devafter the Sepolia deploy so the newdeployment.json(chainId 11155111) is picked up..env.localpins the app to Sepolia so the wallet's network gate stays in sync.
Build note:
npm run builduses the webpack builder, which compiles reliably on Vercel and CI (verified passing β static prerender of/and/_not-found, TypeScript checks included). Next 16's default Turbopack production build can hang on this project (the relayer SDK's WebAssembly), sobuild:turbopackis provided only as an opt-in.next devstill uses Turbopack and is unaffected.
- Connect a Sepolia wallet.
- Faucet +1,000 β mints 1,000 svUSD as an encrypted balance. Click Reveal to decrypt it locally (one EIPβ712 signature).
- New confidential stream β recipient + amount + duration. The amount is encrypted in your browser before it ever touches the chain.
- On the recipient's account, the stream card shows the live, byβtheβsecond accrual animating β computed clientβside from the decrypted rate. Hit Claim to withdraw.
- Pause / Resume / Cancel / Top up as the sender. Cancel refunds the unstreamed remainder confidentially; Top up adds encrypted funds and extends the end date.
- Add auditor β paste an address; that address can now
Revealthe stream's figures (programmable compliance), nobody else can.
- Onβchain: deposits, rates, accrued balances, claimed amounts and token balances are ciphertext handles. Validators, indexers and the public RPC see only opaque
bytes32. - Decryption is authorized & clientβside: a value is readable only by addresses on its FHE ACL. The relayer SDK proves authorization with an EIPβ712 signature; plaintext is returned to the user's browser, never posted onβchain.
- What is intentionally public: stream existence, participant addresses, organization metadata, and the schedule (start/stop/pause times). This is the deliberate "hide the salary, not the payday" model β adjustable if a use case needs more.
- Auditor reveal is explicit and perβstream: only the sender can add an auditor, and only to streams they own.
FHE operations are metered in HCU (Homomorphic Complexity Units) by the coprocessor in addition to EVM gas. StreamVeil keeps the hot paths lean:
pause/resumeperform zero FHE arithmetic β they only move plaintext checkpoints.claimdoes a fixed, small number of FHE ops:mul(scalar) +min+sub+add+ the token transfer.createStreamdoesfromExternal+ scalardiv+ the confidential pull.
Run a gas report with:
REPORT_GAS=true npm test(EVM gas only; HCU is reported by the coprocessor on testnet.)
test/StreamVeil.ts exercises the full lifecycle against the FHEVM mock with genuine encrypt/decrypt:
- faucet mints an encrypted balance
- create stream debits the sender's confidential balance
- accrual over time β recipient claims the exact encrypted amount
- pause freezes accrual; resume extends the end time
- cancel refunds the unstreamed remainder to the sender
- auditor authorization gates decryption (rejected before, succeeds after)
- access control (only sender pauses, only recipient claims)
streamveil/
βββ contracts/
β βββ ConfidentialToken.sol # ERC-7984 asset + faucet
β βββ StreamVeil.sol # core protocol
βββ test/StreamVeil.ts # full-flow mock tests
βββ scripts/
β βββ deploy.ts # deploy + emit frontend artifact
β βββ accounts.ts # print deployer address + balance
β βββ setCooldown.ts # owner: tune the faucet cooldown
β βββ demo.ts # scripted end-to-end demo
βββ deployments/<chainId>.json # generated addresses + ABIs
βββ docs/ # problem Β· solution Β· impact Β· user guide
βββ frontend/ # Next.js 16 dApp
βββ src/{app,components,lib,abi}
@fhevm/solidity 0.11 Β· @fhevm/hardhat-plugin Β· OpenZeppelin Confidential Contracts (ERCβ7984) Β· Hardhat Β· Solidity 0.8.27 (viaIR) Β· @zama-fhe/relayer-sdk Β· Next.js 16 Β· React 19 Β· Tailwind v4 Β· ethers v6 Β· Zustand Β· TanStack Query.
- Strong privacy: real ERCβ7984 confidential asset + encrypted protocol accounting + optβin auditor reveal.
- Real utility: confidential DAO payroll/grants/vesting β a concrete, repeatedlyβrequested confidentialβDeFi use case.
- Endβtoβend & demoable: compiles, 7 passing FHE tests, a oneβcommand scripted demo, and a polished dApp with live encrypted accrual.
- Best practices: audited OZ libraries, current
FHE/ACL APIs (no deprecatedTFHE.decrypt/reencrypt),viaIR, careful overflow/precision handling, clientβside decryption (no oracle hot path).
License: MIT.