{"language":"Solidity","sources":{"src/SashimiLiquidityLocker.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\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\";\nimport {SashimiCurveEngine} from \"./SashimiCurveEngine.sol\";\nimport {INonfungiblePositionManager} from \"./interfaces/IUniswapV3.sol\";\n\n/// @title SashimiLiquidityLocker\n/// @notice Custodies each graduated token's Uniswap V3 LP position NFT **forever** — there is no\n///         function that transfers, burns, or withdraws the position or its principal. Liquidity\n///         is permanent and unruggable, including by the protocol owner.\n/// @dev    The only value that ever leaves is *trading fees*, collected permissionlessly and split\n///         40% creator / 40% protocol / 20% compounded back into the locked position (deepening it).\n///         Positions may live on either graduation venue (community or house Uniswap V3), so the\n///         position manager is recorded per token. The protocol share goes to the engine's CURRENT\n///         fee recipient, so a single blocklisted or lost address can never freeze fee collection.\ncontract SashimiLiquidityLocker is ReentrancyGuard {\n    using SafeERC20 for IERC20;\n\n    SashimiCurveEngine public immutable engine;\n    address public immutable usdc;\n    address public graduator;\n    address public owner;\n\n    uint256 internal constant CREATOR_BPS = 4000; // 40%\n    uint256 internal constant PROTOCOL_BPS = 4000; // 40%\n    // remaining 20% is compounded back into the position\n\n    mapping(address token => uint256 tokenId) public tokenIdOf;\n    mapping(address token => INonfungiblePositionManager npm) public npmOf;\n\n    event PositionLocked(address indexed token, address indexed npm, uint256 indexed tokenId);\n    event FeesCollected(\n        address indexed token, uint256 amount0, uint256 amount1, uint256 toCreator0, uint256 toCreator1\n    );\n\n    error OnlyGraduator();\n    error OnlyOwner();\n    error AlreadySet();\n    error ZeroAddress();\n    error UnknownToken();\n\n    constructor(SashimiCurveEngine engine_, address owner_) {\n        if (address(engine_) == address(0) || owner_ == address(0)) revert ZeroAddress();\n        engine = engine_;\n        usdc = address(engine_.usdc());\n        owner = owner_;\n    }\n\n    /// @notice One-shot: the only address allowed to register positions.\n    function setGraduator(address graduator_) external {\n        if (msg.sender != owner) revert OnlyOwner();\n        if (graduator != address(0)) revert AlreadySet();\n        if (graduator_ == address(0)) revert ZeroAddress();\n        graduator = graduator_;\n    }\n\n    /// @notice Protocol share destination — always the engine's current fee recipient.\n    function feeRecipient() public view returns (address) {\n        return engine.feeRecipient();\n    }\n\n    /// @notice Record a newly-locked position. Called by the graduator, which minted the NFT\n    ///         directly to this contract.\n    function register(address token, INonfungiblePositionManager npm, uint256 tokenId) external {\n        if (msg.sender != graduator) revert OnlyGraduator();\n        if (address(npm) == address(0)) revert ZeroAddress();\n        if (tokenIdOf[token] != 0) revert AlreadySet();\n        tokenIdOf[token] = tokenId;\n        npmOf[token] = npm;\n        emit PositionLocked(token, address(npm), tokenId);\n    }\n\n    /// @notice Collect accrued trading fees for a token and distribute them. Permissionless.\n    function collectFees(address token) external nonReentrant {\n        uint256 tokenId = tokenIdOf[token];\n        if (tokenId == 0) revert UnknownToken();\n        INonfungiblePositionManager positionManager = npmOf[token];\n\n        (uint256 amount0, uint256 amount1) = positionManager.collect(\n            INonfungiblePositionManager.CollectParams({\n                tokenId: tokenId,\n                recipient: address(this),\n                amount0Max: type(uint128).max,\n                amount1Max: type(uint128).max\n            })\n        );\n        if (amount0 == 0 && amount1 == 0) return;\n\n        (address t0, address t1) = _order(token);\n\n        // 20% of each token compounds back into the locked position\n        uint256 comp0 = amount0 / 5;\n        uint256 comp1 = amount1 / 5;\n        if (comp0 > 0 || comp1 > 0) {\n            IERC20(t0).forceApprove(address(positionManager), comp0);\n            IERC20(t1).forceApprove(address(positionManager), comp1);\n            try positionManager.increaseLiquidity(\n                INonfungiblePositionManager.IncreaseLiquidityParams({\n                    tokenId: tokenId,\n                    amount0Desired: comp0,\n                    amount1Desired: comp1,\n                    amount0Min: 0,\n                    amount1Min: 0,\n                    deadline: block.timestamp\n                })\n            ) {} catch {}\n            IERC20(t0).forceApprove(address(positionManager), 0);\n            IERC20(t1).forceApprove(address(positionManager), 0);\n        }\n\n        // remaining balances (post-compound leftovers included) split 50/50 creator/protocol\n        address creator = _creatorOf(token);\n        uint256 toCreator0 = _payoutHalf(IERC20(t0), creator);\n        uint256 toCreator1 = _payoutHalf(IERC20(t1), creator);\n\n        emit FeesCollected(token, amount0, amount1, toCreator0, toCreator1);\n    }\n\n    // ─── Internal ─────────────────────────────────────────────────────────────\n    function _payoutHalf(IERC20 tkn, address creator) internal returns (uint256 toCreator) {\n        uint256 bal = tkn.balanceOf(address(this));\n        if (bal == 0) return 0;\n        toCreator = bal / 2;\n        if (toCreator > 0) tkn.safeTransfer(creator, toCreator);\n        uint256 rest = tkn.balanceOf(address(this));\n        if (rest > 0) tkn.safeTransfer(feeRecipient(), rest);\n    }\n\n    function _order(address token) internal view returns (address token0, address token1) {\n        (token0, token1) = token < usdc ? (token, usdc) : (usdc, token);\n    }\n\n    function _creatorOf(address token) internal view returns (address creator) {\n        (,, creator,,,) = engine.curves(token);\n        if (creator == address(0)) creator = feeRecipient();\n    }\n}\n"},"lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.20;\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.1.0) (token/ERC20/utils/SafeERC20.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"../IERC20.sol\";\nimport {IERC1363} from \"../../../interfaces/IERC1363.sol\";\nimport {Address} from \"../../../utils/Address.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 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"},"src/SashimiCurveEngine.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\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\";\nimport {Ownable2Step, Ownable} from \"@openzeppelin/contracts/access/Ownable2Step.sol\";\nimport {SashimiToken} from \"./SashimiToken.sol\";\nimport {ISashimiGraduator} from \"./interfaces/ISashimiGraduator.sol\";\n\n/// @title SashimiCurveEngine\n/// @notice Singleton bonding-curve engine. Holds every token's supply and USDC reserve, prices\n///         buys/sells on a virtual-reserve constant-product curve ($2,500 -> $40,000 FDV, m=16),\n///         accrues fees, and hands completed curves to a locked Uniswap V3 pool.\n/// @dev    Design invariants:\n///         - Solvency: reserves always cover full sellback (constant-product + fee-on-USDC-leg + ceil rounding).\n///         - No admin path to user funds: the owner can NEVER touch curve reserves, token balances,\n///           accrued creator fees, or escrowed graduation liquidity. The graduation venue is set\n///           exactly once (before any launch) and can never be changed afterwards.\n///         - Sells are never pausable.\n///         - Curve completion happens exactly once, atomically, in the buy that fills the curve.\n///           Locking the liquidity (\"finalization\") is attempted in that same transaction, but if\n///           the venue cannot accept it right then (e.g. someone parked hostile liquidity in the\n///           pool), the assets stay escrowed here and `finalizeGraduation` can be retried by\n///           anyone. A completing buy therefore can never be reverted by a third party.\ncontract SashimiCurveEngine is ReentrancyGuard, Ownable2Step {\n    using SafeERC20 for IERC20;\n\n    // ─── Curve constants (18dp tokens, 6dp USDC) ───────────────────────────────\n    uint256 public constant TOTAL_SUPPLY = 1_000_000_000e18;\n    uint256 public constant CURVE_SUPPLY = 800_000_000e18; // sold along the curve\n    uint256 public constant LP_RESERVE = 200_000_000e18; // reserved for graduation LP\n    uint256 public constant V_USDC_0 = 2_666_666_667; // virtual USDC reserve (6dp) = 2666.666667\n    uint256 public constant V_TOKEN_0 = 1_066_666_666_666_666_666_666_666_667; // virtual token reserve (18dp)\n    uint256 public constant K = V_USDC_0 * V_TOKEN_0; // constant-product invariant\n\n    // ─── Fees ──────────────────────────────────────────────────────────────────\n    uint256 public constant CREATION_FEE = 1e6; // 1 USDC\n    uint256 public constant CURVE_FEE_BPS = 100; // 1.00% total curve trade fee\n    uint256 public constant CREATOR_FEE_BPS = 40; // 0.40% to creator (=> 0.60% protocol)\n    uint256 public constant GRAD_FEE_BPS = 200; // 2% of the raise, at graduation\n    uint256 public constant BPS = 10_000;\n\n    // ─── Anti-snipe surcharge (buy-side, decays to zero) ───────────────────────\n    uint256 public constant SURCHARGE_START_BPS = 5000; // 50% at t=0\n    uint256 public constant SURCHARGE_HALFLIFE = 12; // seconds\n    uint256 public constant SURCHARGE_WINDOW = 90; // seconds until it hits 0\n\n    /// @dev A completing buy must carry at least this much gas so the in-transaction liquidity lock\n    ///      (pool creation ≈ 5M gas + mint) gets a fair attempt. Below it the buy reverts instead of\n    ///      silently deferring, which also makes wallet gas estimation land on the atomic path.\n    uint256 public constant MIN_FINALIZE_GAS = 8_000_000;\n\n    IERC20 public immutable usdc;\n\n    struct Curve {\n        uint128 realUsdc; // USDC in reserve (6dp), net of fees\n        uint128 tokensSold; // tokens sold on the curve (18dp)\n        address creator; // fee recipient for this token\n        uint64 createdAt; // launch timestamp (surcharge clock)\n        bool graduated; // curve completed — trading on the curve is over\n        uint128 creatorFees; // accrued USDC owed to the creator (6dp)\n    }\n\n    /// @dev Escrowed graduation liquidity, held here between curve completion and finalization.\n    struct Graduation {\n        uint128 usdcLp; // USDC destined for the pool (6dp)\n        uint128 tokenLp; // tokens destined for the pool (18dp)\n        uint128 burned; // unpaired LP reserve burned at completion (18dp)\n        uint128 gradFee; // 2% graduation fee taken at completion (6dp)\n        uint64 completedAt; // timestamp of the completing buy\n        bool finalized; // liquidity handed to the venue and locked\n        address pool; // venue pool, set at finalization\n    }\n\n    mapping(address token => Curve) public curves;\n    mapping(address token => Graduation) public graduations;\n    address public factory; // only the factory can register curves / process creation\n    address public feeRecipient; // protocol fee destination\n    ISashimiGraduator public graduator; // graduation venue adapter — set ONCE\n    uint256 public protocolFees; // accrued protocol USDC (6dp), pull-withdrawn\n    bool public createPaused; // gate on NEW launches only (never on trading)\n\n    // ─── Events ────────────────────────────────────────────────────────────────\n    event CurveRegistered(address indexed token, address indexed creator, uint64 createdAt);\n    event Trade(\n        address indexed token,\n        address indexed trader,\n        bool isBuy,\n        uint256 usdcAmount,\n        uint256 feeAmount,\n        uint256 tokenAmount,\n        uint128 realUsdcAfter,\n        uint128 tokensSoldAfter\n    );\n    /// @notice The curve filled. Trading on the curve is over; liquidity is escrowed for the venue.\n    event CurveCompleted(address indexed token, uint256 usdcLp, uint256 tokenLp, uint256 burned, uint256 gradFee);\n    /// @notice Liquidity could not be locked in the completing transaction; anyone may retry\n    ///         `finalizeGraduation(token)`.\n    event GraduationDeferred(address indexed token);\n    /// @notice Liquidity locked in the venue. Transfers are unlocked from here on.\n    event Graduated(address indexed token, address indexed pool, uint256 usdcLp, uint256 tokenLp, uint256 burned, uint256 gradFee);\n    event CreatorFeesClaimed(address indexed token, address indexed creator, uint256 amount);\n    event CreatorRightsTransferred(address indexed token, address indexed from, address indexed to);\n    event ProtocolFeesWithdrawn(address indexed to, uint256 amount);\n    event FeeRecipientUpdated(address indexed recipient);\n    event GraduatorSet(address indexed graduator);\n    event FactorySet(address indexed factory);\n    event CreatePausedSet(bool paused);\n\n    // ─── Errors ──────────────────────────────────────────────────────────────\n    error OnlyFactory();\n    error OnlySelf();\n    error AlreadySet();\n    error CurveGraduated();\n    error UnknownCurve();\n    error DeadlinePassed();\n    error SlippageExceeded();\n    error ZeroAmount();\n    error ExceedsSold();\n    error NotCreator();\n    error ZeroAddress();\n    error GraduatorUnset();\n    error CreateIsPaused();\n    error NotCompleted();\n    error AlreadyFinalized();\n    error InsufficientGasForGraduation();\n\n    constructor(IERC20 usdc_, address feeRecipient_, address owner_) Ownable(owner_) {\n        if (address(usdc_) == address(0) || feeRecipient_ == address(0)) revert ZeroAddress();\n        usdc = usdc_;\n        feeRecipient = feeRecipient_;\n    }\n\n    // ─── Admin (bounded — never touches user funds) ────────────────────────────\n    function setFactory(address factory_) external onlyOwner {\n        if (factory != address(0)) revert AlreadySet();\n        if (factory_ == address(0)) revert ZeroAddress();\n        factory = factory_;\n        emit FactorySet(factory_);\n    }\n\n    /// @notice Set the graduation venue. ONE-SHOT: once set it can never be changed, so the venue\n    ///         a token was launched against is the venue it graduates into — no admin can redirect\n    ///         escrowed liquidity.\n    function setGraduator(address graduator_) external onlyOwner {\n        if (address(graduator) != address(0)) revert AlreadySet();\n        if (graduator_ == address(0)) revert ZeroAddress();\n        graduator = ISashimiGraduator(graduator_);\n        emit GraduatorSet(graduator_);\n    }\n\n    function setFeeRecipient(address recipient_) external onlyOwner {\n        if (recipient_ == address(0)) revert ZeroAddress();\n        feeRecipient = recipient_;\n        emit FeeRecipientUpdated(recipient_);\n    }\n\n    /// @notice Pause only NEW launches. Trading (buy/sell) is never pausable.\n    function setCreatePaused(bool paused) external onlyOwner {\n        createPaused = paused;\n        emit CreatePausedSet(paused);\n    }\n\n    // ─── Factory-only lifecycle ────────────────────────────────────────────────\n    function registerCurve(address token, address creator) external {\n        if (msg.sender != factory) revert OnlyFactory();\n        if (createPaused) revert CreateIsPaused();\n        Curve storage c = curves[token];\n        if (c.createdAt != 0) revert AlreadySet();\n        c.creator = creator;\n        c.createdAt = uint64(block.timestamp);\n        emit CurveRegistered(token, creator, c.createdAt);\n    }\n\n    /// @notice Pull the creation fee (+ optional creator dev-buy) from the creator.\n    /// @dev    Dev-buy executes on the public curve at the public price but is surcharge-exempt.\n    function processCreation(address token, address creator, uint256 devBuyUsdc, uint256 minTokensOut)\n        external\n        nonReentrant\n    {\n        if (msg.sender != factory) revert OnlyFactory();\n        // pull creation fee + dev-buy in one transfer from the creator\n        usdc.safeTransferFrom(creator, address(this), CREATION_FEE + devBuyUsdc);\n        protocolFees += CREATION_FEE;\n        if (devBuyUsdc > 0) {\n            _buy(token, creator, devBuyUsdc, minTokensOut, false);\n        }\n    }\n\n    // ─── Trading ───────────────────────────────────────────────────────────────\n    function buy(address token, uint256 usdcIn, uint256 minTokensOut, uint256 deadline)\n        external\n        nonReentrant\n        returns (uint256 tokensOut)\n    {\n        if (block.timestamp > deadline) revert DeadlinePassed();\n        if (usdcIn == 0) revert ZeroAmount();\n        usdc.safeTransferFrom(msg.sender, address(this), usdcIn);\n        return _buy(token, msg.sender, usdcIn, minTokensOut, true);\n    }\n\n    function buyWithPermit(\n        address token,\n        uint256 usdcIn,\n        uint256 minTokensOut,\n        uint256 deadline,\n        uint8 v,\n        bytes32 r,\n        bytes32 s\n    ) external nonReentrant returns (uint256 tokensOut) {\n        if (block.timestamp > deadline) revert DeadlinePassed();\n        if (usdcIn == 0) revert ZeroAmount();\n        try IERC20Permit(address(usdc)).permit(msg.sender, address(this), usdcIn, deadline, v, r, s) {} catch {}\n        usdc.safeTransferFrom(msg.sender, address(this), usdcIn);\n        return _buy(token, msg.sender, usdcIn, minTokensOut, true);\n    }\n\n    function sell(address token, uint256 tokensIn, uint256 minUsdcOut, uint256 deadline)\n        external\n        nonReentrant\n        returns (uint256 usdcOut)\n    {\n        if (block.timestamp > deadline) revert DeadlinePassed();\n        if (tokensIn == 0) revert ZeroAmount();\n        Curve storage c = curves[token];\n        if (c.createdAt == 0) revert UnknownCurve();\n        if (c.graduated) revert CurveGraduated();\n        uint256 sold = c.tokensSold;\n        if (tokensIn > sold) revert ExceedsSold();\n\n        // pull tokens back to the curve (the token allows this only when the curve is the caller)\n        IERC20(token).safeTransferFrom(msg.sender, address(this), tokensIn);\n\n        uint256 reserve = c.realUsdc;\n        uint256 usdcOutGross = (V_USDC_0 + reserve) - _ceilDiv(K, (V_TOKEN_0 - sold + tokensIn));\n        uint256 feeBase = (usdcOutGross * CURVE_FEE_BPS) / BPS;\n        uint256 creatorCut = (usdcOutGross * CREATOR_FEE_BPS) / BPS;\n        uint256 protocolCut = feeBase - creatorCut;\n        usdcOut = usdcOutGross - feeBase;\n        if (usdcOut < minUsdcOut) revert SlippageExceeded();\n\n        c.realUsdc = uint128(reserve - usdcOutGross);\n        c.tokensSold = uint128(sold - tokensIn);\n        c.creatorFees += uint128(creatorCut);\n        protocolFees += protocolCut;\n\n        usdc.safeTransfer(msg.sender, usdcOut);\n        emit Trade(token, msg.sender, false, usdcOutGross, feeBase, tokensIn, c.realUsdc, c.tokensSold);\n    }\n\n    // ─── Core buy (handles partial fill at the boundary + completion) ──────────\n    function _buy(address token, address buyer, uint256 usdcIn, uint256 minTokensOut, bool applySurcharge)\n        internal\n        returns (uint256 tokensOut)\n    {\n        Curve storage c = curves[token];\n        if (c.createdAt == 0) revert UnknownCurve();\n        if (c.graduated) revert CurveGraduated();\n\n        uint256 reserve = c.realUsdc;\n        uint256 sold = c.tokensSold;\n\n        uint256 surBps = applySurcharge ? _surchargeBps(block.timestamp - c.createdAt) : 0;\n        uint256 totalFeeBps = CURVE_FEE_BPS + surBps;\n\n        // USDC needed to reach the boundary (net into reserve)\n        uint256 netToComplete = (K / (V_TOKEN_0 - CURVE_SUPPLY)) - (V_USDC_0 + reserve);\n\n        uint256 grossUsed;\n        uint256 net;\n        bool completes;\n        {\n            uint256 netFull = (usdcIn * (BPS - totalFeeBps)) / BPS;\n            if (netFull >= netToComplete) {\n                net = netToComplete;\n                grossUsed = _ceilDiv(netToComplete * BPS, (BPS - totalFeeBps));\n                if (grossUsed > usdcIn) grossUsed = usdcIn;\n                completes = true;\n            } else {\n                net = netFull;\n                grossUsed = usdcIn;\n            }\n        }\n\n        if (completes) {\n            tokensOut = CURVE_SUPPLY - sold;\n        } else {\n            tokensOut = (V_TOKEN_0 - sold) - _ceilDiv(K, (V_USDC_0 + reserve + net));\n        }\n        if (tokensOut < minTokensOut) revert SlippageExceeded();\n\n        // fee split on the consumed gross\n        uint256 feeBase = (grossUsed * CURVE_FEE_BPS) / BPS;\n        uint256 creatorCut = (grossUsed * CREATOR_FEE_BPS) / BPS;\n        uint256 protocolCut = feeBase - creatorCut;\n        uint256 surcharge = (grossUsed * surBps) / BPS;\n\n        c.realUsdc = uint128(reserve + net);\n        c.tokensSold = uint128(sold + tokensOut);\n        c.creatorFees += uint128(creatorCut + surcharge);\n        // any integer-rounding dust stays with the protocol (favors solvency)\n        uint256 accounted = net + protocolCut + creatorCut + surcharge;\n        protocolFees += protocolCut + (grossUsed - accounted);\n\n        IERC20(token).safeTransfer(buyer, tokensOut);\n        uint256 refund = usdcIn - grossUsed;\n        if (refund > 0) usdc.safeTransfer(buyer, refund);\n\n        emit Trade(token, buyer, true, grossUsed, feeBase + surcharge, tokensOut, c.realUsdc, c.tokensSold);\n\n        if (completes) _complete(token);\n    }\n\n    // ─── Completion (atomic, exactly once) ─────────────────────────────────────\n    /// @dev Closes the curve, takes the graduation fee, burns the unpaired reserve and escrows the\n    ///      LP assets. Then tries to lock liquidity right away; a failure there is swallowed so the\n    ///      completing buyer's transaction can never be reverted by a third party.\n    function _complete(address token) internal {\n        if (address(graduator) == address(0)) revert GraduatorUnset();\n        Curve storage c = curves[token];\n        c.graduated = true;\n\n        uint256 raise = c.realUsdc;\n        uint256 gradFee = (raise * GRAD_FEE_BPS) / BPS;\n        uint256 usdcLp = raise - gradFee;\n        protocolFees += gradFee;\n        c.realUsdc = 0;\n\n        // price-matched token amount for the LP: tokenLp = usdcLp / pG\n        // pG = (V_USDC_0 + raise) / (V_TOKEN_0 - CURVE_SUPPLY)\n        uint256 numer = V_TOKEN_0 - CURVE_SUPPLY;\n        uint256 denom = V_USDC_0 + raise;\n        uint256 tokenLp = (usdcLp * numer) / denom;\n        if (tokenLp > LP_RESERVE) tokenLp = LP_RESERVE;\n        uint256 burned = LP_RESERVE - tokenLp;\n\n        Graduation storage g = graduations[token];\n        g.usdcLp = uint128(usdcLp);\n        g.tokenLp = uint128(tokenLp);\n        g.burned = uint128(burned);\n        g.gradFee = uint128(gradFee);\n        g.completedAt = uint64(block.timestamp);\n\n        if (burned > 0) SashimiToken(token).burn(burned);\n        emit CurveCompleted(token, usdcLp, tokenLp, burned, gradFee);\n\n        if (gasleft() < MIN_FINALIZE_GAS) revert InsufficientGasForGraduation();\n        // best-effort immediate lock; deferred (retriable by anyone) if the venue refuses right now\n        try this.finalizeFromCurve(token) {}\n        catch {\n            emit GraduationDeferred(token);\n        }\n    }\n\n    /// @notice Lock a completed curve's escrowed liquidity in the venue. Permissionless and\n    ///         retriable; succeeds exactly once.\n    function finalizeGraduation(address token) external nonReentrant returns (address pool) {\n        return _finalize(token);\n    }\n\n    /// @dev Self-call target used for the in-transaction attempt (the reentrancy lock is already\n    ///      held by the completing buy, so this path deliberately carries no guard of its own).\n    function finalizeFromCurve(address token) external returns (address pool) {\n        if (msg.sender != address(this)) revert OnlySelf();\n        return _finalize(token);\n    }\n\n    function _finalize(address token) internal returns (address pool) {\n        Curve storage c = curves[token];\n        Graduation storage g = graduations[token];\n        if (!c.graduated) revert NotCompleted();\n        if (g.finalized) revert AlreadyFinalized();\n        ISashimiGraduator venue = graduator;\n        if (address(venue) == address(0)) revert GraduatorUnset();\n        g.finalized = true;\n\n        // unlock transfers and hand the escrowed LP assets to the venue\n        SashimiToken(token).setGraduated();\n        IERC20(token).safeTransfer(address(venue), g.tokenLp);\n        usdc.safeTransfer(address(venue), g.usdcLp);\n\n        pool = venue.graduate(token, g.usdcLp, g.tokenLp);\n        g.pool = pool;\n        emit Graduated(token, pool, g.usdcLp, g.tokenLp, g.burned, g.gradFee);\n    }\n\n    // ─── Creator fees (pull) ───────────────────────────────────────────────────\n    function claimCreatorFees(address token) external nonReentrant returns (uint256 amount) {\n        Curve storage c = curves[token];\n        if (msg.sender != c.creator) revert NotCreator();\n        amount = c.creatorFees;\n        if (amount == 0) revert ZeroAmount();\n        c.creatorFees = 0;\n        usdc.safeTransfer(c.creator, amount);\n        emit CreatorFeesClaimed(token, c.creator, amount);\n    }\n\n    function transferCreatorRights(address token, address newCreator) external {\n        Curve storage c = curves[token];\n        if (msg.sender != c.creator) revert NotCreator();\n        if (newCreator == address(0)) revert ZeroAddress();\n        c.creator = newCreator;\n        emit CreatorRightsTransferred(token, msg.sender, newCreator);\n    }\n\n    // ─── Protocol fees (pull, permissionless trigger, fixed destination) ───────\n    function withdrawProtocolFees() external nonReentrant returns (uint256 amount) {\n        amount = protocolFees;\n        if (amount == 0) revert ZeroAmount();\n        protocolFees = 0;\n        usdc.safeTransfer(feeRecipient, amount);\n        emit ProtocolFeesWithdrawn(feeRecipient, amount);\n    }\n\n    // ─── Views ─────────────────────────────────────────────────────────────────\n    /// @notice True between curve completion and liquidity lock (retry `finalizeGraduation`).\n    function isGraduationPending(address token) external view returns (bool) {\n        return curves[token].graduated && !graduations[token].finalized;\n    }\n\n    /// @notice Spot price in USD per whole token, scaled by 1e18.\n    /// @dev    USDC is 6dp and tokens are 18dp, so the raw reserve ratio is multiplied by 1e30\n    ///         (1e18 output scale + 1e12 decimal gap) to yield $/token * 1e18.\n    ///         At launch: 2666666667 * 1e30 / V_TOKEN_0 = 2.5e12 == $0.0000025 * 1e18.\n    ///         After completion the curve is closed, so the constant graduation price is returned.\n    function priceX18(address token) public view returns (uint256) {\n        Curve storage c = curves[token];\n        if (c.graduated) return graduationPriceX18();\n        return ((V_USDC_0 + c.realUsdc) * 1e30) / (V_TOKEN_0 - c.tokensSold);\n    }\n\n    /// @notice The protocol-constant price every curve completes at ($40,000 FDV), scaled by 1e18.\n    function graduationPriceX18() public pure returns (uint256) {\n        uint256 tokenRef = V_TOKEN_0 - CURVE_SUPPLY;\n        return ((K / tokenRef) * 1e30) / tokenRef;\n    }\n\n    /// @notice Curve fill progress in basis points (0..10000).\n    function progressBps(address token) external view returns (uint256) {\n        return (uint256(curves[token].tokensSold) * BPS) / CURVE_SUPPLY;\n    }\n\n    /// @notice Exact tokens out for a gross USDC buy (view; ignores partial-fill edge for simplicity).\n    ///         Returns (0, 0) once the curve is closed.\n    function quoteBuy(address token, uint256 usdcIn, bool applySurcharge)\n        external\n        view\n        returns (uint256 tokensOut, uint256 feeTotal)\n    {\n        Curve storage c = curves[token];\n        if (c.graduated) return (0, 0);\n        uint256 surBps = applySurcharge ? _surchargeBps(block.timestamp - c.createdAt) : 0;\n        uint256 totalFeeBps = CURVE_FEE_BPS + surBps;\n        uint256 net = (usdcIn * (BPS - totalFeeBps)) / BPS;\n        uint256 netToComplete = (K / (V_TOKEN_0 - CURVE_SUPPLY)) - (V_USDC_0 + c.realUsdc);\n        if (net > netToComplete) net = netToComplete;\n        tokensOut = (V_TOKEN_0 - c.tokensSold) - _ceilDiv(K, (V_USDC_0 + c.realUsdc + net));\n        feeTotal = (usdcIn * totalFeeBps) / BPS;\n    }\n\n    /// @notice Net USDC out for selling tokens (view). Returns (0, 0) once the curve is closed.\n    function quoteSell(address token, uint256 tokensIn) external view returns (uint256 usdcOut, uint256 fee) {\n        Curve storage c = curves[token];\n        if (c.graduated || tokensIn > c.tokensSold) return (0, 0);\n        uint256 gross = (V_USDC_0 + c.realUsdc) - _ceilDiv(K, (V_TOKEN_0 - c.tokensSold + tokensIn));\n        fee = (gross * CURVE_FEE_BPS) / BPS;\n        usdcOut = gross - fee;\n    }\n\n    function surchargeBpsNow(address token) external view returns (uint256) {\n        return _surchargeBps(block.timestamp - curves[token].createdAt);\n    }\n\n    // ─── Internal math ─────────────────────────────────────────────────────────\n    /// @dev Anti-snipe surcharge: START * 2^(-t/halflife), linearly interpolated within each\n    ///      half-life bucket; 0 after the window. Monotonically decreasing.\n    function _surchargeBps(uint256 elapsed) internal pure returns (uint256) {\n        if (elapsed >= SURCHARGE_WINDOW) return 0;\n        uint256 h = elapsed / SURCHARGE_HALFLIFE;\n        uint256 rem = elapsed % SURCHARGE_HALFLIFE;\n        uint256 base = SURCHARGE_START_BPS >> h;\n        uint256 next = SURCHARGE_START_BPS >> (h + 1);\n        return base - ((base - next) * rem) / SURCHARGE_HALFLIFE;\n    }\n\n    function _ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {\n        return (a + b - 1) / b;\n    }\n}\n\ninterface IERC20Permit {\n    function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)\n        external;\n}\n"},"src/interfaces/IUniswapV3.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\n/// @notice Minimal Uniswap V3 interfaces used by the Sashimi graduator/locker.\n///         Targets the canonical V3 deployment on Arc (factory 0xf0db…3918, NPM 0x3965…1377).\n\ninterface IUniswapV3Factory {\n    function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address pool);\n    function createPool(address tokenA, address tokenB, uint24 fee) external returns (address pool);\n    function feeAmountTickSpacing(uint24 fee) external view returns (int24);\n}\n\ninterface IUniswapV3Pool {\n    function slot0()\n        external\n        view\n        returns (\n            uint160 sqrtPriceX96,\n            int24 tick,\n            uint16 observationIndex,\n            uint16 observationCardinality,\n            uint16 observationCardinalityNext,\n            uint8 feeProtocol,\n            bool unlocked\n        );\n    function initialize(uint160 sqrtPriceX96) external;\n    function factory() external view returns (address);\n    function liquidity() external view returns (uint128);\n    function token0() external view returns (address);\n    function token1() external view returns (address);\n    function fee() external view returns (uint24);\n    function swap(\n        address recipient,\n        bool zeroForOne,\n        int256 amountSpecified,\n        uint160 sqrtPriceLimitX96,\n        bytes calldata data\n    ) external returns (int256 amount0, int256 amount1);\n}\n\ninterface INonfungiblePositionManager {\n    function createAndInitializePoolIfNecessary(address token0, address token1, uint24 fee, uint160 sqrtPriceX96)\n        external\n        returns (address pool);\n\n    struct MintParams {\n        address token0;\n        address token1;\n        uint24 fee;\n        int24 tickLower;\n        int24 tickUpper;\n        uint256 amount0Desired;\n        uint256 amount1Desired;\n        uint256 amount0Min;\n        uint256 amount1Min;\n        address recipient;\n        uint256 deadline;\n    }\n\n    function mint(MintParams calldata params)\n        external\n        returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);\n\n    struct IncreaseLiquidityParams {\n        uint256 tokenId;\n        uint256 amount0Desired;\n        uint256 amount1Desired;\n        uint256 amount0Min;\n        uint256 amount1Min;\n        uint256 deadline;\n    }\n\n    function increaseLiquidity(IncreaseLiquidityParams calldata params)\n        external\n        returns (uint128 liquidity, uint256 amount0, uint256 amount1);\n\n    struct DecreaseLiquidityParams {\n        uint256 tokenId;\n        uint128 liquidity;\n        uint256 amount0Min;\n        uint256 amount1Min;\n        uint256 deadline;\n    }\n\n    function decreaseLiquidity(DecreaseLiquidityParams calldata params)\n        external\n        returns (uint256 amount0, uint256 amount1);\n\n    struct CollectParams {\n        uint256 tokenId;\n        address recipient;\n        uint128 amount0Max;\n        uint128 amount1Max;\n    }\n\n    function collect(CollectParams calldata params) external returns (uint256 amount0, uint256 amount1);\n\n    function positions(uint256 tokenId)\n        external\n        view\n        returns (\n            uint96 nonce,\n            address operator,\n            address token0,\n            address token1,\n            uint24 fee,\n            int24 tickLower,\n            int24 tickUpper,\n            uint128 liquidity,\n            uint256 feeGrowthInside0LastX128,\n            uint256 feeGrowthInside1LastX128,\n            uint128 tokensOwed0,\n            uint128 tokensOwed1\n        );\n\n    function ownerOf(uint256 tokenId) external view returns (address);\n\n    function WETH9() external view returns (address);\n}\n"},"lib/openzeppelin-contracts/contracts/interfaces/IERC1363.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1363.sol)\n\npragma solidity ^0.8.20;\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/utils/Address.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/Address.sol)\n\npragma solidity ^0.8.20;\n\nimport {Errors} from \"./Errors.sol\";\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n    /**\n     * @dev There's no code at `target` (it is not a contract).\n     */\n    error AddressEmptyCode(address target);\n\n    /**\n     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n     * `recipient`, forwarding all available gas and reverting on errors.\n     *\n     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n     * of certain opcodes, possibly making contracts go over the 2300 gas limit\n     * imposed by `transfer`, making them unable to receive funds via\n     * `transfer`. {sendValue} removes this limitation.\n     *\n     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n     *\n     * IMPORTANT: because control is transferred to `recipient`, care must be\n     * taken to not create reentrancy vulnerabilities. Consider using\n     * {ReentrancyGuard} or the\n     * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n     */\n    function sendValue(address payable recipient, uint256 amount) internal {\n        if (address(this).balance < amount) {\n            revert Errors.InsufficientBalance(address(this).balance, amount);\n        }\n\n        (bool success, ) = recipient.call{value: amount}(\"\");\n        if (!success) {\n            revert Errors.FailedCall();\n        }\n    }\n\n    /**\n     * @dev Performs a Solidity function call using a low level `call`. A\n     * plain `call` is an unsafe replacement for a function call: use this\n     * function instead.\n     *\n     * If `target` reverts with a revert reason or custom error, it is bubbled\n     * up by this function (like regular Solidity function calls). However, if\n     * the call reverted with no returned reason, this function reverts with a\n     * {Errors.FailedCall} error.\n     *\n     * Returns the raw returned data. To convert to the expected return value,\n     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n     *\n     * Requirements:\n     *\n     * - `target` must be a contract.\n     * - calling `target` with `data` must not revert.\n     */\n    function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n        return functionCallWithValue(target, data, 0);\n    }\n\n    /**\n     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n     * but also transferring `value` wei to `target`.\n     *\n     * Requirements:\n     *\n     * - the calling contract must have an ETH balance of at least `value`.\n     * - the called Solidity function must be `payable`.\n     */\n    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {\n        if (address(this).balance < value) {\n            revert Errors.InsufficientBalance(address(this).balance, value);\n        }\n        (bool success, bytes memory returndata) = target.call{value: value}(data);\n        return verifyCallResultFromTarget(target, success, returndata);\n    }\n\n    /**\n     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n     * but performing a static call.\n     */\n    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n        (bool success, bytes memory returndata) = target.staticcall(data);\n        return verifyCallResultFromTarget(target, success, returndata);\n    }\n\n    /**\n     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n     * but performing a delegate call.\n     */\n    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n        (bool success, bytes memory returndata) = target.delegatecall(data);\n        return verifyCallResultFromTarget(target, success, returndata);\n    }\n\n    /**\n     * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target\n     * was not a contract or bubbling up the revert reason (falling back to {Errors.FailedCall}) in case\n     * of an unsuccessful call.\n     */\n    function verifyCallResultFromTarget(\n        address target,\n        bool success,\n        bytes memory returndata\n    ) internal view returns (bytes memory) {\n        if (!success) {\n            _revert(returndata);\n        } else {\n            // only check if target is a contract if the call was successful and the return data is empty\n            // otherwise we already know that it was a contract\n            if (returndata.length == 0 && target.code.length == 0) {\n                revert AddressEmptyCode(target);\n            }\n            return returndata;\n        }\n    }\n\n    /**\n     * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the\n     * revert reason or with a default {Errors.FailedCall} error.\n     */\n    function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {\n        if (!success) {\n            _revert(returndata);\n        } else {\n            return returndata;\n        }\n    }\n\n    /**\n     * @dev Reverts with returndata if present. Otherwise reverts with {Errors.FailedCall}.\n     */\n    function _revert(bytes memory returndata) private pure {\n        // Look for revert reason and bubble it up if present\n        if (returndata.length > 0) {\n            // The easiest way to bubble the revert reason is using memory via assembly\n            assembly (\"memory-safe\") {\n                let returndata_size := mload(returndata)\n                revert(add(32, returndata), returndata_size)\n            }\n        } else {\n            revert Errors.FailedCall();\n        }\n    }\n}\n"},"lib/openzeppelin-contracts/contracts/access/Ownable2Step.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (access/Ownable2Step.sol)\n\npragma solidity ^0.8.20;\n\nimport {Ownable} from \"./Ownable.sol\";\n\n/**\n * @dev Contract module which provides access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * This extension of the {Ownable} contract includes a two-step mechanism to transfer\n * ownership, where the new owner must call {acceptOwnership} in order to replace the\n * old one. This can help prevent common mistakes, such as transfers of ownership to\n * incorrect accounts, or to contracts that are unable to interact with the\n * permission system.\n *\n * The initial owner is specified at deployment time in the constructor for `Ownable`. This\n * can later be changed with {transferOwnership} and {acceptOwnership}.\n *\n * This module is used through inheritance. It will make available all functions\n * from parent (Ownable).\n */\nabstract contract Ownable2Step is Ownable {\n    address private _pendingOwner;\n\n    event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);\n\n    /**\n     * @dev Returns the address of the pending owner.\n     */\n    function pendingOwner() public view virtual returns (address) {\n        return _pendingOwner;\n    }\n\n    /**\n     * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.\n     * Can only be called by the current owner.\n     *\n     * Setting `newOwner` to the zero address is allowed; this can be used to cancel an initiated ownership transfer.\n     */\n    function transferOwnership(address newOwner) public virtual override onlyOwner {\n        _pendingOwner = newOwner;\n        emit OwnershipTransferStarted(owner(), newOwner);\n    }\n\n    /**\n     * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.\n     * Internal function without access restriction.\n     */\n    function _transferOwnership(address newOwner) internal virtual override {\n        delete _pendingOwner;\n        super._transferOwnership(newOwner);\n    }\n\n    /**\n     * @dev The new owner accepts the ownership transfer.\n     */\n    function acceptOwnership() public virtual {\n        address sender = _msgSender();\n        if (pendingOwner() != sender) {\n            revert OwnableUnauthorizedAccount(sender);\n        }\n        _transferOwnership(sender);\n    }\n}\n"},"src/SashimiToken.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\n/// @title SashimiToken\n/// @notice The immutable memecoin template. Every Sashimi token is a byte-identical\n///         deployment of this contract — no owner, no mint, no pause, no blacklist,\n///         no transfer tax, no hooks. Honeypots and stealth mints are impossible by construction.\n/// @dev    Fixed 1B supply minted to the bonding curve at construction. Transfers are locked\n///         to/from the curve until graduation, then permanently free. This kills the\n///         pre-created-pool exploit class and premature DEX listings during the curve phase.\n///         Pre-graduation, the ONLY allowed movements are: the curve paying out a buy\n///         (from == curve) and the curve pulling tokens back on a sell (to == curve AND the\n///         curve is the caller). Users cannot burn or push tokens into the curve directly, so no\n///         supply can be stranded or mis-accounted.\ncontract SashimiToken is ERC20 {\n    /// @notice The bonding-curve engine that holds the supply and controls graduation.\n    address public immutable curve;\n    /// @notice Off-chain metadata pointer (IPFS CID) — set once, never changed.\n    string public metadataURI;\n    /// @notice False during the curve phase (transfer-locked); true forever after graduation.\n    bool public graduated;\n\n    error TransferLocked();\n    error OnlyCurve();\n\n    constructor(\n        string memory name_,\n        string memory symbol_,\n        string memory metadataURI_,\n        address curve_,\n        uint256 supply_\n    ) ERC20(name_, symbol_) {\n        curve = curve_;\n        metadataURI = metadataURI_;\n        _mint(curve_, supply_);\n    }\n\n    /// @notice Called once by the curve when graduation liquidity is locked, to permanently\n    ///         unlock transfers.\n    function setGraduated() external {\n        if (msg.sender != curve) revert OnlyCurve();\n        graduated = true;\n    }\n\n    /// @notice Burn — curve only (used to burn the unpaired part of the graduation reserve).\n    function burn(uint256 amount) external {\n        if (msg.sender != curve) revert OnlyCurve();\n        _burn(msg.sender, amount);\n    }\n\n    /// @dev Transfer gate (see contract docs). Mint is only ever the constructor; burn is curve-only.\n    function _update(address from, address to, uint256 value) internal override {\n        if (!graduated && from != address(0) && to != address(0)) {\n            bool curvePaysOut = from == curve;\n            bool curvePullsIn = to == curve && msg.sender == curve;\n            if (!curvePaysOut && !curvePullsIn) revert TransferLocked();\n        }\n        super._update(from, to, value);\n    }\n}\n"},"src/interfaces/ISashimiGraduator.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\n/// @title ISashimiGraduator\n/// @notice Adapter the CurveEngine calls at graduation. The concrete implementation creates a\n///         Uniswap V3 pool at the curve-end price IN THE GRADUATING TRANSACTION, seeds it and\n///         locks the LP position forever. Behind an interface so the venue can be swapped without\n///         touching the CurveEngine.\n/// @dev    The curve transfers `usdcAmount` USDC and `tokenAmount` tokens to the graduator\n///         BEFORE calling this.\ninterface ISashimiGraduator {\n    /// @notice Seed + lock liquidity at graduation. Curve-only.\n    function graduate(address token, uint256 usdcAmount, uint256 tokenAmount)\n        external\n        returns (address pool);\n\n    /// @notice True once the graduator is fully wired (locker set) and can accept graduations.\n    ///         The factory refuses to launch tokens while this is false.\n    function isReady() external view returns (bool);\n}\n"},"lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"../token/ERC20/IERC20.sol\";\n"},"lib/openzeppelin-contracts/contracts/interfaces/IERC165.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC165} from \"../utils/introspection/IERC165.sol\";\n"},"lib/openzeppelin-contracts/contracts/utils/Errors.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Collection of common custom errors used in multiple contracts\n *\n * IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.\n * It is recommended to avoid relying on the error API for critical functionality.\n *\n * _Available since v5.1._\n */\nlibrary Errors {\n    /**\n     * @dev The ETH balance of the account is not enough to perform the operation.\n     */\n    error InsufficientBalance(uint256 balance, uint256 needed);\n\n    /**\n     * @dev A call to an address target failed. The target may have reverted.\n     */\n    error FailedCall();\n\n    /**\n     * @dev The deployment failed.\n     */\n    error FailedDeployment();\n\n    /**\n     * @dev A necessary precompile is missing.\n     */\n    error MissingPrecompile(address);\n}\n"},"lib/openzeppelin-contracts/contracts/access/Ownable.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)\n\npragma solidity ^0.8.20;\n\nimport {Context} from \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * The initial owner is set to the address provided by the deployer. This can\n * later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n    address private _owner;\n\n    /**\n     * @dev The caller account is not authorized to perform an operation.\n     */\n    error OwnableUnauthorizedAccount(address account);\n\n    /**\n     * @dev The owner is not a valid owner account. (eg. `address(0)`)\n     */\n    error OwnableInvalidOwner(address owner);\n\n    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n    /**\n     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.\n     */\n    constructor(address initialOwner) {\n        if (initialOwner == address(0)) {\n            revert OwnableInvalidOwner(address(0));\n        }\n        _transferOwnership(initialOwner);\n    }\n\n    /**\n     * @dev Throws if called by any account other than the owner.\n     */\n    modifier onlyOwner() {\n        _checkOwner();\n        _;\n    }\n\n    /**\n     * @dev Returns the address of the current owner.\n     */\n    function owner() public view virtual returns (address) {\n        return _owner;\n    }\n\n    /**\n     * @dev Throws if the sender is not the owner.\n     */\n    function _checkOwner() internal view virtual {\n        if (owner() != _msgSender()) {\n            revert OwnableUnauthorizedAccount(_msgSender());\n        }\n    }\n\n    /**\n     * @dev Leaves the contract without owner. It will not be possible to call\n     * `onlyOwner` functions. Can only be called by the current owner.\n     *\n     * NOTE: Renouncing ownership will leave the contract without an owner,\n     * thereby disabling any functionality that is only available to the owner.\n     */\n    function renounceOwnership() public virtual onlyOwner {\n        _transferOwnership(address(0));\n    }\n\n    /**\n     * @dev Transfers ownership of the contract to a new account (`newOwner`).\n     * Can only be called by the current owner.\n     */\n    function transferOwnership(address newOwner) public virtual onlyOwner {\n        if (newOwner == address(0)) {\n            revert OwnableInvalidOwner(address(0));\n        }\n        _transferOwnership(newOwner);\n    }\n\n    /**\n     * @dev Transfers ownership of the contract to a new account (`newOwner`).\n     * Internal function without access restriction.\n     */\n    function _transferOwnership(address newOwner) internal virtual {\n        address oldOwner = _owner;\n        _owner = newOwner;\n        emit OwnershipTransferred(oldOwner, newOwner);\n    }\n}\n"},"lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"./IERC20.sol\";\nimport {IERC20Metadata} from \"./extensions/IERC20Metadata.sol\";\nimport {Context} from \"../../utils/Context.sol\";\nimport {IERC20Errors} from \"../../interfaces/draft-IERC6093.sol\";\n\n/**\n * @dev Implementation of the {IERC20} interface.\n *\n * This implementation is agnostic to the way tokens are created. This means\n * that a supply mechanism has to be added in a derived contract using {_mint}.\n *\n * TIP: For a detailed writeup see our guide\n * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How\n * to implement supply mechanisms].\n *\n * The default value of {decimals} is 18. To change this, you should override\n * this function so it returns a different value.\n *\n * We have followed general OpenZeppelin Contracts guidelines: functions revert\n * instead returning `false` on failure. This behavior is nonetheless\n * conventional and does not conflict with the expectations of ERC-20\n * applications.\n */\nabstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {\n    mapping(address account => uint256) private _balances;\n\n    mapping(address account => mapping(address spender => uint256)) private _allowances;\n\n    uint256 private _totalSupply;\n\n    string private _name;\n    string private _symbol;\n\n    /**\n     * @dev Sets the values for {name} and {symbol}.\n     *\n     * All two of these values are immutable: they can only be set once during\n     * construction.\n     */\n    constructor(string memory name_, string memory symbol_) {\n        _name = name_;\n        _symbol = symbol_;\n    }\n\n    /**\n     * @dev Returns the name of the token.\n     */\n    function name() public view virtual returns (string memory) {\n        return _name;\n    }\n\n    /**\n     * @dev Returns the symbol of the token, usually a shorter version of the\n     * name.\n     */\n    function symbol() public view virtual returns (string memory) {\n        return _symbol;\n    }\n\n    /**\n     * @dev Returns the number of decimals used to get its user representation.\n     * For example, if `decimals` equals `2`, a balance of `505` tokens should\n     * be displayed to a user as `5.05` (`505 / 10 ** 2`).\n     *\n     * Tokens usually opt for a value of 18, imitating the relationship between\n     * Ether and Wei. This is the default value returned by this function, unless\n     * it's overridden.\n     *\n     * NOTE: This information is only used for _display_ purposes: it in\n     * no way affects any of the arithmetic of the contract, including\n     * {IERC20-balanceOf} and {IERC20-transfer}.\n     */\n    function decimals() public view virtual returns (uint8) {\n        return 18;\n    }\n\n    /**\n     * @dev See {IERC20-totalSupply}.\n     */\n    function totalSupply() public view virtual returns (uint256) {\n        return _totalSupply;\n    }\n\n    /**\n     * @dev See {IERC20-balanceOf}.\n     */\n    function balanceOf(address account) public view virtual returns (uint256) {\n        return _balances[account];\n    }\n\n    /**\n     * @dev See {IERC20-transfer}.\n     *\n     * Requirements:\n     *\n     * - `to` cannot be the zero address.\n     * - the caller must have a balance of at least `value`.\n     */\n    function transfer(address to, uint256 value) public virtual returns (bool) {\n        address owner = _msgSender();\n        _transfer(owner, to, value);\n        return true;\n    }\n\n    /**\n     * @dev See {IERC20-allowance}.\n     */\n    function allowance(address owner, address spender) public view virtual returns (uint256) {\n        return _allowances[owner][spender];\n    }\n\n    /**\n     * @dev See {IERC20-approve}.\n     *\n     * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on\n     * `transferFrom`. This is semantically equivalent to an infinite approval.\n     *\n     * Requirements:\n     *\n     * - `spender` cannot be the zero address.\n     */\n    function approve(address spender, uint256 value) public virtual returns (bool) {\n        address owner = _msgSender();\n        _approve(owner, spender, value);\n        return true;\n    }\n\n    /**\n     * @dev See {IERC20-transferFrom}.\n     *\n     * Skips emitting an {Approval} event indicating an allowance update. This is not\n     * required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].\n     *\n     * NOTE: Does not update the allowance if the current allowance\n     * is the maximum `uint256`.\n     *\n     * Requirements:\n     *\n     * - `from` and `to` cannot be the zero address.\n     * - `from` must have a balance of at least `value`.\n     * - the caller must have allowance for ``from``'s tokens of at least\n     * `value`.\n     */\n    function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {\n        address spender = _msgSender();\n        _spendAllowance(from, spender, value);\n        _transfer(from, to, value);\n        return true;\n    }\n\n    /**\n     * @dev Moves a `value` amount of tokens from `from` to `to`.\n     *\n     * This internal function is equivalent to {transfer}, and can be used to\n     * e.g. implement automatic token fees, slashing mechanisms, etc.\n     *\n     * Emits a {Transfer} event.\n     *\n     * NOTE: This function is not virtual, {_update} should be overridden instead.\n     */\n    function _transfer(address from, address to, uint256 value) internal {\n        if (from == address(0)) {\n            revert ERC20InvalidSender(address(0));\n        }\n        if (to == address(0)) {\n            revert ERC20InvalidReceiver(address(0));\n        }\n        _update(from, to, value);\n    }\n\n    /**\n     * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`\n     * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding\n     * this function.\n     *\n     * Emits a {Transfer} event.\n     */\n    function _update(address from, address to, uint256 value) internal virtual {\n        if (from == address(0)) {\n            // Overflow check required: The rest of the code assumes that totalSupply never overflows\n            _totalSupply += value;\n        } else {\n            uint256 fromBalance = _balances[from];\n            if (fromBalance < value) {\n                revert ERC20InsufficientBalance(from, fromBalance, value);\n            }\n            unchecked {\n                // Overflow not possible: value <= fromBalance <= totalSupply.\n                _balances[from] = fromBalance - value;\n            }\n        }\n\n        if (to == address(0)) {\n            unchecked {\n                // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.\n                _totalSupply -= value;\n            }\n        } else {\n            unchecked {\n                // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.\n                _balances[to] += value;\n            }\n        }\n\n        emit Transfer(from, to, value);\n    }\n\n    /**\n     * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).\n     * Relies on the `_update` mechanism\n     *\n     * Emits a {Transfer} event with `from` set to the zero address.\n     *\n     * NOTE: This function is not virtual, {_update} should be overridden instead.\n     */\n    function _mint(address account, uint256 value) internal {\n        if (account == address(0)) {\n            revert ERC20InvalidReceiver(address(0));\n        }\n        _update(address(0), account, value);\n    }\n\n    /**\n     * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.\n     * Relies on the `_update` mechanism.\n     *\n     * Emits a {Transfer} event with `to` set to the zero address.\n     *\n     * NOTE: This function is not virtual, {_update} should be overridden instead\n     */\n    function _burn(address account, uint256 value) internal {\n        if (account == address(0)) {\n            revert ERC20InvalidSender(address(0));\n        }\n        _update(account, address(0), value);\n    }\n\n    /**\n     * @dev Sets `value` as the allowance of `spender` over the `owner` s tokens.\n     *\n     * This internal function is equivalent to `approve`, and can be used to\n     * e.g. set automatic allowances for certain subsystems, etc.\n     *\n     * Emits an {Approval} event.\n     *\n     * Requirements:\n     *\n     * - `owner` cannot be the zero address.\n     * - `spender` cannot be the zero address.\n     *\n     * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.\n     */\n    function _approve(address owner, address spender, uint256 value) internal {\n        _approve(owner, spender, value, true);\n    }\n\n    /**\n     * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.\n     *\n     * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by\n     * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any\n     * `Approval` event during `transferFrom` operations.\n     *\n     * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to\n     * true using the following override:\n     *\n     * ```solidity\n     * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {\n     *     super._approve(owner, spender, value, true);\n     * }\n     * ```\n     *\n     * Requirements are the same as {_approve}.\n     */\n    function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {\n        if (owner == address(0)) {\n            revert ERC20InvalidApprover(address(0));\n        }\n        if (spender == address(0)) {\n            revert ERC20InvalidSpender(address(0));\n        }\n        _allowances[owner][spender] = value;\n        if (emitEvent) {\n            emit Approval(owner, spender, value);\n        }\n    }\n\n    /**\n     * @dev Updates `owner` s allowance for `spender` based on spent `value`.\n     *\n     * Does not update the allowance value in case of infinite allowance.\n     * Revert if not enough allowance is available.\n     *\n     * Does not emit an {Approval} event.\n     */\n    function _spendAllowance(address owner, address spender, uint256 value) internal virtual {\n        uint256 currentAllowance = allowance(owner, spender);\n        if (currentAllowance != type(uint256).max) {\n            if (currentAllowance < value) {\n                revert ERC20InsufficientAllowance(spender, currentAllowance, value);\n            }\n            unchecked {\n                _approve(owner, spender, currentAllowance - value, false);\n            }\n        }\n    }\n}\n"},"lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)\n\npragma solidity ^0.8.20;\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"},"lib/openzeppelin-contracts/contracts/utils/Context.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n    function _msgSender() internal view virtual returns (address) {\n        return msg.sender;\n    }\n\n    function _msgData() internal view virtual returns (bytes calldata) {\n        return msg.data;\n    }\n\n    function _contextSuffixLength() internal view virtual returns (uint256) {\n        return 0;\n    }\n}\n"},"lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/IERC20Metadata.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"../IERC20.sol\";\n\n/**\n * @dev Interface for the optional metadata functions from the ERC-20 standard.\n */\ninterface IERC20Metadata is IERC20 {\n    /**\n     * @dev Returns the name of the token.\n     */\n    function name() external view returns (string memory);\n\n    /**\n     * @dev Returns the symbol of the token.\n     */\n    function symbol() external view returns (string memory);\n\n    /**\n     * @dev Returns the decimals places of the token.\n     */\n    function decimals() external view returns (uint8);\n}\n"},"lib/openzeppelin-contracts/contracts/interfaces/draft-IERC6093.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/draft-IERC6093.sol)\npragma solidity ^0.8.20;\n\n/**\n * @dev Standard ERC-20 Errors\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-20 tokens.\n */\ninterface IERC20Errors {\n    /**\n     * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.\n     * @param sender Address whose tokens are being transferred.\n     * @param balance Current balance for the interacting account.\n     * @param needed Minimum amount required to perform a transfer.\n     */\n    error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);\n\n    /**\n     * @dev Indicates a failure with the token `sender`. Used in transfers.\n     * @param sender Address whose tokens are being transferred.\n     */\n    error ERC20InvalidSender(address sender);\n\n    /**\n     * @dev Indicates a failure with the token `receiver`. Used in transfers.\n     * @param receiver Address to which tokens are being transferred.\n     */\n    error ERC20InvalidReceiver(address receiver);\n\n    /**\n     * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.\n     * @param spender Address that may be allowed to operate on tokens without being their owner.\n     * @param allowance Amount of tokens a `spender` is allowed to operate with.\n     * @param needed Minimum amount required to perform a transfer.\n     */\n    error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);\n\n    /**\n     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\n     * @param approver Address initiating an approval operation.\n     */\n    error ERC20InvalidApprover(address approver);\n\n    /**\n     * @dev Indicates a failure with the `spender` to be approved. Used in approvals.\n     * @param spender Address that may be allowed to operate on tokens without being their owner.\n     */\n    error ERC20InvalidSpender(address spender);\n}\n\n/**\n * @dev Standard ERC-721 Errors\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-721 tokens.\n */\ninterface IERC721Errors {\n    /**\n     * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in ERC-20.\n     * Used in balance queries.\n     * @param owner Address of the current owner of a token.\n     */\n    error ERC721InvalidOwner(address owner);\n\n    /**\n     * @dev Indicates a `tokenId` whose `owner` is the zero address.\n     * @param tokenId Identifier number of a token.\n     */\n    error ERC721NonexistentToken(uint256 tokenId);\n\n    /**\n     * @dev Indicates an error related to the ownership over a particular token. Used in transfers.\n     * @param sender Address whose tokens are being transferred.\n     * @param tokenId Identifier number of a token.\n     * @param owner Address of the current owner of a token.\n     */\n    error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);\n\n    /**\n     * @dev Indicates a failure with the token `sender`. Used in transfers.\n     * @param sender Address whose tokens are being transferred.\n     */\n    error ERC721InvalidSender(address sender);\n\n    /**\n     * @dev Indicates a failure with the token `receiver`. Used in transfers.\n     * @param receiver Address to which tokens are being transferred.\n     */\n    error ERC721InvalidReceiver(address receiver);\n\n    /**\n     * @dev Indicates a failure with the `operator`’s approval. Used in transfers.\n     * @param operator Address that may be allowed to operate on tokens without being their owner.\n     * @param tokenId Identifier number of a token.\n     */\n    error ERC721InsufficientApproval(address operator, uint256 tokenId);\n\n    /**\n     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\n     * @param approver Address initiating an approval operation.\n     */\n    error ERC721InvalidApprover(address approver);\n\n    /**\n     * @dev Indicates a failure with the `operator` to be approved. Used in approvals.\n     * @param operator Address that may be allowed to operate on tokens without being their owner.\n     */\n    error ERC721InvalidOperator(address operator);\n}\n\n/**\n * @dev Standard ERC-1155 Errors\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-1155 tokens.\n */\ninterface IERC1155Errors {\n    /**\n     * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.\n     * @param sender Address whose tokens are being transferred.\n     * @param balance Current balance for the interacting account.\n     * @param needed Minimum amount required to perform a transfer.\n     * @param tokenId Identifier number of a token.\n     */\n    error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);\n\n    /**\n     * @dev Indicates a failure with the token `sender`. Used in transfers.\n     * @param sender Address whose tokens are being transferred.\n     */\n    error ERC1155InvalidSender(address sender);\n\n    /**\n     * @dev Indicates a failure with the token `receiver`. Used in transfers.\n     * @param receiver Address to which tokens are being transferred.\n     */\n    error ERC1155InvalidReceiver(address receiver);\n\n    /**\n     * @dev Indicates a failure with the `operator`’s approval. Used in transfers.\n     * @param operator Address that may be allowed to operate on tokens without being their owner.\n     * @param owner Address of the current owner of a token.\n     */\n    error ERC1155MissingApprovalForAll(address operator, address owner);\n\n    /**\n     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\n     * @param approver Address initiating an approval operation.\n     */\n    error ERC1155InvalidApprover(address approver);\n\n    /**\n     * @dev Indicates a failure with the `operator` to be approved. Used in approvals.\n     * @param operator Address that may be allowed to operate on tokens without being their owner.\n     */\n    error ERC1155InvalidOperator(address operator);\n\n    /**\n     * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.\n     * Used in batch transfers.\n     * @param idsLength Length of the array of token identifiers\n     * @param valuesLength Length of the array of token amounts\n     */\n    error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);\n}\n"}},"settings":{"remappings":["@openzeppelin/=lib/openzeppelin-contracts/","ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/","erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/","forge-std/=lib/forge-std/src/","halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/","openzeppelin-contracts/=lib/openzeppelin-contracts/"],"optimizer":{"enabled":true,"runs":200},"metadata":{"useLiteralContent":false,"bytecodeHash":"ipfs","appendCBOR":true},"outputSelection":{"*":{"":["ast"],"*":["abi","evm.bytecode.object","evm.bytecode.sourceMap","evm.bytecode.linkReferences","evm.deployedBytecode.object","evm.deployedBytecode.sourceMap","evm.deployedBytecode.linkReferences","evm.deployedBytecode.immutableReferences","evm.methodIdentifiers","metadata"]}},"evmVersion":"cancun","viaIR":true,"libraries":{}}}
