Skip to content
Contract · Payments

Crowdfunding

Time-boxed ETH fundraising. Contributors are refunded automatically if the soft cap is not met.

All templates
Fundraise
~1.4M gas
Ethereum
Post-deploy checklist
  1. Double-check soft/hard caps and deadline timezone (Unix UTC)
  2. Test contribute + refund paths on a testnet
  3. Publish the verified source so contributors can read the rules
  4. Plan who can withdraw if the soft cap is met
Configure & deploy

Minimum raise for success. Below this, contributors should be able to refund. 1e18 = 1 ETH.

Maximum ETH the contract will accept. Must be ≥ softCap.

Campaign end in Unix seconds (UTC). Use the Timestamp tool if you need conversion help.

Use cases
  • Community fundraising
  • Charity campaigns
  • Product presales
Crowdfunding.solsolidity
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0 (Wizard / Contracts MCP patterns)
// Compiled with Solidity ^0.8.24 — Ethereum Toolset browser solc
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

// Time-boxed ETH fundraising with refunds if soft cap is missed.
contract Crowdfunding is Ownable, ReentrancyGuard {
    uint256 public immutable softCap;
    uint256 public immutable hardCap;
    uint64 public immutable deadline;
    uint256 public totalRaised;
    mapping(address => uint256) public contributions;
    bool public finalized;

    error CampaignEnded();
    error HardCapReached();
    error NotEnded();
    error SoftCapMet();

    constructor(address initialOwner, uint256 _soft, uint256 _hard, uint64 _deadline)
        Ownable(initialOwner)
    {
        softCap = _soft; hardCap = _hard; deadline = _deadline;
    }

    receive() external payable {
        if (block.timestamp > deadline) revert CampaignEnded();
        if (totalRaised + msg.value > hardCap) revert HardCapReached();
        contributions[msg.sender] += msg.value;
        totalRaised += msg.value;
    }

    function withdraw() external onlyOwner {
        if (block.timestamp <= deadline) revert NotEnded();
        require(totalRaised >= softCap, "soft cap not met");
        finalized = true;
        payable(owner()).transfer(address(this).balance);
    }

    function refund() external nonReentrant {
        if (block.timestamp <= deadline) revert NotEnded();
        if (totalRaised >= softCap) revert SoftCapMet();
        uint256 amt = contributions[msg.sender];
        contributions[msg.sender] = 0;
        payable(msg.sender).transfer(amt);
    }
}