false
true
0

Contract Address Details

0x47810bb3ECDc6b080CeB2d39E769F21Ff14AB7E9

Token
HEX Time Token 7000 (HTT-7000)
Creator
0x0d5d61–740206 at 0xdc3211–f8c0e5
Balance
0 PLS ( )
Tokens
Fetching tokens...
Transactions
1,924 Transactions
Transfers
2,448 Transfers
Gas Used
78,170,169
Last Balance Update
25875313
Contract is not verified. However, we found a verified contract with the same bytecode in Blockscout DB 0xe9e1340a2b31d5d2a2db28fb854a794e106b430a.
All metadata displayed below is from that contract. In order to verify current contract, click Verify & Publish button
Verify & Publish
Contract name:
HEXTimeToken




Optimization enabled
true
Compiler version
v0.8.24+commit.e11b9ed9




Optimization runs
200
Verified at
2024-10-08T03:41:38.466827Z

contracts/HEXTimeToken.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

// import "hardhat/console.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { SafeERC20 } from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
import { Actuator } from "./Actuator.sol"; 
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";

contract HEXTimeToken is ERC20 {
    uint256 public constant CREATION_FEE_RATE = 100; // 100 basis points tax, i.e., 1%
    uint256 private constant ACC_HTT_PRECISION = 1e22; // Original was 1e12. However, HEX has 8 decimals. So 1e22 is used to preserve the relative precision.

    // Info of each user.
    struct UserInfo {
        uint256 amount;     // How many ACTR tokens the user has deposited.
        uint256 rewardDebt; 
        uint256 capitalAdded; // deposit start time
    }

    Actuator public actr;
    uint256 public totalDeposits;
    uint256 public accHttPerShare;
    uint16 public maturity;
    address public httManager;

    // Info of each user that deposits ACTR.
    mapping (address => UserInfo) public userInfo;

    event Deposit(address indexed user, uint256 amount);
    event Withdraw(address indexed user, uint256 amount);
    event CollectFees(address indexed user, uint256 amount);

    constructor(
        uint16 _maturity,
        address actuatorAddress
    ) 
        ERC20(string.concat("HEX Time Token ", Strings.toString(_maturity)), string.concat("HTT-", Strings.toString(_maturity))) 
    {      
        actr = Actuator(actuatorAddress);
        maturity = _maturity;
        httManager = msg.sender;
    }

    modifier onlyHttManager() {
        require(msg.sender == httManager, "A036");
        _;
    }

    modifier onlyActuator() {
        require(msg.sender == address(actr), "A039");
        _;
    }

    function decimals() public view virtual override returns (uint8) {
        return 8;
    }

    /**
     * @dev Priveleged function for the HTT Manager to mint HEX Time Tokens (HTT) and collect a fee/tax.
     * @param to Recipient address.
     * @param amount Amount of HTTs to transfer.
    */
    function mint(address to, uint256 amount) external onlyHttManager {
        if (totalDeposits == 0) {
            _mint(to, amount);
            return;
        }

        uint256 taxAmount = calculateTax(amount);
        uint256 amountAfterTax = amount - taxAmount;
        accHttPerShare = accHttPerShare + (taxAmount * ACC_HTT_PRECISION / totalDeposits);
        _mint(address(this), taxAmount);
        _mint(to, amountAfterTax);
    }

    /**
     * @dev Priveleged function for the HTT Manager to burn HEX Time Tokens (HTT).
     * @param from Address to burn from.
     * @param amount Amount of HTTs to burn.
    */
    function burn(address from, uint256 amount) external onlyHttManager {
        _burn(from, amount);
    }

    /**
     * @dev Calculates the tax for a given input amount.
     * @param amount Amount of HTTs to apply tax.
    */
    function calculateTax(uint256 amount) public pure returns (uint256) {
        return amount * CREATION_FEE_RATE / 10000; // Assumes CREATION_FEE_RATE is in basis points
    }

    /**
     * @dev Deposit ACTR to collect HEX Time Token (HTT) tax.
     * @param account Address of the depositer.
     * @param _amount Amount of ACTR to deposit.
    */
    function deposit(address account, uint256 _amount) external onlyActuator returns (uint256) {
        UserInfo storage user = userInfo[account];

        totalDeposits += _amount;

        uint256 pending = (user.amount * accHttPerShare / ACC_HTT_PRECISION) - user.rewardDebt;

        user.amount = user.amount + _amount;
        user.rewardDebt = user.amount * accHttPerShare / ACC_HTT_PRECISION;
        user.capitalAdded = block.timestamp;

        if (pending > 0) {
            safeHttTransfer(account, pending);
            emit CollectFees(account, pending);
        }

        emit Deposit(account, _amount);

        return user.amount;
    }

    /**
     * @dev Withdraw ACTR from vault.
     * @param account Address of the depositer.
     * @param _amount Amount of ACTR to withdraw.
    */
    function withdraw(address account, uint256 _amount) external onlyActuator returns (uint256, uint256) {  
        UserInfo storage user = userInfo[account];

        require(user.amount >= _amount, "A037");

        totalDeposits -= _amount;

        uint256 pending = (user.amount * accHttPerShare / ACC_HTT_PRECISION) - user.rewardDebt;

        user.amount = user.amount - _amount;
        user.rewardDebt = user.amount * accHttPerShare / ACC_HTT_PRECISION;

        if (pending > 0) {
            safeHttTransfer(account, pending);
            emit CollectFees(account, pending);
        }
        
        emit Withdraw(account, _amount);

        return (user.amount, user.capitalAdded);
    }

    /**
     * @dev Collect accumulated HTT rewards from tax.
     * @return Amount of HTTs collected.
    */
    function collectFees() external returns (uint256) {  
        UserInfo storage user = userInfo[msg.sender];

        require(user.amount > 0, "A026");

        uint256 pending = (user.amount * accHttPerShare / ACC_HTT_PRECISION) - user.rewardDebt;
        
        user.rewardDebt = user.amount * accHttPerShare / ACC_HTT_PRECISION;

        if (pending > 0) {
            safeHttTransfer(msg.sender, pending);
            emit CollectFees(msg.sender, pending);
        }

        return pending;
    }

    /**
     * @dev Safe HTT transfer function, just in case if rounding error causes pool to not have enough HTT.
     * @param _to Recipient address.
     * @param _amount Amount of HTT tokens to transfer.
    */
    function safeHttTransfer(address _to, uint256 _amount) private {
        uint256 httBal = balanceOf(address(this));
        if (_amount > httBal) {
            _transfer(address(this), _to, httBal);
        } else {
            _transfer(address(this), _to, _amount);
        }
    }

}
        

@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using Address for address;

    /**
     * @dev An operation with an ERC20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data);
        if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

        (bool success, bytes memory returndata) = address(token).call(data);
        return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
    }
}
          

@openzeppelin/contracts/utils/Address.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)

pragma solidity ^0.8.20;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev The ETH balance of the account is not enough to perform the operation.
     */
    error AddressInsufficientBalance(address account);

    /**
     * @dev There's no code at `target` (it is not a contract).
     */
    error AddressEmptyCode(address target);

    /**
     * @dev A call to an address target failed. The target may have reverted.
     */
    error FailedInnerCall();

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        if (address(this).balance < amount) {
            revert AddressInsufficientBalance(address(this));
        }

        (bool success, ) = recipient.call{value: amount}("");
        if (!success) {
            revert FailedInnerCall();
        }
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason or custom error, it is bubbled
     * up by this function (like regular Solidity function calls). However, if
     * the call reverted with no returned reason, this function reverts with a
     * {FailedInnerCall} error.
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        if (address(this).balance < value) {
            revert AddressInsufficientBalance(address(this));
        }
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
     * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
     * unsuccessful call.
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata
    ) internal view returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            // only check if target is a contract if the call was successful and the return data is empty
            // otherwise we already know that it was a contract
            if (returndata.length == 0 && target.code.length == 0) {
                revert AddressEmptyCode(target);
            }
            return returndata;
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
     * revert reason or with a default {FailedInnerCall} error.
     */
    function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            return returndata;
        }
    }

    /**
     * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
     */
    function _revert(bytes memory returndata) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert FailedInnerCall();
        }
    }
}
          

@openzeppelin/contracts/interfaces/draft-IERC6093.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC6093.sol)
pragma solidity ^0.8.20;

/**
 * @dev Standard ERC20 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens.
 */
interface IERC20Errors {
    /**
     * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     * @param balance Current balance for the interacting account.
     * @param needed Minimum amount required to perform a transfer.
     */
    error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);

    /**
     * @dev Indicates a failure with the token `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     */
    error ERC20InvalidSender(address sender);

    /**
     * @dev Indicates a failure with the token `receiver`. Used in transfers.
     * @param receiver Address to which tokens are being transferred.
     */
    error ERC20InvalidReceiver(address receiver);

    /**
     * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.
     * @param spender Address that may be allowed to operate on tokens without being their owner.
     * @param allowance Amount of tokens a `spender` is allowed to operate with.
     * @param needed Minimum amount required to perform a transfer.
     */
    error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);

    /**
     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
     * @param approver Address initiating an approval operation.
     */
    error ERC20InvalidApprover(address approver);

    /**
     * @dev Indicates a failure with the `spender` to be approved. Used in approvals.
     * @param spender Address that may be allowed to operate on tokens without being their owner.
     */
    error ERC20InvalidSpender(address spender);
}

/**
 * @dev Standard ERC721 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens.
 */
interface IERC721Errors {
    /**
     * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20.
     * Used in balance queries.
     * @param owner Address of the current owner of a token.
     */
    error ERC721InvalidOwner(address owner);

    /**
     * @dev Indicates a `tokenId` whose `owner` is the zero address.
     * @param tokenId Identifier number of a token.
     */
    error ERC721NonexistentToken(uint256 tokenId);

    /**
     * @dev Indicates an error related to the ownership over a particular token. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     * @param tokenId Identifier number of a token.
     * @param owner Address of the current owner of a token.
     */
    error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);

    /**
     * @dev Indicates a failure with the token `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     */
    error ERC721InvalidSender(address sender);

    /**
     * @dev Indicates a failure with the token `receiver`. Used in transfers.
     * @param receiver Address to which tokens are being transferred.
     */
    error ERC721InvalidReceiver(address receiver);

    /**
     * @dev Indicates a failure with the `operator`’s approval. Used in transfers.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     * @param tokenId Identifier number of a token.
     */
    error ERC721InsufficientApproval(address operator, uint256 tokenId);

    /**
     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
     * @param approver Address initiating an approval operation.
     */
    error ERC721InvalidApprover(address approver);

    /**
     * @dev Indicates a failure with the `operator` to be approved. Used in approvals.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     */
    error ERC721InvalidOperator(address operator);
}

/**
 * @dev Standard ERC1155 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens.
 */
interface IERC1155Errors {
    /**
     * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     * @param balance Current balance for the interacting account.
     * @param needed Minimum amount required to perform a transfer.
     * @param tokenId Identifier number of a token.
     */
    error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);

    /**
     * @dev Indicates a failure with the token `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     */
    error ERC1155InvalidSender(address sender);

    /**
     * @dev Indicates a failure with the token `receiver`. Used in transfers.
     * @param receiver Address to which tokens are being transferred.
     */
    error ERC1155InvalidReceiver(address receiver);

    /**
     * @dev Indicates a failure with the `operator`’s approval. Used in transfers.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     * @param owner Address of the current owner of a token.
     */
    error ERC1155MissingApprovalForAll(address operator, address owner);

    /**
     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
     * @param approver Address initiating an approval operation.
     */
    error ERC1155InvalidApprover(address approver);

    /**
     * @dev Indicates a failure with the `operator` to be approved. Used in approvals.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     */
    error ERC1155InvalidOperator(address operator);

    /**
     * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.
     * Used in batch transfers.
     * @param idsLength Length of the array of token identifiers
     * @param valuesLength Length of the array of token amounts
     */
    error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);
}
          

