PreSale

0xb99d5c89dc8468090b53704917e2a8daef7a6919

Verification
Verified
v0.8.30+commit.73712a01
Type
Contract
6,985 bytes
ABI entries
33
15 read · 9 write
License
mit

Contract information

Address
0xb99d5c89dc8468090b53704917e2a8daef7a6919
Chain
Robinhood Chain (4663)
Compiler
v0.8.30+commit.73712a01
Optimization
Enabled
Creation tx
0xe5deb2a8e2…b467d9f0ad

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (15)

currentPhase()uint8
distributed(address)bool
getWinnerAddresses()address[]
hardCap()uint256
isWinnerAdded(address)bool
maxPerWallet()uint256
maxSupply()uint256
mintPrice()uint256
owner()address
reservations(address)uint256
softCap()uint256
totalEthRaised()uint256
totalReserved()uint256
winnerAddresses(uint256)address
winners(address)uint256

Events (8)

DistributedEthWithdrawnOwnershipTransferredPhaseChangedRefundClaimedReservationCancelledReservedWinnerSet

ABI

[
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "_maxSupply",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "_mintPrice",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "_maxPerWallet",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "_softCap",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "_hardCap",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "winner",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "Distributed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "owner",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "EthWithdrawn",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferred",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "enum PreSale.Phase",
        "name": "newPhase",
        "type": "uint8"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "timestamp",
        "type": "uint256"
      }
    ],
    "name": "PhaseChanged",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "user",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "quantity",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "timestamp",
        "type": "uint256"
      }
    ],
    "name": "RefundClaimed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "user",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "quantity",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "timestamp",
        "type": "uint256"
      }
    ],
    "name": "ReservationCancelled",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "user",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "quantity",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "timestamp",
        "type": "uint256"
      }
    ],
    "name": "Reserved",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "winner",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "WinnerSet",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "cancelReservation",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "claimRefund",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "currentPhase",
    "outputs": [
      {
        "internalType": "enum PreSale.Phase",
        "name": "",
        "type": "uint8"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "nftContractAddress",
        "type": "address"
      },
      {
        "internalType": "address[]",
        "name": "_winners",
        "type": "address[]"
      }
    ],
    "name": "distribute",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "nftContractAddress",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "startIndex",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "count",
        "type": "uint256"
      }
    ],
    "name": "distributeBatch",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "distributed",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "getWinnerAddresses",
    "outputs": [
      {
        "internalType": "address[]",
        "name": "",
        "type": "address[]"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "hardCap",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "isWinnerAdded",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "maxPerWallet",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "maxSupply",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "mintPrice",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "reservations",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "quantity",
        "type": "uint256"
      }
    ],
    "name": "reserve",
    "outputs": [],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "enum PreSale.Phase",
        "name": "newPhase",
        "type": "uint8"
      }
    ],
    "name": "setPhase",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address[]",
        "name": "_winners",
        "type": "address[]"
      },
      {
        "internalType": "uint256[]",
        "name": "_amounts",
        "type": "uint256[]"
      }
    ],
    "name": "setWinners",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "softCap",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "totalEthRaised",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "totalReserved",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "winnerAddresses",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "winners",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "withdrawETH",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  }
]

Source code

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

// src/Ownable.sol

/// @title Ownable
/// @author SquiBid
/// @notice A simple ownership contract.
abstract contract Ownable {
    address private _owner;

    event OwnershipTransferred(
        address indexed previousOwner,
        address indexed newOwner
    );

    constructor() {
        _owner = msg.sender;
        emit OwnershipTransferred(address(0), msg.sender);
    }

    function owner() public view virtual returns (address) {
        return _owner;
    }

    modifier onlyOwner() {
        require(owner() == msg.sender, "Ownable: caller is not the owner");
        _;
    }

    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(
            newOwner != address(0),
            "Ownable: new owner is the zero address"
        );
        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
    }
}

// src/ReentrancyGuard.sol

/// @title ReentrancyGuard
/// @author SquiBid
/// @notice A simple reentrancy guard to prevent reentrancy attacks.
abstract contract ReentrancyGuard {
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;
    uint256 private _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

    modifier nonReentrant() {
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
        _status = _ENTERED;
        _;
        _status = _NOT_ENTERED;
    }
}

// src/PreSale.sol

/// @title Squibid ($SB)
/// @notice SquiBid is a decentralized, on-chain collectible sale infrastructure that empowers creators and projects.
/// @dev Web: http://squibid.io/ · X: https://x.com/Squibid
/// @custom:version squihood-syndicate-v1
///
/// Squibid allows you to discover, bid on, and collect NFTs through a fair and transparent Initial Collectible Offering process.
/// Our platform ensures that genuine supporters get fair access to the most anticipated drops.
///
contract PreSale is Ownable, ReentrancyGuard {
    enum Phase {
        Open,
        Closed,
        Refund,
        NftDistribution
    }

    Phase public currentPhase;

    // Pre-sale configuration parameters
    uint256 public immutable maxSupply;
    uint256 public immutable mintPrice;
    uint256 public immutable maxPerWallet;
    uint256 public immutable softCap;
    uint256 public immutable hardCap;

    uint256 public totalReserved;
    uint256 public totalEthRaised; // Net active ETH raised (subtracts cancellations/refunds)

    // User reservations and distribution info
    mapping(address => uint256) public reservations;
    mapping(address => uint256) public winners;
    mapping(address => bool) public distributed;

    // Winners tracking
    address[] public winnerAddresses;
    mapping(address => bool) public isWinnerAdded;

    // Events
    event Reserved(
        address indexed user,
        uint256 quantity,
        uint256 amount,
        uint256 timestamp
    );
    event ReservationCancelled(
        address indexed user,
        uint256 quantity,
        uint256 amount,
        uint256 timestamp
    );
    event RefundClaimed(
        address indexed user,
        uint256 quantity,
        uint256 amount,
        uint256 timestamp
    );
    event PhaseChanged(Phase indexed newPhase, uint256 timestamp);
    event WinnerSet(address indexed winner, uint256 amount);
    event Distributed(address indexed winner, uint256 amount);
    event EthWithdrawn(address indexed owner, uint256 amount);

    constructor(
        uint256 _maxSupply,
        uint256 _mintPrice,
        uint256 _maxPerWallet,
        uint256 _softCap,
        uint256 _hardCap
    ) {
        require(_maxSupply > 0, "Invalid max supply");
        require(_mintPrice > 0, "Invalid mint price");
        require(_maxPerWallet > 0, "Invalid max per wallet");
        require(_softCap > 0, "Invalid soft cap");
        require(_hardCap >= _softCap, "Hard cap must be >= soft cap");

        maxSupply = _maxSupply;
        mintPrice = _mintPrice;
        maxPerWallet = _maxPerWallet;
        softCap = _softCap;
        hardCap = _hardCap;

        currentPhase = Phase.Closed; // Starts Closed
    }

    function reserve(uint256 quantity) external payable nonReentrant {
        require(currentPhase == Phase.Open, "Pre-sale is not open");
        require(quantity > 0, "Quantity must be > 0");
        require(
            reservations[msg.sender] + quantity <= maxPerWallet,
            "Exceeds max per wallet limit"
        );
        uint256 requiredEth = quantity * mintPrice;
        require(msg.value == requiredEth, "Incorrect ETH amount sent");

        // Enforce hard cap limit
        require(
            totalEthRaised + msg.value <= hardCap,
            "Exceeds hard cap limit"
        );

        reservations[msg.sender] += quantity;
        totalReserved += quantity;
        totalEthRaised += msg.value;

        emit Reserved(msg.sender, quantity, msg.value, block.timestamp);
    }

    function cancelReservation() external nonReentrant {
        require(currentPhase == Phase.Open, "Can only cancel in Open phase");
        uint256 quantity = reservations[msg.sender];
        require(quantity > 0, "No reservation to cancel");

        uint256 refundAmount = quantity * mintPrice;

        reservations[msg.sender] = 0;
        totalReserved -= quantity;
        totalEthRaised -= refundAmount;

        (bool success, ) = msg.sender.call{value: refundAmount}("");
        require(success, "ETH refund transfer failed");

        emit ReservationCancelled(
            msg.sender,
            quantity,
            refundAmount,
            block.timestamp
        );
    }

    function claimRefund() external nonReentrant {
        require(
            currentPhase == Phase.Refund,
            "Can only claim refund in Refund phase"
        );
        uint256 quantity = reservations[msg.sender];
        require(quantity > 0, "No reservation to refund");

        uint256 refundAmount = quantity * mintPrice;

        reservations[msg.sender] = 0;
        totalReserved -= quantity;
        totalEthRaised -= refundAmount;

        (bool success, ) = msg.sender.call{value: refundAmount}("");
        require(success, "ETH refund transfer failed");

        emit RefundClaimed(msg.sender, quantity, refundAmount, block.timestamp);
    }

    function setPhase(Phase newPhase) external onlyOwner {
        currentPhase = newPhase;
        emit PhaseChanged(newPhase, block.timestamp);
    }

    function setWinners(
        address[] calldata _winners,
        uint256[] calldata _amounts
    ) external onlyOwner {
        require(
            currentPhase == Phase.NftDistribution,
            "Not in NftDistribution phase"
        );
        require(_winners.length == _amounts.length, "Mismatched input arrays");

        for (uint256 i = 0; i < _winners.length; i++) {
            address winner = _winners[i];
            uint256 amount = _amounts[i];
            require(winner != address(0), "Invalid winner address");

            winners[winner] = amount;
            if (!isWinnerAdded[winner]) {
                isWinnerAdded[winner] = true;
                winnerAddresses.push(winner);
            }
            emit WinnerSet(winner, amount);
        }
    }

    function distribute(
        address nftContractAddress,
        address[] calldata _winners
    ) external onlyOwner nonReentrant {
        require(
            currentPhase == Phase.NftDistribution,
            "Not in NftDistribution phase"
        );
        require(
            nftContractAddress != address(0),
            "Invalid NFT contract address"
        );

        for (uint256 i = 0; i < _winners.length; i++) {
            address winner = _winners[i];
            uint256 amount = winners[winner];
            if (amount > 0 && !distributed[winner]) {
                distributed[winner] = true;

                (bool success, ) = nftContractAddress.call(
                    abi.encodeWithSignature(
                        "mint(address,uint256)",
                        winner,
                        amount
                    )
                );
                require(success, "NFT mint call failed");

                emit Distributed(winner, amount);
            }
        }
    }

    function distributeBatch(
        address nftContractAddress,
        uint256 startIndex,
        uint256 count
    ) external onlyOwner nonReentrant {
        require(
            currentPhase == Phase.NftDistribution,
            "Not in NftDistribution phase"
        );
        require(
            nftContractAddress != address(0),
            "Invalid NFT contract address"
        );

        uint256 length = winnerAddresses.length;
        require(startIndex < length, "Start index out of bounds");

        uint256 endIndex = startIndex + count;
        if (endIndex > length) {
            endIndex = length;
        }

        for (uint256 i = startIndex; i < endIndex; i++) {
            address winner = winnerAddresses[i];
            uint256 amount = winners[winner];
            if (amount > 0 && !distributed[winner]) {
                distributed[winner] = true;

                (bool success, ) = nftContractAddress.call(
                    abi.encodeWithSignature(
                        "mint(address,uint256)",
                        winner,
                        amount
                    )
                );
                require(success, "NFT mint call failed");

                emit Distributed(winner, amount);
            }
        }
    }

    function withdrawETH() external onlyOwner nonReentrant {
        uint256 balance = address(this).balance;
        require(balance > 0, "No ETH to withdraw");

        (bool success, ) = msg.sender.call{value: balance}("");
        require(success, "ETH withdrawal failed");

        emit EthWithdrawn(msg.sender, balance);
    }

    function getWinnerAddresses() external view returns (address[] memory) {
        return winnerAddresses;
    }
}
Chain explorer2841msChain node89ms