Skip to content

oscarj007/Fund-My-Cause

Β 
Β 

Repository files navigation

Fund-My-Cause

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.

CI Status License: MIT Contract Version Coverage


Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     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

How It Works

  1. A creator deploys and initializes a campaign with a funding goal, deadline, and minimum contribution.
  2. Contributors pledge tokens before the deadline.
  3. If the goal is met by the deadline, the creator withdraws the funds (minus an optional platform fee).
  4. If the goal is not met, each contributor individually claims their refund via a pull-based model.

Monorepo Structure

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

Smart Contract

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

Pull-based Refund Model

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.

Platform Fee

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.


Frontend

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 Freighter
  • ProgressBar β€” visual funding progress
  • CountdownTimer β€” live countdown to campaign deadline
  • PledgeModal β€” contribution flow with wallet auth

Wallet Integration

The app uses @stellar/freighter-api for wallet connectivity. The WalletContext provider wraps the app and exposes connect, disconnect, address, and signTx.


Prerequisites

Contracts

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

Frontend

Requirement Version Installation
Node.js 18+ nodejs.org
npm 9+ Included with Node.js
Freighter Latest freighter.app

Optional

  • Docker (for containerized deployment)
  • GitHub CLI (for release automation)

Getting Started

1. Clone

git clone https://github.com/Fund-My-Cause/Fund-My-Cause.git
cd Fund-My-Cause

2. Build & test the contract

# Build WASM
cargo build --release --target wasm32-unknown-unknown

# Run tests
cargo test --workspace

3. Deploy to testnet

DEADLINE=$(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.

4. Configure frontend environment

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 2015

5. Run the frontend

cd apps/interface
npm install
npm run dev

Open http://localhost:3000.


Docker

Run with Docker Compose (recommended for local dev)

# Copy and fill in your env vars
cp apps/interface/.env.example apps/interface/.env.local

# Build and start
docker compose up --build

The app will be available at http://localhost:3000.

Build the image manually

docker build -f apps/interface/Dockerfile -t fund-my-cause .
docker run -p 3000:3000 --env-file apps/interface/.env.local fund-my-cause

The Dockerfile uses a multi-stage build:

  1. builder β€” installs deps and builds Next.js with output: 'standalone'
  2. runner β€” copies only the standalone output for a minimal production image

CI/CD

GitHub Actions workflows:

  • rust_ci.yml β€” builds WASM + runs Rust tests on push/PR to main
  • frontend_ci.yml β€” lints and typechecks the frontend on push/PR to main
  • playwright.yml β€” runs Playwright E2E tests on PRs targeting main
  • deploy-testnet.yml β€” deploys contracts to Stellar testnet on push to develop

Dependabot is configured to keep npm, Cargo, and GitHub Actions dependencies up to date weekly.


Code Coverage

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:coverage

Thresholds are configured in apps/interface/jest.config.js under coverageThreshold.global.


Contributing

  1. Fork the repo
  2. Create a feature branch: git checkout -b feat/my-feature
  3. Commit with conventional commits: git commit -m "feat: add X"
  4. Open a pull request

License

MIT β€” see LICENSE.


Reproducible Builds & Cargo.lock

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 build on 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.


Built on Stellar

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.

About

No description, website, or topics provided.

Resources

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • TypeScript 77.2%
  • Rust 21.3%
  • Other 1.5%