diff --git a/CHANGELOG.md b/CHANGELOG.md index d1c42c1..ed48062 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,39 +8,44 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added -- Collateral price oracle (#267): XLM and BTC prices polled every 5 seconds from - CoinGecko, Binance and the Stellar DEX, aggregated by median with outlier - rejection, and pushed on-chain. Fallback chain is live → cached → on-chain - TWAP → refuse to publish. The liquidation keeper now values positions with the - live price instead of a hardcoded constant. See +- Token-based design system (`app/theme.css`, `components/ui/`) with light, + dark and system themes, shared framer-motion presets and animated stat + components; landing, auth and dashboard shell rebuilt on it (#314). +- Neon Postgres + Drizzle ORM data layer with versioned migrations in + `drizzle/`, `npm run db:*` scripts and a CI migration step on `main` (#313). +- Sign-In with Stellar (SEP-10) sessions issued as signed HttpOnly cookies; + private KYC document storage on Vercel Blob (#313). +- GitHub Actions keeper workflow that triggers the liquidation keeper and price + oracle every 5 minutes, working around Vercel's daily-cron limit (#312). +- SEP-24 fiat on/off ramp integration with Stellar anchors (#309). +- Grace period before liquidations can be triggered (#308). +- Collateral price oracle (#267): XLM and BTC prices from CoinGecko, Binance + and the Stellar DEX, aggregated by median with outlier rejection and pushed + on-chain; the liquidation keeper values positions with the live price. See [docs/oracle-price-feeds.md](docs/oracle-price-feeds.md). -- Referral programme (#266): every user gets a unique invite link, and when an - invited friend's first loan is funded the referrer's bonus is transferred - automatically by the new `referral_rewards` Soroban contract during - `activate_loan`. Includes a referral dashboard, attribution APIs, and - `sql/09_referral_program.sql`. See [docs/referral-program.md](docs/referral-program.md). -- Borrowing user guide and FAQ at `/docs/borrowing`, covering the step-by-step - borrowing process, how the liquidation threshold is calculated, Health Factor - bands, and 15 frequently asked questions. Linked from the borrower dashboard - nav and the landing footer (#265). -- Keyboard-accessible glossary tooltips for financial acronyms (APR, APY, LTV, - Trust Score, Health Factor, basis points) across the borrower, lender and - admin dashboards, backed by a shared `lib/glossary` definition list (#264). -- Initial open-source release setup. -- Basic repository files: README, LICENSE, CONTRIBUTING, CODE_OF_CONDUCT, SECURITY. -- GitHub issue and pull request templates. +- Referral programme (#266): unique invite links with the referrer's bonus paid + by the `referral_rewards` contract during `activate_loan`. See + [docs/referral-program.md](docs/referral-program.md). +- Borrowing user guide and FAQ at `/docs/borrowing` (#265) and keyboard + accessible glossary tooltips for financial terms (#264). +- Initial open-source release setup: README, LICENSE, CONTRIBUTING, + CODE_OF_CONDUCT, SECURITY, issue and pull request templates. ### Changed -- None yet. - -### Deprecated -- None yet. +- README rewritten for open-source readers; rate-limiting and payment-due + scheduler details moved to `docs/rate-limiting.md` and + `docs/payment-due-scheduler.md`; roadmap and contributing guide refreshed. +- Vercel cron jobs reduced to daily schedules (Hobby plan limit) (#312). +- Hard-coded colours across dashboards replaced with theme tokens so every + screen renders in both themes (#314). ### Removed -- None yet. +- Supabase client, auth, RLS policies and SQL scripts, replaced by Neon + + Drizzle (#313). +- Dead code, the indexer stack, stale documentation and unused assets (#311). ### Fixed -- None yet. - -### Security -- None yet. +- Chart area fills rendered black because a CSS variable was used as an SVG + gradient id (#314). +- Contract CI job: `usdc_lending_pool` arithmetic widths, token transfer + calls and clippy warnings (#312). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1fc1cdb..a12d3f1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,71 +1,119 @@ # Contributing to TrustLend -First off, thank you for considering contributing to TrustLend! It's people like you that make TrustLend such a great tool. +Thanks for your interest in TrustLend! This guide covers how to set up a +development environment, the conventions we follow, and how to get a change +merged. For the full local-setup walkthrough (Rust/Soroban toolchain, database, +tests) see [docs/getting-started.md](docs/getting-started.md). + +## Ways to contribute + +- **Report bugs** or **request features** through + [GitHub Issues](https://github.com/thisisouvik/trustlend-stellar/issues) using + the provided templates. For anything security-related, follow + [SECURITY.md](SECURITY.md) instead of opening a public issue. +- **Improve documentation** — everything under [`docs/`](docs/) and the README. +- **Write code** — frontend (Next.js / React), backend routes, or Soroban + contracts in [`contracts/`](contracts/). Issues labelled `good first issue` + are a good starting point, and the "contract only" crates listed in the README + still need frontend wiring. + +Before starting on a larger change, open an issue (or comment on an existing +one) so we can agree on the approach first. + +## Development setup + +```bash +git clone https://github.com//trustlend-stellar.git +cd trustlend-stellar +git remote add upstream https://github.com/thisisouvik/trustlend-stellar.git + +npm install # also installs the Husky commit hook +cp .env.example .env.local # set DATABASE_URL, SESSION_SECRET, SIWS_SERVER_SECRET +npm run db:migrate # apply Drizzle migrations to your Neon database +npm run dev # http://localhost:3000 +``` -## Where do I go from here? +Contract work additionally needs a Rust toolchain with the +`wasm32-unknown-unknown` target and the `stellar` CLI — see +[docs/getting-started.md](docs/getting-started.md#4-smart-contract-setup-soroban--rust). -If you've noticed a bug or have a feature request, make one! It's generally best if you get confirmation of your bug or approval for your feature request this way before starting to code. +## Branches -## Fork & create a branch +Work on a branch created from an up-to-date `main`: -If this is something you think you can fix, then fork TrustLend and create a branch with a descriptive name. +```bash +git checkout main +git pull upstream main +git checkout -b feat/short-description # or fix/…, docs/…, chore/… +``` -A good branch name would be (where issue #325 is the ticket you're working on): +Rebase onto `main` (rather than merging) when you need to pick up changes. -```sh -git checkout -b 325-add-stellar-wallet-support -``` +## Commit messages -## Setup Local Development +Commits must follow [Conventional Commits](https://www.conventionalcommits.org/); +a `commit-msg` hook runs commitlint and rejects anything that doesn't match. -**Option 1: Node.js (Standard)** -Make sure you have Node.js and npm installed. -```sh -npm install -npm run dev ``` +(): -**Option 2: Docker Compose (Easier)** -If you prefer not to install dependencies locally, just use Docker: -```sh -docker-compose up + ``` -Ensure everything works correctly on your local machine at `http://localhost:3000`. - -## Implement your fix or feature - -At this point, you're ready to make your changes. Feel free to ask for help; everyone is a beginner at first. - -## Make a Pull Request - -At this point, you should switch back to your master branch and make sure it's up to date with TrustLend's master branch: - -```sh -git remote add upstream git@github.com:thisisouvik/trustlend-stellar.git -git checkout master -git pull upstream master +- **Types:** `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, + `build`, `ci`, `chore`, `revert`, plus project-specific `contract`, + `stellar` and `security`. +- **Scopes** (required): `lending`, `escrow`, `governance`, + `default-management`, `multisig-admin`, `borrower-reputation`, + `auto-compound-vault`, `treasury`, `contracts`, `frontend`, `dashboard`, + `auth`, `kyc`, `api`, `ci`, `db`, `neon`, `drizzle`, `stellar`, `soroban`, + `docs`, `deps`, `config`, `landing`, `hooks`. + +Examples: `feat(lending): add early-repayment discount`, +`fix(auth): reject expired SEP-10 challenges`, `docs(contracts): document escrow revocation window`. +The complete rule set lives in [`commitlint.config.ts`](commitlint.config.ts). + +## Before opening a pull request + +Run the same checks CI runs: + +```bash +npx tsc --noEmit # type check +npm run lint # ESLint +npm test # Vitest unit tests +npm run build # Next.js production build + +# if you touched contracts/ +cd contracts +cargo test +cargo clippy --all-targets -- -D warnings -A clippy::inconsistent_digit_grouping +cargo build --target wasm32-unknown-unknown --release ``` -Then update your feature branch from your local copy of master, and push it! - -```sh -git checkout 325-add-stellar-wallet-support -git rebase master -git push --set-upstream origin 325-add-stellar-wallet-support -``` +A few conventions to keep in mind: -Finally, go to GitHub and make a Pull Request. +- Add or update tests for behaviour you change (`__tests__/` for the app, + `#[cfg(test)]` modules for contracts). +- Schema changes go through Drizzle: edit `lib/db/schema.ts`, run + `npm run db:generate`, and commit the generated migration in `drizzle/`. +- Use the design tokens in `app/theme.css` and the primitives in + `components/ui/` for UI work so both light and dark themes keep working. +- Never commit secrets. `.env.local` is git-ignored; `.env.example` documents + every variable. -## Keeping your Pull Request updated +## Pull requests -If a maintainer asks you to "rebase" your PR, they're saying that a lot of code has changed, and that you need to update your branch so it's easier to merge. +1. Push your branch to your fork and open a PR against `main`. +2. Fill in the PR template: what changed, why, how it was tested, and + screenshots for UI changes. +3. Keep PRs focused. Unrelated refactors are easier to review as separate PRs. +4. CI must pass. A maintainer will review; please respond to feedback in the + same PR rather than opening a new one. -## Merging A PR (maintainers only) +Maintainers merge a PR once it passes CI, has at least one approval, has no +outstanding change requests, and is up to date with `main`. -A PR can only be merged into master by a maintainer if: +## Code of conduct -* It is passing CI. -* It has been approved by at least one maintainer. -* It has no requested changes. -* It is up to date with current master. \ No newline at end of file +This project follows the [Contributor Covenant](CODE_OF_CONDUCT.md). By +participating you agree to uphold it. diff --git a/README.md b/README.md index f6a5b51..19985b9 100644 --- a/README.md +++ b/README.md @@ -1,537 +1,227 @@

- TrustLend Logo + TrustLend logo

TrustLend

-

Reputation is your credit score. Earn trust, unlock capital, and build financial access.

+

+ Reputation-based micro-lending on Stellar.
+ Borrowers build an on-chain trust score instead of posting collateral; lenders earn transparent yield. +

- Next.js - React - TypeScript - Neon Postgres - Stellar - Soroban - Stellar Wave + CI + MIT license + Next.js 16 + Soroban / Rust + Neon Postgres + Stellar Testnet

-

✨ Fast. Transparent. Auditable. Global. ✨

+

+ Live demo · + Video walkthrough · + Getting started · + Roadmap · + Contributing +

- Live Production | - Video Demo | - Roadmap | - Getting Started Guide | - Contributing Guidelines + TrustLend landing page

--- -## 🌍 About The Project +## What is TrustLend? -**TrustLend** is a decentralized micro-lending platform built on Stellar and Soroban. It bridges the gap between: -- **Borrowers** in emerging markets who need fast, collateral-free working capital. -- **Lenders** who want transparent yield with measurable social impact. +Millions of people in emerging markets are locked out of credit because they have no formal credit history and nothing to pledge as collateral. TrustLend replaces collateral with **behaviour**: every on-time repayment raises a borrower's on-chain trust score, which in turn unlocks larger loans and better rates. -Traditional lending excludes millions who lack formal credit history or collateral. TrustLend solves this by utilizing **behavior-based on-chain reputation** and contract-enforced lending rules. - -### 🏆 What Makes TrustLend Unique? -- **Behavior-Based Reputation:** Replaces legacy collateral-first lending with dynamic on-chain scoring. -- **Escrow-Assisted Disbursement:** Includes revocation window controls to protect lenders. -- **Default Management:** Mitigates risk via insurance-pool mechanics. -- **Gasless Fee Sponsorship:** Removes native XLM friction from user actions so borrowers aren't blocked by network fees. -- **End-to-End Traceability:** Complete transparency through on-chain contract events. - ---- +- **Borrowers** sign in with a Stellar wallet, complete KYC and request working capital. Eligibility and pricing are computed from their trust score. +- **Lenders** fund individual requests in a marketplace or deposit into pooled lending for passive yield, with every position traceable on-chain. +- **Admins** review KYC, tune risk parameters and approve sensitive actions through an N-of-M multisig. -## 🚀 Vision: How TrustLend Can Evolve +The protocol runs on a hybrid architecture: **trust-critical logic on Soroban smart contracts** (scoring, escrow, defaults, governance) and a **fast off-chain layer** (Next.js + Neon Postgres) for dashboards, KYC and notifications. -TrustLend is designed as a foundational layer for decentralized, inclusive credit. As an open-source project, our vision for evolution includes: +### Highlights -1. **Decentralized Credit Oracles:** Evolving the reputation system to aggregate off-chain data (utility bills, mobile money history, Web2 integrations) via trusted oracles. -2. **Cross-Chain Liquidity:** Expanding lender pools to accept stablecoins across various ecosystems, routing them securely through Stellar's high-speed finality layer. -3. **DAO Governance:** Transitioning platform parameters (interest rates, insurance pool fees, slashing mechanics) to a community-governed DAO framework. -4. **Institutional Underwriting:** Enabling institutional liquidity providers to plug proprietary risk models into TrustLend's smart contracts to automatically fund specific borrower profiles. -5. **Global Fiat On/Off Ramps:** Deepening integration with Stellar anchors to allow seamless fiat borrowing and repayment in local currencies worldwide. +| | | +|---|---| +| **Behaviour-based credit** | Trust score, tiers and credit limits computed from repayment history — no collateral required for reputation loans. | +| **Escrow-assisted disbursement** | Lender funds are held in escrow with a revocation window before release. | +| **Default management & insurance** | Overdue loans are marked on-chain and insurance payouts are proposed through the multisig. | +| **Wallet-native auth** | Sign-In with Stellar (SEP-10). No passwords, no third-party identity provider. | +| **Fiat on/off ramps** | SEP-24 anchor integration for deposits and withdrawals in local currency. | +| **Collateral price oracle** | Median-of-sources XLM/BTC feed with outlier rejection drives the liquidation keeper. | +| **Gasless UX** | Fee sponsorship so borrowers are never blocked by network fees. | -*We welcome open-source contributors to help us build this vision! Check out our [Roadmap](docs/roadmap.md) for upcoming milestones.* +

+ Borrower dashboard (light theme) + Lender portfolio (dark theme) +

--- -## 🏗️ Architecture & Workflow - -TrustLend uses a practical hybrid architecture: **fast UX off-chain** (Next.js + Neon Postgres) combined with **trust-critical logic on-chain** (Soroban/Stellar). The diagram below maps every component and data flow across all six layers of the platform. +## Architecture ```mermaid -flowchart TB - %% ── Style definitions ──────────────────────────────────────────────────── - classDef client fill:#3b82f6,color:#fff,stroke:#2563eb,stroke-width:2px - classDef backend fill:#8b5cf6,color:#fff,stroke:#7c3aed,stroke-width:2px - classDef automation fill:#f59e0b,color:#1e293b,stroke:#d97706,stroke-width:2px - classDef chain fill:#10b981,color:#fff,stroke:#059669,stroke-width:2px - classDef external fill:#64748b,color:#fff,stroke:#475569,stroke-width:2px - - %% ── Client Layer (Blue) ────────────────────────────────────────────────── - subgraph Client["🖥️ Client Layer"] - direction TB - WA[("🌐 Web App
Next.js 16 + React 19")] - BW[("👛 Stellar Wallet
Freighter / xBull / Albedo
WalletConnect (mobile)")] - RD[("📱 Role Dashboards
Borrower · Lender · Admin")] - end - - %% ── Backend Layer (Purple) ────────────────────────────────────────────── - subgraph Backend["⚙️ Backend Layer (Next.js)"] - direction TB - SA[("📡 Server Actions & API Routes
app/actions + app/api")] - SB[("🗄️ Neon Postgres
Drizzle ORM · sessions · Vercel Blob")] - RM[("🔌 Soroban Client
lib/stellar/soroban.ts")] - SC[("🔐 Server-side Contract Invoker
lib/stellar/server-contract.ts")] - RC[("⚡ Redis Cache
Simulation result cache")] - EM[("📧 Email Service
Resend · Payment notices")] +flowchart LR + subgraph Client + WA["Web app
Next.js 16 · React 19"] + WL["Stellar wallet
Freighter · xBull · Albedo · WalletConnect"] end - %% ── Automation Layer (Amber) ───────────────────────────────────────────── - subgraph Automation["⏰ Automation Layer (Cron / Vercel)"] - direction TB - PD[("📅 Payment-Due Scheduler
lib/scheduler/payment-due.ts
Vercel Cron: hourly")] - DM[("⚖️ Default Management
lib/scheduler/default-management.ts
Insurance + Mark Defaulted")] - LK[("🔨 Liquidation Keeper
scripts/liquidation-keeper.ts
Under-collateralization monitor")] - OC[("📊 Oracle Credit Score Poster
scripts/oracle-post-credit-score.mjs")] + subgraph Backend["Backend (Next.js)"] + API["API routes & server actions"] + DB[("Neon Postgres
Drizzle ORM")] + INV["Soroban client
lib/stellar"] end - %% ── Blockchain Layer (Green) ───────────────────────────────────────────── - subgraph Chain["⛓️ Blockchain Layer (Stellar Soroban)"] - direction TB - LP[("💳 Lending Contract
Loans · Repayments · Flash Loans")] - RP[("⭐ Reputation Contract
Borrower scoring · Tiers · Freeze")] - ES[("🔒 Escrow Contract
Hold funds · Revocation window")] - DF[("🛡️ Default Management
Insurance pool · Phases")] - MS[("🏛️ MultiSigAdmin Contract
N-of-M governance · Admin actions")] - GV[("🗳️ Governance Contract
Voting · Fee changes · Params")] - TR[("💰 Treasury Contract
Fee collection · 50/50 distribution")] - AC[("📈 Auto-Compound Vault
Yield auto-compounding · Harvest")] + subgraph Automation["Automation"] + CRON["Vercel cron (daily)
payment-due · defaults · scoring · liquidation · oracle"] + KEEP["GitHub Actions keepers (5 min)"] end - %% ── External / Stellar Layer (Gray) ───────────────────────────────────── - subgraph External["🌐 External Services & Infrastructure"] - direction TB - SR[("🌌 Soroban RPC
soroban-testnet.stellar.org")] - HZ[("🔭 Horizon API
horizon-testnet.stellar.org")] - WH[("🔔 Webhook
Payment-due notifications")] - KP[("🆔 KYC Provider
Document verification")] - AL[("📣 Alerts
Slack / Discord")] + subgraph Chain["Stellar Soroban (testnet)"] + LP["Lending"] + RP["Borrower reputation"] + ES["Escrow"] + DM["Default management"] + PL["Pooled lending"] + MS["Multisig admin"] + GV["Governance"] end - %% ── Client → Backend ────────────────────────────────────────────────── - WA --> SA - WA --> RD - BW --> WA - - SA --> SB - SA --> RM - SA --> SC - - %% ── Backend → Stellar RPC (Read = simulate, Write = sign+submit) ───── - RM -->|"simulateContractCall (read)"| SR - RM -->|"callContract (write)"| SR - SC -->|"invokeSigned / invokeReadOnly"| SR - - %% ── Backend internal links ─────────────────────────────────────────────── - RM --> RC - SC --> RC - SA --> EM - SA --> WH - - %% ── Automation ─────────────────────────────────────────────────────────── - PD -->|"queries due loans"| SB - PD --> WH - DM -->|"queries overdue loans"| SB - DM --> SC + WL --> WA --> API + API --> DB + API --> INV --> Chain + CRON --> API + KEEP --> API + LP <--> RP + LP <--> ES + LP --> DM DM --> MS - LK -->|"checks LTV from"| SR - LK --> SB - LK --> AL - OC -->|"posts credit score to"| RP - - %% ── Chain contracts ───────────────────────────────────────────────── - LP <-->|"loan approval"| ES - LP <-->|"eligibility check"| RP - LP <-->|"governance fee changes"| GV - LP <-->|"admin + multisig gating"| MS - LP -->|"protocol fees"| TR - LP <-->|"auto-compound vault"| AC - DF -->|"insurance payout (multisig-gated)"| MS - MS -->|"set oracle · whitelist asset · set fee"| RP - - %% ── External ───────────────────────────────────────────────────────── - SR <-->|"network consensus"| HZ - BW ---|"Freighter signs tx"| SR - SB --> KP - - %% ── User labels ────────────────────────────────────────────────────── - User1(("👤 Borrower")) - User2(("👤 Lender")) - User3(("👤 Admin")) - - User1 --> WA - User2 --> WA - User3 --> WA + LP <--> GV ``` -### 📖 How to Read the Diagram - -| Legend | Meaning | -|---|---| -| ➡️ Solid arrow | Direct function call or data flow | -| 📡 `simulateContractCall` | Read-only Soroban invocation (no fee, no signing) | -| ✍️ `callContract` / `invokeSigned` | State-changing Soroban transaction (requires signing) | -| 🔁 `⇄` Double arrow | Bidirectional contract interaction | - -### Core User Flow +**Core loan flow** -```mermaid -flowchart LR - O[1. Onboarding] --> B[2. Borrow Request] - B --> R{Reputation Check} - R -->|Approved| L[3. Lender Funds] - L --> E[4. Escrow Hold] - E --> D[5. Disbursement] - D --> P[6. Repayment] - P --> S[Score Updated] - - style O fill:#3b82f6,color:#fff - style B fill:#8b5cf6,color:#fff - style R fill:#f59e0b,color:#1e293b - style L fill:#10b981,color:#fff - style E fill:#06b6d4,color:#fff - style D fill:#10b981,color:#fff - style P fill:#8b5cf6,color:#fff - style S fill:#3b82f6,color:#fff -``` +1. **Onboard** — the user signs a SEP-10 challenge with their wallet, completes KYC, and a reputation profile is initialised. +2. **Request** — the borrower submits a loan request; the backend reads `calculate_max_loan` / `calculate_interest_rate` from the reputation contract. +3. **Fund** — a lender funds the request; funds are locked via the escrow contract. +4. **Disburse** — after the revocation window the escrow releases funds and the loan is activated. +5. **Repay** — repayments are recorded; on-time payments add reputation events, late ones flow into default management. -1. **Onboarding:** User signs in with their Stellar wallet (SEP-10 challenge signature; no passwords) (Freighter / xBull / Albedo on desktop, or any WalletConnect v2 mobile wallet such as LOBSTR by scanning a QR code), completes KYC verification, and their on-chain reputation profile is initialized. -2. **Borrowing:** Borrower submits a loan request. The Next.js backend calls `ReputationContract.calculate_max_loan` and `calculate_interest_rate` to determine eligibility and terms. -3. **Lending:** Lender reviews the request in the marketplace, approves it, and the `LendingContract.approve_loan` is called. Funds are locked via `EscrowContract.create_escrow_hold`. -4. **Disbursement:** After the 1-hour revocation window expires, the admin confirms disbursement. `EscrowContract.confirm_disbursement` releases funds to the borrower, and `LendingContract.activate_loan` marks the loan as active. -5. **Repayment:** Borrower repays via the dashboard. The backend calls `LendingContract.record_payment`, which updates the loan balance and emits an event. -6. **Reputation Update:** On-time repayments trigger `ReputationContract.add_reputation_event`, boosting the borrower's tier and unlocking better terms for future loans. Defaults trigger the `DefaultManagement` contract. - -### Automation Flows - -| Automation | Trigger | Action | -|---|---|---| -| **Payment-Due Scheduler** | Vercel Cron (daily) | Queries the database for loans due within 48h → Sends webhook & email | -| **Default Management** | Vercel Cron (daily) | Checks overdue loans against ledger time → Marks defaulted on-chain → Proposes insurance payout via MultiSigAdmin (requires N-of-M human approval) | -| **Liquidation Keeper** | Manual / cron | Monitors LTV ratios against dynamic thresholds → Liquidates under-collateralized positions → Posts Slack/Discord alerts | -| **Oracle Credit Score** | Manual / cron | Posts verified off-chain credit scores to the Reputation contract | +See [docs/](docs/) for detailed design notes on each subsystem. --- -## 🛠️ Tech Stack +## Tech stack | Layer | Technology | |---|---| -| **Frontend** | Next.js 16, React 19, TypeScript, Tailwind CSS 4, Framer Motion | -| **Backend & DB** | Neon Postgres + Drizzle ORM, SEP-10 wallet sessions (`jose`), Vercel Blob for KYC files | -| **Blockchain** | Stellar Testnet, Soroban RPC, Horizon API | -| **Wallet** | Freighter Wallet, xBull, Albedo, WalletConnect v2 for mobile wallets (`@creit.tech/stellar-wallets-kit`) | -| **Smart Contracts** | Rust (Soroban, `wasm32v1-none`) — 8 contracts deployed | -| **Cache** | Upstash Redis | -| **Automation** | Vercel Cron Jobs | -| **Email** | Resend | -| **SEP-24** | Stellar Anchor fiat on/off ramp | +| Frontend | Next.js 16 (App Router), React 19, TypeScript, Tailwind CSS 4, Framer Motion | +| Backend | Next.js route handlers & server actions, Neon Postgres + Drizzle ORM, Vercel Blob (KYC documents) | +| Auth | Sign-In with Stellar (SEP-10) → signed HttpOnly session cookie (`jose`) | +| Blockchain | Stellar testnet, Soroban RPC, Horizon; `@stellar/stellar-sdk`, `@creit.tech/stellar-wallets-kit` | +| Smart contracts | Rust / Soroban SDK, Cargo workspace in [`contracts/`](contracts/) | +| Infra | Vercel (app + daily cron), GitHub Actions (CI, keepers, backups), Upstash Redis (rate limits, optional), Resend (email, optional) | --- -## ⚙️ Getting Started (Local Development) - -> 📖 **New contributors should start with the [Getting Started Guide](docs/getting-started.md)** for a thorough walkthrough covering Soroban CLI setup, contract compilation, database setup, and the full test suite. - -### Quick Start +## Quick start ```bash git clone https://github.com/thisisouvik/trustlend-stellar.git cd trustlend-stellar npm install -cp .env.example .env.local -# Fill in your .env.local values, then: -npm run dev -``` - -The app will be available at **http://localhost:3000** with hot-reloading enabled. - -### Docker (Alternative) -```bash -docker-compose up -``` - -### Need more detail? -See the [complete setup guide →](docs/getting-started.md) - ---- - -## 🚀 Deploying Contracts to Testnet - -One command builds every Soroban contract, deploys it to the Stellar Testnet, -initializes and wires the contracts together, and writes the resulting contract -IDs straight into your `.env.local`: - -```bash -npm run deploy:testnet -``` - -There is no prerequisite step: if the `trustlend-admin` identity does not exist -yet, the CLI creates it and funds it from friendbot before deploying. - -Preview the whole run without touching the network or your files: - -```bash -npm run deploy:testnet:dry +cp .env.example .env.local # fill in DATABASE_URL, SESSION_SECRET, SIWS_SERVER_SECRET +npm run db:migrate # apply the Drizzle migrations to your Neon database +npm run dev # http://localhost:3000 ``` -### What it writes - -Contract IDs land directly in `.env.local`. Keys already present are updated **in -place** — your database URL, API secrets and comments are left untouched, and a -`.env.local.bak` is taken first. A reference copy also goes to `.env.contracts`. +Without `DATABASE_URL` the app still boots and renders empty states, which is enough to explore the UI. -| Contract | Env key | -| --- | --- | -| Reputation | `NEXT_PUBLIC_REPUTATION_CONTRACT_ID` | -| Escrow | `NEXT_PUBLIC_ESCROW_CONTRACT_ID` | -| Lending | `NEXT_PUBLIC_LENDING_CONTRACT_ID` | -| Default Management | `NEXT_PUBLIC_DEFAULT_CONTRACT_ID` | -| Pooled Lending | `NEXT_PUBLIC_POOLED_LENDING_CONTRACT_ID` | -| Governance | `NEXT_PUBLIC_GOVERNANCE_CONTRACT_ID` | -| MultiSigAdmin | `NEXT_PUBLIC_MULTISIG_ADMIN_CONTRACT_ID` | -| TLEND token / vesting / airdrop | `NEXT_PUBLIC_TLEND_*_CONTRACT_ID` | +The full walkthrough — Rust/Soroban toolchain, building and deploying contracts, running every test suite — is in **[docs/getting-started.md](docs/getting-started.md)**. -### Options - -```bash -npm run deploy:testnet -- --help -``` +### Useful scripts -| Flag | Purpose | -| --- | --- | -| `--only lending,escrow` | Deploy a subset (always in dependency order) | -| `--resume` | Reuse IDs from the last run instead of redeploying | -| `--skip-build` | Reuse the WASM already in `contracts/target` | -| `--skip-init` / `--skip-bindings` | Skip initialization / TypeScript bindings | -| `--env-file .env.staging` | Write to a different env file | -| `--network futurenet` | Target another network | -| `--dry-run` | Print every command without executing it | - -Contract IDs are recorded to `contracts/.deployments/.json` after each -individual deployment, so a run that fails partway can be picked up with -`--resume` without paying to deploy the same contract twice. - -Optional environment overrides: `MULTISIG_SIGNERS`, `MULTISIG_THRESHOLD`, -`ORACLE_ADDRESS`, `TLEND_TOTAL_SUPPLY`, `TLEND_AIRDROP_MERKLE_ROOT`. - -> The older `contracts/scripts/deploy.sh` / `deploy.ps1` still work but are -> superseded: the CLI is cross-platform, handles key creation and funding, and -> deploys the pooled-lending contract those scripts omitted. +| Command | What it does | +|---|---| +| `npm run dev` / `npm run build` | Next.js dev server / production build | +| `npm test` · `npm run test:coverage` | Vitest unit tests (660+) | +| `npm run test:e2e` | Playwright end-to-end tests | +| `npm run lint` · `npx tsc --noEmit` | ESLint / type check | +| `npm run db:generate` · `npm run db:migrate` · `npm run db:studio` | Drizzle migrations & browser UI | +| `npm run deploy:testnet` (`:dry`) | Build, deploy and wire every contract to Stellar testnet, writing IDs to `.env.local` | +| `cd contracts && cargo test` | Soroban contract tests | --- -
-🧩 Smart Contracts & Deployment Details (Click to Expand) -
+## Smart contracts -**Deployment Credentials:** -- Network: Stellar Testnet -- Admin Address: `GAJRNUO6HSMQG4FNHNWQVRXJZJZ7QRA7HXPYYB6H5PTA3EAAJXJNZD7U` -- Deployment Source Key Alias: `trustlend-admin` +All contracts live in the [`contracts/`](contracts/) Cargo workspace and are tested, linted (`clippy -D warnings`) and built to WASM in CI. -**Contract Registry:** -| Contract | Env Key | Contract ID | +| Contract | Purpose | Frontend wiring | |---|---|---| -| Reputation | `NEXT_PUBLIC_REPUTATION_CONTRACT_ID` | `CD67XYZQ4DDARIXCYP77UR77BW3HWFCMLDHTQ7N6YUDML3NX246DD65G` | -| Escrow | `NEXT_PUBLIC_ESCROW_CONTRACT_ID` | `CABTPZ224ISV65LG5M47CPN3HV4QQKL452PQYWPCBKEQHFG4LSSCSYZO` | -| Lending | `NEXT_PUBLIC_LENDING_CONTRACT_ID` | `CCLVI2JGD7PUV75VHOLTUZF3CVXYBUTOSLKNLHEUUFXOY73BFXUEVEMO` | -| Default Management | `NEXT_PUBLIC_DEFAULT_CONTRACT_ID` | `CCEMBSRCFFRIZLEN54OQVVLSFJBV5QQ3OW5OIIG2BSA33VFJ3NHDYUKG` | -| MultiSigAdmin | `NEXT_PUBLIC_MULTISIG_ADMIN_CONTRACT_ID` | *(set at deployment)* | -| Governance | `NEXT_PUBLIC_GOVERNANCE_CONTRACT_ID` | *(set at deployment)* | -| Treasury | — | *(set at deployment)* | -| Auto-Compound Vault | — | *(set at deployment)* | - -TrustLend utilizes the standard Soroban `Contract` class flow for integrations (`simulateTransaction`, `assembleTransaction`, etc.). Check `lib/stellar/soroban.ts` for reference. -
+| `lending` | Loan lifecycle, repayments, flash loans, fees | ✅ | +| `borrower_reputation` | Trust score, tiers, credit limits, freeze | ✅ | +| `escrow` | Hold funds with a revocation window before disbursement | ✅ | +| `default_management` | Mark defaults, insurance pool, payout phases | ✅ | +| `pooled_lending` | Pool deposits auto-matched to borrower requests | ✅ | +| `multisig_admin` | N-of-M approval for privileged actions | ✅ | +| `governance` | Proposals and voting on protocol parameters | ✅ | +| `tlend_token` · `tlend_vesting` · `tlend_airdrop` | Protocol token, vesting schedules, Merkle airdrop | ✅ | +| `referral_rewards` | Pays referral bonuses on first funded loan | called by `lending` | +| `treasury` | Fee collection and distribution | contract only | +| `auto_compound_vault` | Auto-compounding yield vault | contract only | +| `liquidation_auction` | Dutch auction for liquidated collateral | contract only | +| `usdc_lending_pool` | USDC-denominated pool | contract only | +| `borrower_loyalty` | Loyalty rewards for repeat borrowers | called by `lending` | +| `zk_credit_verifier` | ZK proof verification for off-chain credit data | contract only | + +"Contract only" crates are deployed and tested but not yet called from the app — see the [roadmap](docs/roadmap.md). + +`npm run deploy:testnet` records the deployed contract IDs locally in `contracts/.deployments/.json` and writes the matching `NEXT_PUBLIC_*_CONTRACT_ID` keys (listed in [`.env.example`](.env.example)) into `.env.local`. --- -## 🔔 Payment Due Webhook Scheduler - -TrustLend includes an automated scheduler that checks for loans with payment deadlines approaching within **48 hours** and dispatches webhook notifications to a configured notification service. - -### How It Works +## Documentation -1. An external scheduler (Vercel Cron or any HTTP trigger) calls `POST /api/cron/payment-due` daily. -2. The route queries the database for `active` or `funded` loans with `due_at` between now and +48 hours. -3. A POST webhook is sent to `WEBHOOK_NOTIFICATION_URL` for each qualifying loan. -4. The loan's `metadata.payment_due_notified_at` is set to prevent duplicate notifications. -5. Per-loan errors are logged without stopping the rest of the batch. - -### Required Environment Variables - -| Variable | Description | +| Topic | Document | |---|---| -| `WEBHOOK_NOTIFICATION_URL` | URL of the notification service that receives payment-due webhook POSTs | -| `CRON_SECRET` | Secret token used to authenticate scheduler requests (`Authorization: Bearer `) | -| `DATABASE_URL` | Neon Postgres connection string | -| `RESEND_API_KEY` | Optional Resend API key for borrower email notifications | -| `RESEND_FROM_EMAIL` | Verified sender address used for TrustLend emails | -| `RESEND_REPLY_TO_EMAIL` | Optional reply-to address for support responses | - -When Resend is configured, TrustLend sends borrower emails for loan approval, -loan funding, and overdue payments. Email failures are logged but do not roll -back successful loan state changes. - -### Webhook Payload - -```json -{ - "borrowerId": "uuid", - "loanId": "uuid", - "dueDate": "2026-07-01T12:00:00.000Z", - "paymentAmount": 800.00 -} -``` - -`paymentAmount` is `principal_amount − repaid_amount` (outstanding balance). - -### Triggering the Scheduler - -**Vercel Cron (automatic, hourly):** Configured in `vercel.json` — no additional setup needed. - -**Manual trigger:** -```bash -curl -X POST https://your-app.vercel.app/api/cron/payment-due \ - -H "Authorization: Bearer $CRON_SECRET" -``` - -**Local development (no secret set):** The `Authorization` check is skipped when `CRON_SECRET` is not configured. - -### Failure Handling - -- Individual loan failures are logged and do not block other loans in the same run. -- The scheduler returns a JSON summary: `{ processed, succeeded, failed, errors }`. -- Webhook requests time out after 10 seconds. - ---- - -## 🚦 API Rate Limiting - -All public-facing API routes are rate limited to protect backend services from -brute-force attacks, scraping, and misconfigured clients. - -### Two layers - -| Layer | Scope | Limit | Enforced in | -| --- | --- | --- | --- | -| **Global ceiling** | Every `/api/*` request, keyed by client IP | **100 requests / minute / IP** | [`proxy.ts`](proxy.ts) — before the request reaches a handler | -| **Per-route policy** | One endpoint, keyed by client IP | Stricter, endpoint-specific (e.g. `POST /api/loans/apply` → 5 per 10 min) | `enforceRouteRateLimit()` at the top of each route handler | - -The two layers use independent counters, so an expensive endpoint stays tightly -capped even when the caller is well under the global ceiling. - -### Exceeding a limit - -Requests over the limit are rejected with **HTTP 429** before any database or -Stellar network work is performed: - -```http -HTTP/1.1 429 Too Many Requests -Retry-After: 37 -X-RateLimit-Limit: 100 -X-RateLimit-Remaining: 0 -X-RateLimit-Reset: 1735689600000 - -{ "error": "Too many requests, please slow down." } -``` - -### Storage backend - -Counters are kept in **Upstash Redis** when `UPSTASH_REDIS_REST_URL` and -`UPSTASH_REDIS_REST_TOKEN` are set, so limits hold across serverless instances. -Without them the limiter falls back to a bounded in-memory store (capped at -10,000 buckets with automatic pruning) — per-instance only, and fine for local -development. If Redis is unreachable the limiter **fails open** rather than -blocking legitimate traffic. - -### Client IP resolution - -The caller is identified from the first present header of -`x-vercel-ip-address` → `cf-connecting-ip` → `x-vercel-forwarded-for` → -`x-real-ip` → `x-forwarded-for` (first hop of the chain). Platform-injected -headers are preferred because a client cannot forge them. - -### Bypass - -Two escape hatches skip rate limiting entirely — see [`.env.example`](.env.example): - -- `Authorization: Bearer ` — trusted internal/admin callers. -- `RATE_LIMIT_WHITELIST` — comma-separated IPs (uptime probes, Prometheus scrapers). - -### Adding a policy to a new route - -Register the limit in `ROUTE_POLICIES` (or `ROUTE_PATTERN_POLICIES` for dynamic -segments) in [`lib/rate-limit.ts`](lib/rate-limit.ts), then guard the handler: - -```ts -export async function POST(request: NextRequest) { - const rateLimited = await enforceRouteRateLimit(request); - if (rateLimited) return rateLimited; - - // ...handler logic -} -``` - -Routes with no explicit policy fall back to **20 requests / minute / IP**. -Scheduler (`/api/cron/*`) and anchor-callback (`/api/webhooks/*`) endpoints are -deliberately left to the global ceiling only — they authenticate with a shared -secret or a verified signature, and a per-route cap could drop legitimate -scheduled runs or provider retries. - ---- - -## 👥 Contributors - -Thanks goes to these wonderful people who have contributed to TrustLend: - - - Contributors - - -## 🤝 Contributing - -We love open-source contributors! Whether you're fixing bugs, improving documentation, or proposing new features, your help is welcome. - -Please read our [Contributing Guidelines](CONTRIBUTING.md) and [Code of Conduct](CODE_OF_CONDUCT.md) before submitting a Pull Request. +| Local setup, toolchain, tests | [docs/getting-started.md](docs/getting-started.md) | +| Authentication (SEP-10) | [docs/auth-siws.md](docs/auth-siws.md) | +| Public API | [docs/api.md](docs/api.md) | +| Rate limiting | [docs/rate-limiting.md](docs/rate-limiting.md) | +| Payment-due notifications & email | [docs/payment-due-scheduler.md](docs/payment-due-scheduler.md) | +| Default management automation | [docs/default-automation.md](docs/default-automation.md) | +| Liquidation keeper | [docs/liquidation-keeper.md](docs/liquidation-keeper.md) | +| Collateral price feeds | [docs/oracle-price-feeds.md](docs/oracle-price-feeds.md) | +| APR / yield formulas | [docs/apr-formulas.md](docs/apr-formulas.md) | +| Fiat on/off ramp (SEP-24) | [docs/sep24-fiat-ramp.md](docs/sep24-fiat-ramp.md) | +| Referral program | [docs/referral-program.md](docs/referral-program.md) | +| Backups & disaster recovery | [docs/disaster-recovery.md](docs/disaster-recovery.md) | +| Formal verification | [docs/formal-verification.md](docs/formal-verification.md) | +| Contract design notes | [docs/contracts/](docs/contracts/) — flash loans, governance, multisig, credit oracle | --- -## 🛡️ Security +## Contributing -If you discover a security vulnerability within TrustLend, please refer to our [Security Policy](SECURITY.md) for reporting instructions. Do **not** open a public issue for security-related matters. +Contributions are welcome — bug reports, docs, tests, contracts or UI. Please read [CONTRIBUTING.md](CONTRIBUTING.md) for the branch and commit conventions (Conventional Commits are enforced by a commit hook) and the [Code of Conduct](CODE_OF_CONDUCT.md). ---- +Good first stops: the [roadmap](docs/roadmap.md), open issues labelled `good first issue`, and the "contract only" crates above that still need frontend wiring. -## 💾 Backups & Disaster Recovery +## Security -The PostgreSQL database is dumped, encrypted with AES-256 and uploaded to Amazon S3 -every night at 00:00 UTC by the [Automated DB Backup](.github/workflows/db-backup.yml) -workflow. Restore steps, bucket/IAM setup and the quarterly restore drill are -documented in [docs/disaster-recovery.md](docs/disaster-recovery.md). +Please do **not** open public issues for vulnerabilities. Follow the disclosure process in [SECURITY.md](SECURITY.md). ---- - -## 📜 License - -This project is licensed under the [MIT License](LICENSE). +## License ---- +[MIT](LICENSE) © TrustLend contributors -

Made with ❤️ by the TrustLend Community.

+

+ + Contributors + +

diff --git a/docs/apr-formulas.md b/docs/apr-formulas.md index 6dbc8bd..9c69b41 100644 --- a/docs/apr-formulas.md +++ b/docs/apr-formulas.md @@ -589,4 +589,4 @@ The lending contract's mathematical functions are formally verified using the ** | `contracts/borrower_reputation/src/lib.rs` | Reputation tiers, oracle credit scoring | | `lib/dashboard/interest-rates.ts` | TypeScript implementation of rate models | | `types/contracts.ts` | TypeScript constants and helper functions | -| `sql/05_interest_rate_model.sql` | Database schema for rate model columns | +| `lib/db/schema.ts` (`loans.rate_model`, `drizzle/0000_init.sql`) | Database schema for rate model columns | diff --git a/docs/auth-siws.md b/docs/auth-siws.md index edd8b47..3a86e6b 100644 --- a/docs/auth-siws.md +++ b/docs/auth-siws.md @@ -43,30 +43,30 @@ a deleted or re-roled account is reflected immediately. ## 2. Backend: challenge endpoint (Task 2) -**`POST /api/auth/siws/challenge`** — [route](app/api/auth/siws/challenge/route.ts) +**`POST /api/auth/siws/challenge`** — [route](../app/api/auth/siws/challenge/route.ts) ```jsonc // request { "address": "GABC...WALLET" } // response 200 { "transaction": "", "networkPassphrase": "Test SDF Network ; September 2015" } ``` -Built with `WebAuth.buildChallengeTx` ([lib/auth/siws-server.ts](lib/auth/siws-server.ts)), +Built with `WebAuth.buildChallengeTx` ([lib/auth/siws-server.ts](../lib/auth/siws-server.ts)), signed by a dedicated **SEP-10 server key** (`SIWS_SERVER_SECRET`, distinct from the platform admin key), valid for 5 minutes, bound to `NEXT_PUBLIC_SIWS_DOMAIN`. Rate-limited via the existing `enforceRouteRateLimit`. ## 3. Client: pick a wallet, then sign (Task 3) -[lib/auth/siws-client.ts](lib/auth/siws-client.ts) `signInWithStellar()` drives the -whole flow; [components/auth/StellarSignInButton.tsx](components/auth/StellarSignInButton.tsx) -is a "Sign in with Stellar" button next to "Continue with Google" on the auth page -([components/auth/AuthPageClient.tsx](components/auth/AuthPageClient.tsx)). It +[lib/auth/siws-client.ts](../lib/auth/siws-client.ts) `signInWithStellar()` drives the +whole flow; [components/auth/StellarSignInButton.tsx](../components/auth/StellarSignInButton.tsx) +is the "Sign in with Stellar" button on the auth page — the only sign-in method +([components/auth/AuthPageClient.tsx](../components/auth/AuthPageClient.tsx)). It reuses the existing multi-wallet signer -([lib/stellar/wallet.ts](lib/stellar/wallet.ts)) so the challenge is signed exactly +([lib/stellar/wallet.ts](../lib/stellar/wallet.ts)) so the challenge is signed exactly like any other TrustLend wallet transaction. Clicking the button opens the wallet picker -([components/ui/WalletSelectionModal.tsx](components/ui/WalletSelectionModal.tsx), +([components/ui/WalletSelectionModal.tsx](../components/ui/WalletSelectionModal.tsx), shared with the dashboard's `WalletCard`), which offers **Freighter**, **WalletConnect**, **xBull** and **Albedo**. The chosen provider is passed into `signInWithStellar(provider, role)`, so the login challenge is signed by whichever @@ -80,7 +80,7 @@ phone for approval, and the signed XDR comes back over the same session. A few details matter for this to work end to end: - **Module id.** The kit registers WalletConnect as `wallet_connect` (underscore). - These ids live in [lib/stellar/wallet-providers.ts](lib/stellar/wallet-providers.ts); + These ids live in [lib/stellar/wallet-providers.ts](../lib/stellar/wallet-providers.ts); `assertWalletModuleIds()` warns in development if the kit ever renames one. - **Chain negotiation.** The session is opened with `allowedChains` derived from `NEXT_PUBLIC_STELLAR_NETWORK_PASSPHRASE` (`stellar:pubnet` for mainnet, otherwise @@ -98,11 +98,11 @@ details matter for this to work end to end: connected Freighter address would be handed to a WalletConnect request that has no matching session. Disconnecting calls `disconnectWallet()`, which closes the pairing. - **CSP.** The relay and Reown AppKit origins are allowlisted in - [next.config.ts](next.config.ts); without them the browser blocks the relay socket. + [next.config.ts](../next.config.ts); without them the browser blocks the relay socket. ## 4. Backend: signature validation → session (Task 4) -**`POST /api/auth/siws/verify`** — [route](app/api/auth/siws/verify/route.ts) +**`POST /api/auth/siws/verify`** — [route](../app/api/auth/siws/verify/route.ts) ```jsonc // request { "address": "GABC...", "signedTxXdr": "" } @@ -125,8 +125,8 @@ page only applies to brand-new accounts; an existing account keeps its role. ## 5. Error states (Task 5) Every failure is a typed `SiwsError` with a `code` + HTTP status -([lib/auth/siws-server.ts](lib/auth/siws-server.ts)), mapped to a friendly message -client-side (`mapVerifyError` in [lib/auth/siws-client.ts](lib/auth/siws-client.ts)): +([lib/auth/siws-server.ts](../lib/auth/siws-server.ts)), mapped to a friendly message +client-side (`mapVerifyError` in [lib/auth/siws-client.ts](../lib/auth/siws-client.ts)): | Code | HTTP | User sees | |---|---|---| @@ -152,7 +152,7 @@ NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID= # Reown/WalletConnect Cloud project id ## 7. Tests -[__tests__/auth/siws.test.ts](__tests__/auth/siws.test.ts) exercises the real +[__tests__/auth/siws.test.ts](../__tests__/auth/siws.test.ts) exercises the real `WebAuth` roundtrip (build → sign → verify) with an in-memory test keypair — happy path, wrong-signer, address mismatch, expired challenge, and malformed XDR — without hitting the network or the database. diff --git a/docs/contracts/flash-loans.md b/docs/contracts/flash-loans.md index eb9f8d5..9386d90 100644 --- a/docs/contracts/flash-loans.md +++ b/docs/contracts/flash-loans.md @@ -11,7 +11,7 @@ arbitrage or re-leveraging. ## 1. `flash_loan` (Task 1) -Added to [`contracts/lending/src/lib.rs`](contracts/lending/src/lib.rs): +Added to [`contracts/lending/src/lib.rs`](../../contracts/lending/src/lib.rs): ```rust pub fn flash_loan(env: Env, receiver: Address, token: Address, amount: i128, params: Bytes) @@ -95,7 +95,7 @@ still succeeds — the surplus simply accrues to the pool. ## 4. Unit tests (Task 5) -12 new tests in [`contracts/lending/src/test.rs`](contracts/lending/src/test.rs), +12 new tests in [`contracts/lending/src/test.rs`](../../contracts/lending/src/test.rs), using a real SEP-41 test token (`env.register_stellar_asset_contract_v2`): | Test | Proves | @@ -131,9 +131,9 @@ which collide if two implementations of the same trait share a module. ## 6. Frontend integration -- [`lib/contracts/lending.ts`](lib/contracts/lending.ts) — `flashLoan()`, +- [`lib/contracts/lending.ts`](../../lib/contracts/lending.ts) — `flashLoan()`, `getFlashLoanFeeBps()`, `setFlashLoanFeeBps()`. -- [`lib/stellar/soroban.ts`](lib/stellar/soroban.ts) — new `bytesToScVal()` +- [`lib/stellar/soroban.ts`](../../lib/stellar/soroban.ts) — new `bytesToScVal()` helper for encoding the callback `params`. ## 7. Future evolution diff --git a/docs/contracts/multisig-admin.md b/docs/contracts/multisig-admin.md index 52661dd..1874ce7 100644 --- a/docs/contracts/multisig-admin.md +++ b/docs/contracts/multisig-admin.md @@ -29,7 +29,7 @@ they aren't the kind of operation the issue's examples point at. ## 2. Multi-sig approval before configuration shifts (Task 2) -New [`contracts/multisig_admin`](contracts/multisig_admin/src/lib.rs) contract: +New [`contracts/multisig_admin`](../../contracts/multisig_admin/src/lib.rs) contract: ``` propose(signer, action) -> id — any signer opens a proposal (counts as their own approval) @@ -68,7 +68,7 @@ gains a one-time `set_multisig_admin(admin, multisig)` bootstrap. Once called: ## 3. Integration tests (Task 3) -[`contracts/multisig_admin/src/test.rs`](contracts/multisig_admin/src/test.rs) — +[`contracts/multisig_admin/src/test.rs`](../../contracts/multisig_admin/src/test.rs) — **27 tests**, using the *real* Lending, Default-Management, and Reputation contracts (dev-dependencies), not mocks: @@ -95,7 +95,7 @@ cd contracts && cargo test -p multisig-admin cron (issue #23) can no longer execute payouts unattended — it now **proposes** the payout (its key must be a registered signer) and a human completes the remaining approvals + `execute`. See -[`lib/scheduler/default-management.ts`](lib/scheduler/default-management.ts). +[`lib/scheduler/default-management.ts`](../../lib/scheduler/default-management.ts). ## 5. Verification diff --git a/docs/default-automation.md b/docs/default-automation.md index 9eb8803..1621691 100644 --- a/docs/default-automation.md +++ b/docs/default-automation.md @@ -12,10 +12,10 @@ triggers the on-chain default + insurance contract methods automatically. | File | Role | |---|---| -| [app/api/cron/default-management/route.ts](app/api/cron/default-management/route.ts) | Authenticated serverless endpoint (Vercel Cron / cURL) | -| [lib/scheduler/default-management.ts](lib/scheduler/default-management.ts) | The run: query overdue loans → check ledger time → invoke contracts (idempotent, per-loan error handling) | -| [lib/stellar/server-contract.ts](lib/stellar/server-contract.ts) | Server-side signed Soroban invoker (admin keypair) + ledger-time reader | -| [vercel.json](vercel.json) | Schedules the cron daily at `02:00 UTC` | +| [app/api/cron/default-management/route.ts](../app/api/cron/default-management/route.ts) | Authenticated serverless endpoint (Vercel Cron / cURL) | +| [lib/scheduler/default-management.ts](../lib/scheduler/default-management.ts) | The run: query overdue loans → check ledger time → invoke contracts (idempotent, per-loan error handling) | +| [lib/stellar/server-contract.ts](../lib/stellar/server-contract.ts) | Server-side signed Soroban invoker (admin keypair) + ledger-time reader | +| [vercel.json](../vercel.json) | Schedules the cron daily at `02:00 UTC` | ## 2. Flow @@ -43,7 +43,7 @@ Vercel Cron (02:00 UTC) ──Bearer CRON_SECRET──► /api/cron/default-ma isn't `Bearer ${CRON_SECRET}` (same scheme as the existing `payment-due` cron). In Vercel, set `CRON_SECRET` and Vercel Cron sends it automatically. - **Signing key isolation:** contract calls are signed server-side with - `ADMIN_SECRET_KEY`, read only inside [lib/stellar/server-contract.ts](lib/stellar/server-contract.ts) + `ADMIN_SECRET_KEY`, read only inside [lib/stellar/server-contract.ts](../lib/stellar/server-contract.ts) (never `NEXT_PUBLIC_`, never sent to the browser). - **On-chain authorization:** `mark_defaulted`, `record_default`, and `trigger_insurance_payout` all `require_auth()` the admin and assert diff --git a/docs/disaster-recovery.md b/docs/disaster-recovery.md index 5e0fd3f..fd2d1c9 100644 --- a/docs/disaster-recovery.md +++ b/docs/disaster-recovery.md @@ -3,8 +3,8 @@ How TrustLend's PostgreSQL database is backed up, and how to restore it. - **Schedule:** every day at **00:00 UTC** -- **Runner:** [`.github/workflows/db-backup.yml`](.github/workflows/db-backup.yml) -- **Script:** [`scripts/backup.sh`](scripts/backup.sh) +- **Runner:** [`.github/workflows/db-backup.yml`](../.github/workflows/db-backup.yml) +- **Script:** [`scripts/backup.sh`](../scripts/backup.sh) - **Destination:** `s3://$S3_BUCKET/backups/YYYY/MM/trustlend-.dump.enc` - **Encryption:** AES-256-CBC (PBKDF2, 600k iterations) applied **before** upload - **Retention:** 30 days by default (`BACKUP_RETENTION_DAYS`) @@ -185,7 +185,7 @@ stops before the upload. The workflow also exposes this via Automated coverage for the script's safety properties (missing config is rejected, empty and corrupt dumps are refused, no plaintext survives) lives in -[`__tests__/scripts/backup-script.test.ts`](__tests__/scripts/backup-script.test.ts). +[`__tests__/scripts/backup-script.test.ts`](../__tests__/scripts/backup-script.test.ts). ### Quarterly restore drill diff --git a/docs/getting-started.md b/docs/getting-started.md index a8c8bdd..08e8b5f 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -26,7 +26,6 @@ Install these tools on your machine before cloning the repo. | **npm** | ^10 | Package manager | | **Rust** | stable (via `rustup`) | Soroban smart contract compilation | | **Soroban CLI** | latest | Contract deployment & interaction | -| **Docker** (optional) | ^24 | Alternative dev environment via Compose | | **Freighter Wallet** | latest (browser ext.) | Stellar wallet for testnet interactions | ### Verify Installations @@ -162,32 +161,29 @@ npm run dev The app starts at **http://localhost:3000** with hot-reloading enabled. -### 3.5 Using Docker (Alternative) - -If you prefer a containerized setup, run: - -```bash -docker-compose up -``` - -This starts the Next.js dev server and any required services. The app is available at `http://localhost:3000`. - --- ## 4. Smart Contract Setup (Soroban / Rust) -The contracts live in the `contracts/` directory — a Cargo workspace with 8 crates: +The contracts live in the `contracts/` directory — a Cargo workspace with 16 contract crates (plus `mocks/` for test doubles): | Crate | Path | Purpose | |---|---|---| -| `borrower-reputation` | `contracts/borrower_reputation/` | On-chain reputation scoring | -| `escrow` | `contracts/escrow/` | Escrow-assisted disbursement | -| `lending` | `contracts/lending/` | Core lending logic | -| `default-management` | `contracts/default_management/` | Default & insurance pool | -| `governance` | `contracts/governance/` | DAO governance controls | -| `multisig-admin` | `contracts/multisig_admin/` | Multi-signature admin gating | -| `auto-compound-vault` | `contracts/auto_compound_vault/` | Auto-compounding interest vault | +| `lending` | `contracts/lending/` | Loan lifecycle, repayments, flash loans, fees | +| `borrower_reputation` | `contracts/borrower_reputation/` | Trust score, tiers, credit limits | +| `escrow` | `contracts/escrow/` | Escrow-assisted disbursement with revocation window | +| `default_management` | `contracts/default_management/` | Default marking & insurance pool | +| `pooled_lending` | `contracts/pooled_lending/` | Pooled deposits auto-matched to requests | +| `multisig_admin` | `contracts/multisig_admin/` | N-of-M admin approvals | +| `governance` | `contracts/governance/` | Proposals & voting on protocol parameters | +| `referral_rewards` | `contracts/referral_rewards/` | Referral bonuses on first funded loan | +| `tlend_token` / `tlend_vesting` / `tlend_airdrop` | `contracts/tlend_*/` | Protocol token, vesting, Merkle airdrop | | `treasury` | `contracts/treasury/` | Fee collection & distribution | +| `auto_compound_vault` | `contracts/auto_compound_vault/` | Auto-compounding yield vault | +| `liquidation_auction` | `contracts/liquidation_auction/` | Dutch auction for liquidated collateral | +| `usdc_lending_pool` | `contracts/usdc_lending_pool/` | USDC-denominated lending pool | +| `borrower_loyalty` | `contracts/borrower_loyalty/` | Loyalty rewards for repeat borrowers | +| `zk_credit_verifier` | `contracts/zk_credit_verifier/` | ZK proof verification for off-chain credit data | ### 4.1 Build All Contracts @@ -330,7 +326,10 @@ Our GitHub Actions CI runs these checks on every PR: | Workflow | What It Does | |---|---| | `ci.yml` — **Test Soroban Contracts** | `cargo test` + WASM build | -| `ci.yml` — **Build Next.js** | `tsc --noEmit` → `eslint` → `next build` | +| `ci.yml` — **Build Next.js** | `tsc --noEmit` → `eslint` → `vitest run` → `next build`; runs `drizzle-kit migrate` and the Vercel production deploy on pushes to `main` | +| `e2e-playwright.yml` | Playwright end-to-end tests | +| `keepers.yml` | Every 5 minutes: triggers the liquidation keeper and price oracle (needs `KEEPER_BASE_URL` + `CRON_SECRET` secrets) | +| `db-backup.yml` | Nightly encrypted Postgres dump to S3 | | `contract-security.yml` | `cargo clippy` + `cargo audit` | | `formal-verification.yml` | proptest + Kani model checking | | `coverage.yml` | `cargo tarpaulin` → Codecov upload | @@ -452,7 +451,7 @@ This recreates the `.husky/_/` directory and ensures hooks are activated. The `n ## Next Steps -- Read the [Contributing Guidelines](CONTRIBUTING.md) for the PR workflow. +- Read the [Contributing Guidelines](../CONTRIBUTING.md) for the PR workflow. - Check the [Roadmap](roadmap.md) for upcoming features. - Browse project documentation: [Flash Loans](contracts/flash-loans.md), [MultiSig Admin](contracts/multisig-admin.md), [Oracle Integration](contracts/oracle-integration.md), [Governance](contracts/governance.md). - Join the community discussions on GitHub Issues. diff --git a/docs/payment-due-scheduler.md b/docs/payment-due-scheduler.md new file mode 100644 index 0000000..26dc359 --- /dev/null +++ b/docs/payment-due-scheduler.md @@ -0,0 +1,70 @@ +# Payment-Due Scheduler & Notifications + +TrustLend checks for loans whose payment deadline falls within the next +**48 hours** and dispatches a webhook (and, when configured, an email) for each +of them. + +## How it works + +1. A scheduler calls `POST /api/cron/payment-due`. On Vercel this is the daily + cron declared in [`vercel.json`](../vercel.json); any HTTP trigger works. +2. The route queries the database for `active` or `funded` loans with `due_at` + between now and +48 hours. +3. A POST webhook is sent to `WEBHOOK_NOTIFICATION_URL` for each qualifying loan. +4. The loan's `metadata.payment_due_notified_at` is set to prevent duplicate + notifications. +5. Per-loan errors are logged without stopping the rest of the batch. + +## Environment variables + +| Variable | Description | +|---|---| +| `WEBHOOK_NOTIFICATION_URL` | URL of the notification service that receives payment-due webhook POSTs | +| `CRON_SECRET` | Secret token used to authenticate scheduler requests (`Authorization: Bearer `) | +| `DATABASE_URL` | Neon Postgres connection string | +| `RESEND_API_KEY` | Optional Resend API key for borrower email notifications | +| `RESEND_FROM_EMAIL` | Verified sender address used for TrustLend emails | +| `RESEND_REPLY_TO_EMAIL` | Optional reply-to address for support responses | + +When Resend is configured, TrustLend sends borrower emails for loan approval, +loan funding and overdue payments. Email failures are logged but do not roll +back successful loan state changes. + +## Webhook payload + +```json +{ + "borrowerId": "uuid", + "loanId": "uuid", + "dueDate": "2026-07-01T12:00:00.000Z", + "paymentAmount": 800.00 +} +``` + +`paymentAmount` is `principal_amount − repaid_amount` (outstanding balance). + +## Triggering the scheduler + +**Vercel cron (automatic, daily):** configured in `vercel.json` together with +the other daily jobs (default management, reputation scoring, liquidation, +price oracle). Vercel's Hobby plan only allows daily crons, which is why the +time-sensitive liquidation keeper and price oracle are additionally driven every +5 minutes by the [`keepers.yml`](../.github/workflows/keepers.yml) GitHub +Actions workflow (requires the `KEEPER_BASE_URL` and `CRON_SECRET` repository +secrets). + +**Manual trigger:** + +```bash +curl -X POST https://your-app.vercel.app/api/cron/payment-due \ + -H "Authorization: Bearer $CRON_SECRET" +``` + +**Local development:** the `Authorization` check is skipped when `CRON_SECRET` +is not configured. + +## Failure handling + +- Individual loan failures are logged and do not block other loans in the same run. +- The scheduler returns a JSON summary: `{ processed, succeeded, failed, errors }`. +- Webhook requests time out after 10 seconds. diff --git a/docs/rate-limiting.md b/docs/rate-limiting.md new file mode 100644 index 0000000..ab9ad6b --- /dev/null +++ b/docs/rate-limiting.md @@ -0,0 +1,72 @@ +# API Rate Limiting + +All public-facing API routes are rate limited to protect backend services from +brute-force attacks, scraping and misconfigured clients. + +## Two layers + +| Layer | Scope | Limit | Enforced in | +| --- | --- | --- | --- | +| **Global ceiling** | Every `/api/*` request, keyed by client IP | **100 requests / minute / IP** | [`proxy.ts`](../proxy.ts) — before the request reaches a handler | +| **Per-route policy** | One endpoint, keyed by client IP | Stricter, endpoint-specific (e.g. `POST /api/loans/apply` → 5 per 10 min) | `enforceRouteRateLimit()` at the top of each route handler | + +The two layers use independent counters, so an expensive endpoint stays tightly +capped even when the caller is well under the global ceiling. + +## Exceeding a limit + +Requests over the limit are rejected with **HTTP 429** before any database or +Stellar network work is performed: + +```http +HTTP/1.1 429 Too Many Requests +Retry-After: 37 +X-RateLimit-Limit: 100 +X-RateLimit-Remaining: 0 +X-RateLimit-Reset: 1735689600000 + +{ "error": "Too many requests, please slow down." } +``` + +## Storage backend + +Counters are kept in **Upstash Redis** when `UPSTASH_REDIS_REST_URL` and +`UPSTASH_REDIS_REST_TOKEN` are set, so limits hold across serverless instances. +Without them the limiter falls back to a bounded in-memory store (capped at +10,000 buckets with automatic pruning) — per-instance only, and fine for local +development. If Redis is unreachable the limiter **fails open** rather than +blocking legitimate traffic. + +## Client IP resolution + +The caller is identified from the first present header of +`x-vercel-ip-address` → `cf-connecting-ip` → `x-vercel-forwarded-for` → +`x-real-ip` → `x-forwarded-for` (first hop of the chain). Platform-injected +headers are preferred because a client cannot forge them. + +## Bypass + +Two escape hatches skip rate limiting entirely — see [`.env.example`](../.env.example): + +- `Authorization: Bearer ` — trusted internal/admin callers. +- `RATE_LIMIT_WHITELIST` — comma-separated IPs (uptime probes, Prometheus scrapers). + +## Adding a policy to a new route + +Register the limit in `ROUTE_POLICIES` (or `ROUTE_PATTERN_POLICIES` for dynamic +segments) in [`lib/rate-limit.ts`](../lib/rate-limit.ts), then guard the handler: + +```ts +export async function POST(request: NextRequest) { + const rateLimited = await enforceRouteRateLimit(request); + if (rateLimited) return rateLimited; + + // ...handler logic +} +``` + +Routes with no explicit policy fall back to **20 requests / minute / IP**. +Scheduler (`/api/cron/*`) and anchor-callback (`/api/webhooks/*`) endpoints are +deliberately left to the global ceiling only — they authenticate with a shared +secret or a verified signature, and a per-route cap could drop legitimate +scheduled runs or provider retries. diff --git a/docs/referral-program.md b/docs/referral-program.md index 45dfcf2..8f1a297 100644 --- a/docs/referral-program.md +++ b/docs/referral-program.md @@ -102,7 +102,7 @@ funds, misconfigured, or panicking. ## Database -`sql/09_referral_program.sql` adds: +The Drizzle schema (`lib/db/schema.ts`, applied by `drizzle/0000_init.sql` and `drizzle/0001_functions_and_triggers.sql`) adds: - `profiles.referral_code` — unique, indexed, backfilled. - `public.referrals` — one row per invited user, with `status` moving diff --git a/docs/roadmap.md b/docs/roadmap.md index 2c1f5c0..d82d556 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,26 +1,58 @@ # TrustLend Roadmap -Welcome to the TrustLend Roadmap! This document outlines our high-level goals and planned features. Please note that this roadmap is subject to change based on community feedback and project priorities. - -## 🚀 Phase 1: Foundation (Current) -- [x] Initial smart contract deployment on Stellar/Soroban Testnet -- [x] Wallet-native auth (SEP-10) with Postgres-backed user profiles -- [x] Web frontend MVP with Next.js & React -- [x] Open-source repository setup & documentation - -## 📈 Phase 2: Core Features -- [ ] Comprehensive KYC workflows -- [ ] On-chain reputation and credit scoring v1 -- [ ] Support for multiple stablecoins -- [ ] Improved lender dashboard and analytics -- [ ] Automated escrow and default management - -## 🌍 Phase 3: Expansion & Governance -- [ ] Mainnet launch -- [ ] Decentralized governance framework -- [ ] Advanced risk assessment models -- [ ] Fiat on/off ramps integration -- [ ] Mobile app release +This page tracks where the project is and what comes next. It is a living +document — priorities shift with community feedback, and every item is open for +contribution. Comment on or open an issue if you want to pick something up. + +## Shipped + +- Soroban contracts for lending, borrower reputation, escrow, default + management, pooled lending, multisig admin, governance, referral rewards and + the TLEND token suite, deployed to Stellar testnet with `npm run deploy:testnet`. +- Wallet-native authentication (Sign-In with Stellar / SEP-10) — no passwords, + no third-party identity provider. +- Borrower, lender and admin dashboards (Next.js 16) with a token-based design + system, light/dark themes and motion. +- KYC submission and admin review with private document storage. +- On-chain trust score, tiers and credit limits (reputation v1) driving loan + eligibility and pricing. +- Escrow-assisted disbursement with a revocation window. +- Automated default management with multisig-gated insurance payouts. +- Liquidation keeper backed by a median-of-sources collateral price oracle. +- SEP-24 fiat on/off ramp integration with Stellar anchors. +- Referral programme paid on-chain on the first funded loan. +- Neon Postgres + Drizzle ORM data layer with versioned migrations, nightly + encrypted backups and a documented restore procedure. +- CI: contract tests + WASM build, type check, lint, unit tests, production + build, Playwright E2E, formal verification (proptest / Kani), coverage. + +## In progress + +- **On-chain verification of client-submitted transactions.** Funding, + pool-deposit and repayment endpoints currently trust the `txHash` supplied by + the client; the server will verify the transaction on Soroban RPC (success, + amount, destination) before crediting anything. +- **Full contract wiring.** Route the remaining lifecycle calls + (`activate_loan`, `record_payment`, pooled lending) through the contracts + from the app and make on-chain loan creation mandatory rather than optional. +- **Wire the standalone contracts** — treasury, auto-compound vault, + liquidation auction, USDC lending pool, borrower loyalty and the ZK credit + verifier are built and tested but not yet used by the frontend. +- Generated TypeScript bindings for every contract instead of hand-written + invocation helpers. + +## Planned + +- Multi-asset lending (USDC and other Stellar assets) end to end. +- Governance UI: proposals, voting and parameter changes from the dashboard. +- Decentralised credit oracles that aggregate off-chain signals (mobile money, + utility payments) into the trust score. +- Institutional lender tooling: pluggable underwriting models and bulk funding. +- Mobile-first experience and WalletConnect improvements. +- Mainnet launch after an external contract audit. ## Contributing -We welcome contributions to help us achieve these milestones! Check out our [Contributing Guidelines](CONTRIBUTING.md) to get started. + +See [CONTRIBUTING.md](../CONTRIBUTING.md) for the workflow. Items in +"In progress" are the best place to help right now; "Planned" items usually +need a short design discussion in an issue first. diff --git a/docs/screenshots/borrower-dashboard-light.png b/docs/screenshots/borrower-dashboard-light.png new file mode 100644 index 0000000..fac76e8 Binary files /dev/null and b/docs/screenshots/borrower-dashboard-light.png differ diff --git a/docs/screenshots/landing-dark.png b/docs/screenshots/landing-dark.png new file mode 100644 index 0000000..50dafb3 Binary files /dev/null and b/docs/screenshots/landing-dark.png differ diff --git a/docs/screenshots/lender-portfolio-dark.png b/docs/screenshots/lender-portfolio-dark.png new file mode 100644 index 0000000..53002cf Binary files /dev/null and b/docs/screenshots/lender-portfolio-dark.png differ diff --git a/docs/sep24-fiat-ramp.md b/docs/sep24-fiat-ramp.md index b3fc73d..89e97cb 100644 --- a/docs/sep24-fiat-ramp.md +++ b/docs/sep24-fiat-ramp.md @@ -16,11 +16,11 @@ The issue allows either `@stellar/wallet-sdk` *or* standard SEP-24 flows. We cho **standard flows on plain `fetch`** because: - The rest of the codebase already talks to Stellar over raw `fetch` / JSON-RPC - (see [lib/stellar/soroban.ts](lib/stellar/soroban.ts)) — this keeps the style + (see [lib/stellar/soroban.ts](../lib/stellar/soroban.ts)) — this keeps the style consistent and the bundle small. - `@stellar/wallet-sdk` is deprecated (superseded by `@stellar/typescript-wallet-sdk`); avoiding it removes a heavy, churning dependency. -- We reuse the existing multi-wallet signer ([lib/stellar/wallet.ts](lib/stellar/wallet.ts)) +- We reuse the existing multi-wallet signer ([lib/stellar/wallet.ts](../lib/stellar/wallet.ts)) so SEP-10 challenges are signed by **Freighter or Albedo**, matching the app. ## 2. The flow @@ -50,10 +50,10 @@ Borrower clicks "Withdraw to Fiat" | File | Purpose | |---|---| -| [lib/stellar/sep24-config.ts](lib/stellar/sep24-config.ts) | Env-driven anchor config (home domain, asset code/issuer) with testnet defaults | -| [lib/stellar/sep24.ts](lib/stellar/sep24.ts) | SEP-1 toml discovery, SEP-10 auth, SEP-24 interactive deposit/withdraw, status polling + labels | -| [components/dashboard/WithdrawToFiatButton.tsx](components/dashboard/WithdrawToFiatButton.tsx) | "Withdraw to Fiat" button + modal that drives the whole flow and shows live status | -| [app/dashboard/borrower/page.tsx](app/dashboard/borrower/page.tsx) | Renders the button in the Borrower dashboard | +| [lib/stellar/sep24-config.ts](../lib/stellar/sep24-config.ts) | Env-driven anchor config (home domain, asset code/issuer) with testnet defaults | +| [lib/stellar/sep24.ts](../lib/stellar/sep24.ts) | SEP-1 toml discovery, SEP-10 auth, SEP-24 interactive deposit/withdraw, status polling + labels | +| [components/dashboard/WithdrawToFiatButton.tsx](../components/dashboard/WithdrawToFiatButton.tsx) | "Withdraw to Fiat" button + modal that drives the whole flow and shows live status | +| [app/dashboard/borrower/page.tsx](../app/dashboard/borrower/page.tsx) | Renders the button in the Borrower dashboard | Public API of `lib/stellar/sep24.ts`: diff --git a/public/assets/hero-trust.png b/public/assets/hero-trust.png deleted file mode 100644 index b7dc817..0000000 Binary files a/public/assets/hero-trust.png and /dev/null differ