Skip to content

SamAg19/LienFi

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

73 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

LienFi Banner

LienFi

Trustless Mortgages with Private Credit Scoring & Sealed-Bid Liquidation

A complete on-chain mortgage system where credit data is assessed privately inside a confidential enclave (never touches the chain), property collateral is locked as privacy-preserving NFTs, lenders earn passive yield, and loan defaults trigger sealed-bid Vickrey auctions where bid amounts, bidder identities, and losing bids are never exposed on-chain.

ProblemHow It WorksSystem FlowPrivacyContractsChainlinkTech StackQuick Start


The Problem

DeFi lending today requires overcollateralization because protocols have no way to assess real-world creditworthiness without exposing private financial data on-chain. Traditional on-chain auctions are fully transparent — bid amounts, bidder identities, and losing bids are permanently visible. Together these two gaps make a trustless mortgage impossible.

No existing protocol solves both simultaneously:

Gap Why It Matters
No private credit scoring Borrowers must reveal income, bank history, and debt ratios publicly — or protocols skip underwriting entirely and demand 150%+ collateral
Transparent liquidation auctions Bid amounts are public (competitors snipe), bidder identities are exposed (privacy leak), losing bidders are visible (reputational risk)
No property data privacy Tokenized real estate exposes street addresses, appraisal values, and owner identities on-chain forever

For tokenized real estate worth hundreds of thousands, these aren't inconveniences — they're dealbreakers.


How LienFi Works

LienFi is a complete mortgage primitive with no step requiring a bank, appraiser, or court:

Lenders fund a USDC pool. Borrowers prove creditworthiness privately. Property NFT locked as collateral. Monthly EMI repayments grow pool yield. Default triggers sealed-bid auction to recover funds.

The Lifecycle

Phase What Happens Privacy
A. Pool Funding Lenders deposit USDC, receive clUSDC receipt tokens. Exchange rate rises as EMIs accumulate — passive yield, no staking. clUSDC balances public
B. Property NFT Borrower verifies property via CRE enclave. Only a commitmentHash goes on-chain — no address, no value, no metadata. Full details enclave-only
C. Credit Assessment Borrower submits loan request hash on-chain. CRE auto-triggers: Plaid data fetch, metric extraction, hard rule gates, Gemini AI scoring. Raw data discarded after. Financial data never on-chain
D. Loan Disbursement Approved borrower locks PropertyNFT as collateral. USDC disbursed from pool. Loan record created on-chain. Approval visible, financials hidden
E. Monthly Repayment Borrower pays fixed EMI. Full amount enters pool, raising clUSDC exchange rate. On full repayment, NFT returned. EMI schedule public
F. Default + Auction 3 missed payments trigger default. PropertyNFT transferred to LienFiAuction. Sanitized listing (no address, no "default" label). Sealed bids via CRE. Vickrey settlement. Winner gets NFT + full address reveal. Bids, bidders, losing bids all hidden

Key Features

  • Private Credit Scoring — Plaid bank data fetched inside CRE enclave, pre-processed into metrics, scored by Gemini AI. Raw financial data is discarded after assessment — never persisted, never on-chain.
  • Privacy-Preserving Property NFTs — ERC-721 with only a commitment hash on-chain. No tokenURI, no metadata. Full details stored exclusively in the CRE enclave.
  • Passive Yield for Lenders — clUSDC exchange rate model (same as Compound cTokens). Pool USDC grows as EMIs come in, each clUSDC redeems for more. No staking, no claiming.
  • Sealed-Bid Vickrey Auctions — Bid amounts exist only inside the CRE enclave. On-chain: only opaque bid hashes. Winner pays second-highest price. Losing bids and bidders are never revealed.
  • Event-Driven Assessment PipelineLoanRequestSubmitted event auto-triggers the entire credit assessment. No manual CRE trigger, no separate oracle — one coherent system.
  • Sanitized Listings — Default auctions show property type, neighborhood, size — but never the street address, owner identity, or reason for sale. Winner gets full details post-settlement only.

