Developing Smart Contracts VI On Base: A Crowd Fund Token.

prerequisites:-

  • What is a Crowd Fund Token?

  • Usecases of Crowd Fund Token.

  • Building a Crowd Fund Token.

  • Deployment on Base.

What is a Crowd Fund Token?

A Crowdfund Token is a specific type of digital token that is issued during a blockchain-based crowdfunding campaign. These tokens are typically created using smart contracts on decentralized platforms like Ethereum, Binance Smart Chain, or Solana, and they serve as a mechanism to raise capital from a large number of participants. In return for their contributions (usually in cryptocurrencies), contributors receive tokens that represent various forms of value within the project being funded.

Characteristics of Crowdfund Tokens:

  1. Decentralized Fundraising:

    • Crowdfunding campaigns using tokens are often conducted on decentralized platforms like Ethereum or Binance Smart Chain, which makes them accessible to participants globally without intermediaries.
  2. Programmable Rules:

    • Smart contracts ensure that the rules of the crowdfunding campaign are enforced, such as funding goals (if the campaign doesn't raise enough funds, contributors are refunded) or vesting periods for tokens.
  3. Transparency and Trust:

    • Blockchain-based crowdfund tokens provide transparency, as all transactions and token distributions are recorded on a public ledger. Contributors can trust that the project follows the campaign’s rules because of the immutable nature of smart contracts.

Use Cases of Crowd Fund Token.

  1. Access to Services

    Crowdfund tokens can serve as a gateway to access special features or services within a platform that is being developed. These platforms can range from decentralized apps (dApps) to games, marketplaces, or other blockchain-based services.

    Imagine a blockchain-based gaming platform launching a crowdfunding campaign. Contributors who purchase crowdfund tokens are given exclusive in-game assets, early access to new levels, or discounted transactions. Without these tokens, regular users may have limited or no access to these special features.

2. Governance

Crowdfund tokens can be designed to grant holders voting rights in decisions regarding the project’s development or management. This is a common feature in decentralized autonomous organizations (DAOs) where decisions about the protocol or future direction of the project are made through a democratic voting process involving token holders.

3. Profit Sharing

In some projects, crowdfund tokens can represent a share of future profits or ownership. These tokens function similarly to equity in traditional finance, where holders are entitled to a portion of the profits or dividends generated by the project once it becomes successful.

4. Liquidity

  • Crowdfund tokens are often designed to be tradable on secondary markets (such as cryptocurrency exchanges), offering contributors a way to sell their tokens at a later stage, ideally for a profit if the token’s value increases. This provides liquidity, meaning contributors aren’t locked into holding the tokens indefinitely—they can sell or trade them if they choose. For instance, After contributing to a successful crowdfunding campaign for a decentralized social media platform, the value of the tokens received has increased due to the platform’s growing popularity. Contributors can now sell their tokens on a cryptocurrency exchange for a higher price than what they initially paid, realizing a profit.

BUILDING A MULTI-SIG WALLET.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

interface IERC20 {
    function transfer(address, uint256) external returns (bool);
    function transferFrom(address, address, uint256) external returns (bool);
}

contract CrowdFund {
    event Launch(
        uint256 id,
        address indexed creator,
        uint256 goal,
        uint32 startAt,
        uint32 endAt
    );
    event Cancel(uint256 id);
    event Pledge(uint256 indexed id, address indexed caller, uint256 amount);
    event Unpledge(uint256 indexed id, address indexed caller, uint256 amount);
    event Claim(uint256 id);
    event Refund(uint256 id, address indexed caller, uint256 amount);

    struct Campaign {
        // Creator of campaign
        address creator;
        // Amount of tokens to raise
        uint256 goal;
        // Total amount pledged
        uint256 pledged;
        // Timestamp of start of campaign
        uint32 startAt;
        // Timestamp of end of campaign
        uint32 endAt;
        // True if goal was reached and creator has claimed the tokens.
        bool claimed;
    }

    IERC20 public immutable token;
    // Total count of campaigns created.
    // It is also used to generate id for new campaigns.
    uint256 public count;
    // Mapping from id to Campaign
    mapping(uint256 => Campaign) public campaigns;
    // Mapping from campaign id => pledger => amount pledged
    mapping(uint256 => mapping(address => uint256)) public pledgedAmount;

    constructor(address _token) {
        token = IERC20(_token);
    }

    function launch(uint256 _goal, uint32 _startAt, uint32 _endAt) external {
        require(_startAt >= block.timestamp, "start at < now");
        require(_endAt >= _startAt, "end at < start at");
        require(_endAt <= block.timestamp + 90 days, "end at > max duration");

        count += 1;
        campaigns[count] = Campaign({
            creator: msg.sender,
            goal: _goal,
            pledged: 0,
            startAt: _startAt,
            endAt: _endAt,
            claimed: false
        });

        emit Launch(count, msg.sender, _goal, _startAt, _endAt);
    }

    function cancel(uint256 _id) external {
        Campaign memory campaign = campaigns[_id];
        require(campaign.creator == msg.sender, "not creator");
        require(block.timestamp < campaign.startAt, "started");

        delete campaigns[_id];
        emit Cancel(_id);
    }

    function pledge(uint256 _id, uint256 _amount) external {
        Campaign storage campaign = campaigns[_id];
        require(block.timestamp >= campaign.startAt, "not started");
        require(block.timestamp <= campaign.endAt, "ended");

        campaign.pledged += _amount;
        pledgedAmount[_id][msg.sender] += _amount;
        token.transferFrom(msg.sender, address(this), _amount);

        emit Pledge(_id, msg.sender, _amount);
    }

    function unpledge(uint256 _id, uint256 _amount) external {
        Campaign storage campaign = campaigns[_id];
        require(block.timestamp <= campaign.endAt, "ended");

        campaign.pledged -= _amount;
        pledgedAmount[_id][msg.sender] -= _amount;
        token.transfer(msg.sender, _amount);

        emit Unpledge(_id, msg.sender, _amount);
    }

    function claim(uint256 _id) external {
        Campaign storage campaign = campaigns[_id];
        require(campaign.creator == msg.sender, "not creator");
        require(block.timestamp > campaign.endAt, "not ended");
        require(campaign.pledged >= campaign.goal, "pledged < goal");
        require(!campaign.claimed, "claimed");

        campaign.claimed = true;
        token.transfer(campaign.creator, campaign.pledged);

        emit Claim(_id);
    }

    function refund(uint256 _id) external {
        Campaign memory campaign = campaigns[_id];
        require(block.timestamp > campaign.endAt, "not ended");
        require(campaign.pledged < campaign.goal, "pledged >= goal");

        uint256 bal = pledgedAmount[_id][msg.sender];
        pledgedAmount[_id][msg.sender] = 0;
        token.transfer(msg.sender, bal);

        emit Refund(_id, msg.sender, bal);
    }
}

Interface Definition for ERC20 Token Interaction

solidityCopy codeinterface IERC20 {
    function transfer(address, uint256) external returns (bool);
    function transferFrom(address, address, uint256) external returns (bool);
}
  • IERC20: This interface allows the contract to interact with an ERC20 token, defining two methods:

    • transfer: Allows transferring a certain number of tokens to a specified address.

    • transferFrom: Allows transferring tokens from one address to another, given prior approval.

Contract Definition

solidityCopy codecontract CrowdFund {
  • CrowdFund: The main contract for managing crowdfunding campaigns.

Event Declarations

solidityCopy codeevent Launch(uint256 id, address indexed creator, uint256 goal, uint32 startAt, uint32 endAt);
event Cancel(uint256 id);
event Pledge(uint256 indexed id, address indexed caller, uint256 amount);
event Unpledge(uint256 indexed id, address indexed caller, uint256 amount);
event Claim(uint256 id);
event Refund(uint256 id, address indexed caller, uint256 amount);
  • Events: These events log important actions such as launching a campaign, canceling it, pledging funds, unpledging funds, claiming the raised funds, or refunding them.

    • indexed: Allows filtering of event logs by specific fields (e.g., creator, caller).

    • Example: Launch event logs the launch of a campaign with details like the ID, creator, goal, and campaign duration.

Campaign Data Structure (Struct)

solidityCopy codestruct Campaign {
    address creator;
    uint256 goal;
    uint256 pledged;
    uint32 startAt;
    uint32 endAt;
    bool claimed;
}
  • Campaign struct: This defines the properties of each crowdfunding campaign:

    • creator: The address of the campaign creator.

    • goal: The funding goal in tokens.

    • pledged: The total amount pledged so far.

    • startAt: When the campaign starts (timestamp).

    • endAt: When the campaign ends (timestamp).

    • claimed: A boolean to indicate if the funds have been claimed after the goal was reached.

State Variables

solidityCopy codeIERC20 public immutable token;
uint256 public count;
mapping(uint256 => Campaign) public campaigns;
mapping(uint256 => mapping(address => uint256)) public pledgedAmount;
  • token: The ERC20 token used for pledging, stored as an immutable variable (cannot be changed after deployment).

  • count: Tracks the total number of campaigns created. Also used to generate a unique ID for each campaign.

  • campaigns: A mapping to store campaigns by their ID.

  • pledgedAmount: A nested mapping that tracks how much each user has pledged to each campaign.

Constructor

solidityCopy codeconstructor(address _token) {
    token = IERC20(_token);
}
  • constructor: This function is called once when the contract is deployed. It initializes the token address to the ERC20 token the campaign will use for contributions.

Campaign Launching Function

solidityCopy codefunction launch(uint256 _goal, uint32 _startAt, uint32 _endAt) external {
    require(_startAt >= block.timestamp, "start at < now");
    require(_endAt >= _startAt, "end at < start at");
    require(_endAt <= block.timestamp + 90 days, "end at > max duration");

    count += 1;
    campaigns[count] = Campaign({
        creator: msg.sender,
        goal: _goal,
        pledged: 0,
        startAt: _startAt,
        endAt: _endAt,
        claimed: false
    });

    emit Launch(count, msg.sender, _goal, _startAt, _endAt);
}
  • launch: A function to create a new crowdfunding campaign.

    • Validation:

      • The start time must be greater than or equal to the current block timestamp.

      • The end time must be greater than or equal to the start time.

      • The campaign duration must be less than or equal to 90 days.

    • Campaign Creation: Increments the count to create a new campaign ID and stores the campaign details.

    • Emit Launch Event: Emits the Launch event with campaign details.

Cancel Campaign Function

solidityCopy codefunction cancel(uint256 _id) external {
    Campaign memory campaign = campaigns[_id];
    require(campaign.creator == msg.sender, "not creator");
    require(block.timestamp < campaign.startAt, "started");

    delete campaigns[_id];
    emit Cancel(_id);
}
  • cancel: A function that allows the creator to cancel their campaign if it hasn’t started yet.

    • Validation: Only the creator can cancel it, and the campaign must not have started.

    • Deletion: The campaign is deleted from storage.

    • Emit Cancel Event: Emits the Cancel event.

Pledge Function

solidityCopy codefunction pledge(uint256 _id, uint256 _amount) external {
    Campaign storage campaign = campaigns[_id];
    require(block.timestamp >= campaign.startAt, "not started");
    require(block.timestamp <= campaign.endAt, "ended");

    campaign.pledged += _amount;
    pledgedAmount[_id][msg.sender] += _amount;
    token.transferFrom(msg.sender, address(this), _amount);

    emit Pledge(_id, msg.sender, _amount);
}
  • pledge: Allows users to pledge funds to a specific campaign.

    • Validation: The campaign must have started but not ended yet.

    • Update: The total amount pledged to the campaign and the user's pledged amount are updated.

    • Token Transfer: The pledged tokens are transferred from the user to the contract.

    • Emit Pledge Event: Emits the Pledge event.

Unpledge Function

solidityCopy codefunction unpledge(uint256 _id, uint256 _amount) external {
    Campaign storage campaign = campaigns[_id];
    require(block.timestamp <= campaign.endAt, "ended");

    campaign.pledged -= _amount;
    pledgedAmount[_id][msg.sender] -= _amount;
    token.transfer(msg.sender, _amount);

    emit Unpledge(_id, msg.sender, _amount);
}
  • unpledge: Allows users to withdraw their pledged tokens before the campaign ends.

    • Validation: The campaign must not have ended yet.

    • Update: The total pledged amount and the user's pledged amount are decreased.

    • Token Transfer: The contract transfers the tokens back to the user.

    • Emit Unpledge Event: Emits the Unpledge event.

Claim Function

solidityCopy codefunction claim(uint256 _id) external {
    Campaign storage campaign = campaigns[_id];
    require(campaign.creator == msg.sender, "not creator");
    require(block.timestamp > campaign.endAt, "not ended");
    require(campaign.pledged >= campaign.goal, "pledged < goal");
    require(!campaign.claimed, "claimed");

    campaign.claimed = true;
    token.transfer(campaign.creator, campaign.pledged);

    emit Claim(_id);
}
  • claim: Allows the campaign creator to claim the funds if the campaign has ended and the goal has been reached.

    • Validation: The caller must be the creator, the campaign must have ended, the pledged amount must meet or exceed the goal, and the funds must not have been claimed yet.

    • Update: Marks the campaign as claimed.

    • Token Transfer: Transfers the pledged tokens to the campaign creator.

    • Emit Claim Event: Emits the Claim event.

Refund Function

solidityCopy codefunction refund(uint256 _id) external {
    Campaign memory campaign = campaigns[_id];
    require(block.timestamp > campaign.endAt, "not ended");
    require(campaign.pledged < campaign.goal, "pledged >= goal");

    uint256 bal = pledgedAmount[_id][msg.sender];
    pledgedAmount[_id][msg.sender] = 0;
    token.transfer(msg.sender, bal);

    emit Refund(_id, msg.sender, bal);
}
  • refund: Allows users to get a refund if the campaign failed to meet its goal.

    • Validation: The campaign must have ended, and the total pledged amount must be less than the goal.

    • Token Transfer: Transfers the user's pledged tokens back to them.

    • Emit Refund Event: Emits the Refund event.

DEPLOYMENT ON BASE.

Navigate to your metamask and add Base Sepolia Testnet to the list of networks. Click this to get some gas fees for deployment on base testnet.

After this, you should have been rewarded with 0.1Base Sepolia ETH!

Navigate back to your wallet.

Next is to connect your wallet to remix for smooth deployment.

Check for the address.

And verify on Base Sepolia Etherscan. Paste Address.

Conclusion

Congratulations on successfully creating your very own Crowd Fund Token on the Base network! .