A complete decentralized crowdfunding platform built with ink! smart contracts and a modern React frontend. InkFundMe allows users to create fundraising campaigns, contribute to causes they care about, and manage funds transparently on the blockchain.
- Frontend: https://inkfundme-tutorial.vercel.app
- Tutorial: See TUTORIAL.md for a complete step-by-step guide
- ERC20 Token Integration: Custom INKFUNDME token for all transactions
- Campaign Management: Create, fund, and finalize fundraising campaigns
- Faucet Functionality: Free token minting for testing and onboarding
- Automatic Refund System: Contributors get refunds if campaigns fail
- Transparent Event Logging: All activities tracked on-chain
- Modern React UI: Built with Vite, TypeScript, and Tailwind CSS
- PAPI Integration: Seamless blockchain interaction with Polkadot API
- ReactiveDOT: Real-time updates and reactive state management
- Account Mapping: Automatic SS58 to EVM address conversion
- Responsive Design: Works perfectly on desktop and mobile
- Toast Notifications: Real-time feedback for all user actions
- Loading States: Comprehensive UX with loading indicators
-inkfundme-tutorial/
βββ contracts/ # ink! Smart Contracts
β βββ src/
β β βββ token/ # ERC20 token contract
β β βββ inkfundme/ # Main crowdfunding contract
β βββ Makefile # Build and deployment scripts
β βββ target/ # Compiled contracts
βββ frontend/ # React Frontend
β βββ src/
β β βββ components/ # UI components
β β βββ hooks/ # Custom React hooks
β β βββ pages/ # Route components
β β βββ lib/ # Utilities and contracts
β βββ .papi/ # Generated PAPI descriptors
βββ TUTORIAL.md # Complete tutorial guide
- Token Contract (
contracts/src/token/): ERC20 token with minting capabilities - InkFundMe Contract (
contracts/src/inkfundme/): Main crowdfunding logic
- React 19 with TypeScript for type safety
- Vite for fast development and building
- PAPI for blockchain interaction
- ReactiveDOT for reactive state management
- Tailwind CSS for styling
- shadcn/ui for component library
pub struct Campaign {
pub id: u32, // Unique campaign identifier
pub title: String, // Campaign title
pub description: String, // Campaign description
pub goal: U256, // Fundraising goal in tokens
pub deadline: u64, // Campaign deadline (timestamp)
pub owner: Address, // Campaign creator address
pub raised: U256, // Amount raised so far
pub completed: bool, // Whether campaign is finalized
}Creates a new InkFundMe contract and deploys an ERC20 token with the specified initial supply.
Creates a new InkFundMe contract using an existing ERC20 token.
Public faucet function - Mints tokens for free to any address. Useful for testing and tutorials.
- No restrictions on who can call this function
- No supply cap for simplicity
- Calls the ERC20 contract's mint function via cross-contract call
Creates a new fundraising campaign.
- Returns the campaign ID
- Validates that goal > 0 and deadline is in the future
- Emits
CampaignCreatedevent
Contribute tokens to a campaign.
- Transfers tokens from contributor to contract using
transfer_from - Updates campaign raised amount and contributor tracking
- Fails if deadline passed or campaign completed
- Emits
ContributionMadeevent
Finalizes a campaign after the deadline.
- If goal met: transfers raised funds to campaign owner
- If goal not met: marks campaign as failed (enables refunds)
- Can only be called after deadline
- Emits
CampaignFinalizedevent
Allows contributors to claim refunds from failed campaigns.
- Only works for completed campaigns that didn't meet their goal
- Transfers contributor's tokens back to them
- Removes contribution from tracking
- Emits
RefundClaimedevent
Returns campaign details by ID.
Returns all campaigns.
Returns the amount a specific contributor has contributed to a campaign.
Returns the address of the ERC20 token contract.
Returns the total number of campaigns created.
CampaignCreated {
id: u32,
owner: Address,
goal: U256,
deadline: u64,
}ContributionMade {
campaign_id: u32,
contributor: Address,
amount: U256,
}CampaignFinalized {
campaign_id: u32,
success: bool,
}RefundClaimed {
campaign_id: u32,
contributor: Address,
amount: U256,
}The contract includes comprehensive error handling:
CampaignNotFound: Campaign ID doesn't existDeadlineNotReached: Trying to finalize before deadlineDeadlineReached: Trying to contribute after deadlineCampaignCompleted: Trying to interact with finalized campaignGoalNotMet: Trying to claim refund from successful campaignNoContribution: No contribution found for refundOnlyOwner: Unauthorized access (if applicable)TokenError: ERC20 operation failedInvalidParameters: Invalid input parameters
git clone https://github.com/truthixify/inkfundme.git
cd inkfundme-tutorialcd contracts
# Build contracts
make build
# OR: cargo contract build --manifest-path ./src/token/Cargo.toml
# cargo contract build --manifest-path ./src/inkfundme/Cargo.toml
# Configure for Passet Hub testnet
echo 'ACCOUNT_URI=your-seed-phrase' > .env
echo 'CHAIN=wss://testnet-passet-hub.polkadot.io' >> .env
# Deploy contracts
make instantiate-token
make instantiate-inkfundme
# OR use the full cargo contract instantiate commands (see TUTORIAL.md)cd frontend
# Install dependencies
npm install
# Generate contract descriptors
npm run codegen
# Update contract addresses in src/lib/constants.ts
# TOKEN_ADDRESS = "your-deployed-token-address"
# INK_FUND_ME_ADDRESS = "your-deployed-inkfundme-address"
# Start development server
npm run devcd frontend
npm run build
vercelπ For detailed instructions, see TUTORIAL.md
- Connect Wallet: Use Polkadot.js extension
- Map Account: One-time setup for contract interaction
- Create Campaign: Set title, description, goal, and deadline
- Share Campaign: Get contributions from supporters
- Finalize: After deadline, claim funds if goal is met
- Browse Campaigns: Explore active fundraising campaigns
- Get Test Tokens: Use the built-in faucet for testing
- Contribute: Support campaigns you believe in
- Track Progress: See real-time funding progress
- Get Refunds: Automatic refunds if campaigns fail
- Success: If goal is reached, funds go to campaign creator
- Failure: If goal isn't reached, contributors can claim full refunds
- Transparency: All transactions and events are recorded on-chain
- Security: Built-in protections against common vulnerabilities
- Reentrancy: The contract uses cross-contract calls but follows checks-effects-interactions pattern
- Integer Overflow: Uses checked arithmetic operations where needed
- Access Control: Faucet is intentionally public for tutorial purposes
- Deadline Validation: Ensures deadlines are in the future when creating campaigns
- State Validation: Comprehensive checks before state changes
cd contracts
cargo test --manifest-path src/inkfundme/Cargo.toml
cargo test --manifest-path src/token/Cargo.tomlcd frontend
npm run build # Verify build works
npm run preview # Test production build locally- ink!: Smart contract development framework
- PAPI: Polkadot API for blockchain interaction
- ReactiveDOT: Reactive state management for Substrate
- TypeScript: Type-safe development
- Tailwind CSS: Utility-first CSS framework
- Type-Safe Contract Interaction: Generated TypeScript bindings from ink! contracts
- Real-Time Updates: Reactive UI that updates with blockchain state
- Error Handling: Comprehensive error handling and user feedback
- Mobile Responsive: Works seamlessly across all devices
- Production Ready: Deployed and tested on Passet Hub testnet
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Commit changes:
git commit -m 'Add amazing feature' - Push to branch:
git push origin feature/amazing-feature - Open a Pull Request
- Complete Tutorial - Step-by-step guide to building this dApp
- ink! Documentation
- PAPI Documentation
- ReactiveDOT
- Live Demo
This project is licensed under the MIT License - see the LICENSE file for details.
Built with β€οΈ using ink!, PAPI, and ReactiveDOT