System Flow

                                    LIENFI

 PHASE A: POOL FUNDING
 ──────────────────────────────────────────────────────────────────
  Lender
    |  deposit(USDC)
    v
  LendingPool ──── mint clUSDC ──> Lender
    |
    |  exchangeRate = poolUSDC / clUSDC.totalSupply()
    |  (rate rises as EMIs accumulate -> lenders earn yield passively)


 PHASE B: PROPERTY NFT MINTING
 ──────────────────────────────────────────────────────────────────
  Borrower --> POST /verify-property (propertyId, docs)
                    |
                    v
              API (enclave)
                    |  stores full details internally (never on-chain)
                    |  computes commitmentHash = keccak256(addr+value+docs+secret)
                    |  returns { tokenId, commitmentHash }
                    |
  Borrower --> LoanManager.mintPropertyNFT(commitmentHash)
                    |
                    v
              PropertyNFT ──── tokenId + commitmentHash stored on-chain
                               (no metadata, no address, no value visible)


 PHASE C: LOAN REQUEST + CREDIT ASSESSMENT (EVENT-DRIVEN)
 ──────────────────────────────────────────────────────────────────
  Borrower
    |
    |-(1)-> POST /loanRequest { plaidToken, tokenId, amount, tenure }
    |              |
    |              v
    |         API stores details, returns requestHash
    |
    |-(2)-> LoanManager.submitRequest(requestHash)
    |              |
    |              |  emits LoanRequestSubmitted(borrower, requestHash)
    |              |
    |              v
    |      +------------------------------------------------------------+
    |      |        CRE ENCLAVE (auto-triggered by event)               |
    |      |                                                            |
    |      |  1. Fetch details from API DB using requestHash            |
    |      |  2. Recompute + verify hash (abort if mismatch)            |
    |      |  3. Get appraisedValue from enclave store (tokenId)        |
    |      |  4. Compute EMI = P*r*(1+r)^n / ((1+r)^n - 1)             |
    |      |  5. Fetch Plaid data via Confidential HTTP                 |
    |      |  6. Pre-process: income, DTI, stability, overdraft rate    |
    |      |  7. Hard gates: LTV<=80%, coverage>=3x, no recent defaults |
    |      |  8. Pass metrics (NOT raw data) to Gemini                  |
    |      |  9. Gemini returns: creditScore, verdict, approvedAmount   |
    |      |  10. Discard all raw financial data                        |
    |      |  11. Write verdict via KeystoneForwarder                   |
    |      +------------------------------------------------------------+
    |              |
    |              v
    |      LoanManager._writeVerdict()
    |              |-- APPROVED -> store pendingApprovals[borrower]
    |              |-- REJECTED -> emit LoanRequestRejected


 PHASE D: LOAN DISBURSEMENT
 ──────────────────────────────────────────────────────────────────
  Borrower --> LoanManager.borrow(tokenId, amount, tenure)
                    |
                    |  check pendingApprovals[borrower] exists + not expired
                    |  verify amount <= approvedLimit
                    |  check LendingPool.availableLiquidity() >= amount
                    |
                    |-> PropertyNFT.transferFrom(borrower -> LoanManager)
                    |   (collateral locked)
                    |
                    |-> LendingPool.disburse(borrower, amount)
                    |        |
                    |        --> USDC transferred to borrower wallet
                    |
                    --> Loan record created on-chain
                              { loanId, emiAmount, nextDueDate, status:ACTIVE }


 PHASE E: MONTHLY REPAYMENT
 ──────────────────────────────────────────────────────────────────
  Borrower --> LoanManager.repay(loanId)  [every 30 days]
                    |
                    |  accept exactly emiAmount USDC
                    |  reduce remainingPrincipal
                    |
                    --> LendingPool.repayEMI(emiAmount)
                              |
                              --> pool USDC balance grows
                                        |
                                        --> clUSDC exchange rate rises
                                                  |
                                                  --> lenders earn yield passively

  On full repayment:
    PropertyNFT returned to borrower --> Loan closed


 PHASE F: DEFAULT + SEALED-BID LIQUIDATION
 ──────────────────────────────────────────────────────────────────
  Anyone --> LoanManager.checkDefault(loanId)  [keeper/cron]
                    |
                    |  miss 1 -> emit PaymentMissed (warning)
                    |  miss 2 -> emit PaymentMissed (grace period)
                    |  miss 3 -> _triggerDefault()
                    |                |
                    |                v
                    |       loan.status = DEFAULTED
                    |       PropertyNFT -> LienFiAuction
                    |       LienFiAuction.initiateDefaultAuction(
                    |           tokenId, reservePrice=remainingPrincipal)
                    |
                    v
      +------------------------------------------------------------+
      |  CRE ENCLAVE (listing generation)                          |
      |                                                            |
      |  Retrieve full property details (tokenId -> enclave store) |
      |  Generate sanitized listing:                               |
      |    + property type, size, year built                       |
      |    + city + neighborhood (NOT street address)              |
      |    + verified appraisal value + reserve price              |
      |    - no street address, no owner identity, no "default"    |
      |  Compute listingHash, store on-chain in Auction struct     |
      +------------------------------------------------------------+
                    |
                    v
  Bidders view sanitized listing
                    |
  Bidder --> depositToPool(USDC, lockUntil)
  Bidder --> POST /bid { auctionId, amount, signature } via CRE
                    |
                    v  (Confidential HTTP -- bid stays private)
      +------------------------------------------------------------+
      |  CRE Bid Workflow                                          |
      |    validate EIP-712 signature                              |
      |    check pool balance >= bid amount                        |
      |    store bid encrypted in enclave                          |
      |    return opaque bidHash                                   |
      +------------------------------------------------------------+
                    |
                    --> LienFiAuction: only bidHash stored on-chain
                          (bid amount invisible to all observers)

  [auction deadline passes]
                    |
      +------------------------------------------------------------+
      |  CRE Settlement Workflow (cron-triggered)                  |
      |    retrieve all bids from enclave                          |
      |    Vickrey: winner = highest bid                           |
      |             price  = second-highest bid (or reserve)       |
      |    all losing bids discarded -- never revealed             |
      +------------------------------------------------------------+
                    |
                    v
      LienFiAuction._settleAuction(winner, price)
                    |
                    |-> PropertyNFT transferred to winner
                    |
                    --> LoanManager.onAuctionSettled(loanId, proceeds)
                              |
                              |-- proceeds >= debt -> full repayment to pool
                              |                       surplus -> borrower
                              |-- proceeds < debt  -> partial repayment to pool
                                                      shortfall absorbed by pool

  Winner --> POST /reveal/:auctionId (signed request)
                    |
                    --> CRE returns full street address + ownership docs
                          (Confidential HTTP -- winner only, post-settlement)


 PRIVACY BOUNDARY SUMMARY
 ──────────────────────────────────────────────────────────────────
  PRIVATE (CRE enclave only)          ON-CHAIN (public)
  ----------------------------        --------------------------------
  Plaid financial data                Credit verdict (approve/reject + limit)
  Credit score + Gemini reasoning     requestHash (meaningless without preimage)
  Property address + documents        commitmentHash (unreadable fingerprint)
  Appraisal details                   Loan record (no financial details)
  Bid amounts + bidder identities     EMI schedule + payment history
  Vickrey settlement logic            Opaque bid hashes only
  Full listing details (pre-reveal)   Winner + settlement price
                                      clUSDC balances + exchange rate

