FinTech · AI Underwriting · On-Chain Lending

CreditRail — AI-Assisted
Underwriting & On-Chain Credit Distribution

An end-to-end demo of the technology stack behind a short-duration business-credit fintech — a company that lends working capital to small businesses and funds those loans by distributing them to institutional investors as on-chain vault shares. Every component runs end to end against a real local Postgres database and a real local Hardhat blockchain node — no mocked-up UI over fake data.

Project Type Portfolio / Technical Demo
Core Pipeline 9 Cooperating Components
Chain Layer ERC-4626 + ERC-4337
Industry FinTech / Lending / DeFi
CreditRail — AI-Assisted Underwriting and On-Chain Credit Distribution Platform

Overview

CreditRail is an end-to-end demo of the technology stack behind a short-duration business-credit fintech: a company that lends working capital to small businesses and funds those loans by distributing them to institutional investors as on-chain vault shares. It was built as a portfolio project to demonstrate the exact skill set a fractional CTO / technical-lead role for such a company requires — architecture, ML-based risk scoring, lending business logic, tokenization, security/audit posture, and the judgment to know what to build versus what to buy.

Every component is real and runs end to end against a local Postgres database and a local Hardhat blockchain node — there is no mocked-up UI sitting on top of fake data.

The Problem

A pre-pilot fintech lending to small businesses has to solve five different problems at once, and they don't compose cleanly:

Underwriting

Decide, from messy real-world data (bank statements), whether a business is a safe credit risk, without a credit bureau history to lean on.

Fraud vs. credit risk

"Will this business repay" and "is this business/its documents even real" are different questions. Conflating them into one model is exactly the kind of black-box design a regulator or auditor pushes back on.

Loan servicing

Once approved, someone has to track amortization, payments, missed payments, and defaults correctly, because this is money, not a demo toy.

Funding the loans

The company doesn't have its own balance sheet deep enough to fund loans itself; it needs to package approved loans into a pool and raise capital from institutional investors against that pool.

Investor distribution that survives diligence

Institutional investors expect a transparent, auditable way to hold and value their position (which points toward tokenization), but also expect KYC enforcement that doesn't disappear the moment a token changes hands — and won't accept "one private key controls everything" as a custody answer.

The hard part isn't any one of these in isolation — it's that they have to be wired together (ingestion feeds ML feeds underwriting feeds loans feeds pools feeds on-chain accounting) without the seams silently drifting apart, while keeping every state change auditable.

The Solution

CreditRail implements the full pipeline as nine cooperating pieces, every arrow below a real HTTP call or on-chain transaction — reproducible via the Streamlit dashboard, pytest, or hardhat test, not just a diagram.

Bank statement CSV / invoice image
        │
        ▼
  Ingestion (parse + OCR)
        │
        ▼
  Feature engineering (shared by training AND inference)
        │
        ▼
  XGBoost risk model  +  independent fraud rule engine
        │
        ▼
  Underwriting decision → Loan issuance → amortized repayment schedule
        │
        ▼
  Pool aggregation (off-chain grouping of approved loans)
        │
        ▼
  CreditVault.sol (ERC-4626, on-chain) ← institutional investors
        │  permissioned deposit / withdraw / transfer (Allowlist-gated)
        ▼
  Gasless deposits for KYC'd investors (ERC-4337 account abstraction)
LayerTechnology
BackendPython, FastAPI, SQLAlchemy, PostgreSQL, Alembic
ML / Riskscikit-learn, XGBoost, pandas
ChainSolidity 0.8.24, OpenZeppelin 5, Hardhat, eth-infinitism ERC-4337 reference contracts
Chain Bridgeweb3.py
DashboardStreamlit
InfraDocker Compose

How It Was Built

The build was sequenced in nine phases, each one deliberately laying a foundation the next phase depends on — from backend auth through data ingestion, ML scoring, loan servicing, smart contracts, the chain bridge, and finally gasless account abstraction for investors.

1. Backend Foundation — Auth & Audit Trail

FastAPI + PostgreSQL + SQLAlchemy + Alembic, JWT auth with bcrypt password hashing, and role-based access control (admin / underwriter / investor) enforced per-endpoint. Every state-changing action writes to an AuditLog table.

CreditRail secure role-based login and admin session

2. Data Ingestion — Bank Statements & Invoice OCR

A from-scratch bank-statement CSV parser and categorizer, plus a Tesseract-OCR-based invoice/receipt field extractor (vendor, invoice number, date, amount) for document-extraction demo purposes.

