Privacy-preserving payroll streaming and proof-of-income attestations powered by Zama's fhEVM
PayProof enables employers to stream encrypted salaries on-chain, employees to decrypt their earnings privately, and third-party verifiers to request income threshold proofs without ever learning the actual salary amounts. Built with fully homomorphic encryption (FHE) using Zama's fhEVM technology.
PayProof solves a critical privacy problem in blockchain-based payroll: how to prove income eligibility (for loans, rentals, etc.) without revealing your exact salary. Traditional blockchain transactions are public, making salary information visible to everyone. PayProof uses fully homomorphic encryption to keep salary data encrypted on-chain while still enabling:
- โ Real-time salary streaming with encrypted rates
- โ Private balance decryption for authorized parties only
- โ Zero-knowledge income threshold verification
- โ Tiered attestations (A/B/C) without revealing exact amounts
EncryptedPayroll.sol โ Confidential lockup & streaming
- Encrypted state machine: Streams are represented as structured records containing the encrypted rate, balances, unlock schedules, cliff/transferability flags, hook address, and numeric ID. Lifecycle transitions (Active โ Paused, Cancelled โ Settled) are enforced at the contract level so downstream apps can rely on consistent status semantics.
- Homomorphic balance math: Per-second rates (
euint64) accumulate into encrypted balances (euint128). Top-ups, withdrawals, and accruals all run inside fhEVM, keeping amounts shielded while still allowing deterministic balance queries. - Hook-driven composability: Streams can opt into encrypted callbacks (on withdraw/cancel) for allowlisted contracts such as the IncomeOracle. Hooks receive ciphertext handles, enabling integrations (staking, vaults, credit scoring) without leaking raw values.
- Access controls & settlement: Employers control cancelability, transferability, hooks, and top-ups; employees (or authorized hooks) can withdraw. Withdrawals zero out balances without disrupting active streams, while cancelled streams settle once their final withdrawal completes so NFTs can be recycled safely.
IncomeOracle.sol โ Threshold attestations with encrypted deltas
- Hook ingestion: Implements
IConfidentialLockupRecipientso the payroll contract pushes encrypted withdraw/cancel events. Paid and outstanding amounts are accumulated homomorphically, keeping a running encrypted ledger per stream. - Encrypted comparisons: Verifiers encrypt thresholds and lookback windows. The oracle combines projected income from
EncryptedPayrollwith accumulated paid totals, evaluates the comparison homomorphically, and emits encrypted meets/tier handles. - Tier policy: Tier C (meets threshold), Tier B (โฅ1.1ร), Tier A (โฅ2ร). Since the tier logic executes on ciphertexts, neither the contract nor verifiers learn the underlying salary.
- Queryable handles: Utility getters (
encryptedPaidAmount,encryptedOutstandingOnCancel) let wallets/UI display encrypted balances or request temporary decryption via fhEVM SDKs, keeping the UX responsive while preserving privacy.
Three-persona architecture:
/employer- Create encrypted streams, view active streams, manage payroll/employee- View encrypted streams, decrypt salary amounts, generate proofs/verify- Submit encrypted thresholds, receive tiered attestations
Tech Stack:
- Next.js 15 with App Router
- Zama fhEVM Relayer SDK (
@zama-fhe/relayer-sdk) - fhEVM TypeScript SDK for React (
fhevm-ts-sdk) - Wagmi + Viem for wallet connectivity
- ethers.js for contract interactions
- TailwindCSS for styling
- EncryptedPayroll:
0x6fa3a1adc06fefea333a1ce82b4e36fac539ed9d - IncomeOracle:
0x6cb0aff85dc4fe6afc6d45a2a25334c3d53a802e
- Each payroll stream now mints a transferable (unless explicitly disabled) ERC-721 token to the employee. Wallets can enumerate active and historical streams with standard NFT calls (
balanceOf,tokenOfOwnerByIndex). - For sender views and analytics, use the Graph subgraph in
subgraph/. Configure the endpoint viaNEXT_PUBLIC_PAYPROOF_SUBGRAPH_URLand follow the deployment steps insubgraph/README.md.
PayProof ships a readyโtoโdeploy subgraph under subgraph/. These steps compile the mappings and push them to Graph Studio under the slug pay-proof (as shown in the screenshot above). Adjust the slug/version if you fork the project.
-
Prerequisites
- Install pnpm (
npm i -g pnpm) and the Graph CLI (pnpm add -g @graphprotocol/graph-cli). - Ensure the PayProof contracts have been compiled so the ABI in
subgraph/abis/EncryptedPayroll.jsonis up to date:pnpm --filter @payproof/contracts build
- Install pnpm (
-
Install subgraph tooling & generate types
pnpm --filter @payproof/subgraph install pnpm --filter @payproof/subgraph run codegen pnpm --filter @payproof/subgraph run build
After the build you should see
subgraph/build/subgraph.yamlandEncryptedPayroll/EncryptedPayroll.wasm. -
Deploy to Graph Studio
-
In the Graph Studio UI create (or reuse) the slug
pay-proofand copy the deploy key (e.g.091d92f42ef1470dad9f7b793b97de26). -
Deploy from the repo root:
graph deploy \ --node https://api.studio.thegraph.com/deploy/ \ --access-token <DEPLOY_KEY> \ --version-label v0.1.1 \ pay-proof subgraph/subgraph.yaml
Notes:-
--access-tokenis the flag currently accepted by Studio (the CLI warns it will switch to--deploy-keyin the future).--version-labelcontrols the published version (v0.1.1,v0.2.0, etc.).
-
-
Configure the frontend
- Graph Studio returns the query endpoint in the deploy output, e.g.:
https://api.studio.thegraph.com/query/1704881/pay-proof/v0.1.1 - Add this to
apps/web/.env:NEXT_PUBLIC_PAYPROOF_SUBGRAPH_URL="https://api.studio.thegraph.com/query/1704881/pay-proof/v0.1.1" - Rebuild/redeploy the web app so employers load stream lists via the subgraph.
- Graph Studio returns the query endpoint in the deploy output, e.g.:
-
Subsequent updates
- Bump the version label and rerun the same
graph deploycommand after editing mappings or schema. - If you redeploy contracts to a new network update
subgraph/subgraph.yaml(source.address,network,startBlock) before compiling.
- Bump the version label and rerun the same
- Node.js 20+
- pnpm (recommended) or npm
- MetaMask or compatible Web3 wallet
- Sepolia testnet ETH
# Clone the repository
git clone <repository-url>
cd PayProof
# Install dependencies
pnpm install
# Set up environment variables
cp apps/web/.env.example apps/web/.env
cp contracts/.env.example contracts/.env
# Configure your .env files with:
# - NEXT_PUBLIC_SEPOLIA_RPC_URL
# - NEXT_PUBLIC_PAYPROOF_PAYROLL_CONTRACT=0x6fa3a1adC06fefeA333A1ce82B4e36Fac539ed9D
# - NEXT_PUBLIC_PAYPROOF_ORACLE_CONTRACT=0xFe74a9453f216433A2ad70e06a9D241B29077BB8
# - NEXT_PUBLIC_PAYPROOF_SUBGRAPH_URL=https://api.studio.thegraph.com/query/1704881/pay-proof/v0.1.1
# - Private keys for deployment (contracts/.env)# Start development server
pnpm dev
# Access at http://localhost:3000# Run contract unit tests
pnpm contracts:compile
pnpm test:contracts
# Run end-to-end tests (Playwright)
pnpm test:e2eFrom the repo root:
pnpm contracts:compileโ compile all Solidity sources incontracts/pnpm contracts:cleanโ clear Hardhat artifacts/cachepnpm --filter @payproof/contracts exec -- hardhat run scripts/deployPayroll.ts --network sepoliaโ deploy the latestEncryptedPayrollpnpm --filter @payproof/contracts exec -- hardhat run scripts/deployOracle.ts --network sepoliaโ (re)deploy the oracle if neededpnpm --filter @payproof/contracts exec -- hardhat verify --network sepolia <CONTRACT_ADDRESS>โ submit verification to Etherscan (may require rerunning under Node 20 to avoid bytecode mismatches)
Before deploying or verifying, populate contracts/.env with SEPOLIA_RPC_URL, DEPLOYER_KEY, and ETHERSCAN_API_KEY.
- Connect wallet to Sepolia testnet
- Navigate to
/employer - Enter employee wallet address
- Set stream rate (ETH per month)
- Choose cadence (Monthly, Bi-weekly, Weekly)
- Click "Encrypt & Create Stream"
- Rate is encrypted client-side with fhEVM
- Transaction creates on-chain stream with encrypted rate
- View created streams in dashboard
- Connect wallet to Sepolia testnet
- Navigate to
/employee - See list of active encrypted streams
- Click on a stream to view details
- Click "๐ Decrypt Amount"
- Sign EIP-712 message to authorize decryption
- View decrypted salary rate (ETH/month)
- Only you and employer can decrypt this amount
- Connect wallet to Sepolia testnet
- Navigate to
/verify - Enter employer wallet address
- Set income threshold (ETH per month)
- Set lookback window (days)
- Click "Encrypt & Request Proof"
- Threshold is encrypted client-side
- Oracle performs encrypted computation
- Receive encrypted attestation (meets + tier)
- Click "๐ Decrypt Result"
- See threshold met status and tier (A/B/C)
- Never learn the exact salary amount
- Verifiers learn: โ Threshold met (yes/no) + Tier (A/B/C/None)
- Verifiers NEVER learn: โ Exact salary amount
- On-chain data: Only encrypted ciphertexts (handles)
- Decryption: Requires private key signature (employer/employee only)
Employer FHE SDK EncryptedPayroll Employee Hook / Oracle Lender
| | | | | |
| encrypt rate| | | | |
|-----------> | | | | |
|<--enc. rate-| | | | |
| createStream(encRate, proof) --------------------->| | |
| | |--mint NFT------->| | |
| | |--emit StreamCreated (enc handles)->| |
| | | | | |
|<=========== Both parties decrypt balances with fhEVM SDK ===========>| |
| | (time) | | | |
| |------------------->| syncStream accrues encrypted "accrued" |
| | | | | |
| topUp(encBonus, proof) --------------------------------------------->| |
| | |--buffered += encBonus-------------| |
| | |--emit StreamToppedUp-------------->| |
| | | | | |
| withdrawMax(streamKey, to) ------------------------>| | |
| | |--emit StreamWithdrawn (enc amount)->| |
| | |--notify onConfidentialLockupWithdraw--------------->|
| | | | |--update paid----|
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| |<------- encrypt threshold -----------| | |
| | |<--attestMonthlyIncome(encThreshold, proof, days)---|
| | |--projectedIncome + compare (all FHE) |
| | |--return enc meetsFlag + tier -->| |
| | | | | |
| | | | |<--decrypt result|
- Stream Initialization โ Employer encrypts the salary rate, calls
createStream, the contract mints the stream NFT to the employee, and both parties receive encrypted balance handles. - Stream Accrual โ
syncStreammultiplies the encrypted rate by elapsed time to grow the encrypted accrued balance. - Bonus Top-Up โ Employer encrypts a bonus amount and calls
topUp, adding the ciphertext to the encrypted buffered pool. - Withdrawal Process โ Employee, employer, or hook invokes
withdrawMax. The contract emits a fresh encrypted amount handle and any registered hook (e.g., IncomeOracle) updates paid totals through the callback. - Income Attestation โ A lender encrypts a threshold and calls
attestMonthlyIncome. The oracle compares projected income vs. threshold completely under encryption and returns encrypted yes/no + tier results that only the lender can decrypt.
EncryptedPayroll.sol (35+ tests)
- Stream creation and lifecycle
- Balance accrual over time
- Top-up functionality
- Pause/resume operations
- Balance tracking and queries
- Stream cancellation
- Encrypted balance handle refresh after sync
- Edge cases (small/large rates, multiple streams)
IncomeOracle.sol (30 tests)
- Basic attestation flow
- Tier calculations (A/B/C/None)
- Lookback window variations (7/30/60/90 days)
- Edge cases and boundary conditions
- Multiple streams and attestations
- Privacy guarantees
- Integration with payroll contract
- Gas optimization
- Employer flow: wallet connection, stream creation, encryption preview
- Employee flow: stream list, decryption, proof generation
- Stream detail view: wallet gating for decrypt actions
- Verifier flow: attestation form, threshold encryption, tier decryption
Note: Wallet-connected paths are planned once fhEVM/wagmi mocking is in place
When the frontend calls instance.userDecrypt it submits up to four handles bound to the payroll contract address:
| Handle | Symbol | Meaning | Decrypted Value |
|---|---|---|---|
stream.rateHandle |
r |
Encrypted rate per second | r (wei/s) |
stream.bufferedHandle |
B |
Accrued but still buffered amount | B (wei) |
stream.withdrawnHandle |
W |
Total withdrawn so far | W (wei) |
balanceHandle (from encryptedBalanceOf) |
A |
Withdrawable balance | A (wei) |
The fhEVM contracts guarantee the invariant A = (S + B) โ W, where S represents the encrypted accrual that is neither buffered nor withdrawn. Rearranging gives the core identity the UI uses after decryption:
streamed = A + W โ B
With these cleartext numbers the app derives all user-facing values:
- Monthly rate:
rate_per_month = r ร 30 ร 24 ร 60 ร 60 - Streamed total:
streamed = A + W โ B(fallbackmax(W โ B, 0)ifAis missing) - Buffered balance:
buffered = B - Withdrawn total:
withdrawn = W - Available to withdraw:
available = A - Outstanding debt:
debt = max(W โ streamed โ B, 0)
The handle array therefore fully determines the visible payroll metrics once decrypted, while the ciphertexts remain private on-chain.
- Solidity 0.8.24 - Smart contract language
- fhEVM - Fully homomorphic encryption for Ethereum
- Hardhat - Development environment
- @fhevm/hardhat-plugin - fhEVM testing and mocking
- Sepolia Testnet - Deployment network
- Next.js 15 - React framework with App Router
- React 19 - Latest React with enhanced features
- TypeScript - Type safety
- Wagmi - React hooks for Ethereum
- Viem - TypeScript Ethereum library
- Zama Relayer SDK - Client-side FHE encryption/decryption
- ethers.js - Ethereum wallet implementation
- TailwindCSS - Styling
- Hardhat Test - Solidity unit tests
- Playwright - End-to-end testing
- Chai - Assertion library
- Encrypted payroll stream creation
- Real-time balance accrual
- Client-side encryption/decryption
- Pause and resume streams
- Privacy-preserving attestations
- Tiered income verification
- Comprehensive test coverage (63+ tests)
- Sepolia deployment
-
Employee withdrawal functionality
- Encrypted withdrawal flow (continuous accrual + encrypted hooks)
- UI actions for withdraw & balance handle retrieval
StreamWithdrawnevent mirroring encrypted amount
-
Stream top-up UI
- Employer-side encrypted top-up tooling
- Frontend controls for fhEVM encryption + submission
- Stream detail page surfaces balance handles after top-up
-
Batch operations
- Create multiple streams in one transaction
- Bulk pause/resume streams
- Batch top-ups across multiple employees
-
Confidential ETH wrapper
- Deploy
ConfidentialFungibleTokenERC20Wrapperaround ETH - Support confidential employer funding and withdrawals
- Reuse wrapped balances for cross-contract private transfers
- Deploy
-
Stream templates
- Save commonly used stream configurations
- Quick-create streams from templates
- Department-based default rates
-
Multi-stream aggregation
- Prove combined income from multiple employers
- Encrypted sum of all active streams
- Cross-employer attestations
-
Time-based proofs
- Prove income stability over time periods
- Historical attestation archives
- Trend analysis without revealing amounts
-
Conditional payouts
- Milestone-based encrypted releases
- Performance-linked encrypted bonuses
- Scheduled rate adjustments
-
Privacy-preserving analytics
- Encrypted aggregate statistics
- Department-wide metrics without individual exposure
- Compliance reporting with FHE
-
DeFi integrations
- Use income attestations for undercollateralized loans
- Yield farming with income-based tiers
- Insurance products based on verified income
-
Multi-chain support
- Deploy to Ethereum mainnet with fhEVM
- Cross-chain attestation verification
- Bridge encrypted streams across chains
-
Verifier marketplace
- Decentralized verifier registry
- Reputation system for attestation quality
- Automated verification for common use cases (mortgages, rentals)
-
Mobile app
- React Native mobile wallet
- Push notifications for stream events
- Biometric decryption authorization
-
SDK and tooling
- JavaScript/TypeScript SDK for integration
- CLI tools for stream management
- Webhook notifications for events
-
Documentation
- Interactive tutorials
- Integration guides
- Video walkthroughs
-
Testing improvements
- E2E tests with wallet mocking
- fhEVM integration test environment
- Performance benchmarking suite
- Zero-knowledge proof integration for additional privacy layers
- Homomorphic encryption optimizations for gas reduction
- Privacy-preserving payroll tax calculations
MIT License - See LICENSE file for details
Built with Zama's fhEVM - Fully Homomorphic Encryption for Ethereum
Submitted for the Zama Builder Track by coderlu
Disclaimer: This is experimental software. Do not use in production without thorough security audits.
