Solidity ^0.8.20
Crowdfund contract
Production-ready crowdfunding contract used by this dApp. Deploy to a testnet (Sepolia, Base Sepolia, etc.), then point the frontend ABI at the address.
Name
Crowdfund
License
MIT
Demo address
0xOpenFund0000000000000000000000000001
Interface
Public surface
createCampaign
title, desc, goal, duration
contribute
id (payable)
claim
id
refund
id
getCampaign
id → view
campaignCount
→ view
Crowdfund.sol
Full source — copy into Remix or Foundry
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @title Crowdfund
* @notice Simple on-chain crowdfunding: create campaigns, contribute ETH,
* claim when the goal is met, or refund if the deadline passes short.
* @dev Demo contract for the OpenFund dApp. Deploy to any EVM testnet.
*/
contract Crowdfund {
struct Campaign {
address creator;
string title;
string description;
uint256 goal; // wei
uint256 raised; // wei
uint256 deadline; // unix timestamp
bool claimed;
bool exists;
}
uint256 public campaignCount;
mapping(uint256 => Campaign) public campaigns;
mapping(uint256 => mapping(address => uint256)) public contributions;
event CampaignCreated(
uint256 indexed id,
address indexed creator,
string title,
uint256 goal,
uint256 deadline
);
event Contributed(
uint256 indexed id,
address indexed contributor,
uint256 amount
);
event Claimed(uint256 indexed id, address indexed creator, uint256 amount);
event Refunded(uint256 indexed id, address indexed contributor, uint256 amount);
error EmptyTitle();
error InvalidGoal();
error InvalidDeadline();
error CampaignNotFound();
error CampaignEnded();
error CampaignActive();
error GoalNotMet();
error GoalMet();
error AlreadyClaimed();
error NothingToRefund();
error ZeroContribution();
error NotCreator();
error TransferFailed();
function createCampaign(
string calldata title,
string calldata description,
uint256 goal,
uint256 durationSeconds
) external returns (uint256 id) {
if (bytes(title).length == 0) revert EmptyTitle();
if (goal == 0) revert InvalidGoal();
if (durationSeconds < 60) revert InvalidDeadline();
id = campaignCount;
campaignCount = id + 1;
uint256 deadline = block.timestamp + durationSeconds;
campaigns[id] = Campaign({
creator: msg.sender,
title: title,
description: description,
goal: goal,
raised: 0,
deadline: deadline,
claimed: false,
exists: true
});
emit CampaignCreated(id, msg.sender, title, goal, deadline);
}
function contribute(uint256 id) external payable {
Campaign storage c = campaigns[id];
if (!c.exists) revert CampaignNotFound();
if (block.timestamp >= c.deadline) revert CampaignEnded();
if (msg.value == 0) revert ZeroContribution();
c.raised += msg.value;
contributions[id][msg.sender] += msg.value;
emit Contributed(id, msg.sender, msg.value);
}
function claim(uint256 id) external {
Campaign storage c = campaigns[id];
if (!c.exists) revert CampaignNotFound();
if (msg.sender != c.creator) revert NotCreator();
if (c.claimed) revert AlreadyClaimed();
if (c.raised < c.goal) revert GoalNotMet();
c.claimed = true;
uint256 amount = c.raised;
(bool ok, ) = payable(c.creator).call{value: amount}("");
if (!ok) revert TransferFailed();
emit Claimed(id, c.creator, amount);
}
function refund(uint256 id) external {
Campaign storage c = campaigns[id];
if (!c.exists) revert CampaignNotFound();
if (block.timestamp < c.deadline) revert CampaignActive();
if (c.raised >= c.goal) revert GoalMet();
if (c.claimed) revert AlreadyClaimed();
uint256 amount = contributions[id][msg.sender];
if (amount == 0) revert NothingToRefund();
contributions[id][msg.sender] = 0;
c.raised -= amount;
(bool ok, ) = payable(msg.sender).call{value: amount}("");
if (!ok) revert TransferFailed();
emit Refunded(id, msg.sender, amount);
}
function getCampaign(uint256 id)
external
view
returns (
address creator,
string memory title,
string memory description,
uint256 goal,
uint256 raised,
uint256 deadline,
bool claimed,
bool exists
)
{
Campaign storage c = campaigns[id];
return (
c.creator,
c.title,
c.description,
c.goal,
c.raised,
c.deadline,
c.claimed,
c.exists
);
}
}
ABI (JSON)
Minimal ABI for frontend binding
[
{
"type": "function",
"name": "createCampaign",
"stateMutability": "nonpayable",
"inputs": [
{
"name": "title",
"type": "string"
},
{
"name": "description",
"type": "string"
},
{
"name": "goal",
"type": "uint256"
},
{
"name": "durationSeconds",
"type": "uint256"
}
],
"outputs": [
{
"name": "id",
"type": "uint256"
}
]
},
{
"type": "function",
"name": "contribute",
"stateMutability": "payable",
"inputs": [
{
"name": "id",
"type": "uint256"
}
],
"outputs": []
},
{
"type": "function",
"name": "claim",
"stateMutability": "nonpayable",
"inputs": [
{
"name": "id",
"type": "uint256"
}
],
"outputs": []
},
{
"type": "function",
"name": "refund",
"stateMutability": "nonpayable",
"inputs": [
{
"name": "id",
"type": "uint256"
}
],
"outputs": []
},
{
"type": "function",
"name": "campaignCount",
"stateMutability": "view",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256"
}
]
},
{
"type": "function",
"name": "getCampaign",
"stateMutability": "view",
"inputs": [
{
"name": "id",
"type": "uint256"
}
],
"outputs": [
{
"name": "creator",
"type": "address"
},
{
"name": "title",
"type": "string"
},
{
"name": "description",
"type": "string"
},
{
"name": "goal",
"type": "uint256"
},
{
"name": "raised",
"type": "uint256"
},
{
"name": "deadline",
"type": "uint256"
},
{
"name": "claimed",
"type": "bool"
},
{
"name": "exists",
"type": "bool"
}
]
},
{
"type": "function",
"name": "contributions",
"stateMutability": "view",
"inputs": [
{
"name": "",
"type": "uint256"
},
{
"name": "",
"type": "address"
}
],
"outputs": [
{
"name": "",
"type": "uint256"
}
]
},
{
"type": "event",
"name": "CampaignCreated",
"inputs": [
{
"name": "id",
"type": "uint256",
"indexed": true
},
{
"name": "creator",
"type": "address",
"indexed": true
},
{
"name": "title",
"type": "string",
"indexed": false
},
{
"name": "goal",
"type": "uint256",
"indexed": false
},
{
"name": "deadline",
"type": "uint256",
"indexed": false
}
]
},
{
"type": "event",
"name": "Contributed",
"inputs": [
{
"name": "id",
"type": "uint256",
"indexed": true
},
{
"name": "contributor",
"type": "address",
"indexed": true
},
{
"name": "amount",
"type": "uint256",
"indexed": false
}
]
},
{
"type": "event",
"name": "Claimed",
"inputs": [
{
"name": "id",
"type": "uint256",
"indexed": true
},
{
"name": "creator",
"type": "address",
"indexed": true
},
{
"name": "amount",
"type": "uint256",
"indexed": false
}
]
},
{
"type": "event",
"name": "Refunded",
"inputs": [
{
"name": "id",
"type": "uint256",
"indexed": true
},
{
"name": "contributor",
"type": "address",
"indexed": true
},
{
"name": "amount",
"type": "uint256",
"indexed": false
}
]
}
]Deploy checklist
1Open Remix, paste Crowdfund.sol, compile with 0.8.20+.
2Deploy to a testnet with a funded wallet (MetaMask).
3Set the deployed address in the dApp config and wire CROWDFUND_ABI via viem/ethers.
4Call createCampaign / contribute / claim / refund from this UI against the live chain.