CreditRail business onboarding and bank-statement CSV ingestion CreditRail invoice and receipt OCR upload

3. Loan APIs — Application & Underwriting Intake

Application creation, scoring-gated underwriting decisions, amortized repayment-schedule generation, and payment/missed-payment/default tracking.

CreditRail loan application and requested amount intake

4. ML Scoring — XGBoost Risk Model + Fraud Rule Engine

A synthetic-data generator (since no real loan performance data exists for a pre-pilot company), an XGBoost risk classifier, and a rule-based fraud engine kept deliberately separate from the ML score — so a regulator can ask "why was this flagged" independently for each question.

CreditRail AI risk and fraud scoring with underwriting decision

5. Loan Servicing — Amortized Repayment Schedule

Once issued, loans move into servicing — an amortized repayment schedule with due dates, amounts due/paid, and status, tracked per loan so payments, missed payments, and defaults are never lost in a spreadsheet.

CreditRail loan servicing and amortized repayment schedule

6. Pool Aggregation — Grouping Loans for Investors

Off-chain grouping of approved loans into a pool — loans in pool, total principal, and weighted average rate — the step that packages individual loans into something an institutional investor can fund as a single position.

CreditRail pools and institutional distribution

7. Smart Contracts — CreditVault (ERC-4626)

CreditVault.sol (ERC-4626), Allowlist.sol (KYC registry), and MockUSDC.sol (test stablecoin), built on OpenZeppelin bases and tested with Hardhat/Mocha/Chai — total assets, deployed capital, cash on hand, and live share price, plus an investor wallet lookup.

CreditRail on-chain ERC-4626 vault with share price and investor lookup

8. Backend–Blockchain Bridge — Admin Vault Actions

web3client/vault_client.py, called from a small set of explicit, admin-triggered endpoints — allow an investor onto the KYC allowlist, disburse loan capital to a borrower, record a repayment (principal + yield), or record a default write-off — rather than inlined into loan servicing.

CreditRail admin vault actions — KYC allowlist, disbursement, repayment, default

9. Account Abstraction — Gasless Investor Deposits

An ERC-4337 paymaster so KYC'd institutional investors can deposit into the vault without ever holding ETH for gas — a real institutional-onboarding problem solved with account abstraction rather than asking every investor to manage a funded wallet.

  • A Streamlit dashboard exercises every API endpoint through real HTTP calls, end to end
  • Documentation includes architecture, build-vs-buy reasoning, a SOC 2-mapped security doc, and an RWA-tokenization design doc

Key Engineering Decisions

One feature-engineering function, used by both training and inference.

ml/features.py has no FastAPI or training-script imports — it operates on anything shaped like a transaction, whether that's a live database row or synthetic training data run through the same parser. This closes off a common, silent class of bug: a model trained on one feature computation and served on a slightly different one, with no error, just quietly wrong scores.

The chain bridge is a separate, explicit step — not inlined into loan servicing.