@openzeppelin/contracts/token/ERC20/ERC20.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/ERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * The default value of {decimals} is 18. To change this, you should override
 * this function so it returns a different value.
 *
 * We have followed general OpenZeppelin Contracts guidelines: functions revert
 * instead returning `false` on failure. This behavior is nonetheless
 * conventional and does not conflict with the expectations of ERC20
 * applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 */
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
    mapping(address account => uint256) private _balances;

    mapping(address account => mapping(address spender => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * All two of these values are immutable: they can only be set once during
     * construction.
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5.05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the default value returned by this function, unless
     * it's overridden.
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual returns (uint8) {
        return 18;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - the caller must have a balance of at least `value`.
     */
    function transfer(address to, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, value);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
     * `transferFrom`. This is semantically equivalent to an infinite approval.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, value);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * NOTE: Does not update the allowance if the current allowance
     * is the maximum `uint256`.
     *
     * Requirements:
     *
     * - `from` and `to` cannot be the zero address.
     * - `from` must have a balance of at least `value`.
     * - the caller must have allowance for ``from``'s tokens of at least
     * `value`.
     */
    function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, value);
        _transfer(from, to, value);
        return true;
    }

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to`.
     *
     * This internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _transfer(address from, address to, uint256 value) internal {
        if (from == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        if (to == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(from, to, value);
    }

    /**
     * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
     * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
     * this function.
     *
     * Emits a {Transfer} event.
     */
    function _update(address from, address to, uint256 value) internal virtual {
        if (from == address(0)) {
            // Overflow check required: The rest of the code assumes that totalSupply never overflows
            _totalSupply += value;
        } else {
            uint256 fromBalance = _balances[from];
            if (fromBalance < value) {
                revert ERC20InsufficientBalance(from, fromBalance, value);
            }
            unchecked {
                // Overflow not possible: value <= fromBalance <= totalSupply.
                _balances[from] = fromBalance - value;
            }
        }

        if (to == address(0)) {
            unchecked {
                // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
                _totalSupply -= value;
            }
        } else {
            unchecked {
                // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
                _balances[to] += value;
            }
        }

        emit Transfer(from, to, value);
    }

    /**
     * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
     * Relies on the `_update` mechanism
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _mint(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(address(0), account, value);
    }

    /**
     * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
     * Relies on the `_update` mechanism.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead
     */
    function _burn(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        _update(account, address(0), value);
    }

    /**
     * @dev Sets `value` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     *
     * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
     */
    function _approve(address owner, address spender, uint256 value) internal {
        _approve(owner, spender, value, true);
    }

    /**
     * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
     *
     * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
     * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
     * `Approval` event during `transferFrom` operations.
     *
     * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
     * true using the following override:
     * ```
     * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
     *     super._approve(owner, spender, value, true);
     * }
     * ```
     *
     * Requirements are the same as {_approve}.
     */
    function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
        if (owner == address(0)) {
            revert ERC20InvalidApprover(address(0));
        }
        if (spender == address(0)) {
            revert ERC20InvalidSpender(address(0));
        }
        _allowances[owner][spender] = value;
        if (emitEvent) {
            emit Approval(owner, spender, value);
        }
    }

    /**
     * @dev Updates `owner` s allowance for `spender` based on spent `value`.
     *
     * Does not update the allowance value in case of infinite allowance.
     * Revert if not enough allowance is available.
     *
     * Does not emit an {Approval} event.
     */
    function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            if (currentAllowance < value) {
                revert ERC20InsufficientAllowance(spender, currentAllowance, value);
            }
            unchecked {
                _approve(owner, spender, currentAllowance - value, false);
            }
        }
    }
}
          

@openzeppelin/contracts/token/ERC20/IERC20.sol

// 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/token/ERC20/extensions/IERC20Metadata.sol

// 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/extensions/IERC20Permit.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 *
 * ==== Security Considerations
 *
 * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
 * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
 * considered as an intention to spend the allowance in any specific way. The second is that because permits have
 * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
 * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
 * generally recommended is:
 *
 * ```solidity
 * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
 *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
 *     doThing(..., value);
 * }
 *
 * function doThing(..., uint256 value) public {
 *     token.safeTransferFrom(msg.sender, address(this), value);
 *     ...
 * }
 * ```
 *
 * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
 * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
 * {SafeERC20-safeTransferFrom}).
 *
 * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
 * contracts should have entry points that don't rely on permit.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     *
     * CAUTION: See Security Considerations above.
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}
          

@openzeppelin/contracts/utils/Context.sol

// 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;
    }
}
          

@openzeppelin/contracts/utils/Strings.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Strings.sol)

pragma solidity ^0.8.20;

import {Math} from "./math/Math.sol";
import {SignedMath} from "./math/SignedMath.sol";

/**
 * @dev String operations.
 */
library Strings {
    bytes16 private constant HEX_DIGITS = "0123456789abcdef";
    uint8 private constant ADDRESS_LENGTH = 20;

    /**
     * @dev The `value` string doesn't fit in the specified `length`.
     */
    error StringsInsufficientHexLength(uint256 value, uint256 length);

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        unchecked {
            uint256 length = Math.log10(value) + 1;
            string memory buffer = new string(length);
            uint256 ptr;
            /// @solidity memory-safe-assembly
            assembly {
                ptr := add(buffer, add(32, length))
            }
            while (true) {
                ptr--;
                /// @solidity memory-safe-assembly
                assembly {
                    mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `int256` to its ASCII `string` decimal representation.
     */
    function toStringSigned(int256 value) internal pure returns (string memory) {
        return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value)));
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        unchecked {
            return toHexString(value, Math.log256(value) + 1);
        }
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        uint256 localValue = value;
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 2 * length + 1; i > 1; --i) {
            buffer[i] = HEX_DIGITS[localValue & 0xf];
            localValue >>= 4;
        }
        if (localValue != 0) {
            revert StringsInsufficientHexLength(value, length);
        }
        return string(buffer);
    }

    /**
     * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal
     * representation.
     */
    function toHexString(address addr) internal pure returns (string memory) {
        return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);
    }

    /**
     * @dev Returns true if the two strings are equal.
     */
    function equal(string memory a, string memory b) internal pure returns (bool) {
        return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
    }
}
          

@openzeppelin/contracts/utils/math/Math.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)

pragma solidity ^0.8.20;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    /**
     * @dev Muldiv operation overflow.
     */
    error MathOverflowedMulDiv();

    enum Rounding {
        Floor, // Toward negative infinity
        Ceil, // Toward positive infinity
        Trunc, // Toward zero
        Expand // Away from zero
    }

    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with an overflow flag.
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b > a) return (false, 0);
            return (true, a - b);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) return (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds towards infinity instead
     * of rounding towards zero.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        if (b == 0) {
            // Guarantee the same behavior as in a regular Solidity division.
            return a / b;
        }

        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
     * denominator == 0.
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
     * Uniswap Labs also under MIT license.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0 = x * y; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                // The surrounding unchecked block does not change this fact.
                // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            if (denominator <= prod1) {
                revert MathOverflowedMulDiv();
            }

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator.
            // Always >= 1. See https://cs.stackexchange.com/q/138556/92363.

            uint256 twos = denominator & (0 - denominator);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
            // works in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
        uint256 result = mulDiv(x, y, denominator);
        if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
     * towards zero.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10 ** 64) {
                value /= 10 ** 64;
                result += 64;
            }
            if (value >= 10 ** 32) {
                value /= 10 ** 32;
                result += 32;
            }
            if (value >= 10 ** 16) {
                value /= 10 ** 16;
                result += 16;
            }
            if (value >= 10 ** 8) {
                value /= 10 ** 8;
                result += 8;
            }
            if (value >= 10 ** 4) {
                value /= 10 ** 4;
                result += 4;
            }
            if (value >= 10 ** 2) {
                value /= 10 ** 2;
                result += 2;
            }
            if (value >= 10 ** 1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
        }
    }

    /**
     * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
     */
    function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
        return uint8(rounding) % 2 == 1;
    }
}
          

@openzeppelin/contracts/utils/math/SignedMath.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SignedMath.sol)

pragma solidity ^0.8.20;

/**
 * @dev Standard signed math utilities missing in the Solidity language.
 */
library SignedMath {
    /**
     * @dev Returns the largest of two signed numbers.
     */
    function max(int256 a, int256 b) internal pure returns (int256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two signed numbers.
     */
    function min(int256 a, int256 b) internal pure returns (int256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two signed numbers without overflow.
     * The result is rounded towards zero.
     */
    function average(int256 a, int256 b) internal pure returns (int256) {
        // Formula from the book "Hacker's Delight"
        int256 x = (a & b) + ((a ^ b) >> 1);
        return x + (int256(uint256(x) >> 255) & (a ^ b));
    }

    /**
     * @dev Returns the absolute unsigned value of a signed value.
     */
    function abs(int256 n) internal pure returns (uint256) {
        unchecked {
            // must be unchecked in order to support `n = type(int256).min`
            return uint256(n >= 0 ? n : -n);
        }
    }
}
          

contracts/Actuator.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

// import "hardhat/console.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { HEXTimeToken } from "./HEXTimeToken.sol";
import { HEXTimeTokenManager } from "./HEXTimeTokenManager.sol";

contract Actuator is ERC20 {
    uint256 private constant MIN_VAULT_TIME = 90 days;

    HEXTimeTokenManager public _httm;
    address public masterChef;
    mapping(address => uint16[]) public depositedMaturities;

    constructor(
        address _httmAddress
    ) 
        ERC20('Actuator', 'ACTR') 
    {      
        _httm = HEXTimeTokenManager(_httmAddress);
        masterChef = msg.sender;
    }

    modifier onlyMasterChef() {
        require(msg.sender == masterChef, "A042");
        _;
    }

    function mint(address to, uint256 amount) external onlyMasterChef {
        _mint(to, amount);
    }

    /**
     * @dev Retreives the number of vaults the user has deposited into.
     * @return Number of vaults.
     */
    function vaultCount(
        address user
    )
        external
        view
        returns (uint256)
    {
        return depositedMaturities[user].length;
    }

    /**
     * @dev Deposit ACTR into vault to collect the given HEX Time Token (HTT) tax.
     * @param maturity HTT maturity day to stake against.
     * @param amount Amount of ACTR to deposit.
    */
    function deposit(uint16 maturity, uint256 amount) external {
        require(amount > 0, "A040");
        
        (, address tokenAddress) = _httm.maturityToInfo(maturity);
        require(tokenAddress != address(0), "A033");
        
        uint16[] storage maturities = depositedMaturities[msg.sender];
        maturities.push(maturity);

        uint256 newAmount = HEXTimeToken(tokenAddress).deposit(msg.sender, amount);
        require(newAmount == amount, "A034");

        // bypass allowance
        _transfer(msg.sender, address(this), amount);
    }

    /**
     * @dev Increase deposited ACTR.
     * @param index Index of the user's vaults.
     * @param amount Amount of ACTR to deposit.
    */
    function increaseDeposit(uint256 index, uint256 amount) external {
        require(amount > 0, "A040");
        uint16 maturity = depositedMaturities[msg.sender][index];
        
        (, address tokenAddress) = _httm.maturityToInfo(uint16(maturity));
        uint256 newAmount = HEXTimeToken(tokenAddress).deposit(msg.sender, amount);
        require(newAmount > amount, "A035");

        // bypass allowance
        _transfer(msg.sender, address(this), amount);
    }

    /**
     * @dev Withdraw ACTR from vault.
     * @param index Index of the user's vaults.
     * @param amount Amount of ACTR to withdraw.
    */
    function withdraw(uint256 index, uint256 amount) external {
        require(amount > 0, "A041");
        uint16 maturity = depositedMaturities[msg.sender][index];
        (, address tokenAddress) = _httm.maturityToInfo(uint16(maturity));
        (uint256 newAmount, uint256 capitalAdded) = HEXTimeToken(tokenAddress).withdraw(msg.sender, amount);

        if (newAmount == 0) {
            _pruneDepositedMaturities(msg.sender, index);
        }

        uint256 servedTime = block.timestamp - capitalAdded;        
        if (servedTime < MIN_VAULT_TIME) {
            // Penalty for early withdrawal
            uint256 remainder = amount * servedTime / MIN_VAULT_TIME;
            _burn(address(this), amount - remainder);
            _transfer(address(this), msg.sender, remainder);
        } else {
            _transfer(address(this), msg.sender, amount);
        }
    }

    /**
     * @dev Removes a vault from the user's individual vault list.
     * @param account The relevant user.
     * @param index The index of the vault to remove.
     */
    function _pruneDepositedMaturities(
        address account,
        uint256 index
    )
        private
    {
        uint16[] storage list = depositedMaturities[account];
        uint256 lastIndex = list.length - 1;

        if (index != lastIndex) {
            list[index] = list[lastIndex];
        }

        list.pop();
    }

}
          

contracts/HEXTimeTokenManager.sol

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.24;

// import "hardhat/console.sol";
import { HEXTimeToken } from "./HEXTimeToken.sol";
import { IHEX } from "./interfaces/HEX.sol";
import { HEXStake } from "./declarations/Types.sol";
import { IHEXStakeInstance } from "./interfaces/HEXStakeInstance.sol";
import { IHEXStakeInstanceManager } from "./interfaces/HEXStakeInstanceManager.sol";
import { IHedron } from "./interfaces/Hedron.sol";
import { MasterChef } from "./MasterChef.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { Actuator } from "./Actuator.sol"; 

contract HEXTimeTokenManager {
    uint256 private constant LATE_PENALTY_GRACE_WEEKS = 2;
    uint256 private constant LATE_PENALTY_GRACE_DAYS = LATE_PENALTY_GRACE_WEEKS * 7;
    uint256 private constant LATE_PENALTY_SCALE_WEEKS = 100;
    uint256 private constant LATE_PENALTY_SCALE_DAYS = LATE_PENALTY_SCALE_WEEKS * 7;
    uint256 private constant MAX_REDEMPTION_DEFERMENT = LATE_PENALTY_SCALE_DAYS - (LATE_PENALTY_SCALE_DAYS / 10); 
    uint256 private constant PAYOUT_RESOLUTION = 1e18;
    uint256 private constant RESERVE_FACTOR_SCALE = 1e5;
    uint256 private constant BASE_RESERVE_FACTOR = 1e6;
    uint256 private constant PAYOUT_START_DAY = 800;
    uint256 private constant SUBSIDY_GRACE_DAYS = 3;
    uint256 private constant HEX_LAUNCH = 1575331200;
    address private constant HEX_ADDRESS = 0x2b591e99afE9f32eAA6214f7B7629768c40Eeb39;
    address private constant HSIM_ADDRESS = 0x8BD3d1472A656e312E94fB1BbdD599B8C51D18e3;
    address private constant HEDRON_ADDRESS = 0x3819f64f282bf135d62168C1e513280dAF905e06;
    uint256 private constant EARLY_PENALTY_MIN_DAYS = 90;

    struct Collateral { 
        uint72 amount; 
        uint16 maturity; 
        address owner; 
    }

    struct MaturityInfo { 
        uint72 hexBalance; 
        address tokenAddress; 
    }

    struct MultiEndStakeParams {
        address hsiAddress;
        uint256 hsiIndex;
        uint256 collateralIndex;
        uint256 hedronHsiIndex;
    }

    address public actuatorAddress;
    address public masterChefAddress;
    IHEX private _hx;
    IHEXStakeInstanceManager private _hsim;
    IHedron private _hedron;

    uint72[] public payouts;

    mapping(address => address[]) public hsiLists;
    mapping(uint16 => MaturityInfo) public maturityToInfo;
    mapping(address => Collateral) public hsiToCollateral;
    mapping(uint16 => address[]) public maturityToCollateralizedHsiList;

    event DailyDataUpdate(address indexed updaterAddr, uint16 beforeDay, bool isAuto);
    event CreateHEXTimeToken(uint16 indexed maturity);
    event MintHEXTimeTokens(address indexed user, address indexed hsiAddress, uint16 indexed maturity, uint256 amount);
    event RetireHEXTimeTokens(address indexed user, address indexed hsiAddress, uint16 indexed maturity, uint256 amount);
    event RedeemHEXTimeTokens(address indexed user, uint16 indexed maturity, uint256 amount);
    event DelegateHSI(address indexed user, uint256 indexed tokenId, address indexed hsiAddress);
    event RevokeHSI(address indexed user, uint256 indexed tokenId, address indexed hsiAddress);
    event StartStake(address indexed user, address indexed hsiAddress);
    event EndStake(address indexed user, address indexed hsiAddress);
    event EndCollateralizedStake(address indexed user, address indexed hsiAddress);

    constructor(
        address teamAddress,
        address factoryAddress,
        uint72[] memory initialPayouts,
        uint256 farmStartTime,
        uint256[3] memory _farmSupplySchedule,
        uint256[14] memory _poolPointSchedule
    ) {
        _hx = IHEX(HEX_ADDRESS);
        _hsim = IHEXStakeInstanceManager(HSIM_ADDRESS);
        _hedron = IHedron(HEDRON_ADDRESS);
        MasterChef masterChef = new MasterChef(teamAddress, factoryAddress, farmStartTime, _farmSupplySchedule, _poolPointSchedule);
        actuatorAddress = address(masterChef.actr());
        masterChefAddress = address(masterChef);
        payouts = initialPayouts;
    }

    /**
     * @dev Calculates the current HEX day.
     * @return Number representing the current HEX day.
     */
    function _currentDay()
        private
        view
        returns (uint256)
    {
        return (block.timestamp - HEX_LAUNCH) / 1 days;
    }
    
    /**
     * @dev Update daily payout data.
     */
    function updateDailyData(uint256 beforeDay) external {
        _updateDailyData(beforeDay, false);
    }

    /**
     * @dev Update daily payout data.
     */
    function _updateDailyDataAuto(uint256 beforeDay) private {
        _updateDailyData(beforeDay, true);
    }

    /**
     * @dev Update daily payout data.
     */
    function _updateDailyData(uint256 beforeDay, bool isAuto) private {
        uint256 day = PAYOUT_START_DAY + payouts.length;
        if (day >= beforeDay) return;

        _hx.dailyDataUpdate(beforeDay);

        uint256 dayPayoutPerTShare;
        uint256 dayPayoutTotal;
        uint256 dayStakeSharesTotal;
        uint72 lastPayout = payouts.length > 0 ? payouts[payouts.length - 1] : 0;
        while (day < beforeDay) {
            (dayPayoutTotal, dayStakeSharesTotal,) = _hx.dailyData(day);
            if (dayStakeSharesTotal > 0) {
              dayPayoutPerTShare = dayPayoutTotal * PAYOUT_RESOLUTION / dayStakeSharesTotal;
            } else {
              dayPayoutPerTShare = 0;
            }

            lastPayout = uint72(dayPayoutPerTShare + lastPayout);
            payouts.push(lastPayout);
            day++;
        }

        emit DailyDataUpdate(msg.sender, uint16(beforeDay), isAuto); 
    }

    /**
     * @dev Get HEX Time Token or create token if it doesn't already exist.
     * @param maturity The HEX day the token is redeemable for HEX.
     * @return Address of the HEX Time Token.
     */
    function getOrCreateHEXTimeToken(uint16 maturity) public returns (address) {
        if (maturityToInfo[maturity].tokenAddress == address(0)) {
            ERC20 newToken = new HEXTimeToken(maturity + 1, actuatorAddress);
            maturityToInfo[maturity].tokenAddress = address(newToken);
            emit CreateHEXTimeToken(maturity);
        }
        return maturityToInfo[maturity].tokenAddress;
    }

    /**
     * @dev Creates a new HEX stake instance (HSI) and delegates control to the HEXTimeTokenManager. 
     * @param amount Number of HEX ERC20 tokens to be staked.
     * @param length Number of days the HEX ERC20 tokens will be staked.
     * @return Address of the newly created HSI contract.
     */
    function hexStakeStart(uint256 amount, uint256 length) external returns (address) {
        require(_hx.transferFrom(msg.sender, address(this), amount), "A016");

        _hx.increaseAllowance(HSIM_ADDRESS, amount);       

        address hsiAddress = _hsim.hexStakeStart(amount, length);

        hsiLists[msg.sender].push(hsiAddress);

        emit StartStake(msg.sender, hsiAddress);

        return hsiAddress;
    }

    /**
     * @dev Transfers control of the tokenized HSI to the HEXTimeTokenManager and detokenizes.
     * @param tokenId ID of the HSI ERC721 token to be converted.
     * @return Address of the detokenized HSI contract.
     */
    function delegateHSI(uint256 tokenId) external returns (address) {
        _hsim.transferFrom(msg.sender, address(this), tokenId);

        address hsiAddress = _hsim.hexStakeDetokenize(tokenId);

        hsiLists[msg.sender].push(hsiAddress);

        emit DelegateHSI(msg.sender, tokenId, hsiAddress);

        return hsiAddress;
    }

    /**
     * @dev Mints HEX Maturity tokens (HTTs) against an HSI's underlying HEX. 
     *      When maturity day is after end stake day, extractable HTT quantity is lowered by the maximum possible end stake HEX penalty. 
     * @param hsiIndex Index of the HSI address in the caller's HSI list.
     * @param amount Quanity of HTTs to mint.
     * @param maturity The Hex day which the HTTs mature.
     * @return Address of the HTT token.
     */
    function mintHEXTimeTokens (
        uint256 hsiIndex,
        uint256 amount,
        uint256 maturity
    ) 
        external 
        returns (address) 
    {
        require(amount > 0, "A023");
        require(hsiIndex < hsiLists[msg.sender].length, "A012");
        address hsiAddress = hsiLists[msg.sender][hsiIndex];
        Collateral storage collateral = hsiToCollateral[hsiAddress];
        require(collateral.maturity == 0 || collateral.maturity == maturity, "A013");

        _updateDailyDataAuto(_currentDay());
        uint256 extractableAmount = getExtractableAmount(hsiAddress, maturity);
        require(collateral.amount + amount <= extractableAmount, "A002");

        if (collateral.amount == 0) {
            maturityToCollateralizedHsiList[uint16(maturity)].push(hsiAddress);
            collateral.maturity = uint16(maturity);
            collateral.owner = msg.sender;
        } 
        collateral.amount += uint72(amount);

        address tokenAddress = getOrCreateHEXTimeToken(uint16(maturity));
        HEXTimeToken(tokenAddress).mint(msg.sender, amount);

        emit MintHEXTimeTokens(msg.sender, hsiAddress, uint16(maturity), amount);

        return tokenAddress;
    }

    /**
     * @dev Mints Hedron ERC20 (HDRN) tokens to the sender using a HEX stake instance (HSI) backing.
     * @param hsiIndex Index of the HSI address in the caller's HSI list.
     * @param hedronHsiIndex Index of the HSI address stored in Hedron's HSI list.
     * @return Amount of HDRN ERC20 tokens minted.
     */
    function mintInstanced(
        uint256 hsiIndex,
        uint256 hedronHsiIndex
    ) 
        external
        returns (uint256)
    {
        require(hsiIndex < hsiLists[msg.sender].length, "A012");

        address hsiAddress = hsiLists[msg.sender][hsiIndex];
        uint256 amount = _hedron.mintInstanced(hedronHsiIndex, hsiAddress);
        _hedron.transfer(msg.sender, amount);
        return amount;
    }

    /**
     * @dev Burns Hex Maturity Tokens (HTTs) previously minted against a HSI 
     *      and returns control of the collateralized underlying HEX back to the staker.
     * @param hsiIndex Index of the HSI address in the caller's HSI list.
     * @param collateralIndex Index of the HSI address in the collateralized HSI list.
     * @param amount Number of HTTs to retire.
     */
    function retireHEXTimeTokens(
        uint256 hsiIndex,
        uint256 collateralIndex,
        uint256 amount
    ) 
        external 
    {
        require(hsiIndex < hsiLists[msg.sender].length, "A012");
        require(amount > 0, "A024");
        
        address hsiAddress = hsiLists[msg.sender][hsiIndex];
        Collateral storage collateral = hsiToCollateral[hsiAddress];
        uint16 maturity = collateral.maturity; // stash before prune

        require(hsiAddress == maturityToCollateralizedHsiList[maturity][collateralIndex], "A006");
        require(_currentDay() < maturity, "A003");
        require(amount <= collateral.amount, "A001");

        if (collateral.amount - amount == 0) {
            _pruneCollateralizedHSI(maturity, collateralIndex); 
            delete hsiToCollateral[hsiAddress]; // order matters
        } else {
            collateral.amount -= uint72(amount);
        }

        HEXTimeToken(maturityToInfo[maturity].tokenAddress).burn(msg.sender, amount);

        emit RetireHEXTimeTokens(msg.sender, hsiAddress, maturity, amount);
    }

    /**
     * @dev Tokenizes the HSI and transfers control of the tokenized HSI to caller.
     * @param hsiIndex Index of the HSI address in the caller's HSI list.
     * @param hedronHsiIndex Index of the HSI address stored in Hedron's HSI list.
     * @return Token ID of the HSI ERC721 token.
     */
    function revokeHSIDelegation(uint256 hsiIndex, uint256 hedronHsiIndex) external returns (uint256) {
        address[] storage hsiList = hsiLists[msg.sender];
        require(hsiIndex < hsiList.length, "A012");

        address hsiAddress = hsiList[hsiIndex];
        require(hsiToCollateral[hsiAddress].amount == 0, "A000");

        _pruneHSI(hsiList, hsiIndex);

        uint256 tokenId = _hsim.hexStakeTokenize(hedronHsiIndex, hsiAddress);

        _hsim.transferFrom(address(this), msg.sender, tokenId);

        emit RevokeHSI(msg.sender, tokenId, hsiAddress);

        return tokenId;
    }

    /**
     * @dev Unlocks the stake.
     * @param hsiIndex Index of the HSI address in the caller's HSI list.
     * @param hedronHsiIndex Index of the HSI address stored in Hedron's HSI list.
     */
    function endHEXStake(
        uint256 hsiIndex,
        uint256 hedronHsiIndex
    ) 
        external 
        returns (uint256)
    {
        address[] storage hsiList = hsiLists[msg.sender];
        require(hsiIndex < hsiList.length, "A012");

        address hsiAddress = hsiList[hsiIndex];
        require(hsiToCollateral[hsiAddress].amount == 0, "A019");

        _pruneHSI(hsiList, hsiIndex);

        uint256 hsiBalance = _hsim.hexStakeEnd(hedronHsiIndex, hsiAddress);

        if (hsiBalance > 0) require(_hx.transfer(msg.sender, hsiBalance), "A010");

        emit EndStake(msg.sender, hsiAddress);

        return hsiBalance;
    }

    /**
     * @dev Allows the stake owner to unlock the collateralized HSI once fully served, 
     *      otherwise anyone can unlock once minted HTTs are redeemable.
     * @param hsiAddress Address of the HSI.
     * @param hsiIndex Index of the HSI address in the caller's HSI list.
     * @param collateralIndex Index of the HSI address in the collateralized HSI list.
     * @param hedronHsiIndex Index of the HSI address stored in Hedron's HSI list.
     */
    function endCollateralizedHEXStake(
        address hsiAddress,
        uint256 hsiIndex,
        uint256 collateralIndex,
        uint256 hedronHsiIndex
    ) 
        public 
    {
        Collateral memory collateral = hsiToCollateral[hsiAddress];
        uint256 collateralAmount = collateral.amount;
        uint16 maturity = collateral.maturity;
        address owner = collateral.owner;

        require(collateralAmount > 0, "A018");
        require(hsiAddress == hsiLists[owner][hsiIndex], "A004");
        require(hsiAddress == maturityToCollateralizedHsiList[maturity][collateralIndex], "A006");

        uint256 currentDay = _currentDay(); 
        (,, uint256 stakeShares, uint16 lockedDay, uint16 stakedDays,,) = _hx.stakeLists(hsiAddress, 0);

        if (currentDay < maturity) {
            // stake owner can end stake once fully served
            require(owner == msg.sender && currentDay >= lockedDay + stakedDays, "A022");
        } 

        _pruneHSI(hsiLists[owner], hsiIndex);
        _pruneCollateralizedHSI(maturity, collateralIndex);
        delete hsiToCollateral[hsiAddress];

        _updateDailyDataAuto(currentDay);

        uint256 hsiBalance = _hsim.hexStakeEnd(hedronHsiIndex, hsiAddress);

        // End staker gets 1st priority of unlocked HEX (in event of late end stake)
        if (hsiBalance > 0) {
            uint256 effectiveStakedDays = maturity - lockedDay < stakedDays? maturity - lockedDay: stakedDays;
            uint256 endStakeSubsidy = calcEndStakeSubsidy(lockedDay, effectiveStakedDays, maturity, currentDay, stakeShares);
            if (endStakeSubsidy > 0) {
                endStakeSubsidy = endStakeSubsidy < hsiBalance? endStakeSubsidy: hsiBalance;
                hsiBalance -= endStakeSubsidy;
                require(_hx.transfer(msg.sender, endStakeSubsidy), "A010");
            }
        }
                
        // HTT holders get 2nd priority of unlocked HEX (in event of late end stake)
        if (hsiBalance > 0) {
            collateralAmount = collateralAmount < hsiBalance? collateralAmount: hsiBalance;
            maturityToInfo[uint16(maturity)].hexBalance += uint72(collateralAmount); 
            hsiBalance -= collateralAmount;
        }

        // Stake creator gets last priority of unlocked HEX (in event of late end stake)
        if (hsiBalance > 0) require(_hx.transfer(owner, hsiBalance), "A010");

        emit EndCollateralizedStake(msg.sender, hsiAddress);
    }

    /**
     * @dev Allows any address to unlock a fully matured collateralized stake and subsequently redeem HTTs.
     * @param maturity The maturity of the HTT to redeem.
     * @param amount The amount of HTTs to redeem.
     */
    function endHEXStakesAndRedeem(
        uint256 maturity, 
        uint256 amount, 
        MultiEndStakeParams[] memory data
    ) 
        external 
    {
        endHEXStakes(data);
        redeemHEXTimeTokens(maturity, amount);
    }

    /**
     * @dev Allows any address to unlock fully matured collateralized stakes and subsequently redeem HTTs.
     * @param data The relevant stake data needed to unlock.
     */
    function endHEXStakes(MultiEndStakeParams[] memory data) public {
        for (uint256 i = 0; i < data.length; i++) {
            endCollateralizedHEXStake(data[i].hsiAddress, data[i].hsiIndex, data[i].collateralIndex, data[i].hedronHsiIndex);
        }
    }

    /**
     * @dev Redeem HEX Maturity tokens (HTT) for HEX. 
     * @param maturity Maturity day of the HTT.
     * @param amount Number of HTTs to redeem.
     */
    function redeemHEXTimeTokens(uint256 maturity, uint256 amount) public {
        MaturityInfo storage info = maturityToInfo[uint16(maturity)];

        HEXTimeToken token = HEXTimeToken(info.tokenAddress);

        require(_currentDay() >= maturity, "A009");
        require(amount <= token.balanceOf(msg.sender), "A008");
        require(amount <= info.hexBalance, "A007");

        info.hexBalance -= uint72(amount);

        token.burn(msg.sender, amount);

        require(_hx.transfer(msg.sender, amount), "A010");

        emit RedeemHEXTimeTokens(msg.sender, uint16(maturity), amount);
    }

    /**
     * @dev Removes a HEX stake instance (HSI) address from an individual owner's HSI List.
     * @param hsiList A mapped list of HSI contract addresses.
     * @param hsiIndex The index of the HSI address which will be removed.
     */
    function _pruneHSI(address[] storage hsiList, uint256 hsiIndex) private {
        uint256 lastIndex = hsiList.length - 1;

        if (hsiIndex != lastIndex) {
            hsiList[hsiIndex] = hsiList[lastIndex];
        }

        hsiList.pop();
    }

    /**
     * @dev Removes a HEX stake instance (HSI) address from the collateralized HSI List.
     * @param maturity A mapped list of HSI contract addresses.
     * @param collateralIndex Index of the collateralized HSI address which will be removed.
     */
    function _pruneCollateralizedHSI(uint16 maturity, uint256 collateralIndex) private {
        address[] storage collateralizedHsiList = maturityToCollateralizedHsiList[maturity];

        uint256 lastIndex = collateralizedHsiList.length - 1;

        if (collateralIndex != lastIndex) {
            collateralizedHsiList[collateralIndex] = collateralizedHsiList[lastIndex];
        }

        collateralizedHsiList.pop();
    }

    /**
     * @dev Calculates the total quantity of extractable HEX Maturity Tokens (HTT) from a stake.
     * @param hsiAddress Address of the HSI.
     * @param maturity maturity of the HTT to extract.
     * @return Total quantity of extractable HTT.
     */
    function getExtractableAmount(
        address hsiAddress,
        uint256 maturity
    ) 
        public 
        view
        returns (uint256) 
    {
        (,uint256 stakeValue, uint72 stakeShares, uint256 lockedDay, uint16 stakedDays,,) = _hx.stakeLists(hsiAddress, 0);
        uint256 endStakeDay = lockedDay + stakedDays;
        uint256 currentDay = _currentDay();

        if (maturity < endStakeDay) {
            require(currentDay < maturity, "A045");
            uint256 penaltyDays = (stakedDays + 1) / 2;
            require(penaltyDays >= EARLY_PENALTY_MIN_DAYS, "A046");

            uint256 penaltyEndDay = lockedDay + penaltyDays;
            uint256 effectiveStakedDays = maturity - lockedDay;
            uint256 reserveDay = getReserveDay(lockedDay, effectiveStakedDays, maturity);
            require(reserveDay >= penaltyEndDay, "A047");
            stakeValue += calculateRewards(penaltyEndDay, currentDay < reserveDay? currentDay: reserveDay, stakeShares);
        } else {
            require(currentDay < endStakeDay, "A014");
            require(maturity - endStakeDay < MAX_REDEMPTION_DEFERMENT, "A017");

            uint256 reserveDay = getReserveDay(lockedDay, stakedDays, maturity);
            // only calculate rewards up to the earlier of the current day or the reserve day
            stakeValue += calculateRewards(lockedDay, currentDay < reserveDay? currentDay: reserveDay, stakeShares);

            if (endStakeDay < maturity) {
                // assume worst case scenario and subtract maximal possible late penalty from extractable amount
                stakeValue -= calcLatePenalty(lockedDay, stakedDays, maturity + LATE_PENALTY_GRACE_DAYS, stakeValue);
            }
        }

        return stakeValue;
    }

    /**
     * @dev Finds the index of the HSI address in Hedron's HSI list.
     * @param hsiAddress Address of the HSI.
     * @return Index of the HSI address in Hedron's HSI list.
     */
    function findHedronHSIIndex(address hsiAddress) external view returns (int) {
        uint256 count = _hsim.hsiCount(address(this));
        for (uint256 i = 0; i < count; i++) {
            address addr = _hsim.hsiLists(address(this), i);
            if (addr == hsiAddress) return int(i);
        }
        return -1; 
    }

    /**
     * @dev Finds the index of the HSI address in the individual owner's HSI list.
     *      This function only works on collateralized HSIs.
     * @param hsiAddress Address of the HSI.
     * @return Index of the HSI address in the HSI list.
     */
    function findHSIIndex(address hsiAddress) external view returns (int) {
        address owner = hsiToCollateral[hsiAddress].owner;
        for (uint256 i = 0; i < hsiLists[owner].length; i++) {
            address addr = hsiLists[owner][i];
            if (addr == hsiAddress) return int(i);
        }
        return -1; 
    }

    /**
     * @dev Finds all underlying HSI addresses backing HTTs for the given maturity.
     * @param maturity maturity of the HTT
     * @param start range start
     * @param end range end (non-inclusive)
     * @return list array of HSI addresses
     */
    function hsiListRange(
        uint256 maturity, 
        uint256 start, 
        uint256 end
    ) 
        external 
        view 
        returns (address[] memory list) 
    {
        address[] memory hsiList = maturityToCollateralizedHsiList[uint16(maturity)];
        end = end > hsiList.length? hsiList.length: end;
        if (end - start == 0) return list;

        list = new address[](end - start);  

        uint256 dst;
        uint256 i = start;
        do {
            list[dst++] = hsiList[i];
        } while (++i < end);

        return list;
    }

    /**
     * @dev Finds all underlying HSI data backing HTTs for the given maturity.
     * @param maturity Maturity of the HTT
     * @param start Range start
     * @param end Range end (non-inclusive)
     * @return list Array of packed HSI/collateral data
     */
    function hsiDataListRange(
        uint256 maturity, 
        uint256 start, 
        uint256 end
    ) 
        external 
        view 
        returns (uint256[] memory list) 
    {
        address[] memory hsiList = maturityToCollateralizedHsiList[uint16(maturity)];
        end = end > hsiList.length? hsiList.length: end;
        if (end - start == 0) return list;

        list = new uint256[](end - start);  

        uint256 i = start;
        uint256 dst;
        uint256 v;
        do {
            address hsiAddress = hsiList[i];
            (,, uint72 stakeShares, uint16 lockedDay, uint16 stakedDays,,) = _hx.stakeLists(hsiAddress, 0);
            Collateral memory collateral = hsiToCollateral[hsiAddress];
            v = uint256(collateral.amount) << (72 * 2);
            v |= uint256(stakeShares) << 72;
            v |= uint256(lockedDay) << 16;
            v |= uint256(stakedDays);

            list[dst++] = v;
        } while (++i < end);

        return list;
    }

    /**
     * @dev Finds all cumulative payouts within the range. 
     * @param beginDay First day of data range
     * @param endDay Last day (non-inclusive) of data range
     * @return list Array of cumulative payouts
     */
    function dailyDataRange(uint256 beginDay, uint256 endDay) external view returns (uint256[] memory list) {
        list = new uint256[](endDay - beginDay);

        uint256 src = beginDay;
        uint256 dst;
        do {
            list[dst++] = payouts[src - PAYOUT_START_DAY];
        } while (++src < endDay);

        return list;
    }

    /**
     * @dev function to pull stake and collateral data.
     * @param owner Address used to retrieve the HSI list.
     * @param hsiIndex Index of the HSI address in the owner's HSI list.
     */
    function shareLists(
        address owner, 
        uint256 hsiIndex
    ) 
        external 
        view
        returns (
            uint40 stakeId, 
            uint72 stakedHearts, 
            uint72 stakeShares, 
            uint16 lockedDay, 
            uint16 stakedDays, 
            uint256 collateralAmount, 
            uint256 maturity, 
            address hsiAddress
        )
    {
        hsiAddress = hsiLists[owner][hsiIndex];
        (stakeId, stakedHearts, stakeShares, lockedDay, stakedDays,,) = _hx.stakeLists(hsiAddress, 0);

        collateralAmount = hsiToCollateral[hsiAddress].amount;
        maturity = hsiToCollateral[hsiAddress].maturity;

        return (
            stakeId, 
            stakedHearts, 
            stakeShares, 
            lockedDay, 
            stakedDays, 
            collateralAmount, 
            maturity, 
            hsiAddress
        );
    }

    /**
     * @dev Retreives the number of HSI elements for the given user's HSI list.
     * @param user Address used to retrieve the HSI list.
     * @return Number of HSI elements found in the user's HSI list.
     */
    function hsiCount(
        address user
    ) 
        public
        view 
        returns (uint256) 
    {
        return hsiLists[user].length;
    }

    /**
     * @dev Wrapper for hsiCount allowing for HEX-based apps to fetch stake data.
     * @param user Address used to retrieve the HSI list.
     * @return Number of HSI elements found in the user's HSI list.
    */
    function stakeCount(
        address user
    )
        external
        view
        returns (uint256)
    {
        return hsiCount(user);
    }

    /**
     * @dev Wrapper for hsiLists allowing for HEX-based apps to fetch stake data.
     * @param user Address used to retrieve the HSI list.
     * @param hsiIndex Index of the HSI address in the user's HSI list.
     * @return HEX stake data. 
     */
    function stakeLists(
        address user,
        uint256 hsiIndex
    )
        external
        view
        returns (HEXStake memory)
    {
        address[] storage hsiList = hsiLists[user];

        IHEXStakeInstance hsi = IHEXStakeInstance(hsiList[hsiIndex]);

        return hsi.stakeDataFetch();
    }

    /**
     * @dev Calculates accrued HEX rewards for a given range and share amount. 
     * @param beginDay begin day (inclusive).
     * @param endDay end day (exclusive).
     * @param stakeShares Number of shares.
     * @return Rewards amount.
     */
    function calculateRewards(
        uint256 beginDay, 
        uint256 endDay, 
        uint256 stakeShares
    ) 
        public 
        view 
        returns (uint256) 
    {
        if (beginDay >= endDay) return 0;

        uint256 start = payouts[beginDay - 1 - PAYOUT_START_DAY];
        uint256 end = payouts[endDay - 1 - PAYOUT_START_DAY];

        uint256 rewards = (end - start) * stakeShares / PAYOUT_RESOLUTION;

        // hex contract has less precision for rewards and results up to 1 heart of precision per day loss when calculating rewards
        // thus we assume worst case scenario and subtract the maximal possible precision loss from the rewards (i.e. 1 heart per day)
        uint256 precisionLoss = endDay - beginDay;
        return precisionLoss < rewards? rewards - precisionLoss: 0;
    }

    /**
     * @dev Calculates the subsidy for unlocking a stake. 
     * @param lockedDay begin day (inclusive)
     * @param stakedDays Number of days staked
     * @param maturity Maturity day of the HTTs minted against the stake
     * @param unlockedDay Day the stake is unlocked
     * @param stakeShares Number of shares
     * @return Subsidy amount.
     */
    function calcEndStakeSubsidy(
        uint256 lockedDay, 
        uint256 stakedDays,
        uint256 maturity,
        uint256 unlockedDay, 
        uint256 stakeShares
    ) 
        public 
        view 
        returns (uint256) 
    {
        if (unlockedDay <= maturity + SUBSIDY_GRACE_DAYS) return 0;

        uint256 endDay = lockedDay + stakedDays; 

        uint256 reserveDay = getReserveDay(lockedDay, stakedDays, maturity); 
        uint256 reserves = calculateRewards(reserveDay, endDay, stakeShares);
        uint256 maxSubsidy = reserves - calcLatePenalty(lockedDay, stakedDays, unlockedDay, reserves);

        uint256 daysLate = unlockedDay - maturity - SUBSIDY_GRACE_DAYS;
        uint256 factor = daysLate < 10? daysLate: 10;
        return maxSubsidy * factor / 10;
    }

    /**
     * @dev Calculates the first HEX day to begin reserving stake rewards in the event of an unlock subsidy. 
     * @param lockedDay begin day (inclusive)
     * @param stakedDays Number of days staked
     * @param maturity Maturity day of the HTTs minted against the stake
     * @return Reserve day
     */
    function getReserveDay(
        uint256 lockedDay, 
        uint256 stakedDays,
        uint256 maturity 
    ) 
        public 
        pure 
        returns (uint256) 
    {
        uint256 endDay = lockedDay + stakedDays;
        uint256 factor = BASE_RESERVE_FACTOR; // reserveDays defaults to 10% of stakedDays

        if (maturity > endDay) {
            // scale up reserveDays as potential late penalty increases
            uint256 unpenalizedDays = LATE_PENALTY_SCALE_DAYS - (maturity - endDay);
            factor = factor * LATE_PENALTY_SCALE_DAYS / unpenalizedDays;
        }

        uint256 denominator = 100 * RESERVE_FACTOR_SCALE;
        uint256 reserveDays = (stakedDays * factor + denominator - 1) / denominator;
        return endDay - reserveDays;
    }

    /**
     * @dev Calculates the late end stake penalty enforced by the HEX protocol late penalty calculation.
     * @param lockedDay begin day (inclusive)
     * @param stakedDays Number of days staked
     * @param stakeValue The value of the stake in HEX
     * @return Late penalty
     */
    function calcLatePenalty(
        uint256 lockedDay, 
        uint256 stakedDays,
        uint256 unlockedDay, 
        uint256 stakeValue
    ) 
        public 
        pure 
        returns (uint256) 
    {
        uint256 maxUnlockedDay = lockedDay + stakedDays + LATE_PENALTY_GRACE_DAYS;
        if (unlockedDay <= maxUnlockedDay) return 0;

        return stakeValue * (unlockedDay - maxUnlockedDay) / LATE_PENALTY_SCALE_DAYS;
    }

    /**
     * @dev Calls the HEX function "stakeGoodAccounting" against the HEX stake held within the HSI.
     */
    function stakeGoodAccounting(address hsiAddress)
        external
    {
        IHEXStakeInstance hsi = IHEXStakeInstance(hsiAddress);
        hsi.goodAccounting();
    }
}
          

contracts/MasterChef.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

// import "hardhat/console.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { SafeERC20 } from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
import { Actuator } from "./Actuator.sol"; 
import { HEXTimeTokenManager } from "./HEXTimeTokenManager.sol"; 
import { IPulseXFactory } from "./interfaces/PulseXFactory.sol"; 

// Much of the code in this contract has been copied or adapted from SushiSwap's MasterChef contract.

contract MasterChef {
    using SafeERC20 for IERC20;
    uint256 private constant ACC_ACTR_PRECISION = 1e24; 

    address private constant HEX_ADDRESS = 0x2b591e99afE9f32eAA6214f7B7629768c40Eeb39;
    uint256 constant YEAR = 365 days;
    
    uint256[3] public farmEmissionSchedule;
    uint256[14] public poolPointSchedule;

    uint256 private _lastMassUpdate;

    // Info of each user.
    struct UserInfo {
        uint256 amount;     // How many LP tokens the user has provided.
        uint256 rewardDebt; // Reward debt. See explanation below.
        //
        // We do some fancy math here. Basically, any point in time, the amount of ACTR
        // entitled to a user but is pending to be distributed is:
        //
        //   pending reward = (user.amount * pool.accActrPerShare) - user.rewardDebt
        //
        // Whenever a user deposits or withdraws LP tokens to a pool. Here's what happens:
        //   1. The pool's `accActrPerShare` (and `lastRewardBlock`) gets updated.
        //   2. User receives the pending reward sent to his/her address.
        //   3. User's `amount` gets updated.
        //   4. User's `rewardDebt` gets updated.
    }

    // Info of each pool.
    struct PoolInfo {
        IERC20 lpToken;           // Address of LP token contract.
        uint256 allocPoint;       // How many allocation points assigned to this pool. ACTR to distribute per second.
        uint256 lastRewardTime;  // Last time that ACTR distribution occurs.
        uint256 accActrPerShare; // Accumulated ACTR per share, times 1e24. See below.
    }

    Actuator public actr;

    IPulseXFactory public factory;
    
    HEXTimeTokenManager public _httManager;

    uint256 public constant MaxAllocPoint = 4000;

    // Info of each pool.
    PoolInfo[] public poolInfo;
    // Info of each user that stakes LP tokens.
    mapping (uint256 => mapping (address => UserInfo)) public userInfo;
    // Total allocation points. Must be the sum of all allocation points in all pools.
    uint256 public totalAllocPoint = 0;
    // The block time when ACTR mining starts.
    uint256 public immutable startTime;

    event Deposit(address indexed user, uint256 indexed pid, uint256 amount);
    event Withdraw(address indexed user, uint256 indexed pid, uint256 amount);
    event CollectEmissions(address indexed user, uint256 amount);

    constructor(
        address teamAddress,
        address _factoryAddress,
        uint256 _startTime,
        uint256[3] memory _farmEmissionSchedule,
        uint256[14] memory _poolPointSchedule
    ) {
        _httManager = HEXTimeTokenManager(msg.sender);
        actr = new Actuator(msg.sender);
        startTime = _startTime;
        farmEmissionSchedule = _farmEmissionSchedule;
        poolPointSchedule = _poolPointSchedule;
        factory = IPulseXFactory(_factoryAddress);
        actr.mint(teamAddress, 250000000 * 1e18);
    }

    /**
     * @dev Retreives the number of farm pools.
     * @return Number of pools.
     */
    function poolLength() external view returns (uint256) {
        return poolInfo.length;
    }

    /**
     * @dev Add a new lp to the pool. 
     * @param _allocPoint The allocation points to assign to the pool.
     * @param _lpToken LP Address of the pool to add.
    */
    function _add(uint256 _allocPoint, IERC20 _lpToken) private {
        require(_allocPoint <= MaxAllocPoint, "A029");

        uint256 lastRewardTime = block.timestamp > startTime ? block.timestamp : startTime;
        totalAllocPoint = totalAllocPoint + _allocPoint;
        poolInfo.push(PoolInfo({
            lpToken: _lpToken,
            allocPoint: _allocPoint,
            lastRewardTime: lastRewardTime,
            accActrPerShare: 0
        }));
    }

    /**
     * @dev Update the given pool's ACTR allocation point. 
     * @param _pid Internal ID of the pool to update.
     * @param _allocPoint Updated allocation points to assign to the pool.
    */
    function _set(uint256 _pid, uint256 _allocPoint) private {
        if (poolInfo[_pid].allocPoint > _allocPoint) {
            require(totalAllocPoint - (poolInfo[_pid].allocPoint - _allocPoint) > 0, "A032");
        }
        require(_allocPoint <= MaxAllocPoint, "A031");

        totalAllocPoint = totalAllocPoint - poolInfo[_pid].allocPoint + _allocPoint;
        poolInfo[_pid].allocPoint = _allocPoint;
    }

    /**
     * @dev Public function to fetch the farm ACTR emission amount within 2 timestamp. 
     * @param _from Start date to calculate emissions from.
     * @param _to End date to calculate emissions to.
     * @return Total ACTR Emissions.
    */
    function getFarmEmissions(uint256 _from, uint256 _to) external view returns (uint256) {
        return _getFarmEmissions(_from, _to);
    }

    /**
     * @dev Private function to fetch the farm ACTR emission amount within 2 timestamps. 
     * @param _from Start date to calculate emissions from.
     * @param _to End date to calculate emissions to.
     * @return Total ACTR Emissions.
    */
    function _getFarmEmissions(uint256 _from, uint256 _to) private view returns (uint256) {
        return _getEmissions(_from, _to, farmEmissionSchedule);
    }

    /**
     * @dev Generic function to fetch the ACTR emission amount within 2 dates based on the provided emission schedule.
     * @param _from Start date to calculate emissions from.
     * @param _to End date to calculate emissions to.
     * @param emissionSchedule Array of yearly ACTR emission amount.
     * @return Total ACTR Emissions.
     * 
    */
    function _getEmissions(uint256 _from, uint256 _to, uint256[3] memory emissionSchedule) private view returns (uint256) {
        uint256 start = _from - startTime;
        uint256 end = _to - startTime;
        return _getEmissionsInTimeframe(start, end, emissionSchedule);
    }

    /**
     * @dev Generic function to fetch the ACTR emission amount within 2 timestamps based on the provided emission schedule 
     * and assuming epoch is farm start.
     * @param start Start time to calculate emissions from.
     * @param end End time to calculate emissions to.
     * @param emissionSchedule Array of yearly ACTR emission amount.
     * @return Total ACTR Emissions.
     * 
    */
    function _getEmissionsInTimeframe(uint256 start, uint256 end, uint256[3] memory emissionSchedule) private pure returns (uint256) {
        uint256 mintAmount = 0;
        for (uint256 year = 0; year < emissionSchedule.length; year++) {
            uint256 yearStart = year * YEAR;
            uint256 yearEnd = (year + 1) * YEAR;
            
            // Check for timeframe overlap
            if (end > yearStart && start < yearEnd) {
                uint256 effectiveStart = start > yearStart ? start : yearStart;
                uint256 effectiveEnd = end < yearEnd ? end : yearEnd;
                uint256 elapsed = effectiveEnd - effectiveStart;
                mintAmount += (emissionSchedule[year] * elapsed) / YEAR;
            }
        }
        
        return mintAmount;
    }

    /**
     * @dev View function to see pending ACTR on frontend.
     * @return Pending ACTR amount.
     * 
    */
    function pendingActr(uint256 _pid, address _user) external view returns (uint256) {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][_user];
        uint256 accActrPerShare = pool.accActrPerShare;
        uint256 lpSupply = pool.lpToken.balanceOf(address(this));
        if (block.timestamp > pool.lastRewardTime && lpSupply != 0) {
            uint256 totalRewards = _getFarmEmissions(pool.lastRewardTime, block.timestamp);
            uint256 actrReward = totalRewards * pool.allocPoint / totalAllocPoint;
            accActrPerShare = accActrPerShare + (actrReward * ACC_ACTR_PRECISION / lpSupply);
        }
        return (user.amount * accActrPerShare / ACC_ACTR_PRECISION) - user.rewardDebt;
    }

    /**
     * @dev Update all pools to the latest predefined allocation points. 
     * 
    */
    function massUpdatePools() external {
        for (uint256 pid = 0; pid < poolInfo.length; ++pid) {
            updatePool(pid);
        }

        if (_lastMassUpdate == 0) {
            address pairAddressHTT3000 = factory.getPair(HEX_ADDRESS, _httManager.getOrCreateHEXTimeToken(2999));
            address pairAddressHTT5000 = factory.getPair(HEX_ADDRESS, _httManager.getOrCreateHEXTimeToken(4999));
            address pairAddressHTT7000 = factory.getPair(HEX_ADDRESS, _httManager.getOrCreateHEXTimeToken(6999));
            require(pairAddressHTT3000 != address(0), "A038");
            require(pairAddressHTT5000 != address(0), "A038");
            require(pairAddressHTT7000 != address(0), "A038");

            _add(poolPointSchedule[0], IERC20(pairAddressHTT3000));
            _add(poolPointSchedule[1], IERC20(pairAddressHTT5000));
            _add(poolPointSchedule[2], IERC20(pairAddressHTT7000));
        } else if (_lastMassUpdate < startTime + YEAR && block.timestamp >= startTime + YEAR) {
            address pairAddressHTT4000 = factory.getPair(HEX_ADDRESS, _httManager.getOrCreateHEXTimeToken(3999));
            address pairAddressHTT6000 = factory.getPair(HEX_ADDRESS, _httManager.getOrCreateHEXTimeToken(5999));
            require(pairAddressHTT4000 != address(0), "A038");
            require(pairAddressHTT6000 != address(0), "A038");
            _set(0, poolPointSchedule[3]);
            _add(poolPointSchedule[4], IERC20(pairAddressHTT4000));
            _set(1, poolPointSchedule[5]);
            _add(poolPointSchedule[6], IERC20(pairAddressHTT6000));
            _set(2, poolPointSchedule[7]);
        } else if (_lastMassUpdate < startTime + (YEAR * 2) && block.timestamp >= startTime + (YEAR * 2)) {
            address pairAddressHTT8000 = factory.getPair(HEX_ADDRESS, _httManager.getOrCreateHEXTimeToken(7999));
            require(pairAddressHTT8000 != address(0), "A038");
            _set(0, poolPointSchedule[8]);
            _set(3, poolPointSchedule[9]);
            _set(1, poolPointSchedule[10]);
            _set(4, poolPointSchedule[11]);
            _set(2, poolPointSchedule[12]);
            
            _add(poolPointSchedule[13], IERC20(pairAddressHTT8000));
        }

        _lastMassUpdate = block.timestamp;
    }

    /**
     * @dev Update reward variables of the given pool to be up-to-date.
     * @param _pid Internal ID of the pool.
    */
    function updatePool(uint256 _pid) public {
        PoolInfo storage pool = poolInfo[_pid];
        if (block.timestamp <= pool.lastRewardTime) {
            return;
        }
        uint256 lpSupply = pool.lpToken.balanceOf(address(this));
        if (lpSupply == 0) {
            pool.lastRewardTime = block.timestamp;
            return;
        }
        uint256 totalRewards = _getFarmEmissions(pool.lastRewardTime, block.timestamp);
        uint256 actrReward = totalRewards * pool.allocPoint / totalAllocPoint;

        actr.mint(address(this), actrReward);

        pool.accActrPerShare = pool.accActrPerShare + (actrReward * ACC_ACTR_PRECISION / lpSupply);
        pool.lastRewardTime = block.timestamp;
    }

    /**
     * @dev Deposit LP tokens to MasterChef for ACTR allocation.
     * @param _pid ID of the pool.
     * @param _amount Amount of LP tokens to deposit.
    */
    function deposit(uint256 _pid, uint256 _amount) external {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];

        updatePool(_pid);

        uint256 pending = (user.amount * pool.accActrPerShare / ACC_ACTR_PRECISION) - user.rewardDebt;

        user.amount = user.amount + _amount;
        user.rewardDebt = user.amount * pool.accActrPerShare / ACC_ACTR_PRECISION;

        if (pending > 0) {
            safeActrTransfer(msg.sender, pending);
            emit CollectEmissions(msg.sender, pending);
        }
        pool.lpToken.safeTransferFrom(address(msg.sender), address(this), _amount);

        emit Deposit(msg.sender, _pid, _amount);
    }

    /**
     * @dev Withdraw LP tokens from MasterChef.
     * @param _pid ID of the pool.
     * @param _amount Amount of LP tokens to withdraw.
    */
    function withdraw(uint256 _pid, uint256 _amount) external {  
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];

        require(user.amount >= _amount, "A030");

        updatePool(_pid);

        uint256 pending = (user.amount * pool.accActrPerShare / ACC_ACTR_PRECISION) - user.rewardDebt;

        user.amount = user.amount - _amount;
        user.rewardDebt = user.amount * pool.accActrPerShare / ACC_ACTR_PRECISION;

        if (pending > 0) {
            safeActrTransfer(msg.sender, pending);
            emit CollectEmissions(msg.sender, pending);
        }
        pool.lpToken.safeTransfer(address(msg.sender), _amount);
        
        emit Withdraw(msg.sender, _pid, _amount);
    }

    /**
     * @dev Safe ACTR transfer function, just in case if rounding error causes pool to not have enough ACTR.
     * @param _to Recipient address.
     * @param _amount Amount of ACTR tokens to transfer.
    */
    function safeActrTransfer(address _to, uint256 _amount) private {
        uint256 actrBal = actr.balanceOf(address(this));
        if (_amount > actrBal) {
            actr.transfer(_to, actrBal);
        } else {
            actr.transfer(_to, _amount);
        }
    }

}
          

contracts/declarations/Types.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

struct HEXStake {
    uint40 stakeId;
    uint72 stakedHearts;
    uint72 stakeShares;
    uint16 lockedDay;
    uint16 stakedDays;
    uint16 unlockedDay;
    bool   isAutoStake;
}

struct HEXStakeMinimal {
    uint40 stakeId;
    uint72 stakedHearts;
    uint72 stakeShares;
    uint16 lockedDay;
    uint16 stakedDays;
}

struct ShareStore {
    HEXStakeMinimal stake;
    uint16          mintedDays;
    uint8           launchBonus;
    uint16          loanStart;
    uint16          loanedDays;
    uint32          interestRate;
    uint8           paymentsMade;
    bool            isLoaned;
}

struct ShareCache {
    HEXStakeMinimal _stake;
    uint256         _mintedDays;
    uint256         _launchBonus;
    uint256         _loanStart;
    uint256         _loanedDays;
    uint256         _interestRate;
    uint256         _paymentsMade;
    bool            _isLoaned;
}

library LibPart {
    bytes32 public constant TYPE_HASH = keccak256("Part(address account,uint96 value)");

    struct Part {
        address payable account;
        uint96 value;
    }

    function hash(Part memory part) internal pure returns (bytes32) {
        return keccak256(abi.encode(TYPE_HASH, part.account, part.value));
    }
}

          

contracts/interfaces/HEX.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

interface IHEX {
    event Approval(
        address indexed owner,
        address indexed spender,
        uint256 value
    );
    event Claim(
        uint256 data0,
        uint256 data1,
        bytes20 indexed btcAddr,
        address indexed claimToAddr,
        address indexed referrerAddr
    );
    event ClaimAssist(
        uint256 data0,
        uint256 data1,
        uint256 data2,
        address indexed senderAddr
    );
    event DailyDataUpdate(uint256 data0, address indexed updaterAddr);
    event ShareRateChange(uint256 data0, uint40 indexed stakeId);
    event StakeEnd(
        uint256 data0,
        uint256 data1,
        address indexed stakerAddr,
        uint40 indexed stakeId
    );
    event StakeGoodAccounting(
        uint256 data0,
        uint256 data1,
        address indexed stakerAddr,
        uint40 indexed stakeId,
        address indexed senderAddr
    );
    event StakeStart(
        uint256 data0,
        address indexed stakerAddr,
        uint40 indexed stakeId
    );
    event Transfer(address indexed from, address indexed to, uint256 value);
    event XfLobbyEnter(
        uint256 data0,
        address indexed memberAddr,
        uint256 indexed entryId,
        address indexed referrerAddr
    );
    event XfLobbyExit(
        uint256 data0,
        address indexed memberAddr,
        uint256 indexed entryId,
        address indexed referrerAddr
    );

    function allocatedSupply() external view returns (uint256);

    function allowance(address owner, address spender)
        external
        view
        returns (uint256);

    function approve(address spender, uint256 amount) external returns (bool);

    function balanceOf(address account) external view returns (uint256);

    function btcAddressClaim(
        uint256 rawSatoshis,
        bytes32[] memory proof,
        address claimToAddr,
        bytes32 pubKeyX,
        bytes32 pubKeyY,
        uint8 claimFlags,
        uint8 v,
        bytes32 r,
        bytes32 s,
        uint256 autoStakeDays,
        address referrerAddr
    ) external returns (uint256);

    function btcAddressClaims(bytes20) external view returns (bool);

    function btcAddressIsClaimable(
        bytes20 btcAddr,
        uint256 rawSatoshis,
        bytes32[] memory proof
    ) external view returns (bool);

    function btcAddressIsValid(
        bytes20 btcAddr,
        uint256 rawSatoshis,
        bytes32[] memory proof
    ) external pure returns (bool);

    function claimMessageMatchesSignature(
        address claimToAddr,
        bytes32 claimParamHash,
        bytes32 pubKeyX,
        bytes32 pubKeyY,
        uint8 claimFlags,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external pure returns (bool);

    function currentDay() external view returns (uint256);

    function dailyData(uint256)
        external
        view
        returns (
            uint72 dayPayoutTotal,
            uint72 dayStakeSharesTotal,
            uint56 dayUnclaimedSatoshisTotal
        );

    function dailyDataRange(uint256 beginDay, uint256 endDay)
        external
        view
        returns (uint256[] memory list);

    function dailyDataUpdate(uint256 beforeDay) external;

    function decimals() external view returns (uint8);

    function decreaseAllowance(address spender, uint256 subtractedValue)
        external
        returns (bool);

    function globalInfo() external view returns (uint256[13] memory);

    function globals()
        external
        view
        returns (
            uint72 lockedHeartsTotal,
            uint72 nextStakeSharesTotal,
            uint40 shareRate,
            uint72 stakePenaltyTotal,
            uint16 dailyDataCount,
            uint72 stakeSharesTotal,
            uint40 latestStakeId,
            uint128 claimStats
        );

    function increaseAllowance(address spender, uint256 addedValue)
        external
        returns (bool);

    function merkleProofIsValid(bytes32 merkleLeaf, bytes32[] memory proof)
        external
        pure
        returns (bool);

    function name() external view returns (string memory);

    function pubKeyToBtcAddress(
        bytes32 pubKeyX,
        bytes32 pubKeyY,
        uint8 claimFlags
    ) external pure returns (bytes20);

    function pubKeyToEthAddress(bytes32 pubKeyX, bytes32 pubKeyY)
        external
        pure
        returns (address);

    function stakeCount(address stakerAddr) external view returns (uint256);

    function stakeEnd(uint256 stakeIndex, uint40 stakeIdParam) external;

    function stakeGoodAccounting(
        address stakerAddr,
        uint256 stakeIndex,
        uint40 stakeIdParam
    ) external;

    function stakeLists(address, uint256)
        external
        view
        returns (
            uint40 stakeId,
            uint72 stakedHearts,
            uint72 stakeShares,
            uint16 lockedDay,
            uint16 stakedDays,
            uint16 unlockedDay,
            bool isAutoStake
        );

    function stakeStart(uint256 newStakedHearts, uint256 newStakedDays)
        external;

    function symbol() external view returns (string memory);

    function totalSupply() external view returns (uint256);

    function transfer(address recipient, uint256 amount)
        external
        returns (bool);

    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) external returns (bool);

    function xfLobby(uint256) external view returns (uint256);

    function xfLobbyEnter(address referrerAddr) external payable;

    function xfLobbyEntry(address memberAddr, uint256 entryId)
        external
        view
        returns (uint256 rawAmount, address referrerAddr);

    function xfLobbyExit(uint256 enterDay, uint256 count) external;

    function xfLobbyFlush() external;

    function xfLobbyMembers(uint256, address)
        external
        view
        returns (uint40 headIndex, uint40 tailIndex);

    function xfLobbyPendingDays(address memberAddr)
        external
        view
        returns (uint256[2] memory words);

    function xfLobbyRange(uint256 beginDay, uint256 endDay)
        external
        view
        returns (uint256[] memory list);
}

          

contracts/interfaces/HEXStakeInstance.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import { HEXStakeMinimal, HEXStake } from "../declarations/Types.sol";

interface IHEXStakeInstance {
    /**
     * @dev Calls the HEX function "stakeGoodAccounting" against the
     *      HEX stake held within the HSI.
     */
    function share() external view returns (
      HEXStakeMinimal memory stake,
      uint16          mintedDays,
      uint8           launchBonus,
      uint16          loanStart,
      uint16          loanedDays,
      uint32          interestRate,
      uint8           paymentsMade,
      bool            isLoaned
    );

    /**
     * @dev Calls the HEX function "stakeGoodAccounting" against the
     *      HEX stake held within the HSI.
     */
    function goodAccounting() external;

    /**
     * @dev Fetches stake data from the HEX contract.
     * @return A "HEXStake" object containg the HEX stake data. 
     */
    function stakeDataFetch() external view returns(HEXStake memory);
}
          

contracts/interfaces/HEXStakeInstanceManager.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import "./HEX.sol";
import "../declarations/Types.sol";

interface IHEXStakeInstanceManager {
    event Approval(
        address indexed owner,
        address indexed approved,
        uint256 indexed tokenId
    );
    event ApprovalForAll(
        address indexed owner,
        address indexed operator,
        bool approved
    );
    event HSIDetokenize(
        uint256 timestamp,
        uint256 indexed hsiTokenId,
        address indexed hsiAddress,
        address indexed staker
    );
    event HSIEnd(
        uint256 timestamp,
        address indexed hsiAddress,
        address indexed staker
    );
    event HSIStart(
        uint256 timestamp,
        address indexed hsiAddress,
        address indexed staker
    );
    event HSITokenize(
        uint256 timestamp,
        uint256 indexed hsiTokenId,
        address indexed hsiAddress,
        address indexed staker
    );
    event HSITransfer(
        uint256 timestamp,
        address indexed hsiAddress,
        address indexed oldStaker,
        address indexed newStaker
    );
    event RoyaltiesSet(uint256 tokenId, LibPart.Part[] royalties);
    event Transfer(
        address indexed from,
        address indexed to,
        uint256 indexed tokenId
    );

    function approve(address to, uint256 tokenId) external;

    function balanceOf(address owner) external view returns (uint256);

    function getApproved(uint256 tokenId) external view returns (address);

    function getRaribleV2Royalties(uint256 id)
        external
        view
        returns (LibPart.Part[] memory);

    function hexStakeDetokenize(uint256 tokenId) external returns (address);

    function hexStakeEnd(uint256 hsiIndex, address hsiAddress)
        external
        returns (uint256);

    function hexStakeStart(uint256 amount, uint256 length)
        external
        returns (address);

    function hexStakeTokenize(uint256 hsiIndex, address hsiAddress)
        external
        returns (uint256);

    function hsiCount(address user) external view returns (uint256);

    function hsiLists(address, uint256) external view returns (address);

    function hsiToken(uint256) external view returns (address);

    function hsiTransfer(
        address currentHolder,
        uint256 hsiIndex,
        address hsiAddress,
        address newHolder
    ) external;

    function hsiUpdate(
        address holder,
        uint256 hsiIndex,
        address hsiAddress,
        ShareCache memory share
    ) external;

    function isApprovedForAll(address owner, address operator)
        external
        view
        returns (bool);

    function name() external view returns (string memory);

    function owner() external pure returns (address);

    function ownerOf(uint256 tokenId) external view returns (address);

    function royaltyInfo(uint256 tokenId, uint256 salePrice)
        external
        view
        returns (address receiver, uint256 royaltyAmount);

    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;

    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
    ) external;

    function setApprovalForAll(address operator, bool approved) external;

    function stakeCount(address user) external view returns (uint256);

    function stakeLists(address user, uint256 hsiIndex)
        external
        view
        returns (HEXStake memory);

    function supportsInterface(bytes4 interfaceId) external view returns (bool);

    function symbol() external view returns (string memory);

    function tokenByIndex(uint256 index) external view returns (uint256);

    function tokenOfOwnerByIndex(address owner, uint256 index)
        external
        view
        returns (uint256);

    function tokenURI(uint256 tokenId) external view returns (string memory);

    function totalSupply() external view returns (uint256);

    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;
}
          

contracts/interfaces/Hedron.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import { HEXStakeMinimal } from "../declarations/Types.sol";

interface IHedron {
    event Approval(
        address indexed owner,
        address indexed spender,
        uint256 value
    );
    event Claim(uint256 data, address indexed claimant, uint40 indexed stakeId);
    event LoanEnd(
        uint256 data,
        address indexed borrower,
        uint40 indexed stakeId
    );
    event LoanLiquidateBid(
        uint256 data,
        address indexed bidder,
        uint40 indexed stakeId,
        uint40 indexed liquidationId
    );
    event LoanLiquidateExit(
        uint256 data,
        address indexed liquidator,
        uint40 indexed stakeId,
        uint40 indexed liquidationId
    );
    event LoanLiquidateStart(
        uint256 data,
        address indexed borrower,
        uint40 indexed stakeId,
        uint40 indexed liquidationId
    );
    event LoanPayment(
        uint256 data,
        address indexed borrower,
        uint40 indexed stakeId
    );
    event LoanStart(
        uint256 data,
        address indexed borrower,
        uint40 indexed stakeId
    );
    event Mint(uint256 data, address indexed minter, uint40 indexed stakeId);
    event Transfer(address indexed from, address indexed to, uint256 value);

    function allowance(address owner, address spender)
        external
        view
        returns (uint256);

    function approve(address spender, uint256 amount) external returns (bool);

    function balanceOf(address account) external view returns (uint256);

    function calcLoanPayment(
        address borrower,
        uint256 hsiIndex,
        address hsiAddress
    ) external view returns (uint256, uint256);

    function calcLoanPayoff(
        address borrower,
        uint256 hsiIndex,
        address hsiAddress
    ) external view returns (uint256, uint256);

    function claimInstanced(
        uint256 hsiIndex,
        address hsiAddress,
        address hsiStarterAddress
    ) external;

    function claimNative(uint256 stakeIndex, uint40 stakeId)
        external
        returns (uint256);

    function currentDay() external view returns (uint256);

    function dailyDataList(uint256)
        external
        view
        returns (
            uint72 dayMintedTotal,
            uint72 dayLoanedTotal,
            uint72 dayBurntTotal,
            uint32 dayInterestRate,
            uint8 dayMintMultiplier
        );

    function decimals() external view returns (uint8);

    function decreaseAllowance(address spender, uint256 subtractedValue)
        external
        returns (bool);

    function hsim() external view returns (address);

    function increaseAllowance(address spender, uint256 addedValue)
        external
        returns (bool);

    function liquidationList(uint256)
        external
        view
        returns (
            uint256 liquidationStart,
            address hsiAddress,
            uint96 bidAmount,
            address liquidator,
            uint88 endOffset,
            bool isActive
        );

    function loanInstanced(uint256 hsiIndex, address hsiAddress)
        external
        returns (uint256);

    function loanLiquidate(
        address owner,
        uint256 hsiIndex,
        address hsiAddress
    ) external returns (uint256);

    function loanLiquidateBid(uint256 liquidationId, uint256 liquidationBid)
        external
        returns (uint256);

    function loanLiquidateExit(uint256 hsiIndex, uint256 liquidationId)
        external
        returns (address);

    function loanPayment(uint256 hsiIndex, address hsiAddress)
        external
        returns (uint256);

    function loanPayoff(uint256 hsiIndex, address hsiAddress)
        external
        returns (uint256);

    function loanedSupply() external view returns (uint256);

    function mintInstanced(uint256 hsiIndex, address hsiAddress)
        external
        returns (uint256);

    function mintNative(uint256 stakeIndex, uint40 stakeId)
        external
        returns (uint256);

    function name() external view returns (string memory);

    function proofOfBenevolence(uint256 amount) external;

    function shareList(uint256)
        external
        view
        returns (
            HEXStakeMinimal memory stake,
            uint16 mintedDays,
            uint8 launchBonus,
            uint16 loanStart,
            uint16 loanedDays,
            uint32 interestRate,
            uint8 paymentsMade,
            bool isLoaned
        );

    function symbol() external view returns (string memory);

    function totalSupply() external view returns (uint256);

    function transfer(address recipient, uint256 amount)
        external
        returns (bool);

    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) external returns (bool);
}
          

contracts/interfaces/PulseXFactory.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

interface IPulseXFactory {
    function getPair(address tokenA, address tokenB) external view returns (address pair);
}
          

Compiler Settings

{"outputSelection":{"*":{"*":["abi","evm.bytecode","evm.deployedBytecode","evm.methodIdentifiers","metadata"],"":["ast"]}},"optimizer":{"runs":200,"enabled":true},"libraries":{},"evmVersion":"paris"}
              

Contract ABI

[{"type":"constructor","stateMutability":"nonpayable","inputs":[{"type":"uint16","name":"_maturity","internalType":"uint16"},{"type":"address","name":"actuatorAddress","internalType":"address"}]},{"type":"error","name":"ERC20InsufficientAllowance","inputs":[{"type":"address","name":"spender","internalType":"address"},{"type":"uint256","name":"allowance","internalType":"uint256"},{"type":"uint256","name":"needed","internalType":"uint256"}]},{"type":"error","name":"ERC20InsufficientBalance","inputs":[{"type":"address","name":"sender","internalType":"address"},{"type":"uint256","name":"balance","internalType":"uint256"},{"type":"uint256","name":"needed","internalType":"uint256"}]},{"type":"error","name":"ERC20InvalidApprover","inputs":[{"type":"address","name":"approver","internalType":"address"}]},{"type":"error","name":"ERC20InvalidReceiver","inputs":[{"type":"address","name":"receiver","internalType":"address"}]},{"type":"error","name":"ERC20InvalidSender","inputs":[{"type":"address","name":"sender","internalType":"address"}]},{"type":"error","name":"ERC20InvalidSpender","inputs":[{"type":"address","name":"spender","internalType":"address"}]},{"type":"event","name":"Approval","inputs":[{"type":"address","name":"owner","internalType":"address","indexed":true},{"type":"address","name":"spender","internalType":"address","indexed":true},{"type":"uint256","name":"value","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"CollectFees","inputs":[{"type":"address","name":"user","internalType":"address","indexed":true},{"type":"uint256","name":"amount","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"Deposit","inputs":[{"type":"address","name":"user","internalType":"address","indexed":true},{"type":"uint256","name":"amount","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"Transfer","inputs":[{"type":"address","name":"from","internalType":"address","indexed":true},{"type":"address","name":"to","internalType":"address","indexed":true},{"type":"uint256","name":"value","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"Withdraw","inputs":[{"type":"address","name":"user","internalType":"address","indexed":true},{"type":"uint256","name":"amount","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"CREATION_FEE_RATE","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"accHttPerShare","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"contract Actuator"}],"name":"actr","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"allowance","inputs":[{"type":"address","name":"owner","internalType":"address"},{"type":"address","name":"spender","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"approve","inputs":[{"type":"address","name":"spender","internalType":"address"},{"type":"uint256","name":"value","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"balanceOf","inputs":[{"type":"address","name":"account","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"burn","inputs":[{"type":"address","name":"from","internalType":"address"},{"type":"uint256","name":"amount","internalType":"uint256"}]},{"type":"function","stateMutability":"pure","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"calculateTax","inputs":[{"type":"uint256","name":"amount","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"collectFees","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint8","name":"","internalType":"uint8"}],"name":"decimals","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"deposit","inputs":[{"type":"address","name":"account","internalType":"address"},{"type":"uint256","name":"_amount","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"httManager","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint16","name":"","internalType":"uint16"}],"name":"maturity","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"mint","inputs":[{"type":"address","name":"to","internalType":"address"},{"type":"uint256","name":"amount","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"string","name":"","internalType":"string"}],"name":"name","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"string","name":"","internalType":"string"}],"name":"symbol","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"totalDeposits","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"totalSupply","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"transfer","inputs":[{"type":"address","name":"to","internalType":"address"},{"type":"uint256","name":"value","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"transferFrom","inputs":[{"type":"address","name":"from","internalType":"address"},{"type":"address","name":"to","internalType":"address"},{"type":"uint256","name":"value","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"amount","internalType":"uint256"},{"type":"uint256","name":"rewardDebt","internalType":"uint256"},{"type":"uint256","name":"capitalAdded","internalType":"uint256"}],"name":"userInfo","inputs":[{"type":"address","name":"","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"uint256","name":"","internalType":"uint256"},{"type":"uint256","name":"","internalType":"uint256"}],"name":"withdraw","inputs":[{"type":"address","name":"account","internalType":"address"},{"type":"uint256","name":"_amount","internalType":"uint256"}]}]
              

Contract Creation Code

Verify & Publish
0x60806040523480156200001157600080fd5b506040516200147738038062001477833981016040819052620000349162000284565b6200004361ffff831662000101565b60405160200162000055919062000306565b60408051601f198184030181529190526200007461ffff841662000101565b60405160200162000086919062000333565b60408051601f198184030181529190526003620000a48382620003f5565b506004620000b38282620003f5565b5050600580546001600160a01b0319166001600160a01b039390931692909217909155506008805461ffff929092166001600160b01b031990921691909117336201000002179055620004c1565b6060600062000110836200019a565b60010190506000816001600160401b038111156200013257620001326200034e565b6040519080825280601f01601f1916602001820160405280156200015d576020820181803683370190505b5090508181016020015b600019016f181899199a1a9b1b9c1cb0b131b232b360811b600a86061a8153600a85049450846200016757509392505050565b6000807a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008310620001e4577a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000830492506040015b6d04ee2d6d415b85acef8100000000831062000211576d04ee2d6d415b85acef8100000000830492506020015b662386f26fc1000083106200023057662386f26fc10000830492506010015b6305f5e100831062000249576305f5e100830492506008015b61271083106200025e57612710830492506004015b6064831062000271576064830492506002015b600a83106200027e576001015b92915050565b600080604083850312156200029857600080fd5b825161ffff81168114620002ab57600080fd5b60208401519092506001600160a01b0381168114620002c957600080fd5b809150509250929050565b6000815160005b81811015620002f75760208185018101518683015201620002db565b50600093019283525090919050565b6e02422ac102a34b6b2902a37b5b2b71608d1b815260006200032c600f830184620002d4565b9392505050565b634854542d60e01b815260006200032c6004830184620002d4565b634e487b7160e01b600052604160045260246000fd5b600181811c908216806200037957607f821691505b6020821081036200039a57634e487b7160e01b600052602260045260246000fd5b50919050565b601f821115620003f0576000816000526020600020601f850160051c81016020861015620003cb5750805b601f850160051c820191505b81811015620003ec57828155600101620003d7565b5050505b505050565b81516001600160401b038111156200041157620004116200034e565b620004298162000422845462000364565b84620003a0565b602080601f831160018114620004615760008415620004485750858301515b600019600386901b1c1916600185901b178555620003ec565b600085815260208120601f198616915b82811015620004925788860151825594840194600190910190840162000471565b5085821015620004b15787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b610fa680620004d16000396000f3fe608060405234801561001057600080fd5b50600436106101425760003560e01c80636d2dbe24116100b85780639dc29fac1161007c5780639dc29fac146102de578063a9059cbb146102f1578063bfb9f50214610304578063c879657214610317578063dd62ed3e1461031f578063f3fef3a31461035857600080fd5b80636d2dbe241461029357806370a082311461029b5780637d882097146102c457806395d89b41146102cd5780639cdcfb2b146102d557600080fd5b806323b872dd1161010a57806323b872dd146102055780632a67ba3614610218578063313ce5671461024957806340c10f191461025857806347e7ef241461026d578063483a93101461028057600080fd5b806306fdde0314610147578063095ea7b31461016557806318160ddd146101885780631959a0021461019a578063204f83f9146101e4575b600080fd5b61014f610380565b60405161015c9190610d82565b60405180910390f35b610178610173366004610ded565b610412565b604051901515815260200161015c565b6002545b60405190815260200161015c565b6101c96101a8366004610e17565b60096020526000908152604090208054600182015460029092015490919083565b6040805193845260208401929092529082015260600161015c565b6008546101f29061ffff1681565b60405161ffff909116815260200161015c565b610178610213366004610e39565b61042c565b600854610231906201000090046001600160a01b031681565b6040516001600160a01b03909116815260200161015c565b6040516008815260200161015c565b61026b610266366004610ded565b610450565b005b61018c61027b366004610ded565b610521565b61018c61028e366004610e75565b6106b0565b61018c606481565b61018c6102a9366004610e17565b6001600160a01b031660009081526020819052604090205490565b61018c60065481565b61014f6106ca565b61018c60075481565b61026b6102ec366004610ded565b6106d9565b6101786102ff366004610ded565b61072c565b600554610231906001600160a01b031681565b61018c61073a565b61018c61032d366004610e8e565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b61036b610366366004610ded565b610831565b6040805192835260208301919091520161015c565b60606003805461038f90610ec1565b80601f01602080910402602001604051908101604052809291908181526020018280546103bb90610ec1565b80156104085780601f106103dd57610100808354040283529160200191610408565b820191906000526020600020905b8154815290600101906020018083116103eb57829003601f168201915b5050505050905090565b600033610420818585610a00565b60019150505b92915050565b60003361043a858285610a12565b610445858585610a8a565b506001949350505050565b6008546201000090046001600160a01b031633146104a25760405162461bcd60e51b8152600401610499906020808252600490820152632098199b60e11b604082015260600190565b60405180910390fd5b6006546000036104ba576104b68282610ae9565b5050565b60006104c5826106b0565b905060006104d38284610f11565b6006549091506104ed69021e19e0c9bab240000084610f24565b6104f79190610f3b565b6007546105049190610f5d565b6007556105113083610ae9565b61051b8482610ae9565b50505050565b6005546000906001600160a01b031633146105675760405162461bcd60e51b8152600401610499906020808252600490820152634130333960e01b604082015260600190565b6001600160a01b03831660009081526009602052604081206006805491928592610592908490610f5d565b9091555050600181015460075482546000929169021e19e0c9bab2400000916105bb9190610f24565b6105c59190610f3b565b6105cf9190610f11565b82549091506105df908590610f5d565b80835560075469021e19e0c9bab2400000916105fb9190610f24565b6106059190610f3b565b60018301554260028301558015610664576106208582610b1f565b846001600160a01b03167fc9a0214d4c5fed6341233260a7bc0c9ac1d712cc5882165fa985bb71d4f207ae8260405161065b91815260200190565b60405180910390a25b846001600160a01b03167fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c8560405161069f91815260200190565b60405180910390a250549392505050565b60006127106106c0606484610f24565b6104269190610f3b565b60606004805461038f90610ec1565b6008546201000090046001600160a01b031633146107225760405162461bcd60e51b8152600401610499906020808252600490820152632098199b60e11b604082015260600190565b6104b68282610b4d565b600033610420818585610a8a565b33600090815260096020526040812080546107805760405162461bcd60e51b8152600401610499906020808252600490820152632098191b60e11b604082015260600190565b6000816001015469021e19e0c9bab240000060075484600001546107a49190610f24565b6107ae9190610f3b565b6107b89190610f11565b905069021e19e0c9bab240000060075483600001546107d79190610f24565b6107e19190610f3b565b60018301558015610426576107f63382610b1f565b60405181815233907fc9a0214d4c5fed6341233260a7bc0c9ac1d712cc5882165fa985bb71d4f207ae9060200160405180910390a292915050565b60055460009081906001600160a01b031633146108795760405162461bcd60e51b8152600401610499906020808252600490820152634130333960e01b604082015260600190565b6001600160a01b038416600090815260096020526040902080548411156108cb5760405162461bcd60e51b8152600401610499906020808252600490820152634130333760e01b604082015260600190565b83600660008282546108dd9190610f11565b9091555050600181015460075482546000929169021e19e0c9bab2400000916109069190610f24565b6109109190610f3b565b61091a9190610f11565b825490915061092a908690610f11565b80835560075469021e19e0c9bab2400000916109469190610f24565b6109509190610f3b565b600183015580156109a9576109658682610b1f565b856001600160a01b03167fc9a0214d4c5fed6341233260a7bc0c9ac1d712cc5882165fa985bb71d4f207ae826040516109a091815260200190565b60405180910390a25b856001600160a01b03167f884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364866040516109e491815260200190565b60405180910390a2508054600290910154909590945092505050565b610a0d8383836001610b83565b505050565b6001600160a01b03838116600090815260016020908152604080832093861683529290522054600019811461051b5781811015610a7b57604051637dc7a0d960e11b81526001600160a01b03841660048201526024810182905260448101839052606401610499565b61051b84848484036000610b83565b6001600160a01b038316610ab457604051634b637e8f60e11b815260006004820152602401610499565b6001600160a01b038216610ade5760405163ec442f0560e01b815260006004820152602401610499565b610a0d838383610c58565b6001600160a01b038216610b135760405163ec442f0560e01b815260006004820152602401610499565b6104b660008383610c58565b3060009081526020819052604090205480821115610b4257610a0d308483610a8a565b610a0d308484610a8a565b6001600160a01b038216610b7757604051634b637e8f60e11b815260006004820152602401610499565b6104b682600083610c58565b6001600160a01b038416610bad5760405163e602df0560e01b815260006004820152602401610499565b6001600160a01b038316610bd757604051634a1406b160e11b815260006004820152602401610499565b6001600160a01b038085166000908152600160209081526040808320938716835292905220829055801561051b57826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92584604051610c4a91815260200190565b60405180910390a350505050565b6001600160a01b038316610c83578060026000828254610c789190610f5d565b90915550610cf59050565b6001600160a01b03831660009081526020819052604090205481811015610cd65760405163391434e360e21b81526001600160a01b03851660048201526024810182905260448101839052606401610499565b6001600160a01b03841660009081526020819052604090209082900390555b6001600160a01b038216610d1157600280548290039055610d30565b6001600160a01b03821660009081526020819052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051610d7591815260200190565b60405180910390a3505050565b60006020808352835180602085015260005b81811015610db057858101830151858201604001528201610d94565b506000604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b0381168114610de857600080fd5b919050565b60008060408385031215610e0057600080fd5b610e0983610dd1565b946020939093013593505050565b600060208284031215610e2957600080fd5b610e3282610dd1565b9392505050565b600080600060608486031215610e4e57600080fd5b610e5784610dd1565b9250610e6560208501610dd1565b9150604084013590509250925092565b600060208284031215610e8757600080fd5b5035919050565b60008060408385031215610ea157600080fd5b610eaa83610dd1565b9150610eb860208401610dd1565b90509250929050565b600181811c90821680610ed557607f821691505b602082108103610ef557634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b8181038181111561042657610426610efb565b808202811582820484141761042657610426610efb565b600082610f5857634e487b7160e01b600052601260045260246000fd5b500490565b8082018082111561042657610426610efb56fea264697066735822122015f4664b900775556b001c8d2cd115056d7089314772fe4a815e864c8bfd834f64736f6c634300081800330000000000000000000000000000000000000000000000000000000000001b5800000000000000000000000085df7ce20a4ce0cf859804b45cb540ffe42074da

Deployed ByteCode

0x608060405234801561001057600080fd5b50600436106101425760003560e01c80636d2dbe24116100b85780639dc29fac1161007c5780639dc29fac146102de578063a9059cbb146102f1578063bfb9f50214610304578063c879657214610317578063dd62ed3e1461031f578063f3fef3a31461035857600080fd5b80636d2dbe241461029357806370a082311461029b5780637d882097146102c457806395d89b41146102cd5780639cdcfb2b146102d557600080fd5b806323b872dd1161010a57806323b872dd146102055780632a67ba3614610218578063313ce5671461024957806340c10f191461025857806347e7ef241461026d578063483a93101461028057600080fd5b806306fdde0314610147578063095ea7b31461016557806318160ddd146101885780631959a0021461019a578063204f83f9146101e4575b600080fd5b61014f610380565b60405161015c9190610d82565b60405180910390f35b610178610173366004610ded565b610412565b604051901515815260200161015c565b6002545b60405190815260200161015c565b6101c96101a8366004610e17565b60096020526000908152604090208054600182015460029092015490919083565b6040805193845260208401929092529082015260600161015c565b6008546101f29061ffff1681565b60405161ffff909116815260200161015c565b610178610213366004610e39565b61042c565b600854610231906201000090046001600160a01b031681565b6040516001600160a01b03909116815260200161015c565b6040516008815260200161015c565b61026b610266366004610ded565b610450565b005b61018c61027b366004610ded565b610521565b61018c61028e366004610e75565b6106b0565b61018c606481565b61018c6102a9366004610e17565b6001600160a01b031660009081526020819052604090205490565b61018c60065481565b61014f6106ca565b61018c60075481565b61026b6102ec366004610ded565b6106d9565b6101786102ff366004610ded565b61072c565b600554610231906001600160a01b031681565b61018c61073a565b61018c61032d366004610e8e565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b61036b610366366004610ded565b610831565b6040805192835260208301919091520161015c565b60606003805461038f90610ec1565b80601f01602080910402602001604051908101604052809291908181526020018280546103bb90610ec1565b80156104085780601f106103dd57610100808354040283529160200191610408565b820191906000526020600020905b8154815290600101906020018083116103eb57829003601f168201915b5050505050905090565b600033610420818585610a00565b60019150505b92915050565b60003361043a858285610a12565b610445858585610a8a565b506001949350505050565b6008546201000090046001600160a01b031633146104a25760405162461bcd60e51b8152600401610499906020808252600490820152632098199b60e11b604082015260600190565b60405180910390fd5b6006546000036104ba576104b68282610ae9565b5050565b60006104c5826106b0565b905060006104d38284610f11565b6006549091506104ed69021e19e0c9bab240000084610f24565b6104f79190610f3b565b6007546105049190610f5d565b6007556105113083610ae9565b61051b8482610ae9565b50505050565b6005546000906001600160a01b031633146105675760405162461bcd60e51b8152600401610499906020808252600490820152634130333960e01b604082015260600190565b6001600160a01b03831660009081526009602052604081206006805491928592610592908490610f5d565b9091555050600181015460075482546000929169021e19e0c9bab2400000916105bb9190610f24565b6105c59190610f3b565b6105cf9190610f11565b82549091506105df908590610f5d565b80835560075469021e19e0c9bab2400000916105fb9190610f24565b6106059190610f3b565b60018301554260028301558015610664576106208582610b1f565b846001600160a01b03167fc9a0214d4c5fed6341233260a7bc0c9ac1d712cc5882165fa985bb71d4f207ae8260405161065b91815260200190565b60405180910390a25b846001600160a01b03167fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c8560405161069f91815260200190565b60405180910390a250549392505050565b60006127106106c0606484610f24565b6104269190610f3b565b60606004805461038f90610ec1565b6008546201000090046001600160a01b031633146107225760405162461bcd60e51b8152600401610499906020808252600490820152632098199b60e11b604082015260600190565b6104b68282610b4d565b600033610420818585610a8a565b33600090815260096020526040812080546107805760405162461bcd60e51b8152600401610499906020808252600490820152632098191b60e11b604082015260600190565b6000816001015469021e19e0c9bab240000060075484600001546107a49190610f24565b6107ae9190610f3b565b6107b89190610f11565b905069021e19e0c9bab240000060075483600001546107d79190610f24565b6107e19190610f3b565b60018301558015610426576107f63382610b1f565b60405181815233907fc9a0214d4c5fed6341233260a7bc0c9ac1d712cc5882165fa985bb71d4f207ae9060200160405180910390a292915050565b60055460009081906001600160a01b031633146108795760405162461bcd60e51b8152600401610499906020808252600490820152634130333960e01b604082015260600190565b6001600160a01b038416600090815260096020526040902080548411156108cb5760405162461bcd60e51b8152600401610499906020808252600490820152634130333760e01b604082015260600190565b83600660008282546108dd9190610f11565b9091555050600181015460075482546000929169021e19e0c9bab2400000916109069190610f24565b6109109190610f3b565b61091a9190610f11565b825490915061092a908690610f11565b80835560075469021e19e0c9bab2400000916109469190610f24565b6109509190610f3b565b600183015580156109a9576109658682610b1f565b856001600160a01b03167fc9a0214d4c5fed6341233260a7bc0c9ac1d712cc5882165fa985bb71d4f207ae826040516109a091815260200190565b60405180910390a25b856001600160a01b03167f884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364866040516109e491815260200190565b60405180910390a2508054600290910154909590945092505050565b610a0d8383836001610b83565b505050565b6001600160a01b03838116600090815260016020908152604080832093861683529290522054600019811461051b5781811015610a7b57604051637dc7a0d960e11b81526001600160a01b03841660048201526024810182905260448101839052606401610499565b61051b84848484036000610b83565b6001600160a01b038316610ab457604051634b637e8f60e11b815260006004820152602401610499565b6001600160a01b038216610ade5760405163ec442f0560e01b815260006004820152602401610499565b610a0d838383610c58565b6001600160a01b038216610b135760405163ec442f0560e01b815260006004820152602401610499565b6104b660008383610c58565b3060009081526020819052604090205480821115610b4257610a0d308483610a8a565b610a0d308484610a8a565b6001600160a01b038216610b7757604051634b637e8f60e11b815260006004820152602401610499565b6104b682600083610c58565b6001600160a01b038416610bad5760405163e602df0560e01b815260006004820152602401610499565b6001600160a01b038316610bd757604051634a1406b160e11b815260006004820152602401610499565b6001600160a01b038085166000908152600160209081526040808320938716835292905220829055801561051b57826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92584604051610c4a91815260200190565b60405180910390a350505050565b6001600160a01b038316610c83578060026000828254610c789190610f5d565b90915550610cf59050565b6001600160a01b03831660009081526020819052604090205481811015610cd65760405163391434e360e21b81526001600160a01b03851660048201526024810182905260448101839052606401610499565b6001600160a01b03841660009081526020819052604090209082900390555b6001600160a01b038216610d1157600280548290039055610d30565b6001600160a01b03821660009081526020819052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051610d7591815260200190565b60405180910390a3505050565b60006020808352835180602085015260005b81811015610db057858101830151858201604001528201610d94565b506000604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b0381168114610de857600080fd5b919050565b60008060408385031215610e0057600080fd5b610e0983610dd1565b946020939093013593505050565b600060208284031215610e2957600080fd5b610e3282610dd1565b9392505050565b600080600060608486031215610e4e57600080fd5b610e5784610dd1565b9250610e6560208501610dd1565b9150604084013590509250925092565b600060208284031215610e8757600080fd5b5035919050565b60008060408385031215610ea157600080fd5b610eaa83610dd1565b9150610eb860208401610dd1565b90509250929050565b600181811c90821680610ed557607f821691505b602082108103610ef557634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b8181038181111561042657610426610efb565b808202811582820484141761042657610426610efb565b600082610f5857634e487b7160e01b600052601260045260246000fd5b500490565b8082018082111561042657610426610efb56fea264697066735822122015f4664b900775556b001c8d2cd115056d7089314772fe4a815e864c8bfd834f64736f6c63430008180033