Skip to content
 
 

Repository files navigation

OurDAO logo

OurDAO Frontend

CI License: MIT

A Next.js web app for the OurDAO member-owned lending DAO on Stellar Soroban.

  • Wallet: Freighter via @stellar/freighter-api
  • Chain access: @stellar/stellar-sdk (Soroban RPC — simulate for reads, prepare/sign/submit for writes)
  • Contract: the ourdao-contracts Soroban DAO
  • Off-chain data: the ourdao-backend indexer/API, for anything the contract itself keeps no queryable history of

This repository is one of three that make up OurDAO:

Repo Role
ourdao-contracts The Soroban contract — the single source of truth for all DAO state
ourdao-backend Off-chain indexer + read API
ourdao-frontend (this repo) Next.js web app members actually use

Table of contents

Getting started

npm install
cp .env.example .env.local   # then edit values (all optional; testnet defaults)
npm run dev

Open http://localhost:3000. Install the Freighter browser extension to connect a wallet.

Configuration

All config is env-driven with public-testnet defaults (see .env.example):

Variable Purpose Default
NEXT_PUBLIC_CONTRACT_ID Deployed OurDAO contract id (C…) (empty → read-only "not configured")
NEXT_PUBLIC_SOROBAN_RPC_URL Soroban RPC endpoint https://soroban-testnet.stellar.org
NEXT_PUBLIC_NETWORK_PASSPHRASE Network passphrase testnet
NEXT_PUBLIC_IPFS_GATEWAY Gateway for document content hashes Pinata
NEXT_PUBLIC_BACKEND_URL ourdao-backend indexer/API (loan history, notifications, admin log, events) http://localhost:4000

Without a NEXT_PUBLIC_CONTRACT_ID the UI runs and renders, but on-chain reads/writes are disabled until you point it at a deployed contract. Without a reachable backend, everything backend-derived (loan history, notifications, activity/admin logs) degrades to empty rather than erroring — see src/lib/backend.ts.

Routes

Route Purpose
/ Landing page — product overview, live DAO stats, wallet connect entry point.
/register Join the DAO (pays the membership fee via a real contract call).
/dashboard Member home: membership status, voting weight, pending yield, quick actions, DAO-wide stats, recent activity.
/loans Browse and vote on loan proposals.
/loans/request Multi-step loan request form.
/loans/[id] Loan detail — proposal vote state, and once approved, the real disbursed loan's status (Active/Repaid/Defaulted), due date, and a permissionless "Mark as Defaulted" action once overdue.
/governance Browse and vote on both loan and treasury proposals in one place.
/governance/create Create a treasury withdrawal proposal (optionally private, via commit-reveal).
/treasury Treasury balance, staking (stake/unstake for voting-weight boost), claimable yield.
/privacy What's actually private on-chain (commit-reveal voting, document encryption) and how to use it.
/admin Admin-only: pause/unpause, add/remove admins, set consensus threshold, governance audit log.

Architecture

src/
  app/            # Next.js App Router pages (one folder per route above)
  components/     # Shared UI: AppShell (header/sidebar), ConnectButton, NotificationCenter,
                  # ThemeToggle, DocumentUpload, and the shadcn/ui-derived primitives in ui/
  hooks/          # useDAO.ts (contract reads/writes as React Query hooks),
                  # useNotifications.ts (backend-polled notifications + activity feed),
                  # useNow.ts (a useSyncExternalStore-based clock for countdown displays)
  lib/            # stellar.ts, wallet.tsx, dao-client.ts, backend.ts, ipfs.ts, utils.ts
  types/          # Shared TypeScript types

AppShell (header + sidebar navigation) is rendered by each page individually rather than being a Next.js layout.tsx — every page wraps its content in <AppShell> instead of hand-rolling its own chrome. The landing page and /register are the exceptions, with their own standalone headers since they're meant to work before a user has any DAO context.

Data flows through TanStack Query throughout: useDAO.ts's hooks wrap live Soroban contract reads (the contract itself has no queryable lists, so proposal/loan enumeration counts come from the indexer, then each item is fetched live by id straight from the contract — the count is an off-chain hint, the data is always on-chain-sourced) and Freighter-signed writes; useNotifications.ts wraps the backend's polled REST endpoints.

Where the Stellar integration lives

File Role
src/lib/stellar.ts Network config, RPC client, explorer URLs
src/lib/wallet.tsx Freighter connect/disconnect/sign context (useWallet)
src/lib/dao-client.ts Soroban read/invoke + typed wrappers for every contract method
src/components/ConnectButton.tsx Freighter connect/disconnect UI
src/hooks/useDAO.ts React Query hooks the pages consume

Theming

