Non-custodial yield aggregation protocol built on the ERC-4626 tokenized vault standard. Users deposit underlying assets, receive vault shares, and earn yield passively as the protocol harvests returns from pluggable strategies.
Live on Sepolia — WETH Vault · USDC Vault
┌─────────────┐ ┌──────────────┐ ┌───────────────────┐
│ Frontend │────▶│ YieldVault │────▶│ IYieldStrategy │
│ Next.js 14 │ │ ERC-4626 │ │ (pluggable) │
└──────┬───────┘ └──────┬───────┘ └───────────────────┘
│ │
▼ ▼
┌─────────────┐ ┌──────────────┐
│ Subgraph │ │ AccessControl│
│ The Graph │ │ 3 roles │
└─────────────┘ └──────────────┘
Three layers:
- Vault Layer — ERC-4626 vaults that custody funds, manage share accounting, and enforce role-based access control (Governor, Guardian, Harvester).
- Strategy Layer — Pluggable yield strategies behind the
IYieldStrategyinterface. The vault callsdeposit(),withdraw(), andharvest()on whatever strategy is active. Strategies are upgradeable by governance without migrating user funds. - Automation Layer — ERC-4337 account abstraction via ZeroDev session keys for gasless recurring deposits with spending limits and time bounds.
- User deposits WETH or USDC into a vault and receives shares
deployIdleToStrategy()moves idle funds into the active strategy- The strategy generates yield over time
- A keeper (Chainlink Automation, cron bot, etc.) calls
harvest()periodically to realize yield — this is protocol-level, not user-facing - Harvest increases the vault's
totalAssets, so each share is worth more - User withdraws any time — shares redeem for principal + accumulated yield
- A performance fee (configurable, max 20%) is taken from harvested yield and sent to
feeRecipient
This is a Sepolia demo. The contracts, frontend, and subgraph are fully functional, but some components are simulated.
- YieldVault.sol — fully auditable ERC-4626 vault with role-based access, pause/unpause, inflation attack protection (6-decimal virtual offset), deposit limits, and emergency withdraw
- IYieldStrategy interface — the strategy abstraction layer. Any new strategy just implements this interface
- Frontend — deposit, withdraw, approve, faucet, activity feed, session key lifecycle, dark mode, all pages
- Subgraph — indexes Deposit, Withdraw, Harvested, Transfer events with aggregate tracking and daily snapshots
- Access control — Governor, Guardian, Harvester roles with proper separation of powers
| Component | What it does now | What production needs |
|---|---|---|
| MockYieldStrategy | Linear 5% APR from a pre-funded reservoir. Yield is real on-chain value but comes from a finite pool, not market activity | A real strategy that deploys to Aave, Compound, Lido, etc. Same interface, different implementation |
| MockERC20 tokens | mWETH and mUSDC with public mint(). Anyone can mint unlimited tokens |
Point vaults at canonical WETH/USDC on mainnet. Remove faucet button |
| Session keys | Signs EIP-712 typed data client-side, persists to localStorage. Demonstrates the UX flow | Wire up ZeroDev SDK with a funded paymaster to submit real UserOperations through the EntryPoint |
| Harvest automation | Manual — someone with HARVESTER_ROLE calls harvest() |
Chainlink Automation keeper or a cron job calling harvest on a schedule |
- Write a real
IYieldStrategyimplementation (e.g.,AaveV3Strategy.sol) that deposits into a lending protocol - Deploy new vaults pointing at canonical tokens (WETH, USDC) on your target chain
- Fund a ZeroDev paymaster for gasless session-key deposits
- Set up a Chainlink Automation keeper for periodic harvests
- Contracts would need to be redeployed — the vault and strategy addresses change
cadence/
├── contracts/ Foundry project — Solidity contracts + tests
│ ├── src/
│ │ ├── YieldVault.sol ERC-4626 vault
│ │ ├── MockYieldStrategy.sol Simulated yield strategy
│ │ └── interfaces/
│ │ └── IYieldStrategy.sol Strategy interface
│ ├── test/
│ │ ├── YieldVault.t.sol 23 unit + fuzz tests
│ │ └── invariants/ 3 invariant tests (solvency, shares, totalAssets)
│ └── script/
│ └── Deploy.s.sol Sepolia deployment script
├── frontend/ Next.js 14 + wagmi + viem + shadcn/ui
│ └── src/
│ ├── app/ Pages (/, /activity, /docs)
│ ├── components/ UI components
│ ├── hooks/ useVaultData, useVaultActions, useSessionKey
│ └── lib/ Contracts, utils, subgraph client
└── subgraph/ The Graph subgraph
├── schema.graphql 6 entities
├── src/yield-vault.ts Event handlers
└── tests/ 7 Matchstick tests
| Contract | Address (Sepolia) | Description |
|---|---|---|
| YieldVault (WETH) | 0x5A3d1215F6993534E58b4B669a11804e2CDea94F |
ERC-4626 vault for WETH |
| YieldVault (USDC) | 0x8D29A34E52b5f5aB15eca24326F698b088F8Ea72 |
ERC-4626 vault for USDC |
| MockWETH | 0x683C3Af05cce94dE2116f86B18B905d6C470240f |
Test token (18 decimals) |
| MockUSDC | 0x7BcCD3a3F460D769F6AdA465C5D7917AA7EC89Ce |
Test token (6 decimals) |
| WETH Strategy | 0x04E18F0Aceb1dc09c18f2C376A3F389F0Aff0C10 |
Mock 5% APR strategy |
| USDC Strategy | 0xE3e39625ab42dCC66bf7b855DE1cC312258A04E2 |
Mock 5% APR strategy |
All contracts are verified on Etherscan.
| Role | Permissions |
|---|---|
GOVERNOR_ROLE |
Set strategy, update fees, pause/unpause |
GUARDIAN_ROLE |
Pause vault, emergency withdraw from strategy |
HARVESTER_ROLE |
Call harvest() to realize yield |
- Inflation attack protection — 6-decimal virtual offset via
_decimalsOffset(), making first-depositor attacks uneconomical - Checks-Effects-Interactions pattern enforced throughout
- ReentrancyGuard as defense-in-depth
- SafeERC20 for non-standard token compatibility
- Performance fee hard cap — max 20% (2000 bps), enforced at the contract level
- Per-transaction deposit limit — prevents single-tx pool domination
# Contract tests (23 unit + 3 invariant suites)
cd contracts
forge test -vvv
# Subgraph tests (7 Matchstick tests)
cd subgraph
npx graph test- Fuzz tests: 1,000 runs — deposit/redeem roundtrip, multi-depositor, harvest fee distribution
- Invariant tests: 256 runs, depth 128 — solvency (vault never owes more than it has), share accounting, totalAssets consistency
- Node.js 18+
- Foundry
cd contracts
cp .env.example .env
# Fill in PRIVATE_KEY, SEPOLIA_RPC_URL, ETHERSCAN_API_KEY
# Run tests
forge test
# Deploy to Sepolia
forge script script/Deploy.s.sol --rpc-url $SEPOLIA_RPC_URL --broadcast --verifycd frontend
npm install
cp .env.example .env
# Fill in contract addresses from deployment output + RPC URL
npm run devcd subgraph
npm install
# Update addresses in subgraph.yaml and networks.json
# Then authenticate and deploy:
npx graph auth --studio <DEPLOY_KEY>
npx graph deploy --studio cadence-yield-vault --version-label v0.1.0After deployment, funds need to be deployed to strategies and harvested periodically:
# Deploy idle funds to strategy (anyone can call)
cast send <VAULT_ADDRESS> "deployIdleToStrategy()" --rpc-url $SEPOLIA_RPC_URL --private-key $PRIVATE_KEY
# Harvest yield (requires HARVESTER_ROLE)
cast send <VAULT_ADDRESS> "harvest()" --rpc-url $SEPOLIA_RPC_URL --private-key $PRIVATE_KEY- Contracts: Solidity 0.8.24, OpenZeppelin v5, Foundry
- Frontend: Next.js 14, TypeScript, wagmi v2, viem, shadcn/ui, Recharts
- Indexing: The Graph (Subgraph Studio), AssemblyScript mappings
- Account Abstraction: ZeroDev SDK, ERC-4337, session keys
- Fonts: DM Sans, JetBrains Mono