System Participants

Actor Role
Lender / Investor Deposits USDC into lending pool, earns yield via clUSDC exchange rate appreciation
Borrower Verifies property, applies for loan, locks PropertyNFT as collateral, repays monthly EMIs
CRE Enclave Confidential compute — credit assessment (Plaid + Gemini), property data custodian, auction listing generator, bid/settlement engine
LoanManager Core contract — owns the full mortgage lifecycle from origination through repayment to liquidation
LendingPool Holds USDC, disburses loans, receives EMI repayments, manages clUSDC exchange rate
PropertyNFT ERC-721 — one token per property, commitment hash only, no metadata on-chain
LienFiAuction Sealed-bid Vickrey auction for defaulted properties — deposit pool, opaque bid hashes

Privacy Guarantees

Information On-Chain Visibility Who Sees It
Borrower financials (income, bank data) Never Nobody — discarded after assessment
Credit score / Gemini reasoning Never Nobody — discarded after assessment
Loan request details requestHash only Nobody (hash is meaningless without pre-image)
Property address + ownership docs Never Winner only — post-settlement via CRE
Property appraisal value Via sanitized listing Public (neighborhood-level only during auction)
Bid amounts Never Nobody — only hashes on-chain
Losing bidder identities Never Nobody
Reason for auction Never Nobody — no "default" or "foreclosure" label
Approval verdict Approve / reject + limit On-chain (public)
EMI schedule + payment history On-chain Public
clUSDC balances + exchange rate On-chain Public

Additional privacy layers:

  • Multi-token obfuscation — USDC deposits carry no auction reference, observers can't link deposits to specific auctions
  • API credentials decrypted only inside CRE enclave — never exposed to any party
  • Settlement responses AES-GCM encrypted before leaving enclave
  • PropertyNFT has no tokenURI — zero on-chain metadata leakage

Smart Contracts

Contract Purpose
LienFiAuction.sol Core auction + deposit pool + opaque bid hash storage + Vickrey settlement via CRE
LienFiRWAToken.sol ERC-20 RWA token with restricted minting (to be replaced by PropertyNFT)
MockUSDC.sol Test USDC token (6 decimals) with public mint
ReceiverTemplate.sol Abstract base for receiving Keystone CRE DON-signed reports
LoanManager.sol Full mortgage lifecycle — request anchoring, CRE verdict receiver, loan origination, repayment tracking, default triggering, auction settlement callback
LendingPool.sol USDC pool — lender deposits, loan disbursement, EMI collection. Access-controlled by LoanManager
clUSDC.sol ERC-20 receipt token. Minted on deposit, burned on withdrawal. Exchange rate appreciates as pool USDC grows
PropertyNFT.sol ERC-721 — one token per property, stores only commitmentHash. No tokenURI, no on-chain metadata

