A decentralized crowdfunding platform built on the Stellar network using Soroban smart contracts. Fund-My-Cause lets anyone create a campaign on-chain, accept contributions in XLM or any Stellar token, and automatically release or refund funds based on whether the goal is met.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Next.js Frontend (TypeScript) β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββββββββββ β
β β Navbar β β ProgressBar β β PledgeModal β β
β β (Freighter) β β (Campaign) β β (Contribution) β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββββββββββ β
β β β β β
β βββββββββββββββββββ΄βββββββββββββββββββββββ β
β β β
β WalletContext β
β (Freighter API) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
β (sign transactions)
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Stellar RPC Endpoint β
β (Testnet: https://soroban-testnet.stellar.org) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
β (invoke contracts)
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Soroban Smart Contracts β
β ββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββββββ β
β β Crowdfund Contract β β Registry Contract β β
β β - initialize() β β - register(campaign_id) β β
β β - contribute() β β - list_campaigns() β β
β β - withdraw() β β - get_campaign_count() β β
β β - refund_single() β β β β
β β - get_stats() β β β β
β ββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
β (store state)
βΌ
Stellar Ledger
- A creator deploys and initializes a campaign with a funding goal, deadline, and minimum contribution.
- Contributors pledge tokens before the deadline.
- If the goal is met by the deadline, the creator withdraws the funds (minus an optional platform fee).
- If the goal is not met, each contributor individually claims their refund via a pull-based model.
Fund-My-Cause/
βββ apps/
β βββ interface/ # Next.js 16 frontend (TypeScript + Tailwind)
β βββ src/
β β βββ app/ # Next.js App Router pages & layouts
β β βββ components/ # UI components (Navbar, ProgressBar, PledgeModal, etc.)
β β βββ context/ # WalletContext (Freighter wallet integration)
β β βββ lib/ # Soroban contract client helpers
β β βββ types/ # Shared TypeScript types
β βββ package.json
βββ contracts/
β βββ crowdfund/ # Soroban smart contract (Rust)
β βββ src/
β β βββ lib.rs # Core contract logic
β βββ Cargo.toml
β βββ registry/ # Soroban registry contract for campaign discovery
β βββ src/
β β βββ lib.rs # register/list campaign contract IDs
β βββ Cargo.toml
βββ scripts/
β βββ deploy.sh # Automated deploy + initialize script
βββ .github/
β βββ workflows/
β βββ rust_ci.yml # CI: build WASM + run tests
βββ Cargo.toml # Rust workspace config
βββ package.json # Node workspace config
βββ README.md
The Soroban contract lives in contracts/crowdfund/src/lib.rs and exposes the following interface:
| Function | Description |
|---|---|
initialize(creator, token, goal, deadline, min_contribution, title, description, social_links, platform_config) |
Create a new campaign |
contribute(contributor, amount) |
Pledge tokens before the deadline |
update_metadata(title, description, social_links) |
Update campaign metadata if status is Active |
withdraw() |
Creator claims funds after a successful campaign |
refund_single(contributor) |
Contributor claims their own refund if goal not met |
get_stats() |
Returns CampaignStats (total raised, progress bps, contributor count, etc.) |
total_raised() |
Current total raised |
goal() |
Campaign funding goal |
deadline() |
Campaign deadline (ledger timestamp) |
contribution(contributor) |
Contribution amount for a specific address |
min_contribution() |
Minimum allowed contribution |
title() / description() |
Campaign metadata |
social_links() |
Campaign social URLs |
version() |
Contract version number |
Rather than a single transaction refunding all contributors (which would fail at scale), each contributor calls refund_single to claim their own refund. This is gas-efficient, scalable, and avoids a single point of failure.
An optional PlatformConfig can be set at initialization with a fee in basis points (e.g. 250 = 2.5%). The fee is deducted from the creator's payout on withdrawal and sent to the platform address.
The interface is a Next.js 16 app using the App Router, Tailwind CSS v4, and Freighter wallet integration.
Key components:
Navbarβ wallet connect/disconnect via FreighterProgressBarβ visual funding progressCountdownTimerβ live countdown to campaign deadlinePledgeModalβ contribution flow with wallet auth
The app uses @stellar/freighter-api for wallet connectivity. The WalletContext provider wraps the app and exposes connect, disconnect, address, and signTx.
| Requirement | Version | Installation |
|---|---|---|
| Rust | 1.70+ | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh |
| wasm32 target | - | rustup target add wasm32-unknown-unknown |
| Stellar CLI | 21.0+ | Installation Guide |
| Requirement | Version | Installation |
|---|---|---|
| Node.js | 18+ | nodejs.org |
| npm | 9+ | Included with Node.js |
| Freighter | Latest | freighter.app |
- Docker (for containerized deployment)
- GitHub CLI (for release automation)
git clone https://github.com/Fund-My-Cause/Fund-My-Cause.git
cd Fund-My-Cause# Build WASM
cargo build --release --target wasm32-unknown-unknown
# Run tests
cargo test --workspaceDEADLINE=$(date -d "+30 days" +%s)
./scripts/deploy.sh <CREATOR_ADDRESS> <TOKEN_ADDRESS> 1000 $DEADLINE 10 "My Campaign" "A great cause" null [REGISTRY_CONTRACT_ID]If REGISTRY_CONTRACT_ID is omitted, the script deploys a new registry contract.
After campaign initialization, the script calls registry.register(campaign_id) automatically.
Save the printed Contract ID and Registry ID β you'll need them in frontend config.
Create apps/interface/.env.local:
NEXT_PUBLIC_CROWDFUND_CONTRACT_ID=<CONTRACT_ID>
NEXT_PUBLIC_REGISTRY_CONTRACT_ID=<REGISTRY_ID>
NEXT_PUBLIC_SOROBAN_RPC_URL=https://soroban-testnet.stellar.org
NEXT_PUBLIC_NETWORK_PASSPHRASE=Test SDF Network ; September 2015cd apps/interface
npm install
npm run devOpen http://localhost:3000.
# Copy and fill in your env vars
cp apps/interface/.env.example apps/interface/.env.local
# Build and start
docker compose up --buildThe app will be available at http://localhost:3000.
docker build -f apps/interface/Dockerfile -t fund-my-cause .
docker run -p 3000:3000 --env-file apps/interface/.env.local fund-my-causeThe Dockerfile uses a multi-stage build:
builderβ installs deps and builds Next.js withoutput: 'standalone'runnerβ copies only the standalone output for a minimal production image
GitHub Actions workflows:
rust_ci.ymlβ builds WASM + runs Rust tests on push/PR tomainfrontend_ci.ymlβ lints and typechecks the frontend on push/PR tomainplaywright.ymlβ runs Playwright E2E tests on PRs targetingmaindeploy-testnet.ymlβ deploys contracts to Stellar testnet on push todevelop
Dependabot is configured to keep npm, Cargo, and GitHub Actions dependencies up to date weekly.
The frontend enforces a minimum 80% coverage threshold across all metrics (statements, branches, functions, lines) via Jest. The build fails if any metric drops below this floor.
Run coverage locally:
cd apps/interface
npm run test:coverageThresholds are configured in apps/interface/jest.config.js under coverageThreshold.global.
- Fork the repo
- Create a feature branch:
git checkout -b feat/my-feature - Commit with conventional commits:
git commit -m "feat: add X" - Open a pull request
MIT β see LICENSE.
Cargo.lock is committed to this repository intentionally.
For application binaries and smart contracts, locking every transitive dependency to an exact version is a security requirement β not optional. Without it:
- A
cargo buildon a different machine or at a later date may silently pull in a newer (potentially compromised or breaking) version of any dependency. - Audits and vulnerability scans target specific versions; a floating lock file makes those results meaningless.
- Soroban WASM bytecode must be byte-for-byte reproducible so that on-chain contract hashes can be independently verified.
This follows the Cargo book's recommendation for binaries and aligns with Rust smart contract best practices.
Fund-My-Cause is powered by the Stellar network and Soroban smart contracts. Stellar provides fast, low-cost transactions with 5-second finality, making it ideal for crowdfunding at scale.