Reputation is your credit score. Earn trust, unlock capital, and build financial access.
β¨ Fast. Transparent. Auditable. Global. β¨
Live Production | Video Demo | Roadmap | Getting Started Guide | Contributing Guidelines
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.
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.
- 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.
TrustLend is designed as a foundational layer for decentralized, inclusive credit. As an open-source project, our vision for evolution includes:
- Decentralized Credit Oracles: Evolving the reputation system to aggregate off-chain data (utility bills, mobile money history, Web2 integrations) via trusted oracles.
- Cross-Chain Liquidity: Expanding lender pools to accept stablecoins across various ecosystems, routing them securely through Stellar's high-speed finality layer.
- DAO Governance: Transitioning platform parameters (interest rates, insurance pool fees, slashing mechanics) to a community-governed DAO framework.
- Institutional Underwriting: Enabling institutional liquidity providers to plug proprietary risk models into TrustLend's smart contracts to automatically fund specific borrower profiles.
- Global Fiat On/Off Ramps: Deepening integration with Stellar anchors to allow seamless fiat borrowing and repayment in local currencies worldwide.
We welcome open-source contributors to help us build this vision! Check out our Roadmap for upcoming milestones.
TrustLend uses a practical hybrid architecture: fast UX off-chain (Supabase/Next.js) 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.
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 indexer fill:#06b6d4,color:#fff,stroke:#0891b2,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<br/>Next.js 16 + React 19")]
BW[("π Stellar Wallet<br/>Freighter / xBull / Albedo<br/>WalletConnect (mobile)")]
RD[("π± Role Dashboards<br/>Borrower Β· Lender Β· Admin")]
end
%% ββ Backend Layer (Purple) ββββββββββββββββββββββββββββββββββββββββββββββ
subgraph Backend["βοΈ Backend Layer (Next.js)"]
direction TB
SA[("π‘ Server Actions & API Routes<br/>app/actions + app/api")]
SB[("ποΈ Supabase<br/>PostgreSQL Β· Auth Β· RLS Β· Storage")]
RM[("π Soroban Client<br/>lib/stellar/soroban.ts")]
SC[("π Server-side Contract Invoker<br/>lib/stellar/server-contract.ts")]
RC[("β‘ Redis Cache<br/>Simulation result cache")]
EM[("π§ Email Service<br/>Resend Β· Payment notices")]
end
%% ββ Indexer Layer (Cyan) ββββββββββββββββββββββββββββββββββββββββββββββββ
subgraph Indexer["π Indexer Layer (SubQuery)"]
direction TB
SQ[("π₯ SubQuery Soroban Indexer<br/>project.yaml")]
GR[("ποΈ GraphQL API<br/>schema.graphql")]
RS[("π‘ REST API<br/>Read-model fallback")]
HM[("π Horizon Sync Health<br/>indexer_health table")]
end
%% ββ Automation Layer (Amber) βββββββββββββββββββββββββββββββββββββββββββββ
subgraph Automation["β° Automation Layer (Cron / Vercel)"]
direction TB
PD[("π
Payment-Due Scheduler<br/>lib/scheduler/payment-due.ts<br/>Vercel Cron: hourly")]
DM[("βοΈ Default Management<br/>lib/scheduler/default-management.ts<br/>Insurance + Mark Defaulted")]
LK[("π¨ Liquidation Keeper<br/>scripts/liquidation-keeper.ts<br/>Under-collateralization monitor")]
OC[("π Oracle Credit Score Poster<br/>scripts/oracle-post-credit-score.mjs")]
end
%% ββ Blockchain Layer (Green) βββββββββββββββββββββββββββββββββββββββββββββ
subgraph Chain["βοΈ Blockchain Layer (Stellar Soroban)"]
direction TB
LP[("π³ Lending Contract<br/>Loans Β· Repayments Β· Flash Loans")]
RP[("β Reputation Contract<br/>Borrower scoring Β· Tiers Β· Freeze")]
ES[("π Escrow Contract<br/>Hold funds Β· Revocation window")]
DF[("π‘οΈ Default Management<br/>Insurance pool Β· Phases")]
MS[("ποΈ MultiSigAdmin Contract<br/>N-of-M governance Β· Admin actions")]
GV[("π³οΈ Governance Contract<br/>Voting Β· Fee changes Β· Params")]
TR[("π° Treasury Contract<br/>Fee collection Β· 50/50 distribution")]
AC[("π Auto-Compound Vault<br/>Yield auto-compounding Β· Harvest")]
end
%% ββ External / Stellar Layer (Gray) βββββββββββββββββββββββββββββββββββββ
subgraph External["π External Services & Infrastructure"]
direction TB
SR[("π Soroban RPC<br/>soroban-testnet.stellar.org")]
HZ[("π Horizon API<br/>horizon-testnet.stellar.org")]
WH[("π Webhook<br/>Payment-due notifications")]
KP[("π KYC Provider<br/>Document verification")]
AL[("π£ Alerts<br/>Slack / Discord")]
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
%% ββ Indexer ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
SQ -->|"captures events from"| SR
SQ --> GR
SQ --> RS
GR -->|"GraphQL read-model"| SA
RS -->|"REST read-model"| SA
HZ -->|"ledger stream"| HM
HM -->|"persists sync state"| SB
HZ -.->|"also feeds direct reads"| SB
%% ββ Automation βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
PD -->|"queries due loans"| SB
PD --> WH
DM -->|"queries overdue loans"| SB
DM --> SC
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
| 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 |
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
- Onboarding: User signs up via Supabase Auth, connects a Stellar wallet (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.
- Borrowing: Borrower submits a loan request. The Next.js backend calls
ReputationContract.calculate_max_loanandcalculate_interest_rateto determine eligibility and terms. - Lending: Lender reviews the request in the marketplace, approves it, and the
LendingContract.approve_loanis called. Funds are locked viaEscrowContract.create_escrow_hold. - Disbursement: After the 1-hour revocation window expires, the admin confirms disbursement.
EscrowContract.confirm_disbursementreleases funds to the borrower, andLendingContract.activate_loanmarks the loan as active. - Repayment: Borrower repays via the dashboard. The backend calls
LendingContract.record_payment, which updates the loan balance and emits an event. - Reputation Update: On-time repayments trigger
ReputationContract.add_reputation_event, boosting the borrower's tier and unlocking better terms for future loans. Defaults trigger theDefaultManagementcontract.
| Automation | Trigger | Action |
|---|---|---|
| Payment-Due Scheduler | Vercel Cron (hourly) | Queries Supabase 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 |
| Layer | Technology |
|---|---|
| Frontend | Next.js 16, React 19, TypeScript, Tailwind CSS 4, Framer Motion |
| Backend & DB | Supabase (Auth, Postgres RLS, Storage) |
| Blockchain | Stellar Testnet, Soroban RPC, Horizon API |
| Wallet | Freighter Wallet, xBull, Albedo, WalletConnect v2 for mobile wallets (@stellar/freighter-api, @creit.tech/stellar-wallets-kit) |
| Smart Contracts | Rust (Soroban, wasm32v1-none) β 8 contracts deployed |
| Indexer | SubQuery (@subql/node-stellar, @subql/query) β GraphQL + REST |
| Cache | Upstash Redis |
| Automation | Vercel Cron Jobs |
| Resend | |
| SEP-24 | Stellar Anchor fiat on/off ramp |
π New contributors should start with the Getting Started Guide for a thorough walkthrough covering Soroban CLI setup, contract compilation, database setup, and the full test suite.
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 devThe app will be available at http://localhost:3000 with hot-reloading enabled.
docker-compose upSee the complete setup guide β
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:
npm run deploy:testnetThere 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:
npm run deploy:testnet:dryContract IDs land directly in .env.local. Keys already present are updated in
place β your Supabase keys, API secrets and comments are left untouched, and a
.env.local.bak is taken first. A reference copy also goes to .env.contracts.
| 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 |
npm run deploy:testnet -- --help| 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/<network>.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.ps1still work but are superseded: the CLI is cross-platform, handles key creation and funding, and deploys the pooled-lending contract those scripts omitted.
π§© Smart Contracts & Deployment Details (Click to Expand)
Deployment Credentials:
- Network: Stellar Testnet
- Admin Address:
GAJRNUO6HSMQG4FNHNWQVRXJZJZ7QRA7HXPYYB6H5PTA3EAAJXJNZD7U - Deployment Source Key Alias:
trustlend-admin
Contract Registry:
| Contract | Env Key | Contract ID |
|---|---|---|
| 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.
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.
- An external scheduler (Vercel Cron or any HTTP trigger) calls
POST /api/cron/payment-duehourly. - The route queries Supabase for
activeorfundedloans withdue_atbetween now and +48 hours. - A POST webhook is sent to
WEBHOOK_NOTIFICATION_URLfor each qualifying loan. - The loan's
metadata.payment_due_notified_atis set to prevent duplicate notifications. - Per-loan errors are logged without stopping the rest of the batch.
| 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 <value>) |
SUPABASE_SERVICE_ROLE_KEY |
Supabase service-role key (required for RLS-bypassing loan queries) |
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.
{
"borrowerId": "uuid",
"loanId": "uuid",
"dueDate": "2026-07-01T12:00:00.000Z",
"paymentAmount": 800.00
}paymentAmount is principal_amount β repaid_amount (outstanding balance).
Vercel Cron (automatic, hourly): Configured in vercel.json β no additional setup needed.
Manual trigger:
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.
- 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.
All public-facing API routes are rate limited to protect backend services from brute-force attacks, scraping, and misconfigured clients.
| Layer | Scope | Limit | Enforced in |
|---|---|---|---|
| Global ceiling | Every /api/* request, keyed by client IP |
100 requests / minute / IP | 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.
Requests over the limit are rejected with HTTP 429 before any database or Stellar network work is performed:
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." }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.
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.
Two escape hatches skip rate limiting entirely β see .env.example:
Authorization: Bearer <ADMIN_SECRET_KEY>β trusted internal/admin callers.RATE_LIMIT_WHITELISTβ comma-separated IPs (uptime probes, Prometheus scrapers).
Register the limit in ROUTE_POLICIES (or ROUTE_PATTERN_POLICIES for dynamic
segments) in lib/rate-limit.ts, then guard the handler:
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.
Thanks goes to these wonderful people who have contributed to TrustLend:
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 and Code of Conduct before submitting a Pull Request.
If you discover a security vulnerability within TrustLend, please refer to our Security Policy for reporting instructions. Do not open a public issue for security-related matters.
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 workflow. Restore steps, bucket/IAM setup and the quarterly restore drill are documented in DISASTER_RECOVERY.md.
This project is licensed under the MIT License.
Made with β€οΈ by the TrustLend Community.




