A decentralized launchpad where the community — not a corporation — validates a cause.
ProofOfHeart is a Next.js frontend for community-driven fundraising on the Stellar network. Campaigns, contributions, refunds, revenue sharing and comment moderation are anchored in a Soroban smart contract; the frontend is a wallet-connected UI over that contract. All on-chain interactions are signed by the user's Freighter wallet — the frontend never holds private keys.
The smart contract lives in a separate repository; Iris-IV/ProofOfHeart-stellar.
| Tool | Version | Notes |
|---|---|---|
| Node.js | >= 22 |
Enforced via engines in package.json |
| npm | >= 10 |
Ships with Node 22 |
| Freighter | latest | Browser wallet used to sign transactions |
| Git | any recent | |
| Docker | any recent | Optional. Only for the docker compose workflow below |
No Freighter wallet is needed to run the app in mock mode — it is only required for real testnet/mainnet transactions.
Package manager: use npm. CI installs with
npm ciand the Docker image is built frompackage-lock.json. The repo also contains apnpm-workspace.yamland apnpm-lock.yamlthat mirror the npm overrides, so pnpm works for local development, but do not commit lockfile changes from pnpm.Rust and the Soroban CLI are not needed here. This repo is the TypeScript frontend. Contract development (Rust,
cargo,stellarCLI) happens in Iris-IV/ProofOfHeart-stellar.
Mock mode runs the whole UI against deterministic in-repo data. It needs no contract address, no RPC access and no wallet.
# 1. Install dependencies from the lockfile
npm ci
# 2. Create your local environment file
cp .env.example .env.local
# 3. Make sure mock mode is on in .env.local
# NEXT_PUBLIC_USE_MOCKS=true
# 4. Start the dev server
npm run devOpen http://localhost:3000. The app boots with mock campaigns, contributions and comments.
NEXT_PUBLIC_USE_MOCKS=true (already the default in .env.example) is the only
variable required for this path.
docker-compose.yml runs the dev server in a container, mounting the working tree so
edits hot-reload. It does not load .env.local through env_file; the mounted
directory is what makes .env.local visible to Next.js.
# Build and start the dev server on http://localhost:3000
docker compose up --build
# Stop and remove the containers
docker compose downThe compose file runs npm run dev, not a production build. For a production image,
see the Dockerfile header and docs/DEPLOYMENT_PLAYBOOK.md.
NEXT_PUBLIC_* values are baked into the client bundle at build time, so changing them
requires a rebuild.
| Symptom | Cause and fix |
|---|---|
| Install or build errors after cloning | Check the Node version. package.json requires Node >=22 (node -v). |
npm run build fails with a mock-mode error |
NEXT_PUBLIC_USE_MOCKS=true is set. Set it to false and configure the contract variables, or use npm run dev. |
| Off-chain features disabled or failing | NEXT_PUBLIC_API_URL is unset. See docs/ENVIRONMENT_VARIABLES.md. |
| Env changes are ignored | Restart npm run dev. Next.js reads .env.local at startup. |
To talk to a deployed contract instead of mocks, set NEXT_PUBLIC_USE_MOCKS=false
and fill in the contract coordinates in .env.local:
NEXT_PUBLIC_USE_MOCKS=false
NEXT_PUBLIC_CONTRACT_ADDRESS=CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
NEXT_PUBLIC_SOROBAN_RPC_URL=https://soroban-testnet.stellar.org
NEXT_PUBLIC_STELLAR_NETWORK=testnet
NEXT_PUBLIC_NETWORK_PASSPHRASE=Test SDF Network ; September 2015NEXT_PUBLIC_CONTRACT_ADDRESS— the deployed ProofOfHeart contract (C…).NEXT_PUBLIC_SOROBAN_RPC_URL— Soroban RPC endpoint (public testnet by default).NEXT_PUBLIC_NETWORK_PASSPHRASE— must match the network of the RPC endpoint. Testnet usesTest SDF Network ; September 2015; mainnet usesPublic Global Stellar Network ; September 2015.
Some features (mainnet RPC with an API key, analytics, IPFS uploads, social login,
fiat on-ramp) need extra variables. Every variable is documented in
docs/ENVIRONMENT_VARIABLES.md and declared in
src/lib/envContract.ts.
| Command | What it does |
|---|---|
npm run dev |
Start the Next.js dev server at http://localhost:3000 |
npm run build |
Production build (runs the production-build validator first) |
npm run start |
Serve a production build |
npm run lint |
Run ESLint across the repo |
npm run typecheck |
Type-check with tsc --noEmit |
npm test |
Run the Jest unit/integration suite |
npm run test:watch |
Run Jest in watch mode |
npm run test:e2e |
Run the Playwright end-to-end suite (mock mode) |
npm run storybook |
Start Storybook on http://localhost:6006 |
npm run format |
Format the repo with Prettier |
See package.json for the full list (coverage, bundle-size, audit, changesets, …).
src/
├── app/ Next.js App Router pages, layouts and route handlers (api/)
├── components/ Reusable React UI components
├── hooks/ Data-fetching and lifecycle hooks (useCampaign, …)
├── lib/ Framework-agnostic logic: contract client, stores, env, SEO,
│ feature flags, persistence adapters
├── context/ React context providers (wallet, donations, …)
├── i18n/ Locale routing and helpers
├── types/ Shared TypeScript types
└── __tests__/ Jest test suites, mirroring the source tree
messages/ next-intl translation catalogs (en.json, es.json)
docs/ Developer and operations documentation
Unit and integration tests use Jest + React Testing Library:
npm testEnd-to-end tests use Playwright. They boot the dev server with
NEXT_PUBLIC_USE_MOCKS=true so they run against deterministic mock campaign data:
npm run test:e2ePlaywright defaults are intentionally conservative: headless in CI with a reduced browser matrix, no screenshot noise from animations, and no artifact retention in local runs.
docs/architecture.md— frontend architecture, Soroban transaction lifecycle and IPFS data flows.docs/— architecture, API reference, environment variables, deployment, security, design system and operations guides.docs/CONTRACT_INTEGRATION.md— frontend ↔ contract architecture and function mapping.docs/api.md— API route reference.docs/I18N.md— translations, locale routing and adding a language.docs/state.md— state management architecture and hook dependency standards.docs/GAMIFICATION.md— donor level and badge rules.docs/RECURRING_DONATIONS.md— recurring donation schedules.CHANGELOG.md— versioning, changesets and the release process.CONTRIBUTING.md— local setup, workflow and pre-PR checks.CODE_OF_CONDUCT.md— community expectations.
Campaign cards and detail pages offer an XLM/USD/EUR selector for the funding target. Fiat values are approximate display values from CoinGecko, refreshed every minute while selected; donations and contract amounts remain in XLM. The server caches quotes for 60 seconds and rejects invalid quotes or prices older than ten minutes. If the provider is unavailable or rate limited, the XLM target stays visible and supporters can retry. No provider key is required and wallet addresses are never sent to the quote provider.
Campaign data fetching is separated into focused hooks so each query is easier to reason about and test:
useCampaignowns the base fetch lifecycle and shared loading/error state.useCampaignMetadataexposes title, description, tags and creator metadata.useCampaignFundingexposes funding totals, milestones and percentage calculations.useCampaignRulesexposes lifecycle and contribution gating behavior.
This keeps one source of truth for data loading while avoiding a single monolithic hook with unrelated responsibilities.
Campaign detail pages also show the latest 20 contributions received while the page is open. The scrollable feed shares the funding subscription, deduplicates replayed events, and pauses network activity when the tab is hidden. Pausing the feed freezes its visible entries while funding totals continue to update; resuming shows the latest entries. Mock mode displays an explicit notice instead of simulated donations. Connection failures retain existing entries while the subscription retries.
Shared local-storage log helpers centralize read/write, timestamping and address-based filtering for wallet/admin log features. This reduces duplication and keeps persistence behavior consistent across entries.