Light/dark is handled by next-themes (ThemeProvider in src/components/providers.tsx, toggled via src/components/ThemeToggle.tsx in the header), following the system preference by default and persisting a manual choice in localStorage. Colors are Tailwind v4 @theme tokens defined in src/app/globals.css — a .dark class override block flips the semantic set (background, foreground, card, muted, border, etc.) that src/components/ui/* is built against.

Two things worth knowing if you're touching styling:

  • Those ui/ primitives referenced this token set from the start, but the tokens themselves were never actually defined until this was fixed — bg-card, text-muted-foreground, and friends were silently unstyled before.
  • cn() (src/lib/utils.ts) runs through tailwind-merge, not just clsx — this matters because a component's default variant classes (e.g. Button's default bg-primary) and a caller's override classes (e.g. bg-white) will otherwise both compile to real CSS rules, and which one wins visually depends on Tailwind's generated stylesheet order rather than which class is written later. tailwind-merge resolves that by intent instead.

Scripts

npm run dev       # dev server (http://localhost:3000)
npm run build     # production build
npm start         # serve the production build
npm run lint      # eslint
npm run typecheck # tsc --noEmit
npm test          # vitest

Testing

Vitest + Testing Library, jsdom by default (pure-logic suites that don't need the DOM, like the Soroban ScVal builders, opt into the Node environment per-file via // @vitest-environment node). Coverage: dao-client.ts's ScVal builders and policyToScVal, backend.ts's fetch wrappers (including its fail-soft-on-error behavior), useDAO.ts's pure mapping helpers (including mapLoan, the real disbursed-loan mapper), useNotifications.ts's hooks, and useNow.ts's useSyncExternalStore contract (using fake timers, since the underlying bug it guards against — an infinite render loop — doesn't reproduce reliably just by rendering in jsdom). CI runs lint, typecheck, test, and build on every push/PR — see .github/workflows/ci.yml.

What's real vs. not

Most of the app is wired to the live contract + backend: registration, loan request/vote/repay, treasury propose/vote, staking, name registry, commit-reveal private voting, document content-hash attachment, notifications, admin actions (pause/unpause, add/remove admin, set consensus threshold), an admin/governance audit log, and loan defaults — markLoanDefaulted is exposed in dao-client.ts, and the dashboard's Recent Activity feed labels every real event (including loan_dflt) instead of a generic placeholder. The loan detail page (/loans/[id]) reads the contract's real disbursed Loan (via useLoan) once a proposal is approved — actual status, due date, and outstanding balance, not proposal-status guesswork that never reflected repayment or default.

One known gap remains:

  • IPFS document storage (src/lib/ipfs.ts) — the encryption (AES-GCM) is real, but the upload/download target (Infura's IPFS gateway) has been shut down. Needs a real pinning provider (Pinata/web3.storage) + API key before it actually stores anything.

tsc --noEmit is fully clean and enforced in CI. next.config.ts still sets typescript.ignoreBuildErrors — safe to remove now, kept since the CI gate already covers it (the eslint.ignoreDuringBuilds counterpart was removed outright in the Next 16 upgrade — that config key no longer exists).

Running on Next.js 16 (Turbopack by default) + React 19.2.

Security notes

  • No custody. The frontend never holds a private key — every signature happens inside the Freighter extension, in the user's own browser context. src/lib/wallet.tsx only ever receives a signed transaction XDR back, never a key.
  • Read-only degradation, not silent failure. Without a configured contract id or a reachable backend, the UI runs in an explicit "not configured" / empty state rather than throwing — see Configuration.
  • Error boundaries. error.tsx (route-segment) and global-error.tsx (root-layout-level) catch uncaught render errors and offer a retry instead of the previous behavior, where any single uncaught error anywhere in the tree would take down the entire client-side app with no recovery short of a hard reload.
  • Dependency hygiene. A critical Next.js RCE and several other npm audit findings were patched; remaining findings are rooted entirely in ipfs-http-client's dependency tree, tracked against the IPFS gap above rather than silently ignored.

Roadmap

  • Replace the dead IPFS/Infura endpoint with a real pinning provider.
  • Rework the loan detail page's data model so it derives loan/proposal state more directly (some legacy fields still shadow real on-chain data in places not yet fully migrated).
  • Convert DocumentViewer.tsx's manual fetch-in-effect to React Query, matching the rest of the app's data-fetching convention (currently unused in the app, flagged rather than silently left as-is).

Contributing

Contributions are welcome — see CONTRIBUTING.md for local setup, the checks CI enforces, and the frontend-specific rules (no fabricated content, TanStack Query for all data fetching, cn() for class composition, both themes verified). Please claim an issue before opening a pull request.

Found a security vulnerability? Don't open a public issue — use GitHub's private vulnerability reporting on this repo.

License

MIT

About

OurDAO frontend — Next.js web app for the OurDAO lending DAO on Stellar Soroban.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages