
CanopySplit
Turn Idle Capital into Urban Shade
Demo
About
The problem it solves **1. Passive Climate Impact Without Losing Principal** - Deposit WETH (or any ERC-20 asset) into yield-generating strategies - **Keep 100% of your principal** - withdraw anytime - **All profits automatically fund climate projects** - tree planting, MRV (monitoring/reporting/verification), maintenance - No manual donations, no performance fees, no middlemen **2. Transparent, Epoch-Based Allocation** - **Dynamic splits**: Owner can adjust future epoch weights (e.g., 50% Planters, 30% MRV, 20% Maintenance) - **On-chain receipts**: Every distribution emits `Distributed` events with full breakdown - **Live tracking**: Frontend shows pending donations, lifetime donated, current epoch policy **3. Multi-Source Yield Aggregation** - **Aave v3 Integration**: Deposits earn yield through aTokens (18-decimal WETH on Sepolia) - **ERC-4626 Wrapper**: Standard vault interface for composability - **Uniswap v4 Hook (Local PoC)**: Swap fees can also donate to the same splitter **4. Role-Based Governance** - **Management/Keeper**: Can trigger `report()` to realize profits → mint donation shares - **Owner**: Can adjust epoch weights and roll to new epochs - **Users**: Just deposit/withdraw; yield flows automatically
How it's made
Challenges we ran into ### **Challenge 1: Sepolia Gas Cap Hell (16.7M limit)** **The Bug:** ```bash Error: server returned an error response: error code -32000: transaction gas limit too high (cap: 16777216, tx: 25000000) # Then when we lowered it: Error: execution reverted: EvmError: OutOfGas (gas: 16711680) # Foundry said "Deployed to: 0x..." but: cast code 0xA487... --rpc-url $SEPOLIA_RPC_URL # Returns: 0x (NO CODE!) ``` **What Was Happening:** - Deploying `YieldDonatingTokenizedStrategy` implementation hit Sepolia's **hard per-tx gas cap of 16,777,216** - Contract creation bytecode was **too large** even with optimizer enabled - Foundry printed "Deployed to..." **even when the tx reverted** (misleading!) - We tried 15.7M gas → OOG. Tried 16.7M → rejected by node. Tried 16.6M → OOG again. **How We Fixed It:** ```toml foundry.toml - AGGRESSIVE size reduction [profile.default] optimizer = true optimizer_runs = 1 # Minimize runtime size via_ir = false # Reduce init complexity bytecode_hash = "none" # Remove metadata cbor_metadata = false # Remove CBOR ``` ```bash # Force clean rebuild forge clean && forge build -vvv # Deploy just under cap with legacy tx forge create YieldDonatingTokenizedStrategy \ --gas-limit 0xFD0000 \ # 16,646,144 (under cap) --gas-price 2gwei \ --legacy \ # Avoid EIP-1559 estimation quirks --broadcast -vvvv VERIFY it actually exists: cast code $IMPL --rpc-url $SEPOLIA_RPC_URL | wc -c # > 2 = success! cast call $IMPL "apiVersion()(string)" # "1.0.0" ✅ ``` **Result:** Successfully deployed implementation at `0xE668230D8F3289F9e252cA67Cc7dbEDaE9dB90E5` --- ### **Challenge 2: USDC Supply Cap Full on Sepolia** **The Bug:** ```bash Tried to deploy ATokenVault for USDC: Error: call to non-contract address 0x2f39d218133AFaB8F2B819B1066c7E434Ad94E9e Then when we used correct PoolAddressesProvider: Deposits would fail with "supply cap reached" or liquidity issues ``` **What Was Happening:** - Aave v3 Sepolia USDC market had **supply cap limits** - Test USDC addresses on Sepolia were **fragmented** (multiple versions) - PoolAddressesProvider address from mainnet docs **doesn't exist on Sepolia** **How We Fixed It - THE WETH PIVOT:** ```bash Switched to WETH (18 decimals, no supply cap issues) export USDC_UNDERLYING=0xC558DBdd856501FCd9aaF1E62eae57A9F0629a3c # WETH! export AAVE_ADDRESSES_PROVIDER=0x012bAC54348C0E635dCAc9D5FB99f06F24136C9A # Correct Sepolia Deploy ATokenVault for WETH forge script script/DeployVault.s.sol:DeployVault \ --rpc-url $SEPOLIA_RPC_URL --broadcast -vv SUCCESS: ATokenVault (proxy): 0x6938238e57CBe1b4B51Eb3B51389cEf8d3a88521 ProxyAdmin: 0x119e5211b41f44362e6681e7EDF5fC07a46D7A4a Factory: 0x86b0e29414501643320B05AfF836D699681E450e ``` ### **Challenge 3: Permit2 Allowance Spender Confusion (Uniswap v4)** **The Bug:** ```bash Swap reverted with custom error: Error: custom error 0xe450d38c # InsufficientAllowance-style error Trace showed: Currency0::transferFrom(deployer, PoolManager, 1e18) └─ ← [Revert] custom error 0xe450d38c ``` **What Was Happening:** - We granted Permit2 allowance to **PoolManager** as spender - But the **Router** is the one that calls `permit2.transferFrom()` during settle - Permit2 checks: `allowance[user][token][spender]` where spender = **Router**, not PoolManager! **How We Fixed It:** ```solidity // script/03_Swap.s.sol - BEFORE (wrong): permit2.approve(address(token0), address(poolManager), type(uint160).max, type(uint48).max); // AFTER (correct): // 1. Grant ERC20 approval to Permit2 token0.approve(address(permit2), type(uint256).max); // 2. Grant Permit2 allowance with Router as spender permit2.approve(address(token0), address(swapRouter), type(uint160).max, type(uint48).max); // 3. Also keep direct ERC20 approval to Router (fallback) token0.approve(address(swapRouter), type(uint256).max); ``` **Result:** Swap executed successfully, hook emitted `DonationExecuted`, recipients received donations!