A Zero-Knowledge lending protocol MVP built for Flow EVM testnet, featuring privacy-preserving collateral verification using Circom circuits and SnarkJS.
- Zero-Knowledge Proofs: Private collateral verification without revealing sensitive financial data
- Flow EVM Compatible: Deployed on Flow EVM testnet
- Modular Design: Separate verifier, oracle, and lending contracts
- Liquidation System: Automated liquidation of undercollateralized positions
- Price Oracle Integration: Chainlink price feeds for accurate collateral valuation
zk-lending-mvp/
βββ contracts/
β βββ ZKVerifier.sol # ZK proof verifier (stub for development)
β βββ ZKLending.sol # Main lending contract
β βββ ChainlinkPriceOracle.sol # Price oracle wrapper
βββ scripts/
β βββ deploy.js # Deployment script
βββ circuits/
β βββ collateral_check.circom # Circom circuit for collateral verification
β βββ input.json # Sample circuit input
β βββ generate_proof.js # Proof generation utility
β βββ build/ # Circuit compilation artifacts
βββ hardhat.config.js # Hardhat configuration
βββ package.json # Dependencies and scripts
βββ README.md # This file
- Node.js (v16 or higher)
- npm or yarn
- Git
git clone <your-repo-url>
cd zk-lending-mvp
npm installcp env.example .envEdit .env with your configuration:
# Flow EVM Configuration
PRIVATE_KEY=your_private_key_here
RPC_URL=https://testnet.evm.nodes.onflow.org
# Token Addresses (deploy or find existing tokens on Flow EVM testnet)
STABLE_TOKEN=0x...
COLLATERAL_TOKEN=0x...
# Chainlink Price Feed (if available)
CHAINLINK_FEED=0x...
# Other configurations...- Visit Flow EVM Testnet Faucet
- Get testnet FLOWT tokens for gas fees
- Deploy or find existing ERC20 token contracts for stable and collateral tokens
npm run compilenpm test# Start local Hardhat node
npm run node
# Deploy to local network
npm run deploy:localnpm run deploy:testnet# Update hardhat.config.js for mainnet
npm run deploy:mainnet# Complete circuit setup in one command
npm run circuit:full-setup
# Test the full flow (setup + proof generation)
npm run circuit:test# Install Circom and SnarkJS globally
npm install -g circom snarkjs
# Or install locally (already in package.json)
npm installnpm run circuit:compileThis generates:
circuits/build/collateral_check.r1cs- R1CS constraint systemcircuits/build/collateral_check.wasm- WebAssembly witness generatorcircuits/build/collateral_check_js/- JavaScript witness generator
npm run circuit:setupThis performs:
- Groth16 trusted setup ceremony
- Generates
collateral_check_final.zkey - Exports verification key
npm run circuit:export-verifierThis replaces contracts/Verifier.sol with the SnarkJS-generated verifier contract.
# Update circuits/input.json with your values
npm run circuit:generate-proofThis generates:
circuits/proof.json- ZK proofcircuits/public.json- Public signalscircuits/witness.wtns- Witness file
const lending = await ethers.getContractAt("ZKLending", lendingAddress);
await lending.depositCollateral(ethers.utils.parseEther("100"));// Update circuits/input.json with your values
const { generateProof } = require("./circuits/generate_proof");
const { proof, publicSignals } = await generateProof();// Format proof for SnarkJS verifier
const a = [proof.pi_a[0], proof.pi_a[1]];
const b = [
[proof.pi_b[0][0], proof.pi_b[0][1]],
[proof.pi_b[1][0], proof.pi_b[1][1]]
];
const c = [proof.pi_c[0], proof.pi_c[1]];
await lending.borrow(
ethers.utils.parseEther("50"),
a,
b,
c,
publicSignals
);const { ZKLendingClient } = require("./examples/frontend-integration");
// Setup client
const client = new ZKLendingClient(provider, lendingAddress, verifierAddress);
// Generate proof and borrow
const proofData = await client.generateBorrowProof({
userAddress: wallet.address,
collateralAmount: ethers.utils.parseEther("100").toString(),
collateralPrice: ethers.utils.parseEther("2000").toString(),
borrowAmount: ethers.utils.parseEther("50").toString(),
collateralizationRatio: "150"
});
await client.borrow(ethers.utils.parseEther("50"), proofData);await lending.repay(ethers.utils.parseEther("50"));await lending.withdrawCollateral(ethers.utils.parseEther("50"));The collateral_check.circom circuit verifies:
- Collateral Value Calculation:
collateralValue = collateralAmount Γ price - Maximum Borrowable:
maxBorrowable = (collateralValue Γ 100) / collateralizationRatio - Borrow Check:
borrowAmount β€ maxBorrowable - User Verification: Proof is tied to specific user address
userAddressHash: Hash of user address for verificationborrowAmountHash: Hash of requested borrow amountcollateralValue: Total collateral valuemaxBorrowable: Maximum amount user can borrowcanBorrow: Boolean indicating if user can borrow
- ZKVerifier Stub: Returns
truefor all proofs (development only) - Basic Reentrancy Protection: Uses OpenZeppelin's ReentrancyGuard
- Simple Price Oracle: Basic Chainlink integration
- Real ZK Verifier: Replace stub with generated verifier contract
- Comprehensive Testing: Unit tests, integration tests, circuit tests
- Security Audits: Professional security audit before mainnet
- Access Controls: Proper role-based access control
- Upgradeability: Consider proxy patterns for contract upgrades
npm test# Test circuit compilation
npm run circuit:compile
# Test proof generation
npm run circuit:generate-proof# Test full flow with local node
npm run node
npm run deploy:local
# Run integration tests...The contracts are optimized for gas efficiency:
- Solidity 0.8.19: Latest stable version with optimizations
- Compiler Optimizations: Enabled with 200 runs
- Efficient Storage: Minimal storage operations
- Batch Operations: Where possible, operations are batched
- Testnet:
https://testnet.evm.nodes.onflow.org(Chain ID: 545) - Mainnet:
https://mainnet.evm.nodes.onflow.org(Chain ID: 747)
You need to deploy or find existing ERC20 tokens on Flow EVM:
- Stable Token: USDC, USDT, or similar
- Collateral Token: ETH, FLOW, or other volatile assets
Check Flow EVM documentation for available Chainlink price feeds. If none are available, implement a custom oracle or use a trusted price relay.
- Verifier Contract: Replace placeholder with SnarkJS-generated verifier before deployment
- Mock Price Oracle: Use real price feeds in production
- Test Tokens: Use real tokens on mainnet
- Security Audits: Required before mainnet deployment
The project now includes complete SnarkJS integration:
- Real Verifier: Generated by
snarkjs zkey export solidityverifier - Proof Format: Standard SnarkJS format (a, b, c, publicSignals)
- Circuit Compilation: Automated with
npm run circuit:compile - Trusted Setup: Automated with
npm run circuit:setup - Proof Generation: Automated with
npm run circuit:generate-proof
- Trusted Setup: Requires secure ceremony for production
- Proof Generation: Can be computationally expensive (seconds to minutes)
- Verification Gas: ZK proof verification costs gas (~200k-500k gas)
- Circuit Size: Larger circuits = more gas for verification
- Replace
contracts/Verifier.solwith SnarkJS-generated verifier - Run full circuit setup:
npm run circuit:full-setup - Test proof generation:
npm run circuit:generate-proof - Deploy contracts:
npm run deploy:testnet - Test end-to-end flow with real proofs
- Security audit before mainnet
- Use real price oracles and tokens
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests
- Submit a pull request
MIT License - see LICENSE file for details
- Flow EVM Documentation
- Circom Documentation
- SnarkJS Documentation
- Hardhat Documentation
- OpenZeppelin Contracts
For questions and support:
- Create an issue in the repository
- Join the Flow Discord community
- Check the Flow EVM documentation