Contract Name:
Crowdfunding
Contract Source Code:
File 1 of 1 : Crowdfunding
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Crowdfunding {
string public name;
string public description;
uint256 public goal;
uint256 public deadline;
address public owner;
address public feeRecipient; // Address where platform fees are sent
bool public paused;
uint256 public constant FEE_PERCENTAGE = 169; // Represents 1.69%, in basis points (1.69 * 100)
enum CampaignState { Active, Successful, Failed }
CampaignState public state;
struct Tier {
string name;
uint256 amount;
uint256 backers;
}
struct Backer {
uint256 totalContribution;
mapping(uint256 => bool) fundedTiers;
}
Tier[] public tiers;
mapping(address => Backer) public backers;
modifier onlyOwner() {
require(msg.sender == owner, "Not the owner");
_;
}
modifier campaignOpen() {
require(state == CampaignState.Active, "Campaign is not active.");
_;
}
modifier notPaused() {
require(!paused, "Contract is paused.");
_;
}
/**
* @notice Initializes a new crowdfunding campaign.
* @param _owner The creator of the campaign.
* @param _feeRecipient The address where platform fees are sent.
* @param _name The name of the campaign.
* @param _description The description of the campaign.
* @param _goal The fundraising goal in wei.
* @param _durationInDays Duration of the campaign in days.
*/
constructor(
address _owner,
address _feeRecipient,
string memory _name,
string memory _description,
uint256 _goal,
uint256 _durationInDays
) {
name = _name;
description = _description;
goal = _goal;
deadline = block.timestamp + (_durationInDays * 1 days);
owner = _owner;
feeRecipient = _feeRecipient;
state = CampaignState.Active;
}
/**
* @notice Checks and updates the campaign state based on goal and deadline.
*/
function checkAndUpdateCampaignState() internal {
if (state == CampaignState.Active) {
if (block.timestamp >= deadline) {
state = address(this).balance >= goal ? CampaignState.Successful : CampaignState.Failed;
} else {
state = address(this).balance >= goal ? CampaignState.Successful : CampaignState.Active;
}
}
}
/**
* @notice Allows the campaign owner to withdraw funds if the campaign is successful.
*/
function withdraw() public onlyOwner {
checkAndUpdateCampaignState();
require(state == CampaignState.Successful, "Campaign not successful.");
uint256 balance = address(this).balance;
require(balance > 0, "No balance to withdraw");
uint256 fee = (balance * FEE_PERCENTAGE) / 10000; // Calculate 1.69% fee
uint256 finalAmount = balance - fee;
payable(feeRecipient).transfer(fee); // Transfer fee to feeRecipient
payable(owner).transfer(finalAmount); // Transfer remaining balance to campaign owner
}
/**
* @notice Allows contributors to receive a refund if the campaign failed.
*/
function refund() public {
checkAndUpdateCampaignState();
require(state == CampaignState.Failed, "Refunds not available.");
uint256 amount = backers[msg.sender].totalContribution;
require(amount > 0, "No contribution to refund");
backers[msg.sender].totalContribution = 0;
payable(msg.sender).transfer(amount);
}
/**
* @notice Allows users to contribute to a specific tier.
* @param _tierIndex The index of the tier to contribute to.
*/
function fund(uint256 _tierIndex) public payable campaignOpen notPaused {
require(_tierIndex < tiers.length, "Invalid tier.");
require(msg.value == tiers[_tierIndex].amount, "Incorrect amount.");
tiers[_tierIndex].backers++;
backers[msg.sender].totalContribution += msg.value;
backers[msg.sender].fundedTiers[_tierIndex] = true;
checkAndUpdateCampaignState();
}
/**
* @notice Adds a new funding tier.
* @param _name The name of the tier.
* @param _amount The amount required for the tier.
*/
function addTier(
string memory _name,
uint256 _amount
) public onlyOwner {
require(_amount > 0, "Amount must be greater than 0.");
tiers.push(Tier(_name, _amount, 0));
}
/**
* @notice Removes a specific tier by index.
* @param _index The index of the tier to remove.
*/
function removeTier(uint256 _index) public onlyOwner {
require(_index < tiers.length, "Tier does not exist.");
tiers[_index] = tiers[tiers.length - 1];
tiers.pop();
}
/**
* @notice Retrieves the current campaign balance.
* @return The current balance in wei.
*/
function getContractBalance() public view returns (uint256) {
return address(this).balance;
}
/**
* @notice Checks if a backer has funded a specific tier.
* @param _backer The address of the backer.
* @param _tierIndex The index of the tier.
* @return True if the backer funded the tier, otherwise false.
*/
function hasFundedTier(address _backer, uint256 _tierIndex) public view returns (bool) {
return backers[_backer].fundedTiers[_tierIndex];
}
/**
* @notice Retrieves all funding tiers.
* @return List of all tiers.
*/
function getTiers() public view returns (Tier[] memory) {
return tiers;
}
/**
* @notice Toggles the paused state of the contract.
*/
function togglePause() public onlyOwner {
paused = !paused;
}
/**
* @notice Retrieves the current campaign status.
* @return The current campaign state.
*/
function getCampaignStatus() public view returns (CampaignState) {
if (state == CampaignState.Active && block.timestamp > deadline) {
return address(this).balance >= goal ? CampaignState.Successful : CampaignState.Failed;
}
return state;
}
/**
* @notice Extends the campaign deadline.
* @param _daysToAdd Number of days to extend.
*/
function extendDeadline(uint256 _daysToAdd) public onlyOwner campaignOpen {
deadline += _daysToAdd * 1 days;
}
}