Recording a payment or a default in the database never directly calls the blockchain. The database stays the system of record for loan status (which has regulatory/contractual timelines that can't wait on chain congestion), and a handful of admin-triggered endpoints reconcile confirmed off-chain events onto the vault as a separate, auditable step — mirroring how production fintech-to-chain bridges are actually built.

Permissioned transfers are enforced on every path that moves a share or the underlying asset — not just the obvious one.

A KYC allowlist that only gates the initial deposit() call is a compliance hole — a holder could freely transfer shares to an unapproved wallet afterward. CreditVault overrides three separate hooks (_deposit, _withdraw, _update) because OpenZeppelin's ERC-4626/ERC-20 base contracts don't expose one hook that covers deposit, withdraw, and peer-to-peer transfer at once. Each path has its own test so this doesn't regress silently.

Fraud detection is kept out of the ML model on purpose.

"Will this business repay" (a probabilistic credit judgment) and "is this business/its documents legitimate" (closer to a compliance check) are answered by two independent systems — an XGBoost risk score and a rule-based fraud engine (NSF-fee frequency, round-number deposits, revenue spikes, insufficient history). A single black-box model that mixes both doesn't give a defensible answer to an auditor.

Challenges Encountered — and How They Were Resolved

ERC-4337 counterfactual address mismatch.

While building the gasless-deposit flow, SimpleAccountFactory.getAddress(owner, salt) (the CREATE2 address prediction) returned a different address than what createAccount(owner, salt) actually deployed to, in the reference contracts pulled in for this project. Fix: use the address createAccount actually returns (captured via a .staticCall before sending the real transaction) instead of trusting the predicted address — "counterfactual address" tricks need to be verified against the actual factory version in use, not assumed correct from the spec.

Keeping the demo honest about trust assumptions.

It would have been easy to present the vault's NAV accounting as fully trustless. It isn't — a single admin key currently asserts that a repayment or default happened off-chain. Rather than hide that, the design docs (RWA_TOKENIZATION.md, BUILD_VS_BUY.md) state it plainly and describe the concrete path to a real attestation/oracle design or a multi-sig/custody-provider setup.

A late-discovered UI/backend integration gap.

After the initial build was verified via pytest/hardhat test, walking the dashboard flow end-to-end (rather than just the API) surfaced that the invoice-OCR endpoint (POST /documents/extract-invoice) — fully implemented and tested on the backend — had no corresponding upload control in the Streamlit UI. Unit and integration tests verify the pieces work, not that every piece is wired to the surface a user touches. Fixed with a straightforward UI addition calling the already-existing extract_invoice client method.

Business-vs-application state confusion during manual QA.

A scoring call failed with "No transactions on file for this business," even though bank-statement data had just been uploaded. Root cause: the dashboard's ingestion section and its scoring section use two independent "select a business" dropdowns, and a business had been created twice (an accidental double-submit) — so the CSV was uploaded against one Business row while the loan application lived under a different one with the same display name. A UX footgun, not a logic bug — the kind of failure mode that only shows up when a real person drives the UI instead of a script calling the API directly.

Live Demo

Security & Compliance Posture

Mapped explicitly against SOC 2 Trust Service Criteria in docs/SECURITY_COMPLIANCE.md:

JWT Auth + bcrypt Hashing
Per-Endpoint RBAC
Complete Audit Trail
PII Encrypted at Rest (Fernet)
onlyOwner-Gated Vault Functions
KYC Allowlist-Gated Investor Paths
Pinned Dependency Versions

Just as important, the same document states plainly what a demo like this does not cover — formal policies, real infrastructure controls, a third-party smart-contract audit and penetration test, continuous monitoring, and a real secrets-management story — because a case study that only lists what's done and omits what isn't reads as marketing, not engineering.

Results & Verification

62/62 Backend pytest suite passing — ingestion, features, fraud rules, scoring, servicing, chain-bridge API
13/13 Hardhat smart-contract tests passing — allowlist, NAV mechanics, gasless deposits
$50,500 Exact totalAssets after a live 50,000 mUSDC deposit → 30,000 disbursed → 30,000 + 500 yield repaid
0 ETH Gasless deposit demo — KYC'd investor balance before and after, paymaster absorbs gas
AUC ≈ 0.57 ML model trained on synthetic data — intentionally modest, the point is the pipeline, not a calibrated model

All numbers verified against a real local Hardhat node and Postgres database — reproducible via pytest, hardhat test, and contracts/scripts/gasless_investor_deposit.js, not just claimed.

Honest Limitations

Stated deliberately, because a demo that hides its limitations is worth less than one that states them:

The ML model is trained on synthetic, not real, loan-performance data.
The contracts and backend have not been through a professional security audit or penetration test.
A single admin-controlled key currently reports repayments/defaults on-chain; production needs a real oracle/attestation design or a multi-sig/custody-provider setup.
Contracts are deployed and tested only against a local Hardhat node, not a testnet or mainnet.

What This Case Study Demonstrates

For a hands-on fractional CTO / technical-lead role at an early-stage lending fintech, this project is meant to show, with working code rather than a resume bullet:

  • End-to-end architecture ownership across data ingestion, ML/scoring, APIs, and the issuance/servicing layer.
  • Concrete build-vs-buy reasoning on core infrastructure (KYC/AML, custody, bank-data ingestion) — including why something was built in-house when it was.
  • A security/auditability/compliance posture mapped to a real framework (SOC 2), stated honestly including its gaps.
  • Applied tokenization/DeFi depth: ERC-4626 vault accounting, permissioned transfers enforced across every relevant path, and ERC-4337 account abstraction solving a real institutional-onboarding problem.
  • ML applied to credit risk and fraud detection, with the two kept intentionally separate for auditability.

Need an AI-Powered Lending or Tokenization Platform?

We build custom underwriting engines, loan servicing systems, and on-chain investor distribution tailored to your lending model and regulatory environment.