Back to projects

Lending Protocol

Over-collateralized DeFi lending with health-factor liquidations

Role
Protocol design, smart contracts, and frontend
Team
Solo
Status
Live
Timeline
2026-07 to Present
Solidity
Foundry
Next.js
wagmi
viem

Overview

A lending market built from scratch in Solidity. Depositors supply stablecoins and earn interest. Borrowers lock ETH as collateral and borrow against it. When a borrower's position deteriorates past a threshold, anyone can liquidate it and collect a bonus for doing so.

The whole system is four contracts and 27 Foundry tests. I built it to understand lending mechanics by implementing them rather than reading about them.

Lending Protocol landing page hero: 'Borrow stablecoins without selling your ETH,' with Launch App and Simulate a price crash buttons, and stat cards for 75% max LTV, 80% liquidation threshold, 5% liquidator bonus, and borrow APR from 2%

Problem

Lending without collateral requires knowing who you are lending to. On a public blockchain there is no credit check and no recourse, so the only enforceable guarantee is over-collateralization: the borrower must always have more locked up than they owe.

That turns the design problem into three questions. How much can someone borrow against a given deposit? What price do you trust when valuing their collateral? And when a position goes bad, who cleans it up, and why would they bother?

Architecture

UsersLiquidatorsLendingPoolInterestRateModelPriceOracledeposit · borrow · repayhealth factor < 1.0→ liquidatableutilizationpricesrepay debtcollateral + 5% bonus75% max LTV · 80% liquidation threshold
Users interact only with LendingPool. Rates and prices come from swappable modules; liquidators are the mechanism that keeps the pool solvent.

LendingPool holds the core logic: deposits, collateral accounting, borrowing, repayment, and liquidation. It is the only contract users interact with.

InterestRateModel prices borrowing off pool utilization: a 2% base rate plus a slope of 10× utilization. An empty pool is cheap to borrow from; a nearly drained one is expensive, which pulls in new deposits exactly when they are needed.

MockPriceOracle reports asset prices behind a swappable interface, so the production path is replacing one contract rather than rewriting the pool.

Lending Protocol's Simulate page: a Price simulation panel showing on-chain WETH and USDC oracle prices, a hypothetical WETH price slider, and a Health Factor impact panel with liquidation price and position readouts

ERC20Mock provides test tokens with differing decimals, WETH at 18 and USDC at 6, because decimal mismatches are where this class of contract usually breaks.

Key parameters: 75% max LTV, 80% liquidation threshold, 5% liquidation bonus. The gap between 75 and 80 is deliberate breathing room; a borrower at max LTV is not instantly liquidatable on the first tick of price movement.

Tech Stack

Solidity for the contracts, Foundry for testing and deployment scripts. Foundry over Hardhat because tests are written in Solidity. The fuzzing and the vm.expectRevert assertions are far more direct when the test speaks the same language as the contract.

The frontend is Next.js with wagmi and viem for wallet connection and contract calls. It deploys against a local anvil node or Sepolia.

Trade-offs

A mock oracle instead of Chainlink. Real price feeds bring heartbeat checks, staleness windows, and deviation thresholds, and every one of those is a decision about what to do when data is bad. I put a swappable interface in place and kept the mock, which makes the protocol testable and explicitly not production-ready. That is the honest trade: I understood the interface boundary, not yet the failure modes behind it.

Per-position interest accrual instead of a global index. Real protocols track a single cumulative index and derive each position's debt from it, one storage write per block regardless of user count. I accrue per position, which is far easier to read and reason about and scales badly. The right call for a protocol built to be understood; the wrong one for a protocol built to be used.

Single collateral asset. ETH only. Multi-asset collateral means per-asset risk parameters and a much larger liquidation surface. Keeping it to one let me get the health-factor mathematics right first.

Lessons Learned

The liquidation incentive is the actual product. I initially thought of it as cleanup, a safety valve at the edge of the system. It is not. If the bonus is too small nobody liquidates and the protocol accrues bad debt; too large and borrowers are punished for ordinary volatility. That single number decides whether the pool stays solvent, and it took writing the tests to see it.

Decimals cause more bugs than mathematics. Mixing 18-decimal WETH with 6-decimal USDC produced errors that looked like logic faults and were actually scaling faults. Testing with tokens of differing decimals from the start, rather than uniform 18-decimal mocks, surfaced them immediately.

Writing tests in Solidity changed how I designed the contracts. When a test can call an internal function directly and manipulate block state, exposing seams for testability stops feeling like a compromise.

GitHub
LinkedIn