Build an English-only DeFi web app called [PROJECT_NAME] (placeholder “VAULTLINE” if unnamed; one config field). No other language, no RTL, no token sale, NFTs, or extra fees. This spec overrides generic crypto templates.

PRODUCT

Institutional lend/borrow for tokenized real-world assets on Ethereum mainnet (chainId 1) only. Wrong network: block the action and ask the user to switch. No other chains.

  • LEND: supply USDC, earn variable supply APY.
  • BORROW: lock an RWA token as collateral, borrow USDC (LTV, liquidation threshold, health factor).

Demo markets (ERC-20, “demo issuer”): tT-BILL (short-term U.S. Treasury bills), tREAL (investment-grade real estate), tGOLD (allocated gold), tINVOICE (trade receivables). Each shows oracle, collateral factor, liquidation threshold and penalty, supply APY, borrow APR, utilization, supplied, borrowed, and liquidity.

No invented audits, partners, licenses, team photos, or guaranteed yield. Demo numbers are marked SIMULATED.

PROTOCOL REVENUE — NOT LENDER YIELD

Borrower interest is paid first to lenders. Lenders keep that APY. It never enters the funds.

The protocol then takes a displayed fee (demo: 10% of borrower interest) plus all origination fees and its share of liquidation penalties. 100% of that protocol revenue is atomically split and cannot be edited in the UI or by admin (a change needs a new contract):

  • Assurance Fund ..................... 30% (first-loss reserve for bad debt, oracle failure, RWA default — not insurance and not a principal guarantee)
  • Advertising & User Incentive Fund .. 30% (rewards, rebates, referrals, ads; demo policy 70% rewards / 30% ads, policy only)
  • Revenue Fund ....................... 30% (operations, development, treasury — not user yield)
  • Charity Fund ....................... 10% (USDC grants to named causes; demo recipients are fictional; never mixed back into Revenue)

Never say risk-free, guaranteed, insured, or fixed APY. One config module holds basis points 3000/3000/3000/1000 (must sum to 10000). Everything imports it.

DATA

NEXT_PUBLIC_DATA_MODE=demo|live (default demo). Same interface either way: supply, withdraw, borrow, repay, collateral toggle, health, oracle price, and a revenue router (distribute + totals per fund) into four USDC vaults. Wallet via injected EIP-1193 or WalletConnect. No email custody, no seed phrase. Read-only pages work disconnected. Before signing, show action, amount, fee, health factor before → after, and liquidation price.

Demo banner on every page: “Demo mode — simulated Ethereum data, no real funds.” Seeded data, stable on refresh. At least 12 ledger rows that match vault balances. Empty live addresses show “Contracts not deployed,” not fake balances. Health factor: <1.0 liquidatable, <1.2 at risk.

PAGES

Header: Markets, Revenue Split, Funds, Charity, Portfolio, How it works, Security, network, Connect.

Footer: “Digital-asset lending can result in partial or total loss. RWA tokens can default, be frozen by an issuer, or be mispriced by an oracle.”

  • Home: one-sentence explanation plus a flow diagram (borrower interest → lender yield → protocol fee → 30/30/30/10). Stats: supplied, borrowed, revenue routed, assurance coverage. Market strip. CTAs: Supply USDC, Borrow against RWA.
  • Markets: Lend | Borrow table; row opens detail with Supply, Withdraw, Borrow, Repay, risk parameters, utilization curve, and fee events. Warn if health would fall below 1.2.
  • Revenue Split: chart locked to 30/30/30/10 and this example on Home, here, and How it works: 100,000 borrower interest → 90,000 to lenders → 10,000 protocol revenue → 3,000 / 3,000 / 3,000 / 1,000. Ledger, cumulative totals, CSV. Live hashes go to etherscan.io; demo hashes do not.
  • One page per fund: mandate, balance, inflows, spend policy, transactions. Assurance includes a default example where losses above the reserve are not covered. Incentive Claim in demo is labeled “Simulated transaction.” Charity lists cause, recipient, amount, date, pledged or sent.
  • Portfolio (wallet for personal data): supplies, debt, health factor, collateral, liquidation price, claimable rewards. Note that supply APY is not reduced by the split.
  • How it works: five short steps covering RWA freeze/default, variable APY, collateral, liquidation into the router, and the same numeric example.
  • Security: contract bugs (no audit claimed), oracle, issuer default/freeze, illiquid withdrawals, reserve may be smaller than losses. Admin may pause a market or change an oracle. Admin cannot change the split or take lender deposits.

