{
  "language": "Solidity",
  "sources": {
    "lib/openzeppelin-contracts/contracts/interfaces/IERC1363.sol": {
      "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC1363.sol)\n\npragma solidity >=0.6.2;\n\nimport {IERC20} from \"./IERC20.sol\";\nimport {IERC165} from \"./IERC165.sol\";\n\n/**\n * @title IERC1363\n * @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].\n *\n * Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract\n * after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.\n */\ninterface IERC1363 is IERC20, IERC165 {\n    /*\n     * Note: the ERC-165 identifier for this interface is 0xb0202a11.\n     * 0xb0202a11 ===\n     *   bytes4(keccak256('transferAndCall(address,uint256)')) ^\n     *   bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^\n     *   bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^\n     *   bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^\n     *   bytes4(keccak256('approveAndCall(address,uint256)')) ^\n     *   bytes4(keccak256('approveAndCall(address,uint256,bytes)'))\n     */\n\n    /**\n     * @dev Moves a `value` amount of tokens from the caller's account to `to`\n     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\n     * @param to The address which you want to transfer to.\n     * @param value The amount of tokens to be transferred.\n     * @return A boolean value indicating whether the operation succeeded unless throwing.\n     */\n    function transferAndCall(address to, uint256 value) external returns (bool);\n\n    /**\n     * @dev Moves a `value` amount of tokens from the caller's account to `to`\n     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\n     * @param to The address which you want to transfer to.\n     * @param value The amount of tokens to be transferred.\n     * @param data Additional data with no specified format, sent in call to `to`.\n     * @return A boolean value indicating whether the operation succeeded unless throwing.\n     */\n    function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);\n\n    /**\n     * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism\n     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\n     * @param from The address which you want to send tokens from.\n     * @param to The address which you want to transfer to.\n     * @param value The amount of tokens to be transferred.\n     * @return A boolean value indicating whether the operation succeeded unless throwing.\n     */\n    function transferFromAndCall(address from, address to, uint256 value) external returns (bool);\n\n    /**\n     * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism\n     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\n     * @param from The address which you want to send tokens from.\n     * @param to The address which you want to transfer to.\n     * @param value The amount of tokens to be transferred.\n     * @param data Additional data with no specified format, sent in call to `to`.\n     * @return A boolean value indicating whether the operation succeeded unless throwing.\n     */\n    function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);\n\n    /**\n     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\n     * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.\n     * @param spender The address which will spend the funds.\n     * @param value The amount of tokens to be spent.\n     * @return A boolean value indicating whether the operation succeeded unless throwing.\n     */\n    function approveAndCall(address spender, uint256 value) external returns (bool);\n\n    /**\n     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\n     * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.\n     * @param spender The address which will spend the funds.\n     * @param value The amount of tokens to be spent.\n     * @param data Additional data with no specified format, sent in call to `spender`.\n     * @return A boolean value indicating whether the operation succeeded unless throwing.\n     */\n    function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);\n}\n"
    },
    "lib/openzeppelin-contracts/contracts/interfaces/IERC165.sol": {
      "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC165.sol)\n\npragma solidity >=0.4.16;\n\nimport {IERC165} from \"../utils/introspection/IERC165.sol\";\n"
    },
    "lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC20.sol)\n\npragma solidity >=0.4.16;\n\nimport {IERC20} from \"../token/ERC20/IERC20.sol\";\n"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)\n\npragma solidity >=0.4.16;\n\n/**\n * @dev Interface of the ERC-20 standard as defined in the ERC.\n */\ninterface IERC20 {\n    /**\n     * @dev Emitted when `value` tokens are moved from one account (`from`) to\n     * another (`to`).\n     *\n     * Note that `value` may be zero.\n     */\n    event Transfer(address indexed from, address indexed to, uint256 value);\n\n    /**\n     * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n     * a call to {approve}. `value` is the new allowance.\n     */\n    event Approval(address indexed owner, address indexed spender, uint256 value);\n\n    /**\n     * @dev Returns the value of tokens in existence.\n     */\n    function totalSupply() external view returns (uint256);\n\n    /**\n     * @dev Returns the value of tokens owned by `account`.\n     */\n    function balanceOf(address account) external view returns (uint256);\n\n    /**\n     * @dev Moves a `value` amount of tokens from the caller's account to `to`.\n     *\n     * Returns a boolean value indicating whether the operation succeeded.\n     *\n     * Emits a {Transfer} event.\n     */\n    function transfer(address to, uint256 value) external returns (bool);\n\n    /**\n     * @dev Returns the remaining number of tokens that `spender` will be\n     * allowed to spend on behalf of `owner` through {transferFrom}. This is\n     * zero by default.\n     *\n     * This value changes when {approve} or {transferFrom} are called.\n     */\n    function allowance(address owner, address spender) external view returns (uint256);\n\n    /**\n     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\n     * caller's tokens.\n     *\n     * Returns a boolean value indicating whether the operation succeeded.\n     *\n     * IMPORTANT: Beware that changing an allowance with this method brings the risk\n     * that someone may use both the old and the new allowance by unfortunate\n     * transaction ordering. One possible solution to mitigate this race\n     * condition is to first reduce the spender's allowance to 0 and set the\n     * desired value afterwards:\n     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n     *\n     * Emits an {Approval} event.\n     */\n    function approve(address spender, uint256 value) external returns (bool);\n\n    /**\n     * @dev Moves a `value` amount of tokens from `from` to `to` using the\n     * allowance mechanism. `value` is then deducted from the caller's\n     * allowance.\n     *\n     * Returns a boolean value indicating whether the operation succeeded.\n     *\n     * Emits a {Transfer} event.\n     */\n    function transferFrom(address from, address to, uint256 value) external returns (bool);\n}\n"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"../IERC20.sol\";\nimport {IERC1363} from \"../../../interfaces/IERC1363.sol\";\n\n/**\n * @title SafeERC20\n * @dev Wrappers around ERC-20 operations that throw on failure (when the token\n * contract returns false). Tokens that return no value (and instead revert or\n * throw on failure) are also supported, non-reverting calls are assumed to be\n * successful.\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\n */\nlibrary SafeERC20 {\n    /**\n     * @dev An operation with an ERC-20 token failed.\n     */\n    error SafeERC20FailedOperation(address token);\n\n    /**\n     * @dev Indicates a failed `decreaseAllowance` request.\n     */\n    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);\n\n    /**\n     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,\n     * non-reverting calls are assumed to be successful.\n     */\n    function safeTransfer(IERC20 token, address to, uint256 value) internal {\n        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));\n    }\n\n    /**\n     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the\n     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.\n     */\n    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {\n        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));\n    }\n\n    /**\n     * @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.\n     */\n    function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {\n        return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));\n    }\n\n    /**\n     * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.\n     */\n    function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {\n        return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));\n    }\n\n    /**\n     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,\n     * non-reverting calls are assumed to be successful.\n     *\n     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the \"client\"\n     * smart contract uses ERC-7674 to set temporary allowances, then the \"client\" smart contract should avoid using\n     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract\n     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.\n     */\n    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {\n        uint256 oldAllowance = token.allowance(address(this), spender);\n        forceApprove(token, spender, oldAllowance + value);\n    }\n\n    /**\n     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no\n     * value, non-reverting calls are assumed to be successful.\n     *\n     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the \"client\"\n     * smart contract uses ERC-7674 to set temporary allowances, then the \"client\" smart contract should avoid using\n     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract\n     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.\n     */\n    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {\n        unchecked {\n            uint256 currentAllowance = token.allowance(address(this), spender);\n            if (currentAllowance < requestedDecrease) {\n                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);\n            }\n            forceApprove(token, spender, currentAllowance - requestedDecrease);\n        }\n    }\n\n    /**\n     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,\n     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval\n     * to be set to zero before setting it to a non-zero value, such as USDT.\n     *\n     * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function\n     * only sets the \"standard\" allowance. Any temporary allowance will remain active, in addition to the value being\n     * set here.\n     */\n    function forceApprove(IERC20 token, address spender, uint256 value) internal {\n        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));\n\n        if (!_callOptionalReturnBool(token, approvalCall)) {\n            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));\n            _callOptionalReturn(token, approvalCall);\n        }\n    }\n\n    /**\n     * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no\n     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when\n     * targeting contracts.\n     *\n     * Reverts if the returned value is other than `true`.\n     */\n    function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {\n        if (to.code.length == 0) {\n            safeTransfer(token, to, value);\n        } else if (!token.transferAndCall(to, value, data)) {\n            revert SafeERC20FailedOperation(address(token));\n        }\n    }\n\n    /**\n     * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target\n     * has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when\n     * targeting contracts.\n     *\n     * Reverts if the returned value is other than `true`.\n     */\n    function transferFromAndCallRelaxed(\n        IERC1363 token,\n        address from,\n        address to,\n        uint256 value,\n        bytes memory data\n    ) internal {\n        if (to.code.length == 0) {\n            safeTransferFrom(token, from, to, value);\n        } else if (!token.transferFromAndCall(from, to, value, data)) {\n            revert SafeERC20FailedOperation(address(token));\n        }\n    }\n\n    /**\n     * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no\n     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when\n     * targeting contracts.\n     *\n     * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.\n     * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}\n     * once without retrying, and relies on the returned value to be true.\n     *\n     * Reverts if the returned value is other than `true`.\n     */\n    function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {\n        if (to.code.length == 0) {\n            forceApprove(token, to, value);\n        } else if (!token.approveAndCall(to, value, data)) {\n            revert SafeERC20FailedOperation(address(token));\n        }\n    }\n\n    /**\n     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\n     * on the return value: the return value is optional (but if data is returned, it must not be false).\n     * @param token The token targeted by the call.\n     * @param data The call data (encoded using abi.encode or one of its variants).\n     *\n     * This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.\n     */\n    function _callOptionalReturn(IERC20 token, bytes memory data) private {\n        uint256 returnSize;\n        uint256 returnValue;\n        assembly (\"memory-safe\") {\n            let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)\n            // bubble errors\n            if iszero(success) {\n                let ptr := mload(0x40)\n                returndatacopy(ptr, 0, returndatasize())\n                revert(ptr, returndatasize())\n            }\n            returnSize := returndatasize()\n            returnValue := mload(0)\n        }\n\n        if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {\n            revert SafeERC20FailedOperation(address(token));\n        }\n    }\n\n    /**\n     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\n     * on the return value: the return value is optional (but if data is returned, it must not be false).\n     * @param token The token targeted by the call.\n     * @param data The call data (encoded using abi.encode or one of its variants).\n     *\n     * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.\n     */\n    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {\n        bool success;\n        uint256 returnSize;\n        uint256 returnValue;\n        assembly (\"memory-safe\") {\n            success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)\n            returnSize := returndatasize()\n            returnValue := mload(0)\n        }\n        return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);\n    }\n}\n"
    },
    "lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol": {
      "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Contract module that helps prevent reentrant calls to a function.\n *\n * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier\n * available, which can be applied to functions to make sure there are no nested\n * (reentrant) calls to them.\n *\n * Note that because there is a single `nonReentrant` guard, functions marked as\n * `nonReentrant` may not call one another. This can be worked around by making\n * those functions `private`, and then adding `external` `nonReentrant` entry\n * points to them.\n *\n * TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,\n * consider using {ReentrancyGuardTransient} instead.\n *\n * TIP: If you would like to learn more about reentrancy and alternative ways\n * to protect against it, check out our blog post\n * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].\n */\nabstract contract ReentrancyGuard {\n    // Booleans are more expensive than uint256 or any type that takes up a full\n    // word because each write operation emits an extra SLOAD to first read the\n    // slot's contents, replace the bits taken up by the boolean, and then write\n    // back. This is the compiler's defense against contract upgrades and\n    // pointer aliasing, and it cannot be disabled.\n\n    // The values being non-zero value makes deployment a bit more expensive,\n    // but in exchange the refund on every call to nonReentrant will be lower in\n    // amount. Since refunds are capped to a percentage of the total\n    // transaction's gas, it is best to keep them low in cases like this one, to\n    // increase the likelihood of the full refund coming into effect.\n    uint256 private constant NOT_ENTERED = 1;\n    uint256 private constant ENTERED = 2;\n\n    uint256 private _status;\n\n    /**\n     * @dev Unauthorized reentrant call.\n     */\n    error ReentrancyGuardReentrantCall();\n\n    constructor() {\n        _status = NOT_ENTERED;\n    }\n\n    /**\n     * @dev Prevents a contract from calling itself, directly or indirectly.\n     * Calling a `nonReentrant` function from another `nonReentrant`\n     * function is not supported. It is possible to prevent this from happening\n     * by making the `nonReentrant` function external, and making it call a\n     * `private` function that does the actual work.\n     */\n    modifier nonReentrant() {\n        _nonReentrantBefore();\n        _;\n        _nonReentrantAfter();\n    }\n\n    function _nonReentrantBefore() private {\n        // On the first call to nonReentrant, _status will be NOT_ENTERED\n        if (_status == ENTERED) {\n            revert ReentrancyGuardReentrantCall();\n        }\n\n        // Any calls to nonReentrant after this point will fail\n        _status = ENTERED;\n    }\n\n    function _nonReentrantAfter() private {\n        // By storing the original value once again, a refund is triggered (see\n        // https://eips.ethereum.org/EIPS/eip-2200)\n        _status = NOT_ENTERED;\n    }\n\n    /**\n     * @dev Returns true if the reentrancy guard is currently set to \"entered\", which indicates there is a\n     * `nonReentrant` function in the call stack.\n     */\n    function _reentrancyGuardEntered() internal view returns (bool) {\n        return _status == ENTERED;\n    }\n}\n"
    },
    "lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol": {
      "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.4.0) (utils/introspection/IERC165.sol)\n\npragma solidity >=0.4.16;\n\n/**\n * @dev Interface of the ERC-165 standard, as defined in the\n * https://eips.ethereum.org/EIPS/eip-165[ERC].\n *\n * Implementers can declare support of contract interfaces, which can then be\n * queried by others ({ERC165Checker}).\n *\n * For an implementation, see {ERC165}.\n */\ninterface IERC165 {\n    /**\n     * @dev Returns true if this contract implements the interface defined by\n     * `interfaceId`. See the corresponding\n     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]\n     * to learn more about how these ids are created.\n     *\n     * This function call must use less than 30 000 gas.\n     */\n    function supportsInterface(bytes4 interfaceId) external view returns (bool);\n}\n"
    },
    "src/HenPublicMintBond.sol": {
      "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.30;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { ReentrancyGuard } from \"@openzeppelin/contracts/utils/ReentrancyGuard.sol\";\n\ninterface IHenPublicMintAllocation {\n    function allocationFinalized() external view returns (bool);\n    function publicAllocationCount() external view returns (uint32);\n}\n\n/// @notice Holds one refundable HEGG bond per public-mint entrant and selects winners on-chain.\n/// @dev There is deliberately no owner withdrawal or bond-seizure path. A deployment is one mint round.\ncontract HenPublicMintBond is ReentrancyGuard {\n    using SafeERC20 for IERC20;\n\n    uint64 public constant QUALIFICATION_PERIOD = 120 minutes;\n    uint64 public constant CLAIM_WINDOW = 24 hours;\n    uint64 public constant WINNER_REFUND_DELAY = 2 hours;\n    uint32 public constant MAX_ENTROPY_DELAY_BLOCKS = 200;\n\n    struct Entry {\n        uint64 ticket;\n        uint64 claimedAt;\n        bool locked;\n    }\n\n    IERC20 public immutable hegg;\n    address public immutable claimController;\n    uint256 public immutable bondAmount;\n    uint64 public immutable lockStart;\n    uint64 public immutable lockCutoff;\n    uint64 public immutable publicMintStart;\n    uint64 public immutable claimDeadline;\n    bytes32 public immutable entropyCommitment;\n    uint32 public immutable entropyDelayBlocks;\n\n    uint64 public entrantCount;\n    uint64 public winnerCount;\n    uint64 public entropyBlock;\n    uint256 public totalLocked;\n    bytes32 public capturedBlockHash;\n    bytes32 public selectionSeed;\n    uint64 public permutationMultiplier;\n    uint64 public permutationOffset;\n    bool public selectionRequested;\n    bool public selectionFinalized;\n\n    mapping(address account => Entry) public entries;\n\n    event BondLocked(address indexed account, uint256 amount, uint256 indexed ticket);\n    event SelectionRequested(uint256 entrantCount, uint256 winnerCount, uint256 entropyBlock);\n    event EntropyCaptured(uint256 indexed entropyBlock, bytes32 blockHash);\n    event SelectionFinalized(bytes32 indexed seed, uint256 winnerCount, uint256 multiplier, uint256 offset);\n    event ClaimConsumed(\n        address indexed account,\n        uint256 indexed ticket,\n        uint256 indexed allocationIndex,\n        uint256 refundAvailableAt\n    );\n    event BondWithdrawn(address indexed account, uint256 amount);\n\n    error ZeroAddress();\n    error InvalidAmount();\n    error InvalidSchedule();\n    error InvalidCommitment();\n    error InvalidEntropyDelay();\n    error LockNotOpen();\n    error LockClosed();\n    error AlreadyEntered();\n    error UnexpectedTransferAmount();\n    error AllocationNotFinalized();\n    error SelectionAlreadyRequested();\n    error SelectionNotRequested();\n    error SelectionAlreadyFinalized();\n    error SelectionWindowClosed();\n    error EntropyNotReady();\n    error EntropyExpired();\n    error EntropyAlreadyCaptured();\n    error InvalidEntropyReveal();\n    error SelectionNotFinalized();\n    error NotClaimController();\n    error ClaimNotOpen();\n    error ClaimClosed();\n    error NotEntered();\n    error NotSelected();\n    error AlreadyClaimed();\n    error RefundNotAvailable();\n\n    constructor(\n        IERC20 hegg_,\n        address claimController_,\n        uint256 bondAmount_,\n        uint64 lockStart_,\n        uint64 publicMintStart_,\n        bytes32 entropyCommitment_,\n        uint32 entropyDelayBlocks_\n    ) {\n        if (address(hegg_) == address(0) || claimController_ == address(0)) {\n            revert ZeroAddress();\n        }\n        if (bondAmount_ == 0) revert InvalidAmount();\n        if (publicMintStart_ <= QUALIFICATION_PERIOD || lockStart_ >= publicMintStart_ - QUALIFICATION_PERIOD)\n        {\n            revert InvalidSchedule();\n        }\n        if (entropyCommitment_ == bytes32(0)) revert InvalidCommitment();\n        if (entropyDelayBlocks_ == 0 || entropyDelayBlocks_ > MAX_ENTROPY_DELAY_BLOCKS) {\n            revert InvalidEntropyDelay();\n        }\n\n        hegg = hegg_;\n        claimController = claimController_;\n        bondAmount = bondAmount_;\n        lockStart = lockStart_;\n        lockCutoff = publicMintStart_ - QUALIFICATION_PERIOD;\n        publicMintStart = publicMintStart_;\n        claimDeadline = publicMintStart_ + CLAIM_WINDOW;\n        entropyCommitment = entropyCommitment_;\n        entropyDelayBlocks = entropyDelayBlocks_;\n    }\n\n    /// @notice Locks exactly one bond and assigns the caller one immutable ticket.\n    function lock() external nonReentrant {\n        if (block.timestamp < lockStart) revert LockNotOpen();\n        if (block.timestamp >= lockCutoff) revert LockClosed();\n        if (entries[msg.sender].locked) revert AlreadyEntered();\n\n        uint64 ticket = entrantCount;\n        entries[msg.sender] = Entry({ ticket: ticket, claimedAt: 0, locked: true });\n        entrantCount = ticket + 1;\n        totalLocked += bondAmount;\n\n        uint256 balanceBefore = hegg.balanceOf(address(this));\n        hegg.safeTransferFrom(msg.sender, address(this), bondAmount);\n        if (hegg.balanceOf(address(this)) - balanceBefore != bondAmount) {\n            revert UnexpectedTransferAmount();\n        }\n        emit BondLocked(msg.sender, bondAmount, ticket);\n    }\n\n    /// @notice Freezes the entrant count and obtains the public Hen supply from the mint controller.\n    /// @dev Anyone may call this after the cutoff. Under-subscribed rounds finalize immediately.\n    function requestSelection() external {\n        if (block.timestamp < lockCutoff) revert LockNotOpen();\n        if (block.timestamp >= publicMintStart) revert SelectionWindowClosed();\n        if (selectionRequested) revert SelectionAlreadyRequested();\n        IHenPublicMintAllocation controller = IHenPublicMintAllocation(claimController);\n        if (!controller.allocationFinalized()) revert AllocationNotFinalized();\n\n        uint64 available = uint64(controller.publicAllocationCount());\n        uint64 selected = entrantCount < available ? entrantCount : available;\n        selectionRequested = true;\n        winnerCount = selected;\n\n        if (selected == entrantCount || selected == 0) {\n            selectionFinalized = true;\n            emit SelectionRequested(entrantCount, selected, 0);\n            emit SelectionFinalized(bytes32(0), selected, 0, 0);\n            return;\n        }\n\n        entropyBlock = uint64(block.number + entropyDelayBlocks);\n        emit SelectionRequested(entrantCount, selected, entropyBlock);\n    }\n\n    /// @notice Stores the committed future block hash before the EVM's 256-block lookup window expires.\n    function captureEntropy() external {\n        if (!selectionRequested) revert SelectionNotRequested();\n        if (selectionFinalized) revert SelectionAlreadyFinalized();\n        if (block.timestamp >= publicMintStart) revert SelectionWindowClosed();\n        if (capturedBlockHash != bytes32(0)) revert EntropyAlreadyCaptured();\n        if (block.number <= entropyBlock) revert EntropyNotReady();\n        if (block.number > uint256(entropyBlock) + 256) revert EntropyExpired();\n\n        bytes32 entropy = blockhash(entropyBlock);\n        if (entropy == bytes32(0)) revert EntropyExpired();\n        capturedBlockHash = entropy;\n        emit EntropyCaptured(entropyBlock, entropy);\n    }\n\n    /// @notice Reveals the precommitted secret and finalizes a deterministic on-chain permutation.\n    /// @dev The secret cannot be changed after deployment, and the future block hash was unknown then.\n    function finalizeSelection(bytes32 secret) external {\n        if (!selectionRequested) revert SelectionNotRequested();\n        if (selectionFinalized) revert SelectionAlreadyFinalized();\n        if (block.timestamp >= publicMintStart) revert SelectionWindowClosed();\n        if (capturedBlockHash == bytes32(0)) revert EntropyNotReady();\n        if (keccak256(abi.encodePacked(secret)) != entropyCommitment) revert InvalidEntropyReveal();\n\n        bytes32 seed = keccak256(\n            abi.encode(secret, capturedBlockHash, block.chainid, address(this), entrantCount, winnerCount)\n        );\n        uint64 multiplier = _coprimeMultiplier(uint256(seed), entrantCount);\n        uint64 offset = uint64(uint256(keccak256(abi.encode(seed, \"HENS_OFFSET\"))) % entrantCount);\n\n        selectionSeed = seed;\n        permutationMultiplier = multiplier;\n        permutationOffset = offset;\n        selectionFinalized = true;\n        emit SelectionFinalized(seed, winnerCount, multiplier, offset);\n    }\n\n    /// @notice Called atomically by the mint controller immediately before minting a selected wallet's Hen.\n    function consumeClaim(address account) external returns (uint32 allocationIndex_) {\n        if (msg.sender != claimController) revert NotClaimController();\n        if (!selectionFinalized) revert SelectionNotFinalized();\n        if (block.timestamp < publicMintStart) revert ClaimNotOpen();\n        if (block.timestamp >= claimDeadline) revert ClaimClosed();\n\n        Entry storage entry = entries[account];\n        if (!entry.locked) revert NotEntered();\n        if (entry.claimedAt != 0) revert AlreadyClaimed();\n        if (!_isWinningTicket(entry.ticket)) revert NotSelected();\n\n        entry.claimedAt = uint64(block.timestamp);\n        allocationIndex_ = uint32(_allocationIndex(entry.ticket));\n        emit ClaimConsumed(account, entry.ticket, allocationIndex_, block.timestamp + WINNER_REFUND_DELAY);\n    }\n\n    /// @notice Returns the full bond. Losers may withdraw after selection; winners unlock two hours\n    /// after claiming, or at the end of the 24-hour claim window if they never claim.\n    function withdraw() external nonReentrant {\n        Entry storage entry = entries[msg.sender];\n        if (!entry.locked) revert NotEntered();\n\n        bool available;\n        if (!selectionFinalized) {\n            available = block.timestamp >= publicMintStart;\n        } else if (entry.claimedAt != 0) {\n            available = block.timestamp >= uint256(entry.claimedAt) + WINNER_REFUND_DELAY;\n        } else if (!_isWinningTicket(entry.ticket)) {\n            available = true;\n        } else {\n            available = block.timestamp >= claimDeadline;\n        }\n        if (!available) revert RefundNotAvailable();\n\n        entry.locked = false;\n        totalLocked -= bondAmount;\n        hegg.safeTransfer(msg.sender, bondAmount);\n        emit BondWithdrawn(msg.sender, bondAmount);\n    }\n\n    function isSelected(address account) external view returns (bool) {\n        Entry memory entry = entries[account];\n        return selectionFinalized && entry.locked && _isWinningTicket(entry.ticket);\n    }\n\n    function refundAvailableAt(address account) external view returns (uint256) {\n        Entry memory entry = entries[account];\n        if (!entry.locked) return 0;\n        if (!selectionFinalized) return publicMintStart;\n        if (entry.claimedAt != 0) return uint256(entry.claimedAt) + WINNER_REFUND_DELAY;\n        if (!_isWinningTicket(entry.ticket)) return block.timestamp;\n        return claimDeadline;\n    }\n\n    function permutedTicket(uint256 ticket) external view returns (uint256) {\n        if (!selectionFinalized) revert SelectionNotFinalized();\n        if (ticket >= entrantCount) revert NotEntered();\n        return _permutedTicket(ticket);\n    }\n\n    /// @notice The winner's unique index in the published public-Hen allocation tree.\n    function allocationIndex(address account) external view returns (uint256) {\n        if (!selectionFinalized) revert SelectionNotFinalized();\n        Entry memory entry = entries[account];\n        if (!entry.locked) revert NotEntered();\n        if (!_isWinningTicket(entry.ticket)) revert NotSelected();\n        return _allocationIndex(entry.ticket);\n    }\n\n    function _isWinningTicket(uint256 ticket) internal view returns (bool) {\n        if (winnerCount == entrantCount) return true;\n        return _permutedTicket(ticket) < winnerCount;\n    }\n\n    function _allocationIndex(uint256 ticket) internal view returns (uint256) {\n        if (winnerCount == entrantCount) return ticket;\n        return _permutedTicket(ticket);\n    }\n\n    function _permutedTicket(uint256 ticket) internal view returns (uint256) {\n        return addmod(mulmod(permutationMultiplier, ticket, entrantCount), permutationOffset, entrantCount);\n    }\n\n    function _coprimeMultiplier(uint256 seed, uint64 modulus) internal pure returns (uint64) {\n        if (modulus <= 1) return 0;\n        uint64 candidate = uint64(seed % modulus);\n        if (candidate == 0) candidate = 1;\n        while (_gcd(candidate, modulus) != 1) {\n            candidate = candidate == modulus - 1 ? 1 : candidate + 1;\n        }\n        return candidate;\n    }\n\n    function _gcd(uint64 a, uint64 b) internal pure returns (uint64) {\n        while (b != 0) {\n            (a, b) = (b, a % b);\n        }\n        return a;\n    }\n}\n"
    }
  },
  "settings": {
    "evmVersion": "cancun",
    "libraries": {},
    "metadata": {
      "bytecodeHash": "ipfs"
    },
    "optimizer": {
      "enabled": true,
      "runs": 500
    },
    "remappings": [
      ":@ensdomains/=lib/v4-core/node_modules/@ensdomains/",
      ":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/",
      ":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
      ":@uniswap/v4-core/=lib/v4-core/",
      ":ds-test/=lib/v4-core/lib/forge-std/lib/ds-test/src/",
      ":erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/",
      ":forge-gas-snapshot/=lib/v4-periphery/lib/permit2/lib/forge-gas-snapshot/src/",
      ":forge-std/=lib/forge-std/src/",
      ":halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/",
      ":hardhat/=lib/v4-core/node_modules/hardhat/",
      ":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
      ":openzeppelin-contracts/=lib/openzeppelin-contracts/",
      ":permit2/=lib/v4-periphery/lib/permit2/",
      ":solmate/=lib/v4-core/lib/solmate/",
      ":v4-core/=lib/v4-core/src/",
      ":v4-periphery/=lib/v4-periphery/"
    ],
    "viaIR": true,
    "outputSelection": {
      "src/HenPublicMintBond.sol": {
        "HenPublicMintBond": [
          "abi",
          "metadata",
          "evm.bytecode",
          "evm.deployedBytecode",
          "evm.methodIdentifiers"
        ]
      }
    }
  }
}