Architecture Decisions

Decision Rationale
ERC-721 (not ERC-20) for property One token per property, no fractions needed
No metadata on-chain Property details in CRE enclave only — commitment hash as tamper-proof anchor
clUSDC exchange rate model No per-lender yield tracking needed — pool USDC grows as EMIs come in, rate rises automatically (same as Compound cTokens)
LoanManager owns all loan state No separate oracle contract — approval verdict and loan lifecycle in one contract
requestHash as DB key + on-chain anchor Single value proves request integrity; DB lookup key off-chain, tamper check on-chain
Event-driven CRE trigger LoanRequestSubmitted event auto-triggers assessment — one coherent system, no manual step
EMI computed in enclave Used for income coverage gate check; stored in approval; read at disbursement — no recomputation

Chainlink Services Used

Service Usage Files
CRE Workflow Engine 4 workflows orchestrating the entire system — credit assessment, bid collection, auction creation, Vickrey settlement bid-workflow/main.ts · create-auction-workflow/main.ts · credit-assessment-workflow/main.ts · settlement-workflow/main.ts
Confidential HTTP Plaid bank data fetch and Groq AI scoring inside the enclave — raw data never leaves credit-assessment-workflow/main.ts
Vault DON Secrets API keys (Plaid, Groq, bid API) and AES encryption key stored securely, decrypted only inside enclave cre-workflows/.env.example · secrets.yaml
Encrypted Output AES-GCM encryption of settlement results and credit verdicts before leaving enclave settlement-workflow/main.ts · credit-assessment-workflow/main.ts
Log-Based Trigger LoanRequestSubmitted event auto-triggers credit assessment — no manual step credit-assessment-workflow/workflow.yaml · LoanManager.sol
Cron Trigger Settlement workflow polls every 30 seconds for expired auctions settlement-workflow/workflow.yaml · create-auction-workflow/workflow.yaml
EVM Read On-chain state reads (loan status, auction data, pool balances) inside workflows bid-workflow/main.ts · settlement-workflow/main.ts · create-auction-workflow/main.ts
EVM Write (KeystoneForwarder) DON-signed report submission delivering credit verdicts, bid hashes, and settlement results on-chain LoanManager.sol · LienFiAuction.sol · ReceiverTemplate.sol · IReceiver.sol
CRE Project Config RPC endpoints and staging/production target settings project.yaml · bid-workflow/config.staging.json · credit-assessment-workflow/config.staging.json · create-auction-workflow/config.staging.json · settlement-workflow/config.staging.json

Tech Stack

Layer Technology Purpose
Smart Contracts Solidity 0.8.24 + Foundry LoanManager, LendingPool, LienFiAuction, PropertyNFT, clUSDC
Contract Libraries OpenZeppelin ERC-20, ERC-721, Ownable, ReentrancyGuard
Confidential Compute Chainlink CRE 5 workflows — mint, bid, settle, credit assessment, listing
Credit Data Plaid API (Sandbox) Bank account data, transaction history, income verification
AI Scoring Google Gemini Credit scoring from pre-processed financial metrics
Network Ethereum Sepolia Testnet deployment
Private API Express.js + TypeScript Bid storage, EIP-712 verification, Vickrey logic, loan request storage, property data custodian
Signature Standard EIP-712 Typed structured data for bid signing and identity verification
Encryption AES-GCM Settlement and credit verdict encryption in enclave

Quick Start

Prerequisites

  • Foundry (foundryup)
  • CRE CLI + authenticated (cre auth login)
  • Node.js 20+ and Bun
  • Sepolia ETH for gas
  • Plaid sandbox credentials
  • Groq API key (used by credit assessment workflow)

1. Clone

git clone https://github.com/yourusername/lienfi.git
cd lienfi

2. Smart Contracts

Contracts are already deployed — see Deployed Contracts above. Only needed if redeploying:

cd contracts
forge install
# Edit foundry.toml or set env vars: PRIVATE_KEY, SEPOLIA_RPC_URL

forge script script/DeployLienFi.s.sol:DeployLienFi \
  --rpc-url "$SEPOLIA_RPC_URL" --broadcast

3. Private API

cd api
npm install
cp .env.example .env

Edit api/.env:

PORT=3001
BID_API_KEY=<openssl rand -hex 32>
VERIFYING_CONTRACT=0x<LienFiAuction-address>
USDC_ADDRESS=0x<MockUSDC-address>
CHAIN_ID=11155111
RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY
MONGODB_URI=<mongodb connection string>
npm run dev     # dev server with hot reload (tsx watch)
npm run build   # compile to dist/
npm start       # run compiled build
# API running at http://localhost:3001

4. CRE Workflows

cd cre-workflows
cp .env.example .env

Edit cre-workflows/.env:

CRE_ETH_PRIVATE_KEY=0x<your-sepolia-private-key>
CRE_TARGET=staging-settings
MY_API_KEY_ALL=<same as BID_API_KEY above>
AES_KEY_ALL=<openssl rand -hex 32>
GROQ_API_KEY_ALL=<from console.groq.com>
PLAID_SECRET_ALL=<plaid sandbox secret>
PLAID_CLIENT_ID_ALL=<plaid client id>

Install dependencies in each workflow:

cd bid-workflow && bun install && cd ..
cd create-auction-workflow && bun install && cd ..
cd credit-assessment-workflow && bun install && cd ..
cd settlement-workflow && bun install && cd ..

Simulate a workflow locally (example: bid):

cre workflow simulate ./bid-workflow \
  --target staging-settings \
  --http-payload @bid-payload.json \
  --non-interactive --trigger-index 0

5. Frontend

cd frontend
npm install

Create frontend/.env.local:

NEXT_PUBLIC_PRIVY_APP_ID=<from privy.io dashboard>
NEXT_PUBLIC_ALCHEMY_API_KEY=<from alchemy.com>
NEXT_PUBLIC_PLAID_ACCESS_TOKEN=<plaid sandbox access token>
npm run dev     # http://localhost:3000
npm run build   # production build

6. Demo Script

cd demo
npm install
cp .env.example .env
# Edit demo/.env — see Running the Demo section below

npm run demo          # run all phases A → F3
npm run demo:fresh    # clear checkpoint and start over
npm run demo:from -- e  # resume from a specific phase

End-to-End Flow

 1.  Lender deposits USDC          -> receives clUSDC at current exchange rate
 2.  Borrower verifies property     -> CRE computes commitmentHash, stores details in enclave
 3.  Borrower mints PropertyNFT    -> tokenId + commitmentHash on-chain, no metadata
 4.  Borrower POSTs loan details   -> stored in DB keyed by requestHash, receives requestHash
 5.  Borrower calls submitRequest  -> requestHash anchored on-chain, LoanRequestSubmitted emitted
 6.  CRE auto-triggered by event   -> fetches DB, verifies hash, Plaid -> Gemini -> verdict on-chain
 7.  Borrower calls borrow()       -> approval checked, NFT locked, USDC disbursed from pool
 8.  Borrower repays monthly       -> full EMI enters pool, clUSDC exchange rate rises
 9.  3 missed payments             -> checkDefault() called, NFT to auction, default triggered
10.  Sanitized listing published   -> neighborhood-level details + listingHash on-chain
11.  Bidders participate           -> USDC deposited, signed bids via CRE, hashes on-chain only
12.  Auction settles               -> Vickrey in enclave, winner + price on-chain, pool repaid
13.  Winner requests reveal        -> signed request, CRE returns full address via Confidential HTTP

What's Already Built

The full LienFi system is implemented and deployed on Sepolia across two environments (frontend + demo script):

Smart Contracts

Contract Description Status
LienFiAuction.sol Deposit pool, opaque bid hashes, Vickrey settlement Deployed
LoanManager.sol Full mortgage lifecycle — loan origination, CRE verdict receiver, repayment tracking, default triggering, auction settlement callback Deployed
LendingPool.sol USDC pool — lender deposits, loan disbursement, EMI collection Deployed
clUSDC.sol Receipt token with appreciating exchange rate (Compound cToken model) Deployed
PropertyNFT.sol ERC-721 — commitment hash only, no on-chain metadata Deployed
MockUSDC.sol 6-decimal test USDC with public mint Deployed

CRE Workflows

Workflow Trigger Description Status
credit-assessment-workflow Log (LoanRequestSubmitted) Plaid bank data fetch → metric extraction → Gemini AI scoring → on-chain verdict Deployed
create-auction-workflow HTTP Default detection, sanitized listing generation, auction creation on-chain Deployed
bid-workflow HTTP EIP-712 bid validation, encrypted bid storage in enclave, opaque hash on-chain Deployed
settlement-workflow Cron (30s) Vickrey settlement after auction deadline — winner + second-price on-chain Deployed

Private API (Render)

