A dense, dark-mode frontend for Lendify, an Aave-style lending protocol deployed on Sepolia. Supply collateral, borrow against it, get liquidated if your health factor drops below 1.00 — all read and written straight from the chain. There's no backend, no database, and no indexer; every number on screen comes from a live contract call.
Built with Next.js 16 (App Router, Turbopack), wagmi v2, viem, and RainbowKit.
Live reserve data for all four listed assets — price, supply/borrow totals, APYs, utilization, LTV and liquidation threshold shown as separate columns.
Your position at a glance: total supplied/borrowed, net APY, health factor (color-coded, with an explainer for what triggers liquidation), and per-asset supply/borrow rows with a collateral toggle.
Opening the Supply modal for USDC — the amount input reads the wallet's real balance for the
Max button, not a cached figure.
Approvals are explicit two-step actions ("1. Approve" → "2. Supply") rather than a single button that silently asks for a second signature:
Paste a borrower address (or pick one from Borrow event logs) to inspect their health factor
and, if it's below 1.00, liquidate part of their debt for a collateral bonus.
Owner-gated reserve configuration — freeze/activate/enable-borrowing per asset, adjust LTV and liquidation parameters, pause the whole pool, and (testnet-only) push fresh prices through the mock price feed.
pnpm install
pnpm devOpen http://localhost:3000 and connect a wallet on Sepolia.
RainbowKit needs a WalletConnect Cloud project ID for the QR/mobile wallet flow. Without one, an
injected wallet (e.g. MetaMask's browser extension) still works, but you'll see a console warning.
Create a project at cloud.reown.com and set it in .env.local:
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=your-project-idVideo link: https://www.youtube.com/watch?v=N6pNZwhzdQs&t=6s
pnpm dev— start the dev server (Turbopack)pnpm build— production build, type-checkedpnpm test— unit tests for the decimal/RAY conversion helpers inlib/format.tsandlib/math.tspnpm dlx @wagmi/cli generate— regeneratelib/generated.tsafter editing an ABI orwagmi.config.ts
| Contract | Address |
|---|---|
| LendingPool | 0x0e45445C291304b0c7bF6343CCfa75228f96dB2D |
| LendifyDataProvider | 0x395Cf49c6e01DD43b89633C0671439BAaA1Cc2c7 |
| PriceOracle | 0xff8529Ec32d4040E4AF0132358C1aBa8962289D8 |
| WETHGateway | 0x1C67F08F127C96D36C29E33472805feefC997fE8 |
| MockAssetFactory (faucet + demo prices) | 0x6F8EDdE53001D82CAEb573E86211beaA3dA1fF33 |
| Token | Address | Decimals |
|---|---|---|
| WETH | 0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14 |
18 |
| USDC | 0xF190509003A16265c317A9606699F69558ce36f0 |
6 |
| DAI | 0x3E2b8428f63A26E3e7193Dc541591DCA1be30238 |
18 |
| WBTC | 0x84Cc70BaEA953Fdf772e8d3AfB8914441626E3fE |
8 |
LendingPoolConfigurator isn't in this table because it isn't a fixed address — the pool looks it
up dynamically via LendingPool.configurator(), and the Admin page follows that pointer at
runtime rather than hardcoding it.
Addresses and token metadata live in lib/addresses.ts. ABIs are hand-written in lib/abis/ from
the documented contract surface (no Foundry out/ or Etherscan verification was available when
this was built) — regenerate typed hooks after any change with the command above.
- The interest rate curve on the markets page is reconstructed from the single live
(utilization, borrowRate)point returned bygetAllReserves(), anchored at an assumed 80% kink — there's no on-chainInterestRateModeladdress in this app's contract surface to sample directly. Seelib/rateModel.ts. - The liquidations page discovers borrower addresses by reading
Borrowevent logs from block 0, which can be slow or rate-limited on some public RPCs; the manual address input always works regardless. - Contract reads are never retried. A reverting call is a deterministic failure — retrying it
can't fix a revert, and doing so anyway delays the error state showing up by several seconds
(worse, it can collide with the 15s market refetch interval badly enough that the UI never
visibly settles into an error at all). Transient network blips are already retried at the
transport layer by viem. See the
QueryClientconfig inapp/providers.tsx. - Stale price feeds will break every price-dependent read, including the whole Markets page.
PriceOraclewraps real Chainlink Sepolia aggregators per asset with a configurable heartbeat; testnet feeds for less-watched pairs (stablecoins, in particular) can go quiet for a long time since nobody has an incentive to keep pushing updates. If reads start reverting withStalePrice(), either push a fresh price throughMockAssetFactory(ETH/BTC only) or raise the affected asset's heartbeat viaPriceOracle.setAssetFeed(asset, aggregator, heartbeat)— see the Admin page's Demo Controls, or call it directly if you're the oracle owner.








