Reputo
Risk-aware lending with auto top-ups, OCCR(credit risk), and Self-verified identities
Problem Statement
Risk-Aware Lending with Agentic Auto Top-Ups (Sepolia + Amoy + Alfajores) TL;DRA non-custodial lending protocol on Ethereum Sepolia with a built-in Repay Buffer and a personalized credit score (OCCR). A lightweight agent on Polygon Amoy pays a tiny HTTP 402 fee to authorize automated “top-up” repayments (no swaps, no bridging). Self Protocol on Celo Alfajores enforces “one human → one credit line.” Clean mono-repo, fully instrumented demo, and tidy commits.What problem are we solving?Liquidation risk is scary for retail users, especially in volatile markets.Cross-chain automation is flaky: bridging assets/swaps introduce slippage, MEV, and operational risk.One-size-fits-all LTV ignores user behavior and on-chain reputation.Sybil abuse can sabotage risk controls and incentive design.Our answer:Keep user funds on the lending chain (Sepolia), in a Repay Buffer owned by the lending pool.Make automation pay-per-action via a 402 fee on Polygon Amoy—no value transfer cross-chain.Personalize collateral limits with OCCR (On-Chain Credit Risk) scoring.Gate borrowing with Self Protocol verification on Celo for unique humans.High-level flow (happy path)Verify identity (Celo Alfajores): User completes Self verification → our backend/allowlist attests → IdentityVerifier.setVerified(user, true) on Sepolia (or Merkle proof).Deposit collateral (Sepolia): User deposits cWETH; pool tracks collateralBalance.Borrow (Sepolia): Pool computes personalized LTV using OCCR → user borrows tUSDC.Pre-fund Repay Buffer (Sepolia): User sends some tUSDC to their Repay Buffer (repayBuffer[user]).Turn on Auto Top-Up (Frontend): Set HF threshold (e.g., 1.10). An Agent monitors HF.Market dips: HF falls below threshold. Agent calls /api/topup → receives 402 challenge (pay small fee on Polygon Amoy).Agent pays 402 on Amoy: Sends 0.5 USDC (or MATIC) to payTo with a session memo → submits proofTxHash back to /api/topup.Server verifies + repays from Buffer: Server confirms Amoy payment, re-reads HF on Sepolia, computes needed repayment, and calls repayFromBuffer(user, amount). → HF recovers without swaps/bridges; user funds never left Sepolia.Why this design?No-swap safety: Eliminates slippage, MEV, and bridge risk. All principal/interest stays on Sepolia.Agentic but opt-in: Paying the 402 fee proves user/agent intent for each automated action.Personalized risk: OCCR adjusts LTVs using observable on-chain behavior, not just static tiers.Sybil resistance: Self enforces one human per credit line for sane risk controls.Architecture (three chains, clear roles)Ethereum Sepolia (Core Protocol)LendingPool.sol — deposits/borrows/repays/liquidations + Repay Buffer (depositBuffer, withdrawBuffer, repayFromBuffer).OCCRScore.sol — maintains scoreMicro[user] in [0..1e6] and emits CreditScoreUpdated.IdentityVerifier.sol — isVerified(address) gate for borrowing; supports admin attest or Merkle allowlist.TestToken.sol — simple 18-decimals tokens for collateral (cWETH) and debt (tUSDC).Polygon Amoy (x402 lane)Agent pays a small on-chain fee (USDC/MATIC) to payTo. No user funds bridged. Fee → permission to trigger automation.Celo Alfajores (Identity)Self Protocol verification → backend attests or Merkleizes verified addresses for IdentityVerifier.Repo map (mono-repo):/contracts # Foundry/Hardhat (Sepolia) /frontend # Next.js 15 (App Router, TS) + /api/topup (402 endpoint) /agents # Node/TS agent that watches HF & pays 402 /indexer # Lightweight event indexer + REST (Token API for scores/positions)Core protocol details (Sepolia) LendingPool (essentials)Config: baseLTVbps, liqThresholdBps, liquidationBonusBps, price1e18.State: collateralBalance[user], debtBalance[user], repayBuffer[user].Views:valueOfCollateral(user)userMaxBorrowable(user)isUnderwater(user)getHealthFactor(user) = (value * liqThresholdBps/10000) / max(debt, 1)Actions:deposit(amount)borrow(amount) → requires IdentityVerifier.isVerified(msg.sender)repay(amount)liquidate(user, repayAmount)Buffer ops: depositBuffer(amount), withdrawBuffer(amount), repayFromBuffer(user, amount)(Optional) repayOnBehalf(user, amount)No-swap rule: All repayFromBuffer sources are the user’s buffer inside the pool on Sepolia.OCCRScore (personalized LTV)scoreMicro[user] ∈ [0..1e6] where 0 = best.Weights (example): Historical 35%, Current 25%, Utilization 15%, Txn 15%, New 10%.Hooks: onBorrow, onRepay, onLiquidation adjust subscores.Personalized LTV:risk = scoreMicro / 1e6personalizedLTVbps = baseLTVbps * (1 - risk)Emit CreditScoreUpdated(user, scoreMicro)Intuition:Historical: repayments vs. liquidations over time.Current: recent behavior, e.g., days since last repay, active HF.Utilization: debt / (debt + free capacity) or variant.Txn: on-chain activity breadth/frequency (anti-dust behavior).New: cold-start penalty that fades as data accrues.IdentityVerifierisVerified(address) → bool.Path A (on-chain-lite): backend verifies Self proof and calls setVerified(user, true) (owner-only).Path B (Merkle): set merkleRoot; user calls prove(proof) to set verifiedHuman[user] = true.x402 Agentic loop (Amoy + Frontend API) /api/topup (Next.js API route)Request: { user, minHF, proofTxHash? }If no proof: return 402 challenge JSON{ "payTo": "0x...amoy", "amount": "500000", "token": "USDC", "chain": "polygon-amoy", "session": "<uuid>" }If proofTxHash:Verify on Amoy that fee amount token was sent to payTo (and memo session if used).Read user HF on Sepolia.Compute needed = targetHF - currentHF → estimate minimal repay.Call repayFromBuffer(user, min(needed, repayBuffer[user])).Return { ok: true, repaid: "<amount>" }.Agent (Node/TS)Monitors HF via Sepolia RPC.When HF < threshold:POST /api/topup → get 402.Pay Amoy fee (USDC/MATIC).POST /api/topup with proofTxHash.Logs: “Repaid X from buffer; HF now Y.”Key point: The fee is the only cross-chain value. Collateral and debt never move chains.Indexer + Token API (off-chain analytics surface)Lightweight service (ethers.js listeners + sqlite/json) that ingests events: Deposit, Borrow, Repay, Liquidate, BufferDeposit, BufferWithdraw, BufferRepay, CreditScoreUpdated.Maintains per-user state (positions, counts, tx stats) and recomputes OCCR off-chain for comparison/tuning.REST endpoints:GET /api/scores/:address → { scoreMicro, factors, updatedAt }GET /api/users/:address/position → { collateral, debt, hf, buffer, history }Frontend: shows On-chain OCCR vs Indexed OCCR (beta) + mini-charts (score & HF over time).Security & trust model (transparent for judges)Non-custodial: Users control deposits; Repay Buffer is inside the pool and withdrawable unless used.No bridging risk: We never move principal/interest off Sepolia.x402 fee is low-risk: Only a small fee on Amoy; failing to pay just means no automation that cycle.Identity trust hop: For MVP, backend attests Self verification (or Merkle). We document the path to direct on-chain verification post-hackathon.Upgradability / Admins: Minimal, clearly documented (e.g., owner price control for demo).Observability: Event logs for every state change; indexer provides time-series for audits.User experience (what the judge sees)Connect wallet → Not Verified (borrow locked).Click Verify with Self → returns Verified; borrow unlocked.Deposit cWETH → Borrow tUSDC (LTV personalized by OCCR).Deposit Repay Buffer (e.g., 50 tUSDC).Toggle Auto Top-Up; set threshold HF = 1.10.Admin simulates price drop → HF dips.Agent pays x402 on Amoy → server repays from Buffer → HF recovers.OCCR updates (on-chain & indexed).User withdraws leftover buffer, repays, then withdraws collateral.What’s innovative here?Agentic safety net without swaps: automation that never touches principal across chains.Behavior-aware credit: OCCR turns on-chain traces into live LTV personalization.Human-bound credit lines: Self verification ensures one unique borrower per line.Composable lanes: Three chains, each doing what they’re best at (credit core, cheap fee signal, identity).Tech stack (concise)Contracts: Solidity (Foundry/Hardhat), Sepolia deployments.Frontend: Next.js 15 (App Router, TS), wagmi/ethers, /api/topup.Agent: Node/TS script (Amoy RPC, simple loop).Indexer: Node/TS service with ethers listeners + sqlite/json.Infra: Alchemy RPCs; test tokens; minimal admin price control for demos.CI/dev-ex: Env templates, scripts, first-commit scaffolds, architecture diagram (PNG/SVG).Example math (readable for judges)HF: HF = (collateralValue * liqThresholdBps / 10000) / max(debt, 1)Personalized LTV: risk = scoreMicro / 1e6 personalizedLTVbps = baseLTVbps * (1 - risk) Example: baseLTV = 70%, scoreMicro = 200k → risk=0.2 → LTV = 56%.Roadmap after hackathonOn-chain Self proof verification or cross-chain attestation.Price oracles (Pyth/Chainlink) + multi-asset markets.Smarter OCCR (ML-assisted factors, anomaly detection) and “Good-Borrower” rewards.Optional DEX integration as separate module (while keeping Buffer-first design).Substreams migration for scalable indexing.
Solution
High-Level ArchitectureEthereum Sepolia (core protocol): LendingPool.sol, OCCRScore.sol, IdentityVerifier.sol, TestToken.solPolygon Amoy (x402 lane): Agent pays a tiny on-chain fee (USDC/MATIC) to unlock a privileged backend actionCelo Alfajores (Self identity): Unique-human verification feeding back into Sepolia via attestation/MerkleFrontend (Next.js 15): App Router, TypeScript, minimal admin panel; API route /api/topup implements HTTP 402Agents (Node/TS): Poll health factor (HF), handle 402 challenge, pay fee on Amoy, trigger repay-from-bufferIndexer (Node/TS + ethers + sqlite/json): Listen to events → recompute OCCR off-chain → expose Token APIRepo layout (monorepo):/contracts /frontend /agents /indexer README.mdTrack 1 — Self Protocol (Celo Alfajores): Unique Human → One Credit Line Why Self helpedSelf gives us Sybil-resistant borrowing without doxxing users. For a lending protocol that personalizes LTV via OCCR, “one human → one line” keeps incentives sane and curbs gaming.What we actually builtTwo pragmatic integration paths so the demo never blocks on heavy cryptography:On-chain-lite (chosen for MVP):User completes Self verification on Celo AlfajoresOur backend verifies success via Self’s SDK/APIBackend attests the EVM address by calling IdentityVerifier.setVerified(user, true) on Sepolia (owner-only)LendingPool.borrow() gates on identity.isVerified(msg.sender)Merkle allowlist (fallback):Collect verified addresses off-chain → compute merkleRootIdentityVerifier.setMerkleRoot(root) on SepoliaUsers prove inclusion once via prove(bytes32[] proof) → we set verifiedHuman[user] = trueBoth satisfy “one human → one verified account” for the hackathon. We document the path to direct on-chain Self proof verification post-hackathon (no trust hop).Tech details & glueContracts: IdentityVerifier.sol is a thin gate with isVerified(address) + setVerified or setMerkleRoot/proveBackend: Tiny verifier service (Node/TS) that never stores PII; it only flips setVerified or builds the Merkle treeFrontend: “Verify with Self” button → opens Self flow; on success, UI shows ✅ Verified and unlocks “Borrow”Security notes:setVerified is owner-only, wired to a deployer/admin key held just for the demoClear audit trail via events: UserVerified(user)We rate-limit backend attest calls and log the Self session ID → EVM address mapping (hashed)What’s hacky (but effective):The admin-attest shortcut trades zero-knowledge purity for a faster demo loop. It’s documented and easily swappable for a full on-chain verification later.Track 2 — Polygon x402 (Amoy): Pay-Per-Action Automation Without Swaps Why x402 helpedWe wanted agentic safety without moving principal cross-chain. The 402 fee is a cheap signal of intent that lets a bot do something privileged (trigger a repay-from-buffer) without touching user funds on Amoy.What we actually builtHTTP 402 challenge at POST /api/topup:Request: { user, minHF }If no proof: returns 402 JSON with { payTo, amount, token, chain, session }If proofTxHash: server verifies fee payment on Amoy, then calls repayFromBuffer(user, amount) on SepoliaAgent (Node/TS):Polls HF on Sepolia via RPCIf HF < threshold: calls /api/topup → gets 402Pays USDC/MATIC on Amoy to payTo with memo = sessionCalls /api/topup again with proofTxHashServer confirms payment and triggers repayFromBufferLogs: “Repaid X from buffer; HF now Y”No-swap rule: All repayments use the Repay Buffer on Sepolia. Nothing bridges or swaps on Polygon.Tech details & glueAmoy USDC/MATIC via Alchemy RPC; agent signs with AGENT_PRIVATE_KEYFee verification:We check to == payTo, amount >= X, token == expectedOptionally parse tx input/logs for a session memo to bind proof ↔ requestBasic reorg guard: wait N confirmations (configurable)Server math:Reads HF_current from LendingPoolComputes needed to reach minHF (conservative estimate)Executes repayFromBuffer(user, min(needed, repayBuffer[user]))Observability:Events emitted on BufferRepay, Repay, CreditScoreUpdatedAPI returns { ok: true, repaid } for UI toast + logsWhat’s hacky (but notable):Using a session UUID as a tx memo on Polygon gives us a near-stateless binding between a 402 challenge and an on-chain payment proof.We accept either native MATIC or ERC-20 USDC as “fee tokens” using a small token registry on the server to validate decimals/amounts.Core Contracts (Sepolia) — Nitty-Gritty LendingPool.solState: collateralBalance, debtBalance, repayBufferConfig: baseLTVbps, liqThresholdBps, liquidationBonusBps, price1e18Views: valueOfCollateral(), userMaxBorrowable(), getHealthFactor(), isUnderwater()Actions: deposit, borrow (requires verified human), repay, liquidateBuffer: depositBuffer, withdrawBuffer, repayFromBuffer, optional repayOnBehalfEvents: Deposit, Borrow, Repay, Liquidate, BufferDeposit, BufferWithdraw, BufferRepayDemo-only admin: price setter for price-drop simulations (owner-only)OCCRScore.solscoreMicro ∈ [0..1e6] (0 best); emits CreditScoreUpdatedWeights (example): 35% Historical, 25% Current, 15% Util, 15% Txn, 10% NewHooks: onBorrow/onRepay/onLiquidation adjust factorsPersonalized LTV: baseLTV * (1 - scoreMicro/1e6)IdentityVerifier.solisVerified(address) gate for borrowsetVerified(address,bool) (owner-only) and/or setMerkleRoot / proveEmits UserVerifiedImplementation notes:CEI pattern (Checks-Effects-Interactions)Minimal reentrancy surface; no external calls in state-mutating functions except ERC-20 transfersGas-savvy math: precalc liqThresholdWAD / price1e18, use unchecked where safe, emit compact eventsFrontend & API (Next.js 15)App Router + TS, wagmi/ethers for chain callsEnv-driven config: RPCs, contract addresses, fee token/amount, indexer URLUI blocks:Position card (collateral, debt, HF)OCCR card (on-chain vs Indexed OCCR (beta))Repay Buffer panel (approve/deposit/withdraw)Auto Top-Up toggle + thresholdIdentity page: “Verify with Self” → shows ✅ and unblocks Borrow/pages/api/topup.ts implements the 402 handshake and fee proof verificationAgent (Node/TS)Reads env: AMOY_RPC_URL, AGENT_PRIVATE_KEY, FRONTEND_TOPUP_URL, BORROWER_ADDRLoop:Fetch HF (Sepolia RPC → getHealthFactor)If below threshold: POST /api/topupPay x402 fee on Amoy (USDC or MATIC)POST /api/topup with proofTxHashLog outcomes (and surface errors: insufficient buffer, fee mismatch, session expired)Reliability bits:Backoff & jitter to avoid hammering endpointsConfirmations before accepting a fee proof (N configurable)Idempotency key: we include the session; server short-circuits duplicate proofsIndexer & Token APIListeners: ethers.js on Sepolia for pool/score eventsStore: sqlite or json with a simple DAO layerRecompute OCCR off-chain to compare with on-chain (helps tuning)Endpoints:GET /api/scores/:address → { scoreMicro, factors, updatedAt }GET /api/users/:address/position → { collateral, debt, hf, buffer, history }Frontend: shows on-chain vs indexed values + tiny sparkline charts for score/HF historyWhat’s hacky (but smart for a hackathon):We double-compute credit score (on-chain & off-chain). It’s extra work, but it gives us debugging levers and ML-ready signals later.Tooling, Testing & DevExContracts: Foundry/Hardhat (unit tests for deposit→borrow→repay, buffer repay, liquidation math, score updates)Frontend/API: Vitest + Supertest for /api/topup; MSW for RPC stubs where neededAgent: End-to-end against Amoy test token (USDC) and MATIC; retries + confirmation depth testsLint/Type-safety: eslint, typescript strict, no-explicit-anyDX niceties: .env.example for each package, first-run check:rpc script that prints latest block heights on Sepolia/Amoy/AlfajoresDocs: README with contract addresses, envs, scripts, and an SVG architecture diagramPartner Tech — Concrete Benefits Self Protocol (Celo)Unique human constraint unlocks safer personalized LTV and fairer incentivesSmooth dev ergonomics with SDK/API; low-friction MVP via admin-attest → can evolve to full on-chain proofsPolygon x402 (Amoy)Automation as a service: tiny on-chain fee becomes a clean, auditable triggerNo cross-chain asset risk: we never bridge principal—only send a fee signalCheap & fast UX for the agent loopEdge Cases & SafeguardsFee paid, but HF already recovered: server re-reads HF and no-ops (returns {ok:true, repaid:"0"}); we plan refunds/credits laterInsufficient buffer: server returns an actionable error; UI highlights “Top up your Repay Buffer”Session replay attempts: session UUID bound to single use; we also check timestamp and amount ≥ quotedChain reorgs on Amoy: wait N confirmations before accepting proofPrice oracle in demo: owner-only setter for price drops; post-hackathon we’ll add Pyth/ChainlinkWhat’s uniquely oursRepay Buffer first: a clean, no-swap automation surface that’s hard to mess up in live demosOCCR everywhere: same formula on-chain and off-chain, making the system tunable and explainableThree-chain composition with minimal trust: credit core (Sepolia), fee signal (Amoy), identity (Celo)
Hackathon
ETHGlobal New Delhi
2025
Contributors
- terabyte-trifler
13 contributions