Endpoint Description Status
POST /loanRequest Store loan request details, return requestHash Live
POST /verify-property Property verification, compute + store commitmentHash Live
POST /bid EIP-712 validation + encrypted bid storage Live
GET /listing/:auctionId Sanitized auction listing (no address, no owner) Live
POST /reveal/:auctionId Winner-only full property address reveal Live
POST /settle Trigger Vickrey settlement Live
GET /status/:auctionId Auction status and bid count Live

Frontend — Next.js dashboard with Privy wallet auth, pool deposit/withdraw, borrow flow, live auction view, and CRE workflow log viewer. Deployed on Vercel.

Demo Script — Single-command tsx orchestrator that runs the full A→F3 lifecycle end-to-end on Sepolia with checkpoint/resume support.


Deployed Contracts (Sepolia)

The system has two separate deployments — one for the frontend demo with longer time windows for realistic UX, and one for the end-to-end demo script with compressed timings for fast iteration.

Frontend Deployment

EMI period: 2 minutes · Auction duration: 10 minutes

Contract Address
MockUSDC 0x9771d5745a9aF03f2e7eFA8aad7943e6D500F722
clUSDC 0xFb1801bF2EB14F9c6E3D1F7c5e72167Ef76080B0
LendingPool 0xA160CDD6F6652dbcdBa1d8dFa1Bf52DCBB95771c
LoanManager 0xf2739669965979B886df2A2187Fd284484AfB710
PropertyNFT 0xA403E4A10b5EA1Fd981DE61d6fF57b8Fc18A14E2
LienFiAuction 0xFC3E4Bd8793316D2345ad67ff9322bbE62755DE2

Demo Script Deployment

EMI period: 1 minute · Auction duration: 5 minutes

Contract Address
MockUSDC 0xFa5B0cF5301C6263Df3F39624984BC9aE918faA3
clUSDC 0x9eCf84eC8EB63BCFD9DD3B487D6935d9Cc319019
LendingPool 0x87BcBc638d700bb91b36B8607cB4d22dcD4c8A61
LoanManager 0x11B0a5D5B1A922a46C17C21Fb4cb6A8559C3076F
PropertyNFT 0x30D49451E729756627A085a9ee0bc2A0eE2F2806
LienFiAuction 0xffE7cE1f9C1624c6419FDd9E9d3A57d95E36DAa6

Project Structure

