A trustless, transparent, and decentralized crowdfunding platform built on blockchain technology. Every donation is verifiable, every transaction immutable.
Overview · Architecture · Smart Contract · Frontend · Getting Started · Flows
- Overview
- Architecture
- Smart Contract
- Frontend Structure
- User Flows
- Key Features
- Tech Stack
- Getting Started
- Environment Variables
- Contract Deployment
- Pages & Components
- State Management
- Security Model
CrowdFunding is a fully decentralized crowdfunding application that removes intermediaries between campaign creators and donors. Built on the Arbitrum network, it leverages smart contracts for:
- Transparent fund management — all transactions are publicly verifiable on-chain
- Trustless donations — funds go directly to campaign owners via smart contract
- Admin-governed moderation — campaigns require approval before going live
- Immutable records — campaign history and donation data cannot be altered
┌─────────────────────────────────────────────────────────────────┐
│ CLIENT (Browser) │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ React + Vite │ │ TailwindCSS │ │ React Router v6 │ │
│ └──────────────┘ └──────────────┘ └──────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ StateContext (Global State) │ │
│ │ address │ contract │ campaigns │ donations │ isAdmin │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Thirdweb SDK (@thirdweb-dev/react) │ │
│ │ Wallet Connection │ Contract Interaction │ │
│ └─────────────────────────────────────────────────────────┘ │
└───────────────────────────────┬─────────────────────────────────┘
│ JSON-RPC / WebSocket
▼
┌─────────────────────────────────────────────────────────────────┐
│ ARBITRUM NETWORK (L2) │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ CrowdFunding.sol (Smart Contract) │ │
│ │ │ │
│ │ createCampaign() │ donateToCampaign() │ │
│ │ approveCampaign() │ rejectCampaign() │ │
│ │ getCampaigns() │ getDonators() │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ Contract Address: 0x642a4ebf0f280e1a107eab1ab86bc21ac1815dfc │
└─────────────────────────────────────────────────────────────────┘
File: web3/contracts/CroudFunding.sol
Standard: Solidity ^0.8.20 with OpenZeppelin AccessControl
CrowdFunding (inherits AccessControl)
│
├── Roles
│ ├── DEFAULT_ADMIN_ROLE → Super admin (deployer)
│ └── ADMIN_ROLE → Can approve/reject campaigns
│
├── Data
│ ├── Campaign (struct)
│ │ ├── owner address
│ │ ├── title string
│ │ ├── description string
│ │ ├── target uint256 (wei)
│ │ ├── deadline uint256 (unix timestamp)
│ │ ├── amountCollected uint256 (wei)
│ │ ├── image string (URL)
│ │ ├── donators address[]
│ │ ├── donations uint256[]
│ │ ├── status CampaignStatus (0=Pending, 1=Approved, 2=Rejected)
│ │ └── rejectionReason string
│ │
│ └── campaigns mapping(uint256 => Campaign)
│
├── Functions (Public)
│ ├── createCampaign() → Creates campaign in Pending state
│ ├── donateToCampaign() → Sends ETH to campaign owner (payable)
│ ├── getDonators() → Returns donators + amounts for a campaign
│ └── getCampaigns() → Returns all campaigns array
│
├── Functions (Admin Only)
│ ├── approveCampaign() → Sets status to Approved
│ ├── rejectCampaign() → Sets status to Rejected with reason
│ ├── grantAdminRole() → Grants ADMIN_ROLE to an address
│ └── revokeAdminRole() → Revokes ADMIN_ROLE from an address
│
└── Events
├── CampaignCreated(campaignId, owner)
├── CampaignApproved(campaignId)
├── CampaignRejected(campaignId, reason)
└── DonationReceived(campaignId, donor, amount)
┌─────────────┐
createCampaign │ │
────────────────▶│ PENDING │
│ (status=0)│
└──────┬──────┘
│
┌───────────┴───────────┐
│ │
approve│ reject│ + reason
▼ ▼
┌──────────┐ ┌──────────────┐
│ APPROVED │ │ REJECTED │
│(status=1)│ │ (status=2) │
└──────────┘ └──────────────┘
│
Accepts donations
(deadline must be
in the future)
Frontend/src/
│
├── @types/
│ └── vite-env.d.ts # Vite environment type declarations
│
├── assets/ # Images, icons, logos
│
├── components/
│ ├── CampaignFilters.tsx # Search + sort + category filters
│ ├── CampaignStats.tsx # Aggregate stats (total raised, backers, etc.)
│ ├── HeroSection.tsx # Landing page hero with CTAs
│ ├── Layout.tsx # Page layout wrapper with Navbar
│ ├── TopNavbar.tsx # Top navigation with wallet connect
│ ├── countBox.tsx # Single stat display box
│ ├── customButton.tsx # Reusable styled button
│ ├── displayCampaigns.tsx # Campaign grid with cards
│ ├── formField.tsx # Form input / textarea field
│ ├── loader.tsx # Full-screen transaction loading overlay
│ ├── navbar.tsx # Secondary navbar with search bar
│ └── sidebarIcon.tsx # Sidebar navigation icon
│
├── constants/
│ └── index.ts # Navigation links config
│
├── contexts/
│ └── index.tsx # Global StateContext + blockchain functions
│
├── pages/
│ ├── admin.tsx # Admin moderation dashboard
│ ├── campaignDetails.tsx # Individual campaign view + donation panel
│ ├── createCampaign.tsx # Campaign creation form
│ ├── home.tsx # Main campaigns listing page
│ ├── landing.tsx # Public landing page
│ └── profile.tsx # User's own campaigns
│
└── utils/
└── index.ts # daysLeft, calculateBarPercentage, checkIfImage
User Frontend Smart Contract Admin
│ │ │ │
│ Fill campaign form │ │ │
│─────────────────────────▶│ │ │
│ │ Validate image URL │ │
│ │ (checkIfImage util) │ │
│ │ │ │
│ │ createCampaign(args) │ │
│ │──────────────────────────▶│ │
│ │ │ Stores campaign │
│ │ │ status = PENDING │
│ │ ◀── tx confirmed ────────│ │
│ │ │ │
│ Redirect to /admin │ │ │
│◀────────────────────────│ │ │
│ │ │ │
│ │ │ Admin reviews │
│ │ │◀───────────────────│
│ │ │ │
│ │ │ approveCampaign() │
│ │ │◀───────────────────│
│ │ │ status = APPROVED │
│ │ │ │
│ Campaign visible on /home │ │
│◀───────────────────────────────────────────────────── │
Donor Frontend Smart Contract
│ │ │
│ Browse campaigns │ │
│───────────────────────▶│ │
│ │ getApprovedCampaigns() │
│ │───────────────────────────▶│
│ │◀─── campaigns array ───────│
│ │ │
│ Click campaign card │ │
│───────────────────────▶│ Navigate to /campaign-details/:title
│ │ │
│ getDonations(pId) │ │
│ │───────────────────────────▶│
│ │◀─── [donators, amounts] ───│
│ │ │
│ Enter donation amount │ │
│ Click "Donate Now" │ │
│───────────────────────▶│ │
│ │ donateToCampaign(pId) │
│ │ { value: ethers.parseEther(amount) }
│ │───────────────────────────▶│
│ │ │
│ │ ETH transferred
│ │ directly to campaign.owner
│ │ amountCollected += amount
│ │ │
│ │◀─── tx confirmed ─────────│
│ Donation list updates │ │
│◀───────────────────────│ │
Admin Frontend Blockchain
│ │ │
│ Connect wallet │ │
│───────────────────────▶│ │
│ │ hasRole(ADMIN_ROLE, addr)│
│ │──────────────────────────▶│
│ │◀─── true/false ───────────│
│ │ │
│ Navigate to /admin │ (redirects if not admin) │
│───────────────────────▶│ │
│ │ │
│ │ getPendingCampaigns() │
│ │──────────────────────────▶│
│ │◀─── pending[] ────────────│
│ │ │
│ View campaign details │ │
│ │ │
│ ┌─────────────────────────────────────────────┐ │
│ │ DECISION POINT │ │
│ │ │ │
│ │ [Approve] [Reject + Reason] │ │
│ └──────────┬──────────────────────┬───────────┘ │
│ │ │ │
│ ▼ ▼ │
│ approveCampaign(id) rejectCampaign(id, reason) │
│ │ │ │
│ └────────────┬─────────┘ │
│ │──────────────────────────▶│
│ │◀─── tx confirmed ─────────│
│ │ │
│ Data refreshes │ refreshCampaigns() │
│◀─────────────────────────│──────────────────────────▶│
User Thirdweb SDK MetaMask / Wallet
│ │ │
│ Click "Connect Wallet" │ │
│────────────────────────▶│ │
│ │ connect(metamaskWallet())│
│ │──────────────────────────▶│
│ │ Prompt user
│ │◀─── wallet address ───────│
│ │ │
│ address stored in │ │
│ StateContext │ │
│ │ │
│ useEffect triggers: │ │
│ ├─ checkAdminStatus() │ │
│ ├─ refreshCampaigns() │ │
│ └─ isAdmin flag set │ │
│ │ │
│ UI updates: │ │
│ ├─ Show "Create Campaign" button │
│ ├─ Show /admin link (if isAdmin) │
│ └─ Show wallet address in nav │
| Feature | Description |
|---|---|
| 🔐 Wallet Authentication | MetaMask/Web3 wallet connection via Thirdweb SDK |
| 📋 Campaign Moderation | Admin role-based approval/rejection before campaigns go live |
| 💸 Direct Donations | ETH sent directly to campaign owners — no escrow, no fees |
| 🔍 Search & Filter | Filter by category, sort by date/funding/deadline |
| 📊 Live Stats | Real-time aggregate stats — total raised, backers, active campaigns |
| ⏰ Urgency Indicators | "Ending Soon" and "Urgent" badges for campaigns near deadline |
| 👤 Profile Page | View all campaigns created by the connected wallet |
| 🛡 Admin Dashboard | Full overview table + pending review queue for admins |
| 📱 Responsive Design | Mobile-first UI with collapsible drawer navigation |
| 🔄 Campaign Caching | 5-second cache to reduce redundant RPC calls |
| Layer | Technology | Version |
|---|---|---|
| Framework | React | 18.2 |
| Language | TypeScript | 4.7 |
| Build Tool | Vite | 3.x |
| Styling | TailwindCSS | 3.4 |
| Routing | React Router DOM | 6.22 |
| Icons | Lucide React | 0.483 |
| Animations | Framer Motion | 12.x |
| Notifications | Sonner | 1.4 |
| Layer | Technology | Version |
|---|---|---|
| Network | Arbitrum (L2) | — |
| SDK | Thirdweb React | 4.x |
| Ethereum Library | ethers.js | 5.x |
| Smart Contract | Solidity | 0.8.20 |
| Access Control | OpenZeppelin | 5.3 |
| Dev Framework | Hardhat | 2.23 |
- Node.js
>= 18.0.0 - Yarn or npm
- MetaMask browser extension
- Arbitrum testnet/mainnet ETH
git clone <repository-url>
cd crowdfundingcd Frontend
yarn installCreate a .env file in the Frontend/ directory:
VITE_TEMPLATE_CLIENT_ID=your_thirdweb_client_idyarn devThe app will be available at http://localhost:5173
yarn buildyarn deploy| Variable | Description | Required |
|---|---|---|
VITE_TEMPLATE_CLIENT_ID |
Thirdweb API Client ID (get from thirdweb.com/dashboard) | ✅ Yes |
cd web3
yarn installPRIVATE_KEY=your_deployer_wallet_private_keyyarn deployThe deployed contract address must be updated in
Frontend/src/contexts/index.tsx:const { contract } = useContract("YOUR_CONTRACT_ADDRESS")
| Route | Component | Access | Description |
|---|---|---|---|
/ |
LandingPage |
Public | Hero section with CTAs and stats |
/home |
Home |
Public | Browse all approved campaigns |
/create-campaign |
CreateCampaign |
Wallet connected | Campaign creation form |
/campaign-details/:title |
CampaignDetails |
Public | Campaign info + donation panel |
/profile |
Profile |
Wallet connected | User's own campaigns |
/admin |
AdminDashboard |
Admin only | Moderation queue + overview |
| Component | Purpose |
|---|---|
DisplayCampaigns |
Renders campaign grid with filtering; shows active-only or all based on showAllCampaigns prop |
CampaignFilters |
Search input + expandable sort/category dropdowns |
CampaignStats |
4-stat summary bar (total raised, success rate, active campaigns, total backers) |
TopNavbar |
Persistent nav with wallet connect and mobile drawer |
Loader |
Full-screen overlay during blockchain transactions |
FormField |
Reusable input/textarea with consistent styling |
All global state is managed via React Context (StateContext):
StateContextType {
// Wallet
address: string | undefined
connect: (wallet) => void
disconnect: () => void
// Contract
contract: SmartContract | undefined
// Campaigns
getCampaigns: () => Promise<ParsedCampaign[]>
getApprovedCampaigns: () => Promise<ParsedCampaign[]>
getUserCampaigns: () => Promise<ParsedCampaign[]>
getPendingCampaigns: () => Promise<ParsedCampaign[]>
createCampaign: (form) => Promise<void>
refreshCampaigns: () => Promise<void>
// Donations
donate: (pId, amount) => Promise<any>
getDonations: (pId) => Promise<ParsedDonation[]>
// Admin
isAdmin: boolean
adminCheckCompleted: boolean
approveCampaign: (id) => Promise<void>
rejectCampaign: (id, reason) => Promise<void>
// UI
searchCampaign: string
setSearchCampaign: (value) => void
}Request for campaigns
│
▼
Cache age < 5 seconds?
│
YES │ NO
│ │
│ ▼
│ contract.call('getCampaigns')
│ │
│ ▼
│ Update allCampaignsCache
│ Update lastRefresh timestamp
│ │
└───┘
│
▼
Return cached data
DEFAULT_ADMIN_ROLE (deployer)
│
└── Can grant/revoke ADMIN_ROLE
└── Can approve/reject campaigns
ADMIN_ROLE
│
└── Can approve campaigns
└── Can reject campaigns (with reason)
└── Can grant ADMIN_ROLE to others
Any Wallet Address
│
└── Can create campaigns (requires connected wallet)
└── Can donate to APPROVED campaigns
└── Cannot approve/reject
- Donations only accepted on campaigns with
status == APPROVED - Donations only accepted before campaign
deadline - ETH transferred directly to
campaign.ownerviacall{value}— no platform custody - Failed transfers do not increment
amountCollected
- Admin dashboard checks
isAdminandadminCheckCompletedbefore rendering - Wallet connection required for campaign creation
- Campaign status badges shown on all campaign cards for full transparency
Campaign (from blockchain)
│
├── amountCollected / target → Progress bar % + "X% funded"
├── deadline (unix timestamp) → daysLeft() util → "N days left"
├── status (0/1/2) → Status badge (Pending/Active/Rejected)
├── donators.length → Backer count
├── owner address → Avatar initials + shortened address
└── image URL → Card hero image
- Fork the repository
- Create a feature branch:
git checkout -b feature/your-feature - Commit your changes:
git commit -m 'feat: add your feature' - Push to the branch:
git push origin feature/your-feature - Open a Pull Request
This project is open source. See LICENSE for details.
Built with ❤️ on Arbitrum · Powered by Thirdweb · Secured by OpenZeppelin