TrustBridge Dashboard is the open-source web interface for the TrustBridge project. It connects GitHub contributor identities to Stellar G-addresses and validates payout readiness before Wave disbursements.
The problem: Stellar payments fail with
PAYMENT_NO_TRUSTwhen a recipient account lacks a trustline for the asset being sent (e.g. USDC). Maintainers need a single source of truth mappinggithub_username → stellar_addresswith live trustline and reserve checks — before batch payouts.
- Features
- Quick start
- Documentation
- Tech stack
- Routes & API
- Environment variables
- Deployment
- Contributing
- License
| Audience | Capability |
|---|---|
| Contributors | Sign in with GitHub OAuth, register a Stellar G-address, get live Horizon validation (funding, USDC trustline, XLM reserve) |
| Maintainers | View all registrations, filter by readiness, batch re-check via Horizon, export CSV for Wave payout prep, review recent Soroban contract events |
| Everyone | Public landing page with Wave readiness stats |
| Status | Badge | Meaning |
|---|---|---|
| Ready | ✅ | Funded, USDC trustline active and authorized, spendable XLM ≥ minimum reserve |
| Low reserve | Funded + authorized trustline, but spendable XLM below threshold | |
| Not ready | ❌ | Unfunded, missing trustline, or a present-but-unauthorized trustline |
Authorization matters: a trustline can exist but remain unauthorized by the asset issuer (Stellar
AUTH_REQUIREDassets). Payments to an unauthorized trustline still fail, so the dashboard tracksis_authorizedseparately and an account is only verified (✅ on-chain badge) when funded and holding an authorized trustline.
Spendable vs. raw XLM: every Stellar account must keep a minimum reserve locked up —
baseReserve * (2 + subentries + sponsoring − sponsored)— plus any XLM tied up inselling_liabilities. That reserve is not available for payments, so the reserve check compares against spendable XLM (spendableXlmBalance), not the rawxlm_balance. A contributor with 5 XLM raw balance and 3 trustlines can show ~3.5 XLM locked in reserve, leaving well under 1 XLM spendable — this islow_reserve, notready, even though the raw balance alone looks healthy.
- Node.js 18+ and npm
- PostgreSQL database (local, Neon, Supabase, or Vercel Postgres)
- GitHub OAuth App (create one here)
git clone https://github.com/your-org/trustbridge-dashboard.git
cd trustbridge-dashboard
npm installcp .env.example .env.localFill in all required values — see docs/ENVIRONMENT.md for full reference.
npm run db:pushnpm run devOpen http://localhost:3000.
GitHub OAuth callback URL (local):
http://localhost:3000/api/auth/callback/github
All docs are cross-linked from this README:
| Document | Description |
|---|---|
| Setup guide | Step-by-step local development setup |
| Environment variables | Every env var explained |
| Architecture | System design, data flow, auth model |
| Project structure | Directory layout and key files |
| Deployment | Vercel deployment checklist |
| Contributing | How to contribute to this repo |
| CSRF protection | Threat model, protected routes, non-browser client policy, testing guide |
| Layer | Technology |
|---|---|
| Framework | Next.js 14 (App Router) |
| Language | TypeScript |
| Styling | Tailwind CSS, shadcn/ui patterns |
| Data fetching | TanStack React Query |
| Auth | NextAuth.js + GitHub OAuth |
| Database | PostgreSQL + Prisma ORM |
| Blockchain | stellar-sdk + Horizon API |
| Deployment | Vercel (recommended) |
Brand colors: Stellar purple #3E1BDB, cyan accent #00B4D8. Dark mode supported.
| Route | Auth | Description |
|---|---|---|
/ |
Public | Landing page, TrustBridge explainer, Wave stats |
/register |
GitHub OAuth | Contributor Stellar address registration |
/dashboard |
GitHub OAuth + org member | Maintainer payout readiness table |
| Endpoint | Method | Description |
|---|---|---|
/api/auth/[...nextauth] |
GET/POST | NextAuth.js handlers |
/api/check |
POST | Horizon validation { address, asset_code?, asset_issuer? } |
/api/register |
GET/POST | Read/save contributor registration (authenticated) |
/api/contributors |
GET/POST | List contributors / batch re-check (maintainer only) |
/api/stats |
GET | Aggregate readiness statistics |
/api/check |
POST | Horizon validation { address, asset_code?, asset_issuer? } — returns trustline_authorized and verified |
/api/register |
GET/POST | Read/save contributor registration (authenticated) |
/api/contributors |
GET/POST | List contributors / batch re-check (maintainer only) |
/api/contributors/[id] |
POST | Re-check a single contributor via Horizon (maintainer only) |
/api/audit |
GET | Recent maintainer actions — audit log (maintainer only) |
/api/stats |
GET | Aggregate readiness statistics |
/api/actions/lookup |
GET | Cached Horizon readiness lookup + wizard nextAction guidance, ?address=G... |
/api/soroban/events |
GET | Recent events for SOROBAN_CONTRACT_ID (maintainer only) |
/api/settings/network |
GET | Resolved Horizon/Soroban network + mismatch warnings (maintainer only) |
- Horizon circuit breaker —
src/lib/circuit-breaker.tswraps Horizon API calls. After 5 consecutive failures, the breaker opens and fast-fails for 30s, returning a friendly "Horizon is temporarily unavailable" message. Configurable viaHORIZON_CB_FAILURE_THRESHOLD,HORIZON_CB_RECOVERY_MS, andHORIZON_CB_SUCCESS_THRESHOLD. - Stale CSV export guard —
src/lib/stale-export.tscheckslastCheckedAttimestamps before CSV export. If any contributor hasn't been verified within the configured window (default 24h), the dashboard shows an amber warning banner and requires confirmation before exporting. Configurable viaSTALE_CSV_MAX_AGE_MS.
src/middleware.ts protects /register (requires sign-in) and /dashboard (requires sign-in + GITHUB_MAINTAINER_ORG membership).
- CSRF protection — All mutating API routes validate the
Origin/Refererheader against the application's host. See docs/CSRF.md. - Rate limiting —
POST /api/checkis rate-limited per IP (default 10 requests per minute) to prevent Horizon API abuse. Configure viaRATE_LIMIT_WINDOW_MSandRATE_LIMIT_MAX_REQUESTS. - CSV / JSON exports — Maintainer dashboard exports contributor data as CSV or JSON. Export helpers live in
src/lib/csv.tsand are covered by snapshot tests.
Copy .env.example to .env.local and configure:
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=
TOKEN_ENCRYPTION_KEY= # required, openssl rand -base64 32 — encrypts stored access tokens
GITHUB_MAINTAINER_ORG=
GITHUB_MAINTAINER_TEAM= # optional, team slug within GITHUB_MAINTAINER_ORG — org-only check if unset
DATABASE_URL=
NEXT_PUBLIC_HORIZON_URL=https://horizon.stellar.org
NEXT_PUBLIC_DEFAULT_ASSET_CODE=USDC
NEXT_PUBLIC_DEFAULT_ASSET_ISSUER=GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX5IHOWEBMGJI55ITFSZ6
NEXT_PUBLIC_MIN_XLM_BALANCE=1
NEXT_PUBLIC_BASE_RESERVE_XLM=0.5 # optional, Stellar base reserve used for spendable-balance checks
SOROBAN_CONTRACT_ID= # optional, future on-chain registry + event timeline panel
SOROBAN_RPC_URL= # optional, defaults to soroban-testnet.stellar.orgNote:
NEXT_PUBLIC_HORIZON_URLandSOROBAN_RPC_URLshould point at the same Stellar network. Their defaults don't (mainnet vs. testnet) — the dashboard detects and warns on this mismatch via/api/settings/networkand the maintainer dashboard's network status panel, and records it to the audit log.
See docs/ENVIRONMENT.md for details.
Generate NEXTAUTH_SECRET:
openssl rand -base64 32Unit tests run on Vitest and cover the pure business logic
(readiness/authorization rules, the Horizon retry helper, audit-log formatting,
batch verification, and the Soroban event-timeline read path — including the
GET /api/soroban/events maintainer guard, a missing SOROBAN_CONTRACT_ID,
and simulated RPC outages/rate limits, all without a live network call):
npm test # run once
npm run test:watchTests also run in CI on every push and pull request, before the build.
Schema note: issue #7 adds a
trustlineAuthorizedcolumn toRegistration, issue #22 adds anAuditLogtable, and issue #8 adds aspendableXlmBalancecolumn toRegistration. After pulling these changes, sync your database withnpm run db:push.
This project is optimized for Vercel:
- Push to GitHub
- Import repo in Vercel
- Add environment variables from docs/ENVIRONMENT.md
- Attach a Postgres database (Vercel Postgres, Neon, etc.)
- Run
npm run db:pushagainst productionDATABASE_URL - Set GitHub OAuth callback to
https://your-domain.vercel.app/api/auth/callback/github
Full checklist: docs/DEPLOYMENT.md
We welcome contributions! Please read docs/CONTRIBUTING.md before opening a PR.
Quick flow:
- Fork the repo
- Create a feature branch (
git checkout -b feat/my-feature) - Commit with clear messages
- Run tests (
npm run test) - Open a pull request against
main
PostgreSQL is the source of truth for registrations. Setting SOROBAN_CONTRACT_ID
(+ optionally SOROBAN_RPC_URL) today enables the read-only maintainer event
timeline (GET /api/soroban/events), which fetches recent contract events and
degrades gracefully — never failing the dashboard — on RPC outages, rate limits,
or a missing contract ID.
Mirroring registrations to a Soroban contract (write-through) is designed but not yet implemented — see docs/ARCHITECTURE.md § Soroban register write-through for the read-vs-write-through breakdown, the intended write design, and how each edge case (outage, missing config, rate limits) is or would be handled.
This project is licensed under the MIT License.