lienfi/
├── contracts/                           # Solidity smart contracts (Foundry)
│   ├── src/
│   │   ├── LienFiAuction.sol           # Auction: deposit pool + sealed bids + Vickrey
│   │   ├── LienFiRWAToken.sol          # ERC-20 RWA token (legacy)
│   │   ├── LoanManager.sol             # Full mortgage lifecycle + CRE verdict receiver
│   │   ├── LendingPool.sol             # USDC pool — disburse, EMI collection
│   │   ├── clUSDC.sol                  # Receipt token with appreciating exchange rate
│   │   ├── PropertyNFT.sol             # ERC-721 — commitment hash only, no on-chain metadata
│   │   ├── ReceiverTemplate.sol        # Abstract base for Keystone CRE DON-signed reports
│   │   ├── interfaces/
│   │   │   ├── ILendingPool.sol
│   │   │   ├── ILienFiAuction.sol
│   │   │   ├── ILienFiRWAToken.sol
│   │   │   ├── ILoanManager.sol
│   │   │   ├── IPropertyNFT.sol
│   │   │   ├── IReceiver.sol
│   │   ├── libraries/
│   │   └── mocks/
│   │       └── MockUSDC.sol            # 6-decimal test USDC
│   ├── script/
│   │   └── DeployLienFi.s.sol         # Full deployment + wiring script
│   └── foundry.toml
├── api/                                 # Private API (Express.js + TypeScript)
│   └── src/
│       ├── server.ts
│       ├── routes/
│       │   ├── bid.ts                  # POST /bid — EIP-712 validation + bid storage
│       │   ├── bidHash.ts              # GET /bidHash/:auctionId/:bidder — retrieve bid hash
│       │   ├── listing.ts              # GET /listing/:auctionId — sanitized auction listing
│       │   ├── listingHash.ts          # GET /listingHash/:auctionId — listing hash lookup
│       │   ├── loanRequest.ts          # POST /loanRequest — store request details, return hash
│       │   ├── pendingBid.ts           # GET /pendingBid/:auctionId — pending bid status
│       │   ├── reveal.ts               # POST /reveal/:auctionId — winner-only address reveal
│       │   ├── settle.ts               # POST /settle — trigger Vickrey settlement
│       │   ├── status.ts               # GET /status/:auctionId — auction status
│       │   ├── verifyProperty.ts       # POST /verify-property — property verification + commitment hash
│       │   └── workflowLogs.ts         # GET /workflow-logs — CRE workflow execution logs
│       └── lib/
│           ├── auth.ts                 # API key middleware
│           ├── chain.ts                # On-chain state reads (viem)
│           ├── db.ts                   # Persistent storage layer
│           ├── eip712.ts               # EIP-712 signature verification
│           ├── store.ts                # In-memory bid + loan request + property store
│           └── vickrey.ts              # Second-price auction settlement logic
├── cre-workflows/                       # Chainlink CRE Workflows
│   ├── project.yaml                    # CRE project settings (RPC URLs, chains)
│   ├── secrets.yaml                    # Vault DON secret mappings
│   ├── abis/                           # Contract ABIs consumed by workflows
│   │   ├── LienFiAuctionABI.json
│   │   └── LoanManagerABI.json
│   ├── bid-workflow/                   # Sealed bid collection (HTTP trigger)
│   ├── create-auction-workflow/        # Default detection + auction creation (HTTP trigger)
│   ├── credit-assessment-workflow/     # Plaid + Gemini credit scoring (log trigger)
│   ├── settlement-workflow/            # Vickrey settlement (cron trigger)
│   └── generate-bid-payload.ts         # Helper: generate EIP-712 signed test bids
├── frontend/                            # Next.js dashboard (Privy + wagmi)
│   └── src/
│       ├── app/
│       │   ├── auctions/               # Auction listing + detail pages
│       │   ├── borrow/                 # Borrower loan application flow
│       │   ├── pool/                   # Lender deposit + clUSDC stats
│       │   └── workflows/              # CRE workflow execution log viewer
│       ├── components/
│       │   ├── charts/                 # Exchange rate + pool composition charts
│       │   ├── layout/                 # Sidebar, TopNav, BottomDock
│       │   └── ui/                     # Shared UI primitives (shadcn-style)
│       ├── config/                     # Contract addresses, Privy, wagmi config
│       ├── hooks/                      # useAuction, useLoan, usePool, useTokenBalances
│       └── lib/                        # API client, tx history, notifications
├── demo/                                # End-to-end demo orchestrator (tsx)
│   └── src/
│       ├── main.ts                     # CLI entry — arg parsing, startup checks, phase runner
│       ├── config.ts                   # Env var loading + defaults
│       ├── clients.ts                  # viem wallet/public clients per actor
│       ├── abis.ts                     # Contract ABIs
│       ├── logger.ts                   # Chalk-coloured per-phase logger
│       ├── checkpoint.ts               # Phase checkpoint read/write
│       ├── cre.ts                      # CRE CLI wrapper (spawn + capture)
│       ├── utils.ts                    # poll, sleep, format helpers
│       └── phases/
│           ├── a-pool-funding.ts
│           ├── b-property-mint.ts
│           ├── c-credit-assessment.ts
│           ├── d-loan-disbursement.ts
│           ├── e-repayment.ts
│           ├── f1-default-auction.ts
│           ├── f2-sealed-bidding.ts
│           └── f3-settlement.ts
├── assets/                              # Logo, banner, diagrams
└── README.md

Credit Assessment Pipeline

The credit assessment is the core innovation — a fully private underwriting flow inside Chainlink CRE:

Borrower                    API                         CRE Enclave
   |                         |                              |
   |-- POST /loanRequest --->|                              |
   |   {plaidToken, tokenId, |                              |
   |    amount, tenure}      |                              |
   |                         |-- store details,             |
   |                         |   compute requestHash        |
   |<-- { requestHash } -----|                              |
   |                         |                              |
   |-- submitRequest(hash) --|------ on-chain tx ---------> |
   |   (LoanManager)         |                              |
   |                         |   LoanRequestSubmitted event |
   |                         |                              |
   |                         |<-- fetch by requestHash -----|
   |                         |-- return details ----------->|
   |                         |                              |
   |                         |   1. Verify hash matches     |
   |                         |   2. Get appraised value     |
   |                         |   3. Compute EMI             |
   |                         |                              |
   |                         |<-- Plaid API (Conf. HTTP) ---|
   |                         |-- bank data ---------------->|
   |                         |                              |
   |                         |   4. Extract metrics:        |
   |                         |      income, DTI, stability  |
   |                         |   5. Hard gates:             |
   |                         |      LTV <= 80%              |
   |                         |      coverage >= 3x          |
   |                         |      no recent defaults      |
   |                         |                              |
   |                         |<-- Gemini (Conf. HTTP) ------|
   |                         |-- metrics only ------------->|
   |                         |                              |
   |                         |   6. creditScore, verdict,   |
   |                         |      approvedAmount          |
   |                         |   7. DISCARD all raw data    |
   |                         |                              |
   |                         |   8. Write verdict on-chain  |
   |                         |      via KeystoneForwarder   |
   |                         |                              |
   |<---- LoanRequestApproved / LoanRequestRejected -------|