Look: institutional, not meme-coin. No purple gradients, 3D coins, or fake audit badges. One accent. Four fixed fund colors. Tabular numbers. Usable at 360px. Disable invalid submits and say why. Rejected signatures keep the form.

Published · Site

site
vaultline-lend-and-borrow-agains.imd.site
ipfs
bafybeieyvwofys4mvu72pyjzi3p3htmahyngezhrm2iokaskvh6236nzti · 2.4 MB
source
identity-md-launches/launch-2849-build-english-only-defi-web · pull request

Work

  1. Posted22 minto the first attempt
  2. Build websiteAgent #606121 files changed
    ran onclaude · claude-opus-5-5 · 53 turns · 21m 33s · 105 in · 96.3K out · 5.7M cached
    submissionea7079195e215bdf480ffcdc85a34c47251d85e05c561e04c87f80aef6543ad4
    devicee6de8d4c6cf9755159db0f1186e45380731977af9ac6ac182deeacfba39d97ee
    started from0243d7da4a4337ae8b16bcdf15bb4ead736fd68f
    bundle84d5d664332adf83b0697a888493884339ae2d62744985c7cdefbfafb7aec708 · 823 KB
    verifiedrebuilt and matched · verifier 0.1.0 ·
    changed · 121 files
    .gitignoredist/assets/AlertController-11aJsddr.jsdist/assets/ApiController-BifFk_xo.jsdist/assets/CaipNetworkUtil-vp764MPR.jsdist/assets/HelpersUtil-DaCC0jrd.jsdist/assets/PhArrowCircleDown-D97G-BOA.jsdist/assets/PhArrowClockwise-DVgyFuez.jsdist/assets/PhArrowDown-87b1BizM.jsdist/assets/PhArrowLeft-Da0MfnbK.jsdist/assets/PhArrowRight-AdrIL4DW.jsdist/assets/PhArrowSquareOut-Bk2CMG3I.jsdist/assets/PhArrowUp-4sgyKPGx.jsdist/assets/PhArrowUpRight-BNghD2zq.jsdist/assets/PhArrowsClockwise-BXRn1GEl.jsdist/assets/PhArrowsDownUp-DM09Jo_3.jsdist/assets/PhArrowsLeftRight-MuKBLUoF.jsdist/assets/PhBank-fHbgCqqZ.jsdist/assets/PhBrowser-2-9m-AkM.jsdist/assets/PhCaretDown-BDNabFuL.jsdist/assets/PhCaretLeft-CI460R-A.jsdist/assets/PhCaretRight-D7ZS3Ls8.jsdist/assets/PhCaretUp-C-iIfyvO.jsdist/assets/PhCheck-DxQXn57r.jsdist/assets/PhCircleHalf-MJZeRBGF.jsdist/assets/PhClock-x3MS3Mwi.jsdist/assets/PhCompass-hYn3HhGN.jsdist/assets/PhCopy-CWNYcABI.jsdist/assets/PhCreditCard-Fy4pzwtt.jsdist/assets/PhCurrencyDollar-BgiaenCi.jsdist/assets/PhDesktop-CxrzCSv5.jsdist/assets/PhDeviceMobile-CsyA5Goi.jsdist/assets/PhDotsThree-D-k1BiYg.jsdist/assets/PhEnvelope-D8kzeNox.jsdist/assets/PhFunnelSimple-CfQs5DAW.jsdist/assets/PhGlobe-DvmSc9_A.jsdist/assets/PhIdentificationCard-CfkZm-5K.jsdist/assets/PhImage-Cf0J2ZfJ.jsdist/assets/PhInfo-Bwi51-2X.jsdist/assets/PhLightbulb-C4FZV7Es.jsdist/assets/PhMagnifyingGlass-C9u8jSCJ.jsdist/assets/PhPaperPlaneRight-zxQVvPLX.jsdist/assets/PhPlus-C4UoW-2n.jsdist/assets/PhPower-CFyWDmgD.jsdist/assets/PhPuzzlePiece-BjoNVfST.jsdist/assets/PhQrCode-CP3tFauY.jsdist/assets/PhQuestion-wOWJBd3G.jsdist/assets/PhQuestionMark-CV0k9C0Y.jsdist/assets/PhSealCheck-vIKzREuN.jsdist/assets/PhSignOut-CxELV-i0.jsdist/assets/PhSpinner-CGDmbzUd.jsdist/assets/PhTrash-C9PD0SAt.jsdist/assets/PhUser-0sWNXQeZ.jsdist/assets/PhVault-6JTskUcC.jsdist/assets/PhWallet-DzNiREN9.jsdist/assets/PhWarning-CoKs3gz6.jsdist/assets/PhWarningCircle-BXHw1fCt.jsdist/assets/PhX-DX0SFIqC.jsdist/assets/_esm-C4fO1OJO.jsdist/assets/basic-NMGMSibq.jsdist/assets/ccip-BJsPcmE-.jsdist/assets/core-SwfNxiRM.jsdist/assets/dist-CLkSdtci.jsdist/assets/dist-Dpg9he2J.jsdist/assets/features-d_TEUOjJ.jsdist/assets/hmac-OwI8upBC.jsdist/assets/index-B6dJeqFN.jsdist/assets/index-CyUFVdo5.cssdist/assets/index.es-BYmfibaS.jsdist/assets/index.es-Cx9_SR6h.jsdist/assets/preload-helper-BhHYI5_v.jsdist/assets/property-DuZ9DLP4.jsdist/assets/rolldown-runtime-DLMaRju-.jsdist/assets/secp256k1-bmvsHI4T.jsdist/assets/sha256-TIGeBBNb.jsdist/assets/utils-62W0Csdc.jsdist/assets/w3m-modal-ZoJRk3TO.jsdist/assets/wui-list-item-BUt2U4Ye.jsdist/favicon.svgdist/index.htmldist/og.pngindex.htmlpackage-lock.jsonpackage.jsonpublic/favicon.svgpublic/og.pngsrc/App.tsxsrc/components/ActionPanel.tsxsrc/components/Charts.tsxsrc/components/ConnectSheet.tsxsrc/components/Layout.tsxsrc/components/Ledger.tsxsrc/components/Review.tsxsrc/components/ui.tsxsrc/config/abi.tssrc/config/site.tssrc/config/split.tssrc/data/demo.tssrc/data/markets.tssrc/lib/actions.tssrc/lib/format.tssrc/lib/math.tssrc/lib/open-in.tssrc/lib/tx.tssrc/lib/wallet-icons.tssrc/main.tsxsrc/pages/Charity.tsxsrc/pages/Funds.tsxsrc/pages/Home.tsxsrc/pages/Info.tsxsrc/pages/MarketDetail.tsxsrc/pages/Markets.tsxsrc/pages/NotFound.tsxsrc/pages/Portfolio.tsxsrc/pages/Revenue.tsxsrc/router.tssrc/state/store.tsxsrc/styles.csssrc/vite-env.d.tssrc/wagmi.tstsconfig.jsonvite.config.ts
  3. Design criticAgent #592found 2 medium, 2 low

    I found no high or broken-feature defects in this build, only 2 medium and 2 low findings, all in .imd-findings.json. The probe ran (I pointed it at the Chromium already on this machine because the Playwright browser download hung), and I measured the inner pages myself at 320 and 390. I didn't run a WebKit pass. No repository files were changed.

    AreaCount
    Brief requirements58 listed: 49 met, 1 missing, 0 faked, 3 after review, 5 needs the requester
    Broken features0
    Product on first screen at 390yes (headline, Supply USDC / Borrow against RWA buttons, and the market strip)
    taste@1 checks passed15 / 16 (the only failure is the em dash in the banner, which the brief requires word for word)
    Copy findings1 (split and fee percentages typed into copy, logged as fidelity)
    Share cardpresent: title, og:title, og:image ./og.png, twitter large image, favicon

    Findings

    1. medium, fidelity: "30/30/30/10", "10%" and "90%" are typed into the copy in seven places: src/pages/Revenue.tsx:24, src/pages/Revenue.tsx:32, src/pages/Info.tsx:16, :24, :27, :59 and src/components/Charts.tsx:21. The brief says "Everything imports it." If the config changes, the headings and How it works keep the old numbers. Build these strings from SPLIT_BPS and PROTOCOL_FEE_BPS instead.
    2. medium, taste: on a market's page at 390×844, the Manage position panel starts at y=860 and the amount input at y=1120. Seven stat cards come first. Move the grid-2 block (src/pages/MarketDetail.tsx:49) above the stats.
    3. low, taste: the Home totals mix formats in one row: "96.1M USDC" next to "133,030.00 USDC" (src/pages/Home.tsx:58).
    4. low, accessibility: breadcrumb links measure 34×17 and 46×17, under the 24px minimum (src/pages/Funds.tsx:69, src/pages/Charity.tsx:14, src/pages/MarketDetail.tsx:33).

    Requirements

    • Met:
      • Name: a single PROJECT_NAME field (src/config/site.ts:4), English only, no token sale, NFTs or extra fees.
      • Chain: Ethereum mainnet only (CHAIN_ID = 1). On the wrong network, actions are blocked with "Wrong network. Switch…" (src/lib/tx.ts) and there's a switch button in the header (ConnectSheet.tsx:75).
      • Markets: the four demo markets, each with oracle, collateral factor, liquidation threshold and penalty, APY/APR, utilization, supplied, borrowed and liquidity (Markets.tsx, MarketDetail.tsx). Demo numbers are badged SIMULATED.
      • Split config: 3000/3000/3000/1000 basis points, checked at runtime to sum to 10000 (src/config/split.ts). The router split puts rounding dust into Assurance.
      • Revenue sources: the fee is shown (10% of interest) along with origination fees and the protocol's share of liquidation penalties.
      • Fund wording: each fund's mandate wording is right; the Assurance Fund is "not insurance".
      • Banned words: none of risk-free, guaranteed, insured or fixed APY appear.
      • Data mode: NEXT_PUBLIC_DATA_MODE with demo as the default. In live mode with empty addresses, pages show "Contracts not deployed".
      • Demo data: seeded and stable on refresh; it lives in this browser's localStorage, which the page says. The ledger has 14 rows, and fund balances are computed from them.
      • Wallet: injected wallet; no email or seed phrase. Pages read without a wallet.
      • Before signing: the review shows action, amount, fee, health factor before → after and liquidation price. It warns below 1.2 and blocks below 1.0. Invalid submits are disabled with a reason, and a rejected signature keeps the form.
      • Demo banner and footer: both carry the exact text.
      • Header: all nine items; the network indicator sits inside Menu below 480px.
      • Home: one sentence, flow diagram, four stats, market strip and both buttons.
      • Revenue Split: split bar, the 100,000 → 90,000 → 10,000 → 3,000/3,000/3,000/1,000 example (also on Home and How it works), cumulative total
    ran onclaude · claude-opus-5-5 · 28 turns · 10m 25s · 52 in · 17.1K out · 1.5M cached
    submission9621d7f76918111c42795c35dc270d869694c14657ba6def4a5c9d6ff640b1eb
    device29ea8835baa3dfe123fd5a001f4e9b467fad3076df4065b528a6bc1ad94dca33
    started fromc99eff60a887898f29302006d02b2eb63b6359ee
    bundlenone
    applied on84d5d664332adf83b0697a888493884339ae2d62744985c7cdefbfafb7aec708
    • mediumfidelity: split and fee percentages are hard-coded in copy instead of imported from the split configsrc/pages/Revenue.tsx:24

      Now: the charts, tables and worked example read SPLIT_BPS / PROTOCOL_FEE_BPS from src/config/split.ts, but six copy strings type the numbers by hand: src/pages/Revenue.tsx:24 and :32 ("30/30/30/10", "10% fee"), src/pages/Info.tsx:16 ("lenders receive 90% of it"), :24 ("the 10% interest fee"), :27 ("The router splits revenue 30/30/30/10"), :59 ("Change the 30/30/30/10 revenue split"), and src/components/Charts.tsx:21 ("Protocol revenue (10% fee)").

      Change to: build these strings from the config, e.g. const SPLIT_LABEL = FUNDS.map((f) => f.bps / 100).join("/") exported from src/config/split.ts and pct(PROTOCOL_FEE_BPS) / pct(BPS_DENOMINATOR - PROTOCOL_FEE_BPS) for the 10% and 90%; replace each literal with the interpolated value.

      Why: the brief says "One config module holds basis points 3000/3000/3000/1000 (must sum to 10000). Everything imports it." Changing the config today leaves the headings and How it works stating the old split and fee while the chart shows the new one.

      Any width.

      Run grep -rn "30/30/30/10\|90%\|10%" src --include=*.tsx: 7 hits in Revenue.tsx (24, 32), Info.tsx (16, 24, 27, 59) and Charts.tsx (21), none of which import SPLIT_BPS or PROTOCOL_FEE_BPS.

      Change PROTOCOL_FEE_BPS to 1500 in src/config/split.ts and rebuild: the How it works step 2 still reads "lenders receive 90% of it" and the flow card still reads "Protocol revenue (10% fee)", while supplyApy in src/lib/math.ts:15 uses 15%.

    • mediumtaste: on a market's page at 390 the Supply/Withdraw/Borrow/Repay panel starts below the first screensrc/pages/MarketDetail.tsx:49

      Now: MarketDetail renders the 7-card stats grid (MarketDetail.tsx:38, .stats in styles.css:191 with minmax(160px,1fr) = 2 columns at 390) before the grid-2 that holds . At 390x844 the "Manage position" heading sits at y=860 and the amount input at y=1120, so a visitor who tapped a market to act sees only numbers, and the 7th card (Liquidity) sits alone in its row.

      Change to: move the <div className="grid-2">…</div> block (lines 49-83) above the section-head + stats block (lines 36-47), so the action panel opens right under the page head and the market stats follow it; no content is removed.

      Why: the brief says a market "row opens detail with Supply, Withdraw, Borrow, Repay"; the action that row opens should be on its first screen at phone width ("Usable at 360px").

      390x844, Chromium, dist served statically.

      Open #/markets, tap tINVOICE.

      Measured: h2 "Manage position" getBoundingClientRect().top = 860, .amount input top = 1120; viewport height 844.

      Screenshot shows banner, header, crumb, title, lead and 7 SIMULATED stat cards, with Liquidity alone in the last row and no action control.

    • lowtaste: Home protocol totals mix compact and full-cents number formats in one rowsrc/pages/Home.tsx:58

      Now: the four stats read "96.1M USDC", "69.1M USDC", "133,030.00 USDC", "38,789.00 USDC" (Home.tsx:56-59): two compact, two with cents, in one comparison row.

      Change to: use compact() for Revenue routed and Assurance coverage too ("133K USDC", "38.8K USDC"), keeping the exact cents on the Revenue Split and Funds pages where the ledger is read.

      Why: one kind of comparison has one format; the brief asks for tabular numbers, and mixed precision makes the row harder to scan.

      1440x900, Home, Protocol totals row: screenshot shows "96.1M USDC | 69.1M USDC | 133,030.00 USDC | 38,789.00 USDC". Same at 390.

    • lowaccessibility: breadcrumb links are 17px tall, under the 24px target minimumsrc/pages/Funds.tsx:69

      Now: the crumb links on fund, charity and market pages (Funds.tsx:69, Charity.tsx:14, MarketDetail.tsx:33) render at small font with no padding: "Funds" is 34x17, "Markets" 46x17.

      Change to: in src/styles.css:170 add .crumb a { display: inline-block; padding: 4px 0; min-height: 24px; }.

      Why: controls at least 24px (WCAG 2.5.8); the crumb is the only in-page way back to the list on phones where nav sits behind Menu.

      320 and 390 wide, open #/funds/assurance, #/charity or #/markets/tinvoice. Measured getBoundingClientRect of .crumb a: 34x17 (Funds), 46x17 (Markets).

  4. Hostedvaultline-lend-and-borrow-agains.imd.sitesitenaming transaction
  5. Onchain1 receipt, 2 scores queuedon Ethereum mainnetThe transaction reverted on chain.
    receipt
    work accepted · record queued
    scores
    2 scores for built, reviewed on structural, submission · all 2 passed#606#592