Stellar Goal Vault is a lightweight crowdfunding MVP for the Stellar ecosystem.
It includes:
- A React dashboard to create and manage funding campaigns
- A Node.js + Express API backed by SQLite
- A Soroban contract scaffold for on-chain campaign creation, pledges, claims, and refunds
- A seeded contribution backlog you can turn into GitHub issues after publishing
Creators open a campaign with a target amount and deadline.
Contributors can pledge until the deadline:
- If the target is reached, the creator can claim the vault
- If the target is missed, contributors can refund their pledges
This repo is intentionally scoped as an MVP so it is easy to extend with wallet signing, event indexing, and production-grade UX.
Frontend (frontend, port 3000)
- React + Vite dashboard
- Campaign board, detail panel, timeline, and contribution backlog
- Uses
/apiproxy for backend calls - Freighter-backed pledge flow that simulates, signs, submits, and then reconciles local state
Backend (backend, port 3001)
- Express REST API
- SQLite persistence for campaigns, pledges, and event history
- Real-time campaign status derived from current timestamps and stored pledges
- Exposes contract/network config to the frontend and reconciles confirmed pledge hashes
Contract (contracts)
- Soroban Rust scaffold
- Implements
create_campaign,contribute,claim,refund,get_campaign, andget_contribution - Not yet wired into live wallet signing flow in the frontend
Architecture decision records
- Key architecture choices are documented in
adr/. - See
adr/0001-sqlite-off-chain-mvp.mdfor the SQLite off-chain MVP decision. - See
adr/0002-react-express-mvp.mdfor the React + Express + Soroban MVP architecture decision.
Each campaign stores:
creatortitledescriptionassetCodetargetAmountpledgedAmountdeadline
Campaign states:
openwhen deadline has not passed and target is not yet metfundedwhen pledged amount is at least the target and funds have not been claimedclaimedwhen the creator has claimed a funded vaultfailedwhen deadline has passed without reaching the target
Base URL:
- Local backend:
http://localhost:3001 - Frontend proxy:
/api
- Service health check
- Response:
{
"service": "stellar-goal-vault-backend",
"status": "ok",
"timestamp": "2026-03-27T21:30:00.000Z",
"uptimeSeconds": 12.345,
"database": {
"status": "up",
"reachable": true
}
}statusisokwhen the API and database probe succeed, otherwisedegradeddatabase.statusisupordownbased on a lightweight SQLite reachability check
- Returns all campaigns with computed progress
- Query parameters:
q(optional): Search query to filter campaigns by title, creator, or campaign ID (case-insensitive)asset(optional): Filter campaigns by asset code (e.g., USDC, XLM)status(optional): Filter campaigns by status (open, funded, claimed, failed)
- Returns one campaign with pledges and event history
- Create a campaign
Request body:
creatortitledescriptionassetCodetargetAmountdeadlinemaxPerContributor(optional): Maximum total pledge amount a single contributor can contribute to this campaign. If not set, no per-contributor limit applies. Can also be set globally viaDEFAULT_MAX_PER_CONTRIBUTORenv variable.
- Add a pledge to a live campaign
Request body:
contributoramount
- Record a confirmed on-chain pledge locally after the Soroban transaction succeeds
Request body:
contributoramounttransactionHashconfirmedAt(optional)
- Claim a funded campaign after deadline
Request body:
creator
- Refund all active pledges from one contributor on a failed campaign
Request body:
contributor
- Fetch local event history for the selected campaign
Returns contributor summary (grouped pledges with refund status).
Response:
{
"data": [
{
"contributor": "GBEZH6T5V7VHUWGMAHVICBFV7WSNULSIHHMV7B2LFNJA6J3XVMT7M2LVY",
"totalPledged": 150.0,
"refundedAmount": 0,
"isFullyRefunded": false
},
{
"contributor": "GABC123...",
"totalPledged": 0,
"refundedAmount": 50.0,
"isFullyRefunded": true
}
]
}Empty campaigns return {"data": []}. Invalid IDs return 404.
- Returns seeded issue ideas for public open-source contribution
Prerequisites:
- Node.js 18+
- npm 9+
- Optional for contract work: Rust + Soroban toolchain
From repo root:
npm run install:all
npm run dev:backend
npm run dev:frontendOpen:
- Frontend:
http://localhost:3000 - Backend:
http://localhost:3001
Build:
npm run buildThe backend includes a configurable load test script built with autocannon to simulate concurrent campaign reads and pledge writes.
- Start the backend locally:
npm run dev:backend- In a second terminal, run the load test:
cd backend
npm run load:test -- --base-url http://127.0.0.1:3001 --connections 20 --duration 20The script seeds synthetic campaigns first, then runs a mixed workload across:
GET /api/campaignsGET /api/campaigns/:idPOST /api/campaigns/:id/pledges
The console output includes:
- Latency percentiles (
p50,p90,p97.5,p99,max) - Error rate, timeout count, and non-2xx responses
- Average requests per second and throughput
Useful flags:
--connections <number>: concurrent connections--duration <seconds>: test duration--campaigns <number>: number of seed campaigns created before the run--read-weight <number>: relative share of campaign read requests--pledge-weight <number>: relative share of pledge requests--pledge-amount <number>: amount sent in each pledge request--target-amount <number>: target amount assigned to each seed campaign--asset-code <code>: asset code used while seeding campaigns--deadline-hours <hours>: how far into the future seeded campaign deadlines are set
Example validation run:
cd backend
npm run load:test -- --base-url http://127.0.0.1:3001 --duration 5 --connections 5 --campaigns 4 --read-weight 3 --pledge-weight 2Set a funded Stellar testnet secret key and run:
SECRET_KEY="S..." npm run deploy:contractThe script will:
- Build the Soroban contract
- Deploy to Stellar testnet
- Output the contract ID
- Save the ID to
contracts/contract_id.txt
Backend:
PORTdefaults to3001DB_PATHdefaults tobackend/data/campaigns.dbSOROBAN_RPC_URLdefaults to Stellar testnet RPCCONTRACT_IDis required for Freighter pledge signingNETWORK_PASSPHRASEdefaults to Stellar testnetCONTRACT_AMOUNT_DECIMALSdefaults to2and controls display-to-contract unit scaling
Frontend:
VITE_API_URLdefaults to/api
Contract deployment:
SECRET_KEYrequiredNETWORK_PASSPHRASEoptionalRPC_URLoptional
The main contribution issue for this repo is:
Implement Freighter-signed pledge transactions
That issue is already represented in:
backend/src/services/openIssues.tsOPEN_SOURCE_ISSUES.md- The frontend backlog panel
Please see the Contributing Guide for setup and contribution guidelines.
- Campaign creation is still local-first, so pledges will only simulate successfully for campaign IDs that also exist in the configured contract
- No authentication or rate limiting on write endpoints
- No background indexer for on-chain event sync yet
- Replace mock pledge actions with Freighter + Soroban transactions
- Index on-chain events into SQLite
- Add filters, sorting, and campaign pages
- Add contract tests and backend integration tests
The frontend now includes a theme toggle in the header with icon controls for light/dark mode.
- Toggle between light and dark themes directly in the UI
- Persist selected theme in
localStorageusingstellar-goal-vault-theme - Respect system
prefers-color-schemeon first load when no saved theme exists - Apply dark/light color variants across core UI surfaces (cards, tables, forms, buttons, and controls)