ICO

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "@openzeppelin/contracts/access/Ownable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is set to the address provided by the deployer. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

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

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}
"
    },
    "@openzeppelin/contracts/proxy/utils/Initializable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol)

pragma solidity ^0.8.20;

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
 * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
 * case an upgrade adds a module that needs to be initialized.
 *
 * For example:
 *
 * [.hljs-theme-light.nopadding]
 * ```solidity
 * contract MyToken is ERC20Upgradeable {
 *     function initialize() initializer public {
 *         __ERC20_init("MyToken", "MTK");
 *     }
 * }
 *
 * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
 *     function initializeV2() reinitializer(2) public {
 *         __ERC20Permit_init("MyToken");
 *     }
 * }
 * ```
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 *
 * [CAUTION]
 * ====
 * Avoid leaving a contract uninitialized.
 *
 * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
 * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
 * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * /// @custom:oz-upgrades-unsafe-allow constructor
 * constructor() {
 *     _disableInitializers();
 * }
 * ```
 * ====
 */
abstract contract Initializable {
    /**
     * @dev Storage of the initializable contract.
     *
     * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
     * when using with upgradeable contracts.
     *
     * @custom:storage-location erc7201:openzeppelin.storage.Initializable
     */
    struct InitializableStorage {
        /**
         * @dev Indicates that the contract has been initialized.
         */
        uint64 _initialized;
        /**
         * @dev Indicates that the contract is in the process of being initialized.
         */
        bool _initializing;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;

    /**
     * @dev The contract is already initialized.
     */
    error InvalidInitialization();

    /**
     * @dev The contract is not initializing.
     */
    error NotInitializing();

    /**
     * @dev Triggered when the contract has been initialized or reinitialized.
     */
    event Initialized(uint64 version);

    /**
     * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
     * `onlyInitializing` functions can be used to initialize parent contracts.
     *
     * Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
     * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
     * production.
     *
     * Emits an {Initialized} event.
     */
    modifier initializer() {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        // Cache values to avoid duplicated sloads
        bool isTopLevelCall = !$._initializing;
        uint64 initialized = $._initialized;

        // Allowed calls:
        // - initialSetup: the contract is not in the initializing state and no previous version was
        //                 initialized
        // - construction: the contract is initialized at version 1 (no reininitialization) and the
        //                 current contract is just being deployed
        bool initialSetup = initialized == 0 && isTopLevelCall;
        bool construction = initialized == 1 && address(this).code.length == 0;

        if (!initialSetup && !construction) {
            revert InvalidInitialization();
        }
        $._initialized = 1;
        if (isTopLevelCall) {
            $._initializing = true;
        }
        _;
        if (isTopLevelCall) {
            $._initializing = false;
            emit Initialized(1);
        }
    }

    /**
     * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
     * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
     * used to initialize parent contracts.
     *
     * A reinitializer may be used after the original initialization step. This is essential to configure modules that
     * are added through upgrades and that require initialization.
     *
     * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
     * cannot be nested. If one is invoked in the context of another, execution will revert.
     *
     * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
     * a contract, executing them in the right order is up to the developer or operator.
     *
     * WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
     *
     * Emits an {Initialized} event.
     */
    modifier reinitializer(uint64 version) {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        if ($._initializing || $._initialized >= version) {
            revert InvalidInitialization();
        }
        $._initialized = version;
        $._initializing = true;
        _;
        $._initializing = false;
        emit Initialized(version);
    }

    /**
     * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
     * {initializer} and {reinitializer} modifiers, directly or indirectly.
     */
    modifier onlyInitializing() {
        _checkInitializing();
        _;
    }

    /**
     * @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
     */
    function _checkInitializing() internal view virtual {
        if (!_isInitializing()) {
            revert NotInitializing();
        }
    }

    /**
     * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
     * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
     * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
     * through proxies.
     *
     * Emits an {Initialized} event the first time it is successfully executed.
     */
    function _disableInitializers() internal virtual {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        if ($._initializing) {
            revert InvalidInitialization();
        }
        if ($._initialized != type(uint64).max) {
            $._initialized = type(uint64).max;
            emit Initialized(type(uint64).max);
        }
    }

    /**
     * @dev Returns the highest version that has been initialized. See {reinitializer}.
     */
    function _getInitializedVersion() internal view returns (uint64) {
        return _getInitializableStorage()._initialized;
    }

    /**
     * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
     */
    function _isInitializing() internal view returns (bool) {
        return _getInitializableStorage()._initializing;
    }

    /**
     * @dev Returns a pointer to the storage namespace.
     */
    // solhint-disable-next-line var-name-mixedcase
    function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
        assembly {
            $.slot := INITIALIZABLE_STORAGE
        }
    }
}
"
    },
    "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}
"
    },
    "@openzeppelin/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the value of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 value) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}
"
    },
    "@openzeppelin/contracts/utils/Context.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}
"
    },
    "contracts/ICO.sol": {
      "content": "//SPDX-License-Identifier: MIT
pragma solidity =0.8.20;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import "./interfaces/IICO.sol";
import "./utils/ReentrancyGuard.sol";
import "./libraries/TransferHelper.sol";
import "./interfaces/OracleWrapper.sol";

contract ICO is Ownable, Initializable, ReentrancyGuard, IICO {
    uint256 public tokensToBeSold;
    uint256 public totalTokenSold;
    uint256 public totalUSDRaised;
    uint256 public tokenDecimal;
    uint8 public defaultPhase;
    uint8 public totalPhases;

    address public receiverAddress;
    address public investor;
    address public founderA;
    address public founderB;
    address public founderC;

    /* ================ STRUCT SECTION ================ */
    /* Stores phases */
    struct Phases {
        uint256 tokenSold;
        uint256 tokenLimit;
        uint32 startTime;
        uint32 expirationTimestamp;
        uint32 price /* 10 ** 8 */;
        uint32 percentage;
        bool isComplete;
    }

    /**
     * User Data Structure for users info like:-
     * Users total amount for claim.
     * Users claimed amount that is till claimed.
     * Users claim for how many times user claims the amount.
     * The Categories are:-
     *      Seed sale = 1
     *      Pre sale = 2
     *      Public sale = 3
     *      Investor = 4
     *      FounderA = 5
     *      FounderB = 5
     *      FounderC = 5
     */
    struct UserData {
        uint256 totalAmount;
        uint256 claimedAmount;
        uint8 claims;
        bool isClaimed;
    }

    struct VestingInfo {
        uint32 startTime;
        uint32 claimGap;
        uint8 claims;
    }

    mapping(uint256 => Phases) public phaseInfo;
    mapping(address => mapping(uint8 => UserData)) public userMapping;
    mapping(uint8 => VestingInfo) public vestingInfoMapping;
    mapping(uint8 => uint256[]) public percentageArray;

    IERC20Metadata public tokenInstance; /* Lumen token instance */
    IERC20Metadata public usdtInstance; /* USDT token instance */
    IERC20Metadata public usdcInstance; /* USDC token instance */

    OracleWrapper public USDTOracle;
    OracleWrapper public USDCOracle;
    OracleWrapper public ETHOracle;

    /* ================ CONSTRUCTOR SECTION ================ */
    constructor() Ownable(msg.sender) {}

    function initialize(
        address _tokenAddress,
        address _usdtToken,
        address _usdcToken,
        address _owner
    ) external initializer {
        receiverAddress = 0xAe6Bb9bBe47865A0834649A2600DDFab93d6277b;
        investor = 0x75e4fF970a3B225d7770027bE8f4D5f041460974;
        founderA = 0xC4b2aCFF87A2121461a13F141854e67D86274B75;
        founderB = 0x7f59DA2eb8eb9deb73229E36Fecec35C6502A604;
        founderC = 0xa8396FF84b94D4aed3Ec8EaCc063c2cf40B897D2;

        USDTOracle = OracleWrapper(0x3E7d1eAB13ad0104d2750B8863b489D65364e32D);
        USDCOracle = OracleWrapper(0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6);
        ETHOracle = OracleWrapper(0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419);

        tokenInstance = IERC20Metadata(_tokenAddress);
        usdtInstance = IERC20Metadata(_usdtToken);
        usdcInstance = IERC20Metadata(_usdcToken);

        totalPhases = 3;
        defaultPhase = 1;
        tokenDecimal = uint256(10 ** tokenInstance.decimals());
        tokensToBeSold = 220_000_000 * tokenDecimal;

        percentageArray[1] = [3_333_333_333]; // 33.33%
        percentageArray[2] = [3_333_333_333]; // 33.33%
        percentageArray[3] = [5_000_000_000]; // 50.00%
        percentageArray[4] = [
            1_000_000_000,
            1_500_000_000,
            1_500_000_000,
            1_500_000_000,
            1_500_000_000,
            1_500_000_000,
            1_500_000_000
        ];
        percentageArray[5] = [
            800_000_000,
            1_533_333_333,
            1_533_333_333,
            1_533_333_333,
            1_533_333_333,
            1_533_333_333,
            1_533_333_333
        ];

        // phases timming is not defined yet so we just use dummy date for phase = 6 days
        // but other vesting suration is proper.
        setVestingCategory(1764547199, 30 days, 1, 3); // ico end time
        setVestingCategory(1764547199, 30 days, 2, 3); // ico end time
        setVestingCategory(1764547199, 30 days, 3, 2); // ico end time
        setVestingCategory(1764547199 + 30 days, 30 days, 4, 7); // TGE + 30 days (TGE = Token Generation event.)
        setVestingCategory(1764547199 + 90 days, 30 days, 5, 7); // TGE + 90 days

        /* setup the vesting amount */
        _registerUser(
            (250_000_000 * (10 ** tokenInstance.decimals())),
            4,
            investor
        );
        _registerUser(
            (60_000_000 * (10 ** tokenInstance.decimals())),
            5,
            founderA
        );
        _registerUser(
            (60_000_000 * (10 ** tokenInstance.decimals())),
            5,
            founderB
        );
        _registerUser(
            (60_000_000 * (10 ** tokenInstance.decimals())),
            5,
            founderC
        );

        phaseInfo[1] = Phases({
            tokenLimit: 50_000_000 * tokenDecimal,
            tokenSold: 0,
            startTime: 1762387200, // 2025-11-06 00:00:00
            expirationTimestamp: 1762819199, // 2025-11-10 23:59:59
            price: 60000 /* 0.0006 */,
            percentage: 2000,
            isComplete: false
        });
        phaseInfo[2] = Phases({
            tokenLimit: 70_000_000 * tokenDecimal,
            tokenSold: 0,
            startTime: 1762819200, // 2025-11-11 00:00:00
            expirationTimestamp: 1763683199, // 2025-11-20 23:59:59
            price: 80000 /* 0.0008 */,
            percentage: 3000,
            isComplete: false
        });
        phaseInfo[3] = Phases({
            tokenLimit: 100_000_000 * tokenDecimal,
            tokenSold: 0,
            startTime: 1763683200, // 2025-11-21 00:00:00
            expirationTimestamp: 1764547199, // 2025-11-30 23:59:59
            price: 100000 /* 0.001 */,
            percentage: 5000,
            isComplete: false
        });
        _transferOwnership(_owner);
    }

    /* ================ BUYING TOKENS SECTION ================ */

    /* Receive Function */
    receive() external payable {
        /* Sending deposited currency to the receiver address */
        TransferHelper.safeTransferETH(receiverAddress, msg.value);
    }

    /** Function lets user buy ALTR tokens || Type 1 = BNB or ETH,
     * Type = 2 for USDT,
     * Type = 3 for USDC
     */
    function buyTokens(
        uint8 _type,
        uint256 _usdtAmount
    ) external payable override nonReentrant {
        uint8 _currentPhase = getCurrentPhase();
        require(
            block.timestamp >= phaseInfo[_currentPhase].startTime,
            "ICO has not started yet."
        );
        require(
            block.timestamp < phaseInfo[(totalPhases)].expirationTimestamp,
            "Buying phases are over."
        );

        uint256 _buyAmount;
        IERC20Metadata _instance;
        /* If type == 1 */
        if (_type == 1) {
            _buyAmount = msg.value;
        }
        /* If type == 2 */
        else {
            require(msg.value == 0, "ETH amount should be zero.");
            _buyAmount = _usdtAmount;
            _instance = _type == 2 ? usdtInstance : usdcInstance;
            /* Balance Check */
            require(
                _instance.balanceOf(_msgSender()) >= _buyAmount,
                "User doesn't have enough balance."
            );

            /* Allowance Check */
            require(
                _instance.allowance(_msgSender(), address(this)) >= _buyAmount,
                "Allowance provided is low."
            );
        }
        require(_buyAmount > 0, "Please enter value more than 0.");

        /* Token calculation */
        (
            uint256 _tokenAmount,
            uint256 _amountToUSD,
            uint8 _phaseNo
        ) = calculateTokens(_buyAmount, 0, defaultPhase, _type);
        require(_phaseNo != 0, "All phases are over.");
        /* Setup for vesting in vesting contract */
        require(_tokenAmount > 0, "Token amount should be more then zero.");

        /* Phase info setting */
        setPhaseInfo(_tokenAmount, defaultPhase);

        /* Update Phase number and add token amount */
        defaultPhase = _phaseNo;

        totalTokenSold += _tokenAmount;
        totalUSDRaised += _amountToUSD;

        uint256 _amountToTransfer = ((_tokenAmount *
            phaseInfo[_phaseNo].percentage) / 10000);

        _registerUser(_tokenAmount - _amountToTransfer, _phaseNo, _msgSender());

        if (_type == 1) {
            /* Sending deposited currency to the receiver address */
            TransferHelper.safeTransferETH(receiverAddress, _buyAmount);
        } else {
            /* Sending deposited currency to the receiver address */
            TransferHelper.safeTransferFrom(
                address(_instance),
                _msgSender(),
                receiverAddress,
                _buyAmount
            );
        }

        TransferHelper.safeTransfer(
            address(tokenInstance),
            _msgSender(),
            _amountToTransfer
        );

        /* Emits event */

        emit BuyTokenDetail(
            _buyAmount,
            _tokenAmount,
            _amountToTransfer,
            _tokenAmount - _amountToTransfer,
            _amountToUSD,
            uint32(block.timestamp),
            _type,
            _phaseNo,
            _msgSender()
        );
    }

    function getCurrentPhase() public view returns (uint8) {
        uint8 phase;
        for (uint8 i = defaultPhase; i <= totalPhases; i++) {
            if (
                block.timestamp >= phaseInfo[i].startTime &&
                block.timestamp < phaseInfo[i].expirationTimestamp
            ) {
                phase = i;
                break;
            } else if (block.timestamp < phaseInfo[i].startTime) {
                phase = i;
                break;
            }
        }
        return phase;
    }

    /* Internal function to calculate tokens */
    // _types  0 == USD, 1 == ETH, 2 == USDT, 3 == USDC
    function calculateTokens(
        uint256 _amount,
        uint256 _previousTokens,
        uint8 _phaseNo,
        uint8 _buyType
    ) public view returns (uint256, uint256, uint8) {
        (uint256 _usdAmount, uint256 _typeDecimal) = cryptoValues(_buyType);
        _amount =
            (_amount * _usdAmount * (10 ** 8)) /
            (_typeDecimal * (10 ** 8));

        (uint256 _tokenAmount, uint8 _newPhaseNo) = _calculateTokens(
            _amount,
            _previousTokens,
            0,
            _phaseNo
        );
        return (_tokenAmount, _amount, _newPhaseNo);
    }

    function _calculateTokens(
        uint256 _amount,
        uint256 _previousTokens,
        uint256 _previousPhaseTokens,
        uint8 _phaseNo
    ) internal view returns (uint256, uint8) {
        /* Phases cannot exceed totalPhases */
        require(_phaseNo <= totalPhases, "Not enough tokens in the ICO.");
        Phases memory pInfo = phaseInfo[_phaseNo];
        if (_phaseNo == totalPhases) {
            require(
                pInfo.expirationTimestamp >= uint32(block.timestamp),
                "ICO is Ended."
            );
        }

        if (pInfo.expirationTimestamp > uint32(block.timestamp)) {
            uint256 _tokensAmount = tokensUserWillGet(_amount, pInfo.price);
            uint256 _tokensLeftToSell = (pInfo.tokenLimit +
                _previousPhaseTokens) - pInfo.tokenSold;
            if (_tokensLeftToSell >= _tokensAmount) {
                return (_tokensAmount + _previousTokens, _phaseNo);
            }
            /*  In case the phase is expired. New will begin after sending the left tokens to the next phase */
            else {
                uint256 _remainingTokens = _tokensAmount - _tokensLeftToSell;
                _previousTokens += _tokensLeftToSell;
                uint256 _remainingUsdAmount = getUsdAmount(
                    _remainingTokens,
                    pInfo.price
                );
                return
                    _calculateTokens(
                        _remainingUsdAmount,
                        _previousTokens,
                        0,
                        _phaseNo + 1
                    );
            }
        } else {
            _previousPhaseTokens += (pInfo.tokenLimit - pInfo.tokenSold);
            return
                _calculateTokens(
                    _amount,
                    _previousTokens,
                    _previousPhaseTokens,
                    _phaseNo + 1
                );
        }
    }

    function getUsdAmount(
        uint256 _remainingTokens,
        uint32 _phasePrice
    ) internal view returns (uint256 _remainingUsdAmount) {
        return ((_remainingTokens * _phasePrice * (10 ** 18) * (10 ** 8)) /
            (tokenDecimal * (10 ** 18) * (10 ** 8)));
    }

    /* Tokens user will get according to the price */
    function tokensUserWillGet(
        uint256 _amount,
        uint32 _price
    ) internal view returns (uint256) {
        return ((_amount * tokenDecimal * (10 ** 8)) /
            ((10 ** 8) * uint256(_price)));
    }

    /* Returns the crypto values used */
    function cryptoValues(
        uint8 _type
    ) internal view returns (uint256, uint256) {
        uint256 _amountToUSD;
        uint256 _typeDecimal;

        if (_type == 1) {
            _amountToUSD = ETHOracle.latestAnswer();
            _typeDecimal = 10 ** 18;
        } else if (_type == 2) {
            _amountToUSD = USDTOracle.latestAnswer();
            _typeDecimal = uint256(10 ** usdtInstance.decimals());
        } else {
            _amountToUSD = USDCOracle.latestAnswer();
            _typeDecimal = uint256(10 ** usdcInstance.decimals());
        }
        return (_amountToUSD, _typeDecimal);
    }

    /* Sets phase info according to the tokens bought */
    function setPhaseInfo(uint256 _tokensUserWillGet, uint8 _phaseNo) internal {
        require(_phaseNo <= totalPhases, "All tokens have been exhausted.");

        Phases storage pInfo = phaseInfo[_phaseNo];

        if (block.timestamp < pInfo.expirationTimestamp) {
            /* when phase has more tokens than reuired */
            if ((pInfo.tokenLimit - pInfo.tokenSold) > _tokensUserWillGet) {
                pInfo.tokenSold += _tokensUserWillGet;
            }
            /* when  phase has equal tokens as reuired */
            else if (
                (pInfo.tokenLimit - pInfo.tokenSold) == _tokensUserWillGet
            ) {
                pInfo.tokenSold = pInfo.tokenLimit;
                pInfo.expirationTimestamp = uint32(block.timestamp);
                pInfo.isComplete = true;
                phaseInfo[_phaseNo + 1].startTime = uint32(block.timestamp) + 1;
            }
            /*  when tokens required are more than left tokens in phase */
            else {
                _tokensUserWillGet -= (pInfo.tokenLimit - pInfo.tokenSold);
                pInfo.tokenSold = pInfo.tokenLimit;
                pInfo.expirationTimestamp = uint32(block.timestamp);
                pInfo.isComplete = true;
                phaseInfo[_phaseNo + 1].startTime = uint32(block.timestamp) + 1;
                setPhaseInfo(_tokensUserWillGet, _phaseNo + 1);
            }
        }
        /* if tokens left in phase afterb completion of expiration time */
        else {
            uint256 remainingTokens = pInfo.tokenLimit - pInfo.tokenSold;
            pInfo.tokenLimit = pInfo.tokenSold;
            pInfo.isComplete = true;

            phaseInfo[_phaseNo + 1].tokenLimit += remainingTokens;
            setPhaseInfo(_tokensUserWillGet, _phaseNo + 1);
        }
    }

    /* =============== Set Vesting Time and Claims ===============*/
    function setVestingCategory(
        uint32 _time,
        uint32 _claimGap,
        uint8 _choice,
        uint8 _claims
    ) internal {
        VestingInfo storage phase = vestingInfoMapping[_choice];
        phase.startTime = _time;
        phase.claimGap = _claimGap;
        phase.claims = _claims;
    }

    /* =============== Register The Address For Claiming ===============*/

    /**
     * Register User for Vesting
     * _amount for Total Claimable Amount
     * _choice for Vesting Category
     * _to for User's Address
     */
    function _registerUser(
        uint256 _amount,
        uint8 _choice,
        address _to
    ) internal {
        UserData storage user = userMapping[_to][_choice];

        user.totalAmount += _amount;

        emit RegisterUser(_amount, _to, _choice);
    }

    /* =============== Token Claiming Functions =============== */
    /**
     * User can claim the tokens with claimTokens function.
     * after start the vesting for that particular vesting category.
     */
    function claimTokens(uint8 _choice) external nonReentrant {
        require(
            userMapping[_msgSender()][_choice].totalAmount > 0,
            "User is not registered with this vesting."
        );

        (uint256 _amount, uint8 _claimCount) = tokensToBeClaimed(
            _msgSender(),
            _choice
        );

        require(_amount > 0, "Nothing to claim right now.");

        UserData storage user = userMapping[_msgSender()][_choice];
        user.claimedAmount += _amount;
        user.claims = _claimCount;

        TransferHelper.safeTransfer(
            address(tokenInstance),
            _msgSender(),
            _amount
        );

        uint8 claims = uint8(vestingInfoMapping[_choice].claims);
        if (claims == _claimCount) {
            user.isClaimed = true;
        }

        emit ClaimedToken(
            _msgSender(),
            _amount,
            uint32(block.timestamp),
            _claimCount,
            _choice
        );
    }

    /* =============== Tokens to be claimed =============== */
    /**
     * tokensToBeClaimed function can be used for checking the claimable amount of the user.
     */
    function tokensToBeClaimed(
        address _to,
        uint8 _choice
    ) public view returns (uint256 _toBeTransfer, uint8 _claimCount) {
        UserData memory user = userMapping[_to][_choice];
        VestingInfo memory _vestingInfo = vestingInfoMapping[_choice];
        if (
            (block.timestamp < (_vestingInfo.startTime)) ||
            (user.totalAmount == 0)
        ) {
            return (0, 0);
        }

        if (user.totalAmount == user.claimedAmount) {
            return (0, 0);
        }

        uint32 _time = uint32(block.timestamp - (_vestingInfo.startTime));
        /* Claim in Ever Month 30 days for main net and 1 minutes for the test */
        uint256 _claimCountInternal = uint256(
            (_time / _vestingInfo.claimGap) + 1
        );
        uint8 claims = uint8(_vestingInfo.claims);

        if (_claimCountInternal > claims) {
            _claimCountInternal = claims;
        }
        _claimCount = uint8(_claimCountInternal);

        if (_claimCount <= user.claims) {
            return (0, _claimCount);
        }
        if (_claimCount == claims) {
            _toBeTransfer = user.totalAmount - user.claimedAmount;
        } else {
            _toBeTransfer = vestingCalulations(
                user.totalAmount,
                _claimCount,
                user.claims,
                _choice
            );
        }
        return (_toBeTransfer, _claimCount);
    }

    function vestingSchedule(
        address _to,
        uint8 _choice
    )
        public
        view
        returns (uint256[] memory _tokensToBeTransferred, uint8 _userClaims)
    {
        UserData memory user = userMapping[_to][_choice];
        VestingInfo memory _vestingInfo = vestingInfoMapping[_choice];
        uint256[] memory _tokensToBeTransferredArray = new uint256[](
            _vestingInfo.claims
        );
        uint256 _lastClaimAmount;
        for (uint8 i = 0; i < _vestingInfo.claims; i++) {
            uint8 _claimCount = uint8(i + 1);
            uint256 _toBeTransfer;
            if (_claimCount == _vestingInfo.claims) {
                _toBeTransfer = user.totalAmount;
            } else {
                _toBeTransfer = vestingCalulations(
                    user.totalAmount,
                    _claimCount,
                    0,
                    _choice
                );
            }
            _tokensToBeTransferredArray[i] = _toBeTransfer - _lastClaimAmount;
            _lastClaimAmount = _toBeTransfer;
        }
        _tokensToBeTransferred = _tokensToBeTransferredArray;
        return (_tokensToBeTransferred, user.claims);
    }

    /* =============== Vesting Calculations =============== */
    /**
     * vestingCalulations function is used for calculating the amount of token for claim
     */
    function vestingCalulations(
        uint256 _userTotalAmount,
        uint8 _claimCount,
        uint8 _userClaimCount,
        uint8 _choice
    ) internal view returns (uint256) {
        uint256 amount;

        for (uint8 i = _userClaimCount; i < _claimCount; ++i) {
            if ((percentageArray[_choice]).length == 1) {
                amount +=
                    (_userTotalAmount * (percentageArray[_choice][0])) /
                    10_000_000_000;
            } else {
                amount +=
                    (_userTotalAmount * (percentageArray[_choice][i])) /
                    10_000_000_000;
            }
        }

        return amount;
    }

    function sendLeftoverTokens() external onlyOwner {
        require(
            phaseInfo[totalPhases].expirationTimestamp < block.timestamp,
            "ICO is not over yet."
        );
        uint256 _balance = (tokensToBeSold - totalTokenSold);
        require(_balance > 0, "No tokens left to send.");

        TransferHelper.safeTransfer(
            address(tokenInstance),
            receiverAddress,
            _balance
        );
    }

    /* ================ OTHER FUNCTIONS SECTION ================ */
    /* Updates Receiver Address */
    function updateReceiverAddress(
        address _receiverAddress
    ) external onlyOwner {
        require(_receiverAddress != address(0), "Zero address passed.");
        receiverAddress = _receiverAddress;
        emit UpdateReceiverAddress(_receiverAddress);
    }
}
"
    },
    "contracts/interfaces/IICO.sol": {
      "content": "//SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IICO {
    event RegisterUser(uint256 totalTokens, address userAddress, uint8 choice);

    event UpdateReceiverAddress(address receiverAddress);

    event ClaimedToken(
        address userAddress,
        uint256 claimedAmount,
        uint32 timestamp,
        uint8 claimCount,
        uint8 choice
    );

    event TokenBought(
        uint256 tokenAmount,
        uint32 timestamp,
        address userAddress
    );

    event BuyTokenDetail(
        uint256 buyAmount,
        uint256 tokenAmount,
        uint256 amountToTransfer,
        uint256 amountToVesting,
        uint256 amountToUSD,
        uint32 timestamp,
        uint8 buyType,
        uint8 phaseNo,
        address userAddress
    );

    function buyTokens(uint8 _type, uint256 _usdtAmount) external payable;
}
"
    },
    "contracts/interfaces/OracleWrapper.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity =0.8.20;

interface OracleWrapper {
    function latestAnswer() external view returns (uint256);
}
"
    },
    "contracts/libraries/TransferHelper.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity =0.8.20;

// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false
library TransferHelper {
    function safeApprove(
        address token,
        address to,
        uint256 value
    ) internal {
        // bytes4(keccak256(bytes('approve(address,uint256)')));
        (bool success, bytes memory data) = token.call(
            abi.encodeWithSelector(0x095ea7b3, to, value)
        );
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            "TransferHelper::safeApprove: approve failed"
        );
    }

    function safeTransfer(
        address token,
        address to,
        uint256 value
    ) internal {
        // bytes4(keccak256(bytes('transfer(address,uint256)')));
        (bool success, bytes memory data) = token.call(
            abi.encodeWithSelector(0xa9059cbb, to, value)
        );
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            "TransferHelper::safeTransfer: transfer failed"
        );
    }

    function safeTransferFrom(
        address token,
        address from,
        address to,
        uint256 value
    ) internal {
        // bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
        (bool success, bytes memory data) = token.call(
            abi.encodeWithSelector(0x23b872dd, from, to, value)
        );
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            "TransferHelper::transferFrom: transferFrom failed"
        );
    }

    function safeTransferETH(address to, uint256 value) internal {
        (bool success, ) = to.call{value: value}(new bytes(0));
        require(
            success,
            "TransferHelper::safeTransferETH: ETH transfer failed"
        );
    }
}"
    },
    "contracts/utils/ReentrancyGuard.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity =0.8.20;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    constructor () {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and make it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        // On the first call to nonReentrant, _notEntered will be true
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;

        _;

        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }
}


"
    }
  },
  "settings": {
    "optimizer": {
      "enabled": true,
      "runs": 200
    },
    "evmVersion": "paris",
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    }
  }
}}

Tags:
ERC20, Multisig, Upgradeable, Multi-Signature, Factory, Oracle|addr:0x43e68070d318dabd2b9918887a60da62d2a0f6bd|verified:true|block:23739384|tx:0x5e03982b7ca76e7dd2cca377e681ce5a067707f57cb173aa63564868affa08a5|first_check:1762429339

Submitted on: 2025-11-06 12:42:22

Comments

Log in to comment.

No comments yet.