creation_sourcecode: {{
    "language": "Solidity",
    "settings": {
      "viaIR": false,
      "optimizer": {
        "runs": 10000,
        "enabled": true
      },
      "metadata": {
        "useLiteralContent": false,
        "bytecodeHash": "none",
        "appendCBOR": true
      },
      "outputSelection": {
        "*": {
          "*": [
            "evm.bytecode",
            "evm.deployedBytecode",
            "devdoc",
            "userdoc",
            "metadata",
            "abi"
          ]
        }
      },
      "evmVersion": "paris",
      "libraries": {},
      "remappings": [
        "@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/",
        "@openzeppelin/contracts-upgradeable/=node_modules/@openzeppelin/contracts-upgradeable/",
        "@prb/test/=node_modules/@prb/test/",
        "forge-std/=node_modules/forge-std/",
        "@sphinx-labs/contracts/=node_modules/@sphinx-labs/contracts/contracts/foundry/",
        "@eth-optimism/=node_modules/@eth-optimism/",
        "hardhat/=node_modules/hardhat/",
        "solmate/=node_modules/solmate/"
      ]
    },
    "sources": {
      "node_modules/@openzeppelin/contracts/access/Ownable.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)\n\npragma solidity ^0.8.20;\n\nimport {Context} from \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * The initial owner is set to the address provided by the deployer. This can\n * later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n    address private _owner;\n\n    /**\n     * @dev The caller account is not authorized to perform an operation.\n     */\n    error OwnableUnauthorizedAccount(address account);\n\n    /**\n     * @dev The owner is not a valid owner account. (eg. `address(0)`)\n     */\n    error OwnableInvalidOwner(address owner);\n\n    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n    /**\n     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.\n     */\n    constructor(address initialOwner) {\n        if (initialOwner == address(0)) {\n            revert OwnableInvalidOwner(address(0));\n        }\n        _transferOwnership(initialOwner);\n    }\n\n    /**\n     * @dev Throws if called by any account other than the owner.\n     */\n    modifier onlyOwner() {\n        _checkOwner();\n        _;\n    }\n\n    /**\n     * @dev Returns the address of the current owner.\n     */\n    function owner() public view virtual returns (address) {\n        return _owner;\n    }\n\n    /**\n     * @dev Throws if the sender is not the owner.\n     */\n    function _checkOwner() internal view virtual {\n        if (owner() != _msgSender()) {\n            revert OwnableUnauthorizedAccount(_msgSender());\n        }\n    }\n\n    /**\n     * @dev Leaves the contract without owner. It will not be possible to call\n     * `onlyOwner` functions. Can only be called by the current owner.\n     *\n     * NOTE: Renouncing ownership will leave the contract without an owner,\n     * thereby disabling any functionality that is only available to the owner.\n     */\n    function renounceOwnership() public virtual onlyOwner {\n        _transferOwnership(address(0));\n    }\n\n    /**\n     * @dev Transfers ownership of the contract to a new account (`newOwner`).\n     * Can only be called by the current owner.\n     */\n    function transferOwnership(address newOwner) public virtual onlyOwner {\n        if (newOwner == address(0)) {\n            revert OwnableInvalidOwner(address(0));\n        }\n        _transferOwnership(newOwner);\n    }\n\n    /**\n     * @dev Transfers ownership of the contract to a new account (`newOwner`).\n     * Internal function without access restriction.\n     */\n    function _transferOwnership(address newOwner) internal virtual {\n        address oldOwner = _owner;\n        _owner = newOwner;\n        emit OwnershipTransferred(oldOwner, newOwner);\n    }\n}\n"
      },
      "node_modules/@openzeppelin/contracts/access/Ownable2Step.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable2Step.sol)\n\npragma solidity ^0.8.20;\n\nimport {Ownable} from \"./Ownable.sol\";\n\n/**\n * @dev Contract module which provides access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * The initial owner is specified at deployment time in the constructor for `Ownable`. This\n * can later be changed with {transferOwnership} and {acceptOwnership}.\n *\n * This module is used through inheritance. It will make available all functions\n * from parent (Ownable).\n */\nabstract contract Ownable2Step is Ownable {\n    address private _pendingOwner;\n\n    event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);\n\n    /**\n     * @dev Returns the address of the pending owner.\n     */\n    function pendingOwner() public view virtual returns (address) {\n        return _pendingOwner;\n    }\n\n    /**\n     * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.\n     * Can only be called by the current owner.\n     */\n    function transferOwnership(address newOwner) public virtual override onlyOwner {\n        _pendingOwner = newOwner;\n        emit OwnershipTransferStarted(owner(), newOwner);\n    }\n\n    /**\n     * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.\n     * Internal function without access restriction.\n     */\n    function _transferOwnership(address newOwner) internal virtual override {\n        delete _pendingOwner;\n        super._transferOwnership(newOwner);\n    }\n\n    /**\n     * @dev The new owner accepts the ownership transfer.\n     */\n    function acceptOwnership() public virtual {\n        address sender = _msgSender();\n        if (pendingOwner() != sender) {\n            revert OwnableUnauthorizedAccount(sender);\n        }\n        _transferOwnership(sender);\n    }\n}\n"
      },
      "node_modules/@openzeppelin/contracts/utils/Context.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n    function _msgSender() internal view virtual returns (address) {\n        return msg.sender;\n    }\n\n    function _msgData() internal view virtual returns (bytes calldata) {\n        return msg.data;\n    }\n\n    function _contextSuffixLength() internal view virtual returns (uint256) {\n        return 0;\n    }\n}\n"
      },
      "src/VestingTokenFactory.sol": {
        "content": "// SPDX-License-Identifier: None\n// Unvest Contracts (last updated v3.0.0) (VestingTokenFactory.sol)\npragma solidity ^0.8.24;\n\nimport { Ownable } from \"@openzeppelin/contracts/access/Ownable2Step.sol\";\n\nimport { FactoryFeeManager } from \"./abstracts/FactoryFeeManager.sol\";\n\nimport { Errors } from \"./libraries/Errors.sol\";\nimport { IFeeManager } from \"./interfaces/IFeeManager.sol\";\nimport { IVestingToken } from \"./interfaces/IVestingToken.sol\";\n\n/*\n\n _   _  _   _ __     __ _____  ____  _____ \n| | | || \\ | |\\ \\   / /| ____|/ ___||_   _|\n| | | ||  \\| | \\ \\ / / |  _|  \\___ \\  | |  \n| |_| || |\\  |  \\ V /  | |___  ___) | | |  \n \\___/ |_| \\_|   \\_/   |_____||____/  |_|  \n                                           \n */\n\n/// @title VestingTokenFactory\n/// @dev The VestingTokenFactory contract can be used to create vesting contracts for any ERC20 token.\n/// @author JA (@ubinatus) v3\n/// @author Klaus Hott (@Janther) v2\ncontract VestingTokenFactory is FactoryFeeManager {\n    /// @param underlyingToken Address of the ERC20 that will be vest into `vestingToken`.\n    /// @param vestingToken    Address of the newly deployed `VestingToken`.\n    event VestingTokenCreated(address indexed underlyingToken, address vestingToken);\n\n    /// @notice The address that will be used as a delegate call target for `VestingToken`s.\n    address public immutable implementation;\n\n    /// @dev It will be used as the salt for create2\n    bytes32 internal _salt;\n\n    /// @dev Maps `underlyingToken`s to an array of `VestingToken`s.\n    mapping(address => address[]) internal _vestingTokensByUnderlyingToken;\n\n    /// @dev Creates a vesting token factory contract.\n    ///\n    /// Requirements:\n    ///\n    /// - `implementationAddress` has to be a contract.\n    /// - `feeCollectorAddress` can't be address 0x0.\n    /// - `transferFeePercentage` must be within minTransferFee and maxTransferFee.\n    ///\n    /// @param implementationAddress    Address of `VestingToken` contract implementation.\n    /// @param feeCollectorAddress      Address of `feeCollector`.\n    /// @param creationFeeValue         Value for `creationFee` that will be charged when deploying `VestingToken`'s.\n    /// @param transferFeePercentage    Value for `transferFeePercentage` that will be charged on `VestingToken`'s\n    /// transfers.\n    /// @param claimFeeValue            Value for `claimFee` that will be charged on `VestingToken`'s claims.\n    constructor(\n        address implementationAddress,\n        address feeCollectorAddress,\n        uint64 creationFeeValue,\n        uint64 transferFeePercentage,\n        uint64 claimFeeValue\n    )\n        Ownable(msg.sender)\n    {\n        if (implementationAddress == address(0)) revert Errors.AddressCanNotBeZero();\n\n        bytes32 seed;\n        assembly (\"memory-safe\") {\n            seed := chainid()\n        }\n        _salt = seed;\n\n        implementation = implementationAddress;\n        setFeeCollector(feeCollectorAddress);\n        scheduleGlobalCreationFee(creationFeeValue);\n        scheduleGlobalTransferFee(transferFeePercentage);\n        scheduleGlobalClaimFee(claimFeeValue);\n        _feeData.creationFee = creationFeeValue;\n        _feeData.transferFeePercentage = transferFeePercentage;\n        _feeData.claimFee = claimFeeValue;\n    }\n\n    /// @notice Increments the salt one step.\n    /// @dev In the rare case that create2 fails, this function can be used to skip that particular salt.\n    function nextSalt() public {\n        assembly {\n            // Allocate memory for the _salt value to hash\n            let ptr := mload(0x40)\n            // Store current _salt value in memory allocated\n            mstore(ptr, sload(_salt.slot))\n            // Perform keccak256 hash of the _salt value stored in memory\n            let hash := keccak256(ptr, 32)\n            // Update _salt with the new hash value\n            sstore(_salt.slot, hash)\n        }\n    }\n\n    /// @notice Creates new VestingToken contracts.\n    ///\n    /// Requirements:\n    ///\n    /// - `underlyingTokenAddress` cannot be the zero address.\n    /// - `timestamps` must be given in ascending order.\n    /// - `percentages` must be given in ascending order and the last one must always be 1 eth, where 1 eth equals to\n    /// 100%.\n    ///\n    /// @param name                   The token collection name.\n    /// @param symbol                 The token collection symbol.\n    /// @param underlyingTokenAddress The ERC20 token that will be held by this contract.\n    /// @param milestonesArray        Array of all Milestones for this Contract's lifetime.\n    function createVestingToken(\n        string calldata name,\n        string calldata symbol,\n        address underlyingTokenAddress,\n        IVestingToken.Milestone[] calldata milestonesArray\n    )\n        external\n        payable\n        returns (address vestingToken)\n    {\n        address impl = implementation;\n        bytes32 salt = _salt;\n\n        // Deploys and returns the address of a clone that mimics the behaviour of `implementation`.\n        assembly (\"memory-safe\") {\n            // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes\n            // of the `implementation` address with the bytecode before the address.\n            mstore(0x00, or(shr(0xe8, shl(0x60, impl)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))\n            // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.\n            mstore(0x20, or(shl(0x78, impl), 0x5af43d82803e903d91602b57fd5bf3))\n            vestingToken := create2(0, 0x09, 0x37, salt)\n        }\n\n        if (vestingToken == address(0)) revert Errors.FailedToDeploy();\n        nextSalt();\n\n        _processCreationFee(underlyingTokenAddress);\n\n        IVestingToken(vestingToken).initialize(name, symbol, underlyingTokenAddress, milestonesArray);\n\n        _vestingTokensByUnderlyingToken[underlyingTokenAddress].push(vestingToken);\n        emit VestingTokenCreated(underlyingTokenAddress, vestingToken);\n    }\n\n    /// @notice Exposes the whole array that `_vestingTokensByUnderlyingToken` maps.\n    function vestingTokens(address underlyingToken) external view returns (address[] memory) {\n        return _vestingTokensByUnderlyingToken[underlyingToken];\n    }\n}\n"
      },
      "src/abstracts/FactoryFeeManager.sol": {
        "content": "// SPDX-License-Identifier: None\n// Unvest Contracts (last updated v3.0.0) (FactoryFeeManager.sol)\npragma solidity ^0.8.24;\n\nimport { Ownable2Step } from \"@openzeppelin/contracts/access/Ownable2Step.sol\";\n\nimport { Errors } from \"../libraries/Errors.sol\";\nimport { IFeeManager } from \"../interfaces/IFeeManager.sol\";\nimport { IFactoryFeeManager } from \"../interfaces/IFactoryFeeManager.sol\";\n\n/// @title FactoryFeeManager\n/// @notice See the documentation in {IFactoryFeeManager}.\n/// @author JA (@ubinatus)\nabstract contract FactoryFeeManager is Ownable2Step, IFactoryFeeManager {\n    /**\n     *\n     * CONSTANTS\n     *\n     */\n\n    /// @dev Transfer fee is calculated using 18 decimals where 0.05 ether is 5%.\n    uint64 internal constant MAX_TRANSFER_FEE = 0.05 ether;\n\n    /**\n     *\n     * STATE\n     *\n     */\n\n    /// @dev Stores fee related information for collection purposes.\n    FeeData internal _feeData;\n\n    /// @dev Stores the info necessary for an upcoming change of the global creation fee.\n    UpcomingFeeData internal _upcomingCreationFee;\n\n    /// @dev Stores the info necessary for an upcoming change of the global transfer fee.\n    UpcomingFeeData internal _upcomingTransferFee;\n\n    /// @dev Stores the info necessary for an upcoming change of the global claim fee.\n    UpcomingFeeData internal _upcomingClaimFee;\n\n    /// @dev Maps `underlyingToken`s to a custom creation fee struct.\n    mapping(address => CustomFeeData) internal _creationFeeByUnderlyingToken;\n\n    /// @dev Maps `underlyingToken`s to a custom transfer fee struct.\n    mapping(address => CustomFeeData) internal _transferFeeByUnderlyingToken;\n\n    /// @dev Maps `underlyingToken`s to a custom claim fee struct.\n    mapping(address => CustomFeeData) internal _claimFeeByUnderlyingToken;\n\n    /**\n     *\n     * FUNCTIONS\n     *\n     */\n\n    /// @inheritdoc IFactoryFeeManager\n    function setFeeCollector(address newFeeCollector) public override onlyOwner {\n        if (newFeeCollector == address(0)) revert Errors.AddressCanNotBeZero();\n\n        _feeData.feeCollector = newFeeCollector;\n        emit FeeCollectorChange(newFeeCollector);\n    }\n\n    /// @inheritdoc IFactoryFeeManager\n    function scheduleGlobalCreationFee(uint64 newFeeValue) public override onlyOwner {\n        if (_upcomingCreationFee.valueChangeAt <= block.timestamp) {\n            _feeData.creationFee = _upcomingCreationFee.nextValue;\n        }\n\n        _upcomingCreationFee.nextValue = newFeeValue;\n        _upcomingCreationFee.valueChangeAt = uint64(block.timestamp + 1 hours);\n\n        emit GlobalCreationFeeChange(newFeeValue);\n    }\n\n    /// @inheritdoc IFactoryFeeManager\n    function scheduleGlobalTransferFee(uint64 newFeePercentage) public override onlyOwner {\n        if (newFeePercentage > MAX_TRANSFER_FEE) revert Errors.FeeOutOfRange();\n\n        _upcomingTransferFee.nextValue = newFeePercentage;\n        _upcomingTransferFee.valueChangeAt = uint64(block.timestamp + 1 hours);\n\n        emit GlobalTransferFeeChange(newFeePercentage);\n    }\n\n    /// @inheritdoc IFactoryFeeManager\n    function scheduleGlobalClaimFee(uint64 newFeeValue) public override onlyOwner {\n        if (_upcomingClaimFee.valueChangeAt <= block.timestamp) {\n            _feeData.claimFee = _upcomingClaimFee.nextValue;\n        }\n\n        _upcomingClaimFee.nextValue = newFeeValue;\n        _upcomingClaimFee.valueChangeAt = uint64(block.timestamp + 1 hours);\n\n        emit GlobalClaimFeeChange(newFeeValue);\n    }\n\n    /// @inheritdoc IFactoryFeeManager\n    function scheduleCustomCreationFee(address underlyingToken, uint64 newFeeValue) external override onlyOwner {\n        CustomFeeData storage customFee = _creationFeeByUnderlyingToken[underlyingToken];\n\n        if (customFee.valueChangeAt <= block.timestamp) {\n            customFee.value = customFee.nextValue;\n        }\n\n        uint64 ts = uint64(block.timestamp + 1 hours);\n\n        customFee.nextEnableState = true;\n        customFee.statusChangeAt = ts;\n        customFee.nextValue = newFeeValue;\n        customFee.valueChangeAt = ts;\n\n        emit CustomCreationFeeChange(underlyingToken, newFeeValue);\n    }\n\n    /// @inheritdoc IFactoryFeeManager\n    function scheduleCustomTransferFee(address underlyingToken, uint64 newFeePercentage) external override onlyOwner {\n        if (newFeePercentage > MAX_TRANSFER_FEE) revert Errors.FeeOutOfRange();\n\n        CustomFeeData storage customFee = _transferFeeByUnderlyingToken[underlyingToken];\n\n        if (customFee.valueChangeAt <= block.timestamp) {\n            customFee.value = customFee.nextValue;\n        }\n\n        uint64 ts = uint64(block.timestamp + 1 hours);\n\n        customFee.nextEnableState = true;\n        customFee.statusChangeAt = ts;\n        customFee.nextValue = newFeePercentage;\n        customFee.valueChangeAt = ts;\n\n        emit CustomTransferFeeChange(underlyingToken, newFeePercentage);\n    }\n\n    /// @inheritdoc IFactoryFeeManager\n    function scheduleCustomClaimFee(address underlyingToken, uint64 newFeeValue) external override onlyOwner {\n        CustomFeeData storage customFee = _claimFeeByUnderlyingToken[underlyingToken];\n\n        if (customFee.valueChangeAt <= block.timestamp) {\n            customFee.value = customFee.nextValue;\n        }\n\n        uint64 ts = uint64(block.timestamp + 1 hours);\n\n        customFee.nextEnableState = true;\n        customFee.statusChangeAt = ts;\n        customFee.nextValue = newFeeValue;\n        customFee.valueChangeAt = ts;\n\n        emit CustomClaimFeeChange(underlyingToken, newFeeValue);\n    }\n\n    /// @inheritdoc IFactoryFeeManager\n    function toggleCustomCreationFee(address underlyingToken, bool enable) external override onlyOwner {\n        CustomFeeData storage customFee = _creationFeeByUnderlyingToken[underlyingToken];\n\n        if (customFee.statusChangeAt <= block.timestamp) {\n            customFee.isEnabled = customFee.nextEnableState;\n        }\n\n        customFee.nextEnableState = enable;\n        customFee.statusChangeAt = uint64(block.timestamp + 1 hours);\n\n        emit CustomCreationFeeToggle(underlyingToken, enable);\n    }\n\n    /// @inheritdoc IFactoryFeeManager\n    function toggleCustomTransferFee(address underlyingToken, bool enable) external override onlyOwner {\n        CustomFeeData storage customFee = _transferFeeByUnderlyingToken[underlyingToken];\n\n        if (customFee.statusChangeAt <= block.timestamp) {\n            customFee.isEnabled = customFee.nextEnableState;\n        }\n\n        customFee.nextEnableState = enable;\n        customFee.statusChangeAt = uint64(block.timestamp + 1 hours);\n\n        emit CustomTransferFeeToggle(underlyingToken, enable);\n    }\n\n    /// @inheritdoc IFactoryFeeManager\n    function toggleCustomClaimFee(address underlyingToken, bool enable) external override onlyOwner {\n        CustomFeeData storage customFee = _claimFeeByUnderlyingToken[underlyingToken];\n\n        if (customFee.statusChangeAt <= block.timestamp) {\n            customFee.isEnabled = customFee.nextEnableState;\n        }\n\n        customFee.nextEnableState = enable;\n        customFee.statusChangeAt = uint64(block.timestamp + 1 hours);\n\n        emit CustomClaimFeeToggle(underlyingToken, enable);\n    }\n\n    /// @inheritdoc IFactoryFeeManager\n    function minTransferFee() external pure override returns (uint64) {\n        return 0;\n    }\n\n    /// @inheritdoc IFactoryFeeManager\n    function maxTransferFee() external pure override returns (uint64) {\n        return MAX_TRANSFER_FEE;\n    }\n\n    /// @inheritdoc IFactoryFeeManager\n    function feeCollector() external view override returns (address) {\n        return _feeData.feeCollector;\n    }\n\n    /// @inheritdoc IFactoryFeeManager\n    function globalCreationFee() external view override returns (uint64) {\n        return block.timestamp >= _upcomingCreationFee.valueChangeAt\n            ? _upcomingCreationFee.nextValue\n            : _feeData.creationFee;\n    }\n\n    /// @inheritdoc IFactoryFeeManager\n    function globalTransferFee() external view override returns (uint64) {\n        return block.timestamp >= _upcomingTransferFee.valueChangeAt\n            ? _upcomingTransferFee.nextValue\n            : _feeData.transferFeePercentage;\n    }\n\n    /// @inheritdoc IFactoryFeeManager\n    function globalClaimFee() external view override returns (uint64) {\n        return block.timestamp >= _upcomingClaimFee.valueChangeAt ? _upcomingClaimFee.nextValue : _feeData.claimFee;\n    }\n\n    /// @inheritdoc IFeeManager\n    function creationFeeData(address underlyingToken)\n        external\n        view\n        returns (address feeCollectorAddress, uint64 creationFeeValue)\n    {\n        feeCollectorAddress = _feeData.feeCollector;\n        creationFeeValue =\n            _getCurrentFee(_feeData.creationFee, _upcomingCreationFee, _creationFeeByUnderlyingToken[underlyingToken]);\n    }\n\n    /// @notice Returns the current transfer fee for a specific underlying token, considering any pending updates.\n    /// @param underlyingToken Address of the `underlyingToken`.\n    function transferFeeData(address underlyingToken)\n        external\n        view\n        returns (address feeCollectorAddress, uint64 transferFeePercentage)\n    {\n        feeCollectorAddress = _feeData.feeCollector;\n        transferFeePercentage = _getCurrentFee(\n            _feeData.transferFeePercentage, _upcomingTransferFee, _transferFeeByUnderlyingToken[underlyingToken]\n        );\n    }\n\n    /// @notice Returns the current claim fee for a specific underlying token, considering any pending updates.\n    /// @param underlyingToken Address of the `underlyingToken`.\n    function claimFeeData(address underlyingToken)\n        external\n        view\n        returns (address feeCollectorAddress, uint64 claimFeeValue)\n    {\n        feeCollectorAddress = _feeData.feeCollector;\n        claimFeeValue =\n            _getCurrentFee(_feeData.claimFee, _upcomingClaimFee, _claimFeeByUnderlyingToken[underlyingToken]);\n    }\n\n    /// @notice Calculates the current fee based on global, custom, and upcoming fee data.\n    /// @dev This function considers the current timestamp and determines the appropriate fee\n    /// based on whether a custom or upcoming fee should be applied.\n    /// @param globalValue The default global fee value used when no custom fees are applicable.\n    /// @param upcomingGlobalFee A struct containing data about an upcoming fee change, including the timestamp\n    /// for the change and the new value to be applied.\n    /// @param customFee A struct containing data about the custom fee, including its enablement status,\n    /// timestamps for changes, and its values (current and new).\n    /// @return currentValue The calculated current fee value, taking into account the global value,\n    /// custom fee, and upcoming fee data based on the current timestamp.\n    function _getCurrentFee(\n        uint64 globalValue,\n        UpcomingFeeData memory upcomingGlobalFee,\n        CustomFeeData memory customFee\n    )\n        internal\n        view\n        returns (uint64 currentValue)\n    {\n        if (block.timestamp >= customFee.statusChangeAt) {\n            // If isCustomFee is true based on status, directly return the value based on the customFee conditions.\n            if (customFee.nextEnableState) {\n                return block.timestamp >= customFee.valueChangeAt ? customFee.nextValue : customFee.value;\n            }\n        } else if (customFee.isEnabled) {\n            // This block handles the case where current timestamp is not past statusChangeAt, but custom is enabled.\n            return block.timestamp >= customFee.valueChangeAt ? customFee.nextValue : customFee.value;\n        }\n\n        // If none of the custom fee conditions apply, return the global or upcoming fee value.\n        return block.timestamp >= upcomingGlobalFee.valueChangeAt ? upcomingGlobalFee.nextValue : globalValue;\n    }\n\n    /// @notice Processes the creation fee for a transaction.\n    /// @dev This function retrieves the creation fee data from the manager contract and, if the creation fee is greater\n    /// than zero, sends the `msg.value` to the fee collector address. Reverts if the transferred value is less than the\n    /// required creation fee or if the transfer fails.\n    function _processCreationFee(address underlyingToken) internal {\n        uint64 creationFeeValue =\n            _getCurrentFee(_feeData.creationFee, _upcomingCreationFee, _creationFeeByUnderlyingToken[underlyingToken]);\n\n        if (creationFeeValue != 0) {\n            if (msg.value < creationFeeValue) revert Errors.InsufficientCreationFee();\n\n            bytes4 unsuccessfulClaimFeeTransfer = Errors.UnsuccessfulCreationFeeTransfer.selector;\n            address feeCollectorAddress = _feeData.feeCollector;\n\n            assembly {\n                let ptr := mload(0x40)\n                let sendSuccess := call(gas(), feeCollectorAddress, callvalue(), 0x00, 0x00, 0x00, 0x00)\n                if iszero(sendSuccess) {\n                    mstore(ptr, unsuccessfulClaimFeeTransfer)\n                    revert(ptr, 0x04)\n                }\n            }\n        }\n    }\n}\n"
      },
      "src/interfaces/IFactoryFeeManager.sol": {
        "content": "// SPDX-License-Identifier: None\n// Unvest Contracts (last updated v3.0.0) (interfaces/IFactoryFeeManager.sol)\npragma solidity ^0.8.24;\n\nimport { IFeeManager } from \"./IFeeManager.sol\";\n\n/// @title IFactoryFeeManager\n/// @dev Interface that describes the struct and accessor function for the data related to the collection of fees.\ninterface IFactoryFeeManager is IFeeManager {\n    /**\n     *\n     * EVENTS\n     *\n     */\n\n    /// @param feeCollector Address of the new fee collector.\n    event FeeCollectorChange(address indexed feeCollector);\n\n    /// @param creationFeeValue Value for the new creation fee.\n    event GlobalCreationFeeChange(uint64 creationFeeValue);\n\n    /// @param transferFeePercentage Value for the new transfer fee.\n    event GlobalTransferFeeChange(uint64 transferFeePercentage);\n\n    /// @param claimFeeValue Value for the new claim fee.\n    event GlobalClaimFeeChange(uint64 claimFeeValue);\n\n    /// @param underlyingToken Address of the underlying token.\n    /// @param creationFeeValue Value for the new creation fee.\n    event CustomCreationFeeChange(address indexed underlyingToken, uint64 creationFeeValue);\n\n    /// @param underlyingToken Address of the underlying token.\n    /// @param enable Indicates the enabled state of the fee.\n    event CustomCreationFeeToggle(address indexed underlyingToken, bool enable);\n\n    /// @param underlyingToken Address of the underlying token.\n    /// @param transferFeePercentage Value for the new transfer fee.\n    event CustomTransferFeeChange(address indexed underlyingToken, uint64 transferFeePercentage);\n\n    /// @param underlyingToken Address of the underlying token.\n    /// @param enable Indicates the enabled state of the fee.\n    event CustomTransferFeeToggle(address indexed underlyingToken, bool enable);\n\n    /// @param underlyingToken Address of the underlying token.\n    /// @param claimFeeValue Value for the new claim fee.\n    event CustomClaimFeeChange(address indexed underlyingToken, uint64 claimFeeValue);\n\n    /// @param underlyingToken Address of the underlying token.\n    /// @param enable Indicates the enabled state of the fee.\n    event CustomClaimFeeToggle(address indexed underlyingToken, bool enable);\n\n    /**\n     *\n     * FUNCTIONS\n     *\n     */\n\n    /// @dev Set address of fee collector.\n    ///\n    /// Requirements:\n    ///\n    /// - `msg.sender` has to be the owner of the contract.\n    /// - `newFeeCollector` can't be address 0x0.\n    ///\n    /// @param newFeeCollector Address of `feeCollector`.\n    ///\n    function setFeeCollector(address newFeeCollector) external;\n\n    /// @notice Sets a new global creation fee value, to take effect after 1 hour.\n    /// @param newFeeValue Value for `creationFee` that will be charged on `VestingToken`'s deployments.\n    function scheduleGlobalCreationFee(uint64 newFeeValue) external;\n\n    /// @notice Sets a new global transfer fee percentage, to take effect after 1 hour.\n    ///\n    /// @dev Percentages and fees are calculated using 18 decimals where 1 ether is 100%.\n    ///\n    /// Requirements:\n    ///\n    /// - `newFeePercentage` must be within minTransferFee and maxTransferFee.\n    ///\n    /// @param newFeePercentage Value for `transferFeePercentage` that will be charged on `VestingToken`'s transfers.\n    function scheduleGlobalTransferFee(uint64 newFeePercentage) external;\n\n    /// @notice Sets a new global claim fee value, to take effect after 1 hour.\n    /// @param newFeeValue Value for `claimFee` that will be charged on `VestingToken`'s claims.\n    function scheduleGlobalClaimFee(uint64 newFeeValue) external;\n\n    /// @notice Sets a new custom creation fee value for a specific underlying token, to be enabled and take effect\n    /// after 1 hour from the time of this transaction.\n    ///\n    /// @dev Allows the contract owner to modify the creation fee associated with a specific underlying token.\n    /// The new fee becomes effective after a delay of 1 hour, aiming to provide a buffer for users to be aware of the\n    /// upcoming fee change.\n    /// This function updates the fee and schedules its activation, ensuring transparency and predictability in fee\n    /// adjustments.\n    /// The fee is specified in wei, allowing for granular control over the fee structure. Emits a\n    /// `CustomCreationFeeChange` event upon successful fee update.\n    ///\n    /// Requirements:\n    /// - The caller must have owner privileges to execute this function.\n    ///\n    /// @param underlyingToken Address of the `underlyingToken`.\n    /// @param newFeeValue The new creation fee amount to be set, in wei, to replace the current fee after the specified\n    /// delay.\n    function scheduleCustomCreationFee(address underlyingToken, uint64 newFeeValue) external;\n\n    /// @notice Sets a new custom transfer fee percentage for a specific underlying token, to be enabled and take effect\n    /// after 1 hour from the time of this transaction.\n    ///\n    /// @dev This function allows the contract owner to adjust the transfer fee for an underlying token.\n    /// The fee adjustment is delayed by 1 hour to provide transparency and predictability. Fees are calculated with\n    /// precision to 18 decimal places, where 1 ether equals 100% fee.\n    /// The function enforces fee limits; `newFeePercentage` must be within the predefined 0-`MAX_TRANSFER_FEE` bounds.\n    /// If the custom fee was previously disabled or set to a different value, this operation schedules the new fee to\n    /// take effect after the delay, enabling it if necessary.\n    /// Emits a `CustomTransferFeeChange` event upon successful execution.\n    ///\n    /// Requirements:\n    /// - Caller must be the contract owner.\n    /// - `newFeePercentage` must be within the range limited by `MAX_TRANSFER_FEE`.\n    ///\n    /// @param underlyingToken Address of the `underlyingToken`.\n    /// @param newFeePercentage The new transfer fee percentage to be applied, expressed in ether terms (18 decimal\n    /// places) where 1 ether represents 100%.\n    function scheduleCustomTransferFee(address underlyingToken, uint64 newFeePercentage) external;\n\n    /// @notice Sets a new custom claim fee value for a specific underlying token, to be enabled and take effect\n    /// after 1 hour from the time of this transaction.\n    ///\n    /// @dev Allows the contract owner to modify the claim fee associated with a specific underlying token.\n    /// The new fee becomes effective after a delay of 1 hour, aiming to provide a buffer for users to be aware of the\n    /// upcoming fee change.\n    /// This function updates the fee and schedules its activation, ensuring transparency and predictability in fee\n    /// adjustments.\n    /// The fee is specified in wei, allowing for granular control over the fee structure. Emits a\n    /// `CustomClaimFeeChange` event upon successful fee update.\n    ///\n    /// Requirements:\n    /// - The caller must have owner privileges to execute this function.\n    ///\n    /// @param underlyingToken Address of the `underlyingToken`.\n    /// @param newFeeValue The new claim fee amount to be set, in wei, to replace the current fee after the specified\n    /// delay.\n    function scheduleCustomClaimFee(address underlyingToken, uint64 newFeeValue) external;\n    /// @notice Enables or disables the custom creation fee for a given underlying token, with the change taking effect\n    /// after 1 hour.\n    /// @param underlyingToken Address of the `underlyingToken`.\n    /// @param enable True to enable the fee, false to disable it.\n    function toggleCustomCreationFee(address underlyingToken, bool enable) external;\n\n    /// @notice Enables or disables the custom transfer fee for a given underlying token, to take effect after 1 hour.\n    ///\n    /// @param underlyingToken Address of the `underlyingToken`.\n    /// @param enable True to enable the fee, false to disable it.\n    function toggleCustomTransferFee(address underlyingToken, bool enable) external;\n\n    /// @notice Enables or disables the custom claim fee for a given underlying token, with the change taking effect\n    /// after 1 hour.\n    /// @param underlyingToken Address of the `underlyingToken`.\n    /// @param enable True to enable the fee, false to disable it.\n    function toggleCustomClaimFee(address underlyingToken, bool enable) external;\n\n    /// @dev Exposes the minimum transfer fee.\n    function minTransferFee() external pure returns (uint64);\n\n    /// @dev Exposes the maximum transfer fee.\n    function maxTransferFee() external pure returns (uint64);\n\n    /// @notice Exposes the `FeeData.feeCollector` to users.\n    function feeCollector() external view returns (address);\n\n    /// @notice Retrieves the current global creation fee to users.\n    function globalCreationFee() external view returns (uint64);\n\n    /// @notice Retrieves the current global transfer fee percentage to users.\n    function globalTransferFee() external view returns (uint64);\n\n    /// @notice Retrieves the current global claim fee to users.\n    function globalClaimFee() external view returns (uint64);\n\n    /// @notice Returns the current creation fee for a specific underlying token, considering any pending updates.\n    /// @param underlyingToken Address of the `underlyingToken`.\n    function creationFeeData(address underlyingToken)\n        external\n        view\n        override\n        returns (address feeCollectorAddress, uint64 creationFeeValue);\n\n    /// @notice Returns the current transfer fee for a specific underlying token, considering any pending updates.\n    /// @param underlyingToken Address of the `underlyingToken`.\n    function transferFeeData(address underlyingToken)\n        external\n        view\n        override\n        returns (address feeCollectorAddress, uint64 transferFeePercentage);\n\n    /// @notice Returns the current claim fee for a specific underlying token, considering any pending updates.\n    /// @param underlyingToken Address of the `underlyingToken`.\n    function claimFeeData(address underlyingToken)\n        external\n        view\n        override\n        returns (address feeCollectorAddress, uint64 claimFeeValue);\n}\n"
      },
      "src/interfaces/IFeeManager.sol": {
        "content": "// SPDX-License-Identifier: None\n// Unvest Contracts (last updated v3.0.0) (interfaces/IFeeManager.sol)\npragma solidity ^0.8.24;\n\n/// @title IFeeManager\n/// @dev Interface that describes the struct and accessor function for the data related to the collection of fees.\ninterface IFeeManager {\n    /// @dev The `FeeData` struct is used to store fee configurations such as the collection address and fee amounts for\n    /// various transaction types in the contract.\n    struct FeeData {\n        /// @notice The address designated to collect fees.\n        /// @dev This address is responsible for receiving fees generated from various sources.\n        address feeCollector;\n        /// @notice The fixed fee amount required to be sent as value with each `createVestingToken` operation.\n        /// @dev `creationFee` is denominated in the smallest unit of the token. It must be sent as the transaction\n        /// value during the execution of the payable `createVestingToken` function.\n        uint64 creationFee;\n        /// @notice The transfer fee expressed in ether, where 0.01 ether corresponds to a 1% fee.\n        /// @dev `transferFeePercentage` is not in basis points but in ether units, with each ether unit representing a\n        /// percentage of the transaction value to be collected as a fee. This structure allows for flexible and easily\n        /// understandable fee calculations for `transfer` and `transferFrom` operations.\n        uint64 transferFeePercentage;\n        /// @notice The fixed fee amount required to be sent as value with each `claim` operation.\n        /// @dev `claimFee` is denominated in the smallest unit of the token. It must be sent as the transaction value\n        /// during the execution of the payable `claim` function.\n        uint64 claimFee;\n    }\n\n    /// @dev Stores global fee data upcoming change and timestamp for that change.\n    struct UpcomingFeeData {\n        /// @notice The new fee value in wei to be applied at `valueChangeAt`.\n        uint64 nextValue;\n        /// @notice Timestamp at which a new fee value becomes effective.\n        uint64 valueChangeAt;\n    }\n\n    /// @dev Stores custom fee data, including its current state, upcoming changes, and the timestamps for those\n    /// changes.\n    struct CustomFeeData {\n        /// @notice Indicates if the custom fee is currently enabled.\n        bool isEnabled;\n        /// @notice The current fee value in wei.\n        uint64 value;\n        /// @notice The new fee value in wei to be applied at `valueChangeAt`.\n        uint64 nextValue;\n        /// @notice Timestamp at which a new fee value becomes effective.\n        uint64 valueChangeAt;\n        /// @notice Indicates the future state of `isEnabled` after `statusChangeAt`.\n        bool nextEnableState;\n        /// @notice Timestamp at which the change to `isEnabled` becomes effective.\n        uint64 statusChangeAt;\n    }\n\n    /// @notice Exposes the creation fee for new `VestingToken`s deployments.\n    /// @param underlyingToken Address of the `underlyingToken`.\n    /// @dev Enabled custom fees overrides the global creation fee.\n    function creationFeeData(address underlyingToken)\n        external\n        view\n        returns (address feeCollector, uint64 creationFeeValue);\n\n    /// @notice Exposes the transfer fee for `VestingToken`s to consume.\n    /// @param underlyingToken Address of the `underlyingToken`.\n    /// @dev Enabled custom fees overrides the global transfer fee.\n    function transferFeeData(address underlyingToken)\n        external\n        view\n        returns (address feeCollector, uint64 transferFeePercentage);\n\n    /// @notice Exposes the claim fee for `VestingToken`s to consume.\n    /// @param underlyingToken Address of the `underlyingToken`.\n    /// @dev Enabled custom fees overrides the global claim fee.\n    function claimFeeData(address underlyingToken) external view returns (address feeCollector, uint64 claimFeeValue);\n}\n"
      },
      "src/interfaces/IVestingToken.sol": {
        "content": "// SPDX-License-Identifier: None\n// Unvest Contracts (last updated v3.0.0) (interfaces/IVestingToken.sol)\npragma solidity ^0.8.24;\n\n/// @title IVestingToken\n/// @dev Interface that describes the Milestone struct and initialize function so the `VestingTokenFactory` knows how to\n/// initialize the `VestingToken`.\ninterface IVestingToken {\n    /// @dev Ramps describes how the periods between release tokens.\n    ///     - Cliff releases nothing until the end of the period.\n    ///     - Linear releases tokens every second according to a linear slope.\n    ///\n    /// (0) Cliff             (1) Linear\n    ///  |                     |\n    ///  |        _____        |        _____\n    ///  |       |             |       /\n    ///  |       |             |      /\n    ///  |_______|_____        |_____/_______\n    ///      T0   T1               T0   T1\n    ///\n    enum Ramp {\n        Cliff,\n        Linear\n    }\n\n    /// @dev `timestamp` represents a moment in time when this Milestone is considered expired.\n    /// @dev `ramp` defines the behaviour of the release of tokens in period between the previous Milestone and the\n    /// current one.\n    /// @dev `percentage` is the percentage of tokens that should be released once this Milestone has expired.\n    struct Milestone {\n        uint64 timestamp;\n        Ramp ramp;\n        uint64 percentage;\n    }\n\n    /// @notice Initializes the contract by setting up the ERC20 variables, the `underlyingToken`, and the\n    /// `milestonesArray` information.\n    ///\n    /// @param name                   The token collection name.\n    /// @param symbol                 The token collection symbol.\n    /// @param underlyingTokenAddress The ERC20 token that will be held by this contract.\n    /// @param milestonesArray        Array of all Milestones for this Contract's lifetime.\n    function initialize(\n        string memory name,\n        string memory symbol,\n        address underlyingTokenAddress,\n        Milestone[] calldata milestonesArray\n    )\n        external;\n}\n"
      },
      "src/libraries/Errors.sol": {
        "content": "// SPDX-License-Identifier: None\n// Unvest Contracts (last updated v3.0.0) (libraries/Errors.sol)\npragma solidity ^0.8.24;\n\n/// @title Errors Library\n/// @notice Provides custom errors for VestingTokenFactory and VestingToken contracts.\nlibrary Errors {\n    /*//////////////////////////////////////////////////////\n                      VestingTokenFactory\n    //////////////////////////////////////////////////////*/\n\n    /// @notice Error to indicate that an address cannot be the zero address.\n    error AddressCanNotBeZero();\n\n    /// @notice Error to indicate that deployment of a contract failed.\n    error FailedToDeploy();\n\n    /// @notice Error to indicate that a fee is out of the accepted range.\n    error FeeOutOfRange();\n\n    /// @notice Error to indicate that the creation fee is insufficient.\n    error InsufficientCreationFee();\n\n    /// @notice Error to indicate an unsuccessful transfer of the creation fee.\n    error UnsuccessfulCreationFeeTransfer();\n\n    /*//////////////////////////////////////////////////////\n                      VestingToken\n    //////////////////////////////////////////////////////*/\n\n    /// @notice Error to indicate that the minimum number of milestones has not been reached.\n    error MinMilestonesNotReached();\n\n    /// @notice Error to indicate that the maximum number of milestones has been exceeded.\n    error MaxAllowedMilestonesHit();\n\n    /// @notice Error to indicate that the claimable amount of an import is greater than expected.\n    error ClaimableAmountOfImportIsGreaterThanExpected();\n\n    /// @notice Error to indicate that equal percentages are only allowed before setting up linear milestones.\n    error EqualPercentagesOnlyAllowedBeforeLinear();\n\n    /// @notice Error to indicate that the sum of all individual amounts is not equal to the `totalAmount`.\n    error InvalidTotalAmount();\n\n    /// @notice Error to indicate that input arrays must have the same length.\n    error InputArraysMustHaveSameLength();\n\n    /// @notice Error to indicate that the last percentage in a milestone must be 100.\n    error LastPercentageMustBe100();\n\n    /// @notice Error to indicate that milestone percentages are not sorted in ascending order.\n    error MilestonePercentagesNotSorted();\n\n    /// @notice Error to indicate that milestone timestamps are not sorted in ascending chronological order.\n    error MilestoneTimestampsNotSorted();\n\n    /// @notice Error to indicate that there are more than two equal percentages, which is not allowed.\n    error MoreThanTwoEqualPercentages();\n\n    /// @notice Error to indicate that only the last percentage in a series can be 100.\n    error OnlyLastPercentageCanBe100();\n\n    /// @notice Error to indicate that the amount unlocked is greater than expected.\n    error UnlockedIsGreaterThanExpected();\n\n    /// @notice Error to indicate an unsuccessful fetch of token balance.\n    error UnsuccessfulFetchOfTokenBalance();\n\n    /// @notice Error to indicate that the claim fee provided does not match the expected claim fee.\n    error IncorrectClaimFee();\n\n    /// @notice Error to indicate an unsuccessful transfer of the claim fee.\n    error UnsuccessfulClaimFeeTransfer();\n\n    /// @notice Error to indicate that there is no balance available to claim.\n    error NoClaimableAmount();\n}\n"
      }
    }
  }}