Running the Demo

The demo script runs the full LienFi lifecycle end-to-end on Sepolia: pool funding → property mint → credit assessment → loan disbursement → repayment → default detection → sealed-bid auction → Vickrey settlement.

Prerequisites

  • Node.js 20+
  • CRE CLI
  • 4 Sepolia wallets funded with ~0.05 ETH each (lender, borrower, bidder A, bidder B)
  • Plaid sandbox credentials (free signup)

1. Set up the demo environment

cd demo
npm install
cp .env.example .env

Edit demo/.env — the following are pre-configured and ready to use:

Variable Value
RPC_URL https://ethereum-sepolia-rpc.publicnode.com
API_URL https://lienfi.onrender.com
API_KEY 33ab8800ae775f6b302118a9f9811bf77d4633450ee690e27afcd2eb4a33cc25
MOCK_USDC_ADDRESS 0xFa5B0cF5301C6263Df3F39624984BC9aE918faA3
LENDING_POOL_ADDRESS 0x87BcBc638d700bb91b36B8607cB4d22dcD4c8A61
LOAN_MANAGER_ADDRESS 0x11B0a5D5B1A922a46C17C21Fb4cb6A8559C3076F
PROPERTY_NFT_ADDRESS 0x30D49451E729756627A085a9ee0bc2A0eE2F2806
LIENFI_AUCTION_ADDRESS 0xffE7cE1f9C1624c6419FDd9E9d3A57d95E36DAa6
CL_USDC_ADDRESS 0x9eCf84eC8EB63BCFD9DD3B487D6935d9Cc319019

You must set these yourself:

Variable How to get it
LENDER_PRIVATE_KEY Sepolia wallet private key (0x-prefixed, 64 hex chars)
BORROWER_PRIVATE_KEY Different Sepolia wallet
BIDDER_A_PRIVATE_KEY Different Sepolia wallet
BIDDER_B_PRIVATE_KEY Different Sepolia wallet
PLAID_CLIENT_ID From Plaid Dashboard (sandbox)
PLAID_SECRET From Plaid Dashboard (sandbox secret)

2. Set up the CRE workflow environment

The demo invokes CRE workflows for credit assessment, bid submission, default detection, and settlement. You need to configure the CRE environment:

cd cre-workflows
cp .env.example .env

Edit cre-workflows/.env — the API key is pre-configured:

MY_API_KEY_ALL=33ab8800ae775f6b302118a9f9811bf77d4633450ee690e27afcd2eb4a33cc25

You must set these yourself:

Variable How to get it
CRE_ETH_PRIVATE_KEY Sepolia wallet private key used by CRE workflows to sign and submit on-chain transactions
AES_KEY_ALL 32-byte hex AES-GCM encryption key (openssl rand -hex 32)
GROQ_API_KEY_ALL From Groq Console
PLAID_SECRET_ALL Same Plaid sandbox secret as demo
PLAID_CLIENT_ID_ALL Same Plaid client ID as demo

3. Run the demo

cd demo

# Run all phases (A through F3)
npm run demo

# Start fresh (clear checkpoint and run from the beginning)
npm run demo:fresh

# Resume from a specific phase
npm run demo:from -- e

# Run a single phase
npx tsx src/main.ts --phase f2

First run? Use npm run demo:fresh to ensure a clean start.

The demo uses a checkpoint file (demo/checkpoint.json) to track progress. If a phase fails, fix the issue and re-run — it will resume from where it left off. Use npm run demo:fresh to clear the checkpoint and start over.

Demo Phases

Phase Description Duration
A Pool Funding — lender deposits USDC, receives clUSDC ~30s
B Property Mint — borrower verifies property, mints NFT ~30s
C Credit Assessment — loan request → CRE → Plaid + AI scoring ~2min
D Loan Disbursement — NFT locked, USDC disbursed ~30s
E Repayment + Default — 1 EMI paid, then wait for default detection ~8min
F1 Default Auction — CRE detects default, creates auction ~1min
F2 Sealed Bidding — two bidders deposit + submit sealed bids ~2min
F3 Vickrey Settlement — CRE settles auction, winner gets NFT ~1min

See demo/sample-output.txt for a full example of what the demo output looks like.


Team

Built by the LienFi Team for the Chainlink Convergence Hackathon.


License

MIT License — see LICENSE for details.


LienFi Logo

Built for the Chainlink Convergence Hackathon 2025
Powered by Chainlink CRE • Credit via Plaid + Gemini • Deployed on Sepolia

About

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors