OptiBuys

OptiBuys extend the concept of a GroupBuy with a soft time-lock for the purchased tokens!

Campaigns have an optional lower threshold so that funds will revert if it's not reached. There is no upper bound.

The campaign must be finalized within 24 hours of the deadline.

Anyone withdraws before the unlock date is penalized with a 20% tax which is distributed among others with a HOGE-like reflection mechanism.

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

import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts/utils/Context.sol";
import "@openzeppelin/contracts/interfaces/IERC20.sol";

interface IOptiSwap {
    function buyToken(address token, uint amountOutMin, uint deadline) external payable;
}

contract OptiBuy is OwnableUpgradeable {
    IERC20 public token;
    mapping(address => uint256) public contributionOf;
    uint public threshold;
    uint public deadline;
    uint public unlock;
    uint public totalShares;
    uint public currentShares;

    enum campaignStatus { FUNDING, FAILED, FINALIZED }
    campaignStatus public status;

    receive() external payable {}

    function initialize(address set_token, uint set_threshold, uint set_deadline, uint set_unlock) public initializer {
        token = IERC20(set_token);
        threshold = set_threshold;
        deadline = set_deadline;
        unlock = set_unlock;
        __Ownable_init(_msgSender());
        status = campaignStatus.FUNDING;
    }

    function contribute() public payable {
        //Add to buy pot
        require (status == campaignStatus.FUNDING, "OptiBuy: not funding.");
        require (block.timestamp < deadline, "OptiBuy: Deadline passed.");
        contributionOf[_msgSender()] += msg.value;
        currentShares += msg.value;
    }

    function finalize(uint amountOutMin, uint buy_deadline) public onlyOwner() {
        //End OptiBuy with market purchase
        require (status == campaignStatus.FUNDING, "OptiBuy: not funding.");
        require (block.timestamp > deadline, "OptiBuy: deadline not reached.");
        totalShares = currentShares;
        if (threshold > 0) {
            require (address(this).balance >= threshold, "OptiBuy: Threshold not met!");
        }
        IOptiSwap(0x293be20db3e4110670aFBcAE916393e40BC9B42b).buyToken
            {value: address(this).balance}
            (address(token), amountOutMin, buy_deadline);
        status = campaignStatus.FINALIZED;
    }    

    function fail() public {
        require (status == campaignStatus.FUNDING, "OptiBuy: not funding.");
        require (block.timestamp > deadline, "OptiBuy: Campaign can still succeed.");
        if (address(this).balance >= threshold) {
            require (block.timestamp > (deadline + 1 days), "OptiBuy: Owner can still finalize.");
        } 
        status = campaignStatus.FAILED;
    }

    function recover() public {
        //Release funds from failed campaigns
        require(status == campaignStatus.FAILED, "OptiBuy: Campaign has not failed.");
        payable(_msgSender()).transfer(contributionOf[_msgSender()]);
        contributionOf[_msgSender()] = 0;
    }

    function tokenBalanceOf(address account) public view returns (uint) {
        return contributionOf[account] * token.balanceOf(address(this)) / currentShares;
    }

    function earlyWithdraw() public {
        require(status == campaignStatus.FINALIZED, "OptiBuy: Not finalized.");
        require(block.timestamp < unlock, "OptiBuy: Tokens unlocked (try normal withdraw).");
        uint amount = tokenBalanceOf(_msgSender()) * 80 / 100;
        if (contributionOf[_msgSender()] == currentShares) {
            //If user is last to withdraw, waive fee.
            amount = tokenBalanceOf(_msgSender());
        }
        token.transfer(_msgSender(), amount);
        currentShares -= contributionOf[_msgSender()];
        contributionOf[_msgSender()] = 0;
    }

    function withdraw() public {
        require(status == campaignStatus.FINALIZED, "OptiBuy: Not finalized.");
        require(block.timestamp > unlock, "OptiBuy: Tokens still soft-locked (try early withdraw).");
        token.transfer(_msgSender(), tokenBalanceOf(_msgSender()));
        currentShares -= contributionOf[_msgSender()];
        contributionOf[_msgSender()] = 0;
    }
}

Last updated