ETH Price: $1,972.95 (+0.22%)

Transaction Decoder

Block:
23520062 at Oct-06-2025 04:57:11 PM +UTC
Transaction Fee:
0.000211535793598668 ETH $0.42
Gas Used:
152,922 Gas / 1.383292094 Gwei

Emitted Events:

402 WETH9.Deposit( dst=0x5141B82f5fFDa4c6fE1E372978F1C5427640a190, wad=6343192334348173 )
403 PunkStrategy.Transfer( from=UniswapV3Pool, to=[Receiver] AggregationRouterV5, amount=149607855781965922010 )
404 WETH9.Transfer( src=0x5141B82f5fFDa4c6fE1E372978F1C5427640a190, dst=UniswapV3Pool, wad=6343192334348173 )
405 UniswapV3Pool.Swap( sender=0x5141B82f5fFDa4c6fE1E372978F1C5427640a190, recipient=[Receiver] AggregationRouterV5, amount0=6343192334348173, amount1=-149607855781965922010, sqrtPriceX96=12228674240635645632763077128913, liquidity=35857897277845087858518, tick=100789 )
406 PunkStrategy.Transfer( from=[Receiver] AggregationRouterV5, to=[Sender] 0xa31642f44293a9d9dab519873e0d2f9f1a3c0660, amount=149607855781965922010 )

Account State Difference:

  Address   Before After State Difference Code
0x2b1a1262...476D2B00e
(Uniswap V3: PNKSTR)
(Titan Builder)
20.460858406944724902 Eth20.460873699144724902 Eth0.0000152922
0x8D413dB4...fd1C52828 1.849914915498847782 Eth1.849978988148689682 Eth0.0000640726498419
0xA31642F4...F1A3C0660
0.009457434794064221 Eth
Nonce: 30
0.00283863401627548 Eth
Nonce: 31
0.006618800777788741
0xC02aaA39...83C756Cc2 2,329,422.636517264667969843 Eth2,329,422.642860457002318016 Eth0.006343192334348173
0xc50673ED...864E33eDF

Execution Trace

ETH 0.006407264984190073 AggregationRouterV5.swap( executor=0x5141B82f5fFDa4c6fE1E372978F1C5427640a190, desc=[{name:srcToken, type:address, order:1, indexed:false, value:0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, valueString:0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE}, {name:dstToken, type:address, order:2, indexed:false, value:0xc50673EDb3A7b94E8CAD8a7d4E0cD68864E33eDF, valueString:0xc50673EDb3A7b94E8CAD8a7d4E0cD68864E33eDF}, {name:srcReceiver, type:address, order:3, indexed:false, value:0x5141B82f5fFDa4c6fE1E372978F1C5427640a190, valueString:0x5141B82f5fFDa4c6fE1E372978F1C5427640a190}, {name:dstReceiver, type:address, order:4, indexed:false, value:0xA31642F44293a9d9DAb519873e0D2f9F1A3C0660, valueString:0xA31642F44293a9d9DAb519873e0D2f9F1A3C0660}, {name:amount, type:uint256, order:5, indexed:false, value:6407264984190073, valueString:6407264984190073}, {name:minReturnAmount, type:uint256, order:6, indexed:false, value:145153956114234839594, valueString:145153956114234839594}, {name:flags, type:uint256, order:7, indexed:false, value:0, valueString:0}], permit=0x, data=0x0000000000000000000000000000000000000000000000CB00006800004E00A0744C8C0900000000000000000000000000000000000000008D413DB42D6901DE42B2C481CC0F6D0FD1C5282800000000000000000000000000000000000000000000000000003A4613889CEC4041C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC2D0E30DB002A0000000000000000000000000000000000000000000000007DE6A7206FB9B922AEE63C1E5812B1A1262D8A9886F949787D85C41344476D2B00EC02AAA39B223FE8D0A0E5C4F27EAD9083C756CC21111111254EEB25477B68FB85ED929F73A960582 ) => ( returnAmount=149607855781965922010, spentAmount=6407264984190073 )
  • ETH 0.006407264984190073 1inch: Aggregation Executor 4.4b64e492( )
    • 1inch: Aggregation Executor 4.744c8c09( )
      • ETH 0.0000640726498419 0x8d413db42d6901de42b2c481cc0f6d0fd1c52828.CALL( )
      • ETH 0.006343192334348173 WETH9.CALL( )
      • 1inch: Aggregation Executor 4.ee63c1e5( )
        • UniswapV3Pool.swap( recipient=0x1111111254EEB25477B68fb85Ed929f73A960582, zeroForOne=True, amountSpecified=6343192334348173, sqrtPriceLimitX96=4295128740, data=0x000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC2 ) => ( amount0=6343192334348173, amount1=-149607855781965922010 )
          • PunkStrategy.transfer( to=0x1111111254EEB25477B68fb85Ed929f73A960582, amount=149607855781965922010 ) => ( True )
          • WETH9.balanceOf( 0x2b1a1262D8A9886F949787d85C41344476D2B00e ) => ( 61982679016800172022 )
          • 1inch: Aggregation Executor 4.fa461e33( )
            • WETH9.transfer( dst=0x2b1a1262D8A9886F949787d85C41344476D2B00e, wad=6343192334348173 ) => ( True )
            • WETH9.balanceOf( 0x2b1a1262D8A9886F949787d85C41344476D2B00e ) => ( 61989022209134520195 )
            • PunkStrategy.balanceOf( owner=0x1111111254EEB25477B68fb85Ed929f73A960582 ) => ( result=149607855781965922011 )
            • PunkStrategy.transfer( to=0xA31642F44293a9d9DAb519873e0D2f9F1A3C0660, amount=149607855781965922010 ) => ( True )
              File 1 of 4: AggregationRouterV5
              /*
                                                                         ,▄▓▓██▌   ,╓▄▄▓▓▓▓▓▓▓▓▄▄▄,,
                                                                      ,▓██▓███▓▄▓███▓╬╬╬╬╬╬╬╬╬╬╬╬╬▓███▓▄,
                                                                ▄█   ▓██╬╣███████╬▓▀╬╬▓▓▓████████████▓█████▄,
                                                               ▓██▌ ▓██╬╣██████╬▓▌  ██████████████████████▌╙╙▀ⁿ
                                                              ▐████████╬▓████▓▓█╨ ▄ ╟█████████▓▓╬╬╬╬╬▓▓█████▓▄
                                                └▀▓▓▄╓        ╟█▓╣█████▓██████▀ ╓█▌ ███████▓▓▓▓▓╬╬╬╬╬╬╬╬╬╬╬╬▓██▓▄
                                                   └▀████▓▄╥  ▐██╬╬██████████╙ Æ▀─ ▓███▀╚╠╬╩▀▀███████▓▓╬╬╬╬╬╬╬╬╬██▄
                                                      └▀██▓▀▀█████▓╬▓██████▀     ▄█████▒╠"      └╙▓██████▓╬╬╬╬╬╬╬╬██▄
                                                         └▀██▄,└╙▀▀████▌└╙    ^"▀╙╙╙"╙██      @▄    ╙▀███████╬╬╬╬╬╬╬██µ
                                                            └▀██▓▄, ██▌       ╒       ╙█▓     ]▓█▓╔    ▀███████▓╬╬╬╬╬▓█▌
                                                                ▀█████       ▓         ╟█▌    ]╠██▓░▒╓   ▀████████╬╬╬╬╣█▌
                                                                ▐████      ╓█▀█▌      ,██▌    ╚Å███▓▒▒╠╓  ╙█████████╬╬╬╣█▌
                                                                └████     ▓█░░▓█      ▀▀▀    φ▒╫████▒▒▒▒╠╓  █████████▓╬╬▓█µ
                                                                 ╘███µ ▌▄█▓▄▓▀`     ,▀    ,╔╠░▓██████▌╠▒▒▒φ  ██████████╬╬██
                                                                 ▐████µ╙▓▀`     ,▀╙,╔╔φφφ╠░▄▓███████▌░▓╙▒▒▒╠ └██╬███████╬▓█⌐
                                                                 ╫██ ▓▌         ▌φ▒▒░▓██████████████▌▒░▓╚▒▒▒╠ ▓██╬▓██████╣█▌
                                                                 ██▌           ▌╔▒▒▄████████████████▒▒▒░▌╠▒▒▒≥▐██▓╬╬███████▌
                                                                 ██▌      ,╓φ╠▓«▒▒▓████▀  ▀█████████▌▒▒▒╟░▒▒▒▒▐███╬╬╣████▓█▌
                                                                ▐██      ╠▒▄▓▓███▓████└     ▀████████▌▒▒░▌╚▒▒▒▐███▓╬╬████ ╙▌
                                                                ███  )  ╠▒░░░▒░╬████▀        └████████░▒▒░╬∩▒▒▓████╬╬╣███
                                                               ▓██    ╠╠▒▒▐█▀▀▌`░╫██           ███████▒▒▒▒░▒▒½█████╬╬╣███
                                                              ███ ,█▄ ╠▒▒▒╫▌,▄▀,▒╫██           ╟██████▒▒▒░╣⌠▒▓█████╬╬╣██▌
                                                             ╘██µ ██` ╠▒▒░██╬φ╠▄▓██`            ██████░░▌φ╠░▓█████▓╬╬▓██
                                                              ╟██  .φ╠▒░▄█▀░░▄██▀└              █████▌▒╣φ▒░▓██████╬╬╣██
                                                               ▀██▄▄▄╓▄███████▀                ▐█████░▓φ▒▄███████▓╬╣██
                                                                 ╙▀▀▀██▀└                      ████▓▄▀φ▄▓████████╬▓█▀
                                                                                              ▓███╬╩╔╣██████████▓██└
                                                                                            ╓████▀▄▓████████▀████▀
                                                                                          ,▓███████████████─]██╙
                                                                                       ,▄▓██████████████▀└  ╙
                                                                                  ,╓▄▓███████████████▀╙
                                                                           `"▀▀▀████████▀▀▀▀`▄███▀▀└
                                                                                            └└
              
              
              
                                  11\   11\                     11\             11\   11\            11\                                       11\
                                1111 |  \__|                    11 |            111\  11 |           11 |                                      11 |
                                \_11 |  11\ 1111111\   1111111\ 1111111\        1111\ 11 | 111111\ 111111\   11\  11\  11\  111111\   111111\  11 |  11\
                                  11 |  11 |11  __11\ 11  _____|11  __11\       11 11\11 |11  __11\\_11  _|  11 | 11 | 11 |11  __11\ 11  __11\ 11 | 11  |
                                  11 |  11 |11 |  11 |11 /      11 |  11 |      11 \1111 |11111111 | 11 |    11 | 11 | 11 |11 /  11 |11 |  \__|111111  /
                                  11 |  11 |11 |  11 |11 |      11 |  11 |      11 |\111 |11   ____| 11 |11\ 11 | 11 | 11 |11 |  11 |11 |      11  _11<
                                111111\ 11 |11 |  11 |\1111111\ 11 |  11 |      11 | \11 |\1111111\  \1111  |\11111\1111  |\111111  |11 |      11 | \11\
                                \______|\__|\__|  \__| \_______|\__|  \__|      \__|  \__| \_______|  \____/  \_____\____/  \______/ \__|      \__|  \__|
              
              
              
                                             111111\                                                               11\     11\
                                            11  __11\                                                              11 |    \__|
                                            11 /  11 | 111111\   111111\   111111\   111111\   111111\   111111\ 111111\   11\  111111\  1111111\
                                            11111111 |11  __11\ 11  __11\ 11  __11\ 11  __11\ 11  __11\  \____11\\_11  _|  11 |11  __11\ 11  __11\
                                            11  __11 |11 /  11 |11 /  11 |11 |  \__|11111111 |11 /  11 | 1111111 | 11 |    11 |11 /  11 |11 |  11 |
                                            11 |  11 |11 |  11 |11 |  11 |11 |      11   ____|11 |  11 |11  __11 | 11 |11\ 11 |11 |  11 |11 |  11 |
                                            11 |  11 |\1111111 |\1111111 |11 |      \1111111\ \1111111 |\1111111 | \1111  |11 |\111111  |11 |  11 |
                                            \__|  \__| \____11 | \____11 |\__|       \_______| \____11 | \_______|  \____/ \__| \______/ \__|  \__|
                                                      11\   11 |11\   11 |                    11\   11 |
                                                      \111111  |\111111  |                    \111111  |
                                                       \______/  \______/                      \______/
                                                              1111111\                        11\
                                                              11  __11\                       11 |
                                                              11 |  11 | 111111\  11\   11\ 111111\    111111\   111111\
                                                              1111111  |11  __11\ 11 |  11 |\_11  _|  11  __11\ 11  __11\
                                                              11  __11< 11 /  11 |11 |  11 |  11 |    11111111 |11 |  \__|
                                                              11 |  11 |11 |  11 |11 |  11 |  11 |11\ 11   ____|11 |
                                                              11 |  11 |\111111  |\111111  |  \1111  |\1111111\ 11 |
                                                              \__|  \__| \______/  \______/    \____/  \_______|\__|
              */
              
              // SPDX-License-Identifier: MIT
              
              // File contracts/interfaces/IClipperExchangeInterface.sol
              
              
              pragma solidity 0.8.17;
              
              /// @title Clipper interface subset used in swaps
              interface IClipperExchangeInterface {
                  struct Signature {
                      uint8 v;
                      bytes32 r;
                      bytes32 s;
                  }
              
                  function sellEthForToken(address outputToken, uint256 inputAmount, uint256 outputAmount, uint256 goodUntil, address destinationAddress, Signature calldata theSignature, bytes calldata auxiliaryData) external payable;
                  function sellTokenForEth(address inputToken, uint256 inputAmount, uint256 outputAmount, uint256 goodUntil, address destinationAddress, Signature calldata theSignature, bytes calldata auxiliaryData) external;
                  function swap(address inputToken, address outputToken, uint256 inputAmount, uint256 outputAmount, uint256 goodUntil, address destinationAddress, Signature calldata theSignature, bytes calldata auxiliaryData) external;
              }
              
              
              // File contracts/helpers/RouterErrors.sol
              
              
              pragma solidity 0.8.17;
              
              library RouterErrors {
                  error ReturnAmountIsNotEnough();
                  error InvalidMsgValue();
                  error ERC20TransferFailed();
              }
              
              
              // File @1inch/solidity-utils/contracts/EthReceiver.sol@v2.1.1
              
              
              pragma solidity ^0.8.0;
              
              abstract contract EthReceiver {
                  error EthDepositRejected();
              
                  receive() external payable {
                      _receive();
                  }
              
                  function _receive() internal virtual {
                      // solhint-disable-next-line avoid-tx-origin
                      if (msg.sender == tx.origin) revert EthDepositRejected();
                  }
              }
              
              
              // File @openzeppelin/contracts/token/ERC20/IERC20.sol@v4.7.3
              
              // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)
              
              pragma solidity ^0.8.0;
              
              /**
               * @dev Interface of the ERC20 standard as defined in the EIP.
               */
              interface IERC20 {
                  /**
                   * @dev Emitted when `value` tokens are moved from one account (`from`) to
                   * another (`to`).
                   *
                   * Note that `value` may be zero.
                   */
                  event Transfer(address indexed from, address indexed to, uint256 value);
              
                  /**
                   * @dev Emitted when the allowance of a `spender` for an `owner` is set by
                   * a call to {approve}. `value` is the new allowance.
                   */
                  event Approval(address indexed owner, address indexed spender, uint256 value);
              
                  /**
                   * @dev Returns the amount of tokens in existence.
                   */
                  function totalSupply() external view returns (uint256);
              
                  /**
                   * @dev Returns the amount of tokens owned by `account`.
                   */
                  function balanceOf(address account) external view returns (uint256);
              
                  /**
                   * @dev Moves `amount` tokens from the caller's account to `to`.
                   *
                   * Returns a boolean value indicating whether the operation succeeded.
                   *
                   * Emits a {Transfer} event.
                   */
                  function transfer(address to, uint256 amount) external returns (bool);
              
                  /**
                   * @dev Returns the remaining number of tokens that `spender` will be
                   * allowed to spend on behalf of `owner` through {transferFrom}. This is
                   * zero by default.
                   *
                   * This value changes when {approve} or {transferFrom} are called.
                   */
                  function allowance(address owner, address spender) external view returns (uint256);
              
                  /**
                   * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
                   *
                   * Returns a boolean value indicating whether the operation succeeded.
                   *
                   * IMPORTANT: Beware that changing an allowance with this method brings the risk
                   * that someone may use both the old and the new allowance by unfortunate
                   * transaction ordering. One possible solution to mitigate this race
                   * condition is to first reduce the spender's allowance to 0 and set the
                   * desired value afterwards:
                   * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                   *
                   * Emits an {Approval} event.
                   */
                  function approve(address spender, uint256 amount) external returns (bool);
              
                  /**
                   * @dev Moves `amount` tokens from `from` to `to` using the
                   * allowance mechanism. `amount` is then deducted from the caller's
                   * allowance.
                   *
                   * Returns a boolean value indicating whether the operation succeeded.
                   *
                   * Emits a {Transfer} event.
                   */
                  function transferFrom(
                      address from,
                      address to,
                      uint256 amount
                  ) external returns (bool);
              }
              
              
              // File @1inch/solidity-utils/contracts/interfaces/IDaiLikePermit.sol@v2.1.1
              
              
              pragma solidity ^0.8.0;
              
              
              interface IDaiLikePermit {
                  function permit(address holder, address spender, uint256 nonce, uint256 expiry, bool allowed, uint8 v, bytes32 r, bytes32 s) external;
              }
              
              
              // File @1inch/solidity-utils/contracts/libraries/RevertReasonForwarder.sol@v2.1.1
              
              
              pragma solidity ^0.8.0;
              
              library RevertReasonForwarder {
                  function reRevert() internal pure {
                      // bubble up revert reason from latest external call
                      /// @solidity memory-safe-assembly
                      assembly { // solhint-disable-line no-inline-assembly
                          let ptr := mload(0x40)
                          returndatacopy(ptr, 0, returndatasize())
                          revert(ptr, returndatasize())
                      }
                  }
              }
              
              
              // File @openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol@v4.7.3
              
              // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)
              
              pragma solidity ^0.8.0;
              
              /**
               * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
               * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
               *
               * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
               * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
               * need to send a transaction, and thus is not required to hold Ether at all.
               */
              interface IERC20Permit {
                  /**
                   * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
                   * given ``owner``'s signed approval.
                   *
                   * IMPORTANT: The same issues {IERC20-approve} has related to transaction
                   * ordering also apply here.
                   *
                   * Emits an {Approval} event.
                   *
                   * Requirements:
                   *
                   * - `spender` cannot be the zero address.
                   * - `deadline` must be a timestamp in the future.
                   * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
                   * over the EIP712-formatted function arguments.
                   * - the signature must use ``owner``'s current nonce (see {nonces}).
                   *
                   * For more information on the signature format, see the
                   * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
                   * section].
                   */
                  function permit(
                      address owner,
                      address spender,
                      uint256 value,
                      uint256 deadline,
                      uint8 v,
                      bytes32 r,
                      bytes32 s
                  ) external;
              
                  /**
                   * @dev Returns the current nonce for `owner`. This value must be
                   * included whenever a signature is generated for {permit}.
                   *
                   * Every successful call to {permit} increases ``owner``'s nonce by one. This
                   * prevents a signature from being used multiple times.
                   */
                  function nonces(address owner) external view returns (uint256);
              
                  /**
                   * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
                   */
                  // solhint-disable-next-line func-name-mixedcase
                  function DOMAIN_SEPARATOR() external view returns (bytes32);
              }
              
              
              // File @1inch/solidity-utils/contracts/libraries/SafeERC20.sol@v2.1.1
              
              
              pragma solidity ^0.8.0;
              
              
              
              
              library SafeERC20 {
                  error SafeTransferFailed();
                  error SafeTransferFromFailed();
                  error ForceApproveFailed();
                  error SafeIncreaseAllowanceFailed();
                  error SafeDecreaseAllowanceFailed();
                  error SafePermitBadLength();
              
                  // Ensures method do not revert or return boolean `true`, admits call to non-smart-contract
                  function safeTransferFrom(IERC20 token, address from, address to, uint256 amount) internal {
                      bytes4 selector = token.transferFrom.selector;
                      bool success;
                      /// @solidity memory-safe-assembly
                      assembly { // solhint-disable-line no-inline-assembly
                          let data := mload(0x40)
              
                          mstore(data, selector)
                          mstore(add(data, 0x04), from)
                          mstore(add(data, 0x24), to)
                          mstore(add(data, 0x44), amount)
                          success := call(gas(), token, 0, data, 100, 0x0, 0x20)
                          if success {
                              switch returndatasize()
                              case 0 { success := gt(extcodesize(token), 0) }
                              default { success := and(gt(returndatasize(), 31), eq(mload(0), 1)) }
                          }
                      }
                      if (!success) revert SafeTransferFromFailed();
                  }
              
                  // Ensures method do not revert or return boolean `true`, admits call to non-smart-contract
                  function safeTransfer(IERC20 token, address to, uint256 value) internal {
                      if (!_makeCall(token, token.transfer.selector, to, value)) {
                          revert SafeTransferFailed();
                      }
                  }
              
                  // If `approve(from, to, amount)` fails, try to `approve(from, to, 0)` before retry
                  function forceApprove(IERC20 token, address spender, uint256 value) internal {
                      if (!_makeCall(token, token.approve.selector, spender, value)) {
                          if (!_makeCall(token, token.approve.selector, spender, 0) ||
                              !_makeCall(token, token.approve.selector, spender, value))
                          {
                              revert ForceApproveFailed();
                          }
                      }
                  }
              
                  function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                      uint256 allowance = token.allowance(address(this), spender);
                      if (value > type(uint256).max - allowance) revert SafeIncreaseAllowanceFailed();
                      forceApprove(token, spender, allowance + value);
                  }
              
                  function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                      uint256 allowance = token.allowance(address(this), spender);
                      if (value > allowance) revert SafeDecreaseAllowanceFailed();
                      forceApprove(token, spender, allowance - value);
                  }
              
                  function safePermit(IERC20 token, bytes calldata permit) internal {
                      bool success;
                      if (permit.length == 32 * 7) {
                          success = _makeCalldataCall(token, IERC20Permit.permit.selector, permit);
                      } else if (permit.length == 32 * 8) {
                          success = _makeCalldataCall(token, IDaiLikePermit.permit.selector, permit);
                      } else {
                          revert SafePermitBadLength();
                      }
                      if (!success) RevertReasonForwarder.reRevert();
                  }
              
                  function _makeCall(IERC20 token, bytes4 selector, address to, uint256 amount) private returns(bool success) {
                      /// @solidity memory-safe-assembly
                      assembly { // solhint-disable-line no-inline-assembly
                          let data := mload(0x40)
              
                          mstore(data, selector)
                          mstore(add(data, 0x04), to)
                          mstore(add(data, 0x24), amount)
                          success := call(gas(), token, 0, data, 0x44, 0x0, 0x20)
                          if success {
                              switch returndatasize()
                              case 0 { success := gt(extcodesize(token), 0) }
                              default { success := and(gt(returndatasize(), 31), eq(mload(0), 1)) }
                          }
                      }
                  }
              
                  function _makeCalldataCall(IERC20 token, bytes4 selector, bytes calldata args) private returns(bool success) {
                      /// @solidity memory-safe-assembly
                      assembly { // solhint-disable-line no-inline-assembly
                          let len := add(4, args.length)
                          let data := mload(0x40)
              
                          mstore(data, selector)
                          calldatacopy(add(data, 0x04), args.offset, args.length)
                          success := call(gas(), token, 0, data, len, 0x0, 0x20)
                          if success {
                              switch returndatasize()
                              case 0 { success := gt(extcodesize(token), 0) }
                              default { success := and(gt(returndatasize(), 31), eq(mload(0), 1)) }
                          }
                      }
                  }
              }
              
              
              // File @1inch/solidity-utils/contracts/interfaces/IWETH.sol@v2.1.1
              
              
              pragma solidity ^0.8.0;
              
              interface IWETH is IERC20 {
                  function deposit() external payable;
                  function withdraw(uint256 amount) external;
              }
              
              
              // File contracts/routers/ClipperRouter.sol
              
              
              pragma solidity 0.8.17;
              
              
              
              
              
              
              /// @title Clipper router that allows to use `ClipperExchangeInterface` for swaps
              contract ClipperRouter is EthReceiver {
                  using SafeERC20 for IERC20;
              
                  uint256 private constant _SIGNATURE_S_MASK = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
                  uint256 private constant _SIGNATURE_V_SHIFT = 255;
                  bytes6 private constant _INCH_TAG_WITH_LENGTH_PREFIX = "\x051INCH";
                  IERC20 private constant _ETH = IERC20(address(0));
                  IWETH private immutable _WETH;  // solhint-disable-line var-name-mixedcase
              
                  constructor(IWETH weth) {
                      _WETH = weth;
                  }
              
                  /// @notice Same as `clipperSwapTo` but calls permit first,
                  /// allowing to approve token spending and make a swap in one transaction.
                  /// @dev See tests for examples
                  /// @param recipient Address that will receive swap funds
                  /// @param srcToken Source token
                  /// @param dstToken Destination token
                  /// @param inputAmount Amount of source tokens to swap
                  /// @param outputAmount Amount of destination tokens to receive
                  /// @param goodUntil Timestamp until the swap will be valid
                  /// @param r Clipper order signature (r part)
                  /// @param vs Clipper order signature (vs part)
                  /// @param permit Should contain valid permit that can be used in `IERC20Permit.permit` calls.
                  /// @return returnAmount Amount of destination tokens received
                  function clipperSwapToWithPermit(
                      IClipperExchangeInterface clipperExchange,
                      address payable recipient,
                      IERC20 srcToken,
                      IERC20 dstToken,
                      uint256 inputAmount,
                      uint256 outputAmount,
                      uint256 goodUntil,
                      bytes32 r,
                      bytes32 vs,
                      bytes calldata permit
                  ) external returns(uint256 returnAmount) {
                      srcToken.safePermit(permit);
                      return clipperSwapTo(clipperExchange, recipient, srcToken, dstToken, inputAmount, outputAmount, goodUntil, r, vs);
                  }
              
                  /// @notice Same as `clipperSwapTo` but uses `msg.sender` as recipient
                  /// @param srcToken Source token
                  /// @param dstToken Destination token
                  /// @param inputAmount Amount of source tokens to swap
                  /// @param outputAmount Amount of destination tokens to receive
                  /// @param goodUntil Timestamp until the swap will be valid
                  /// @param r Clipper order signature (r part)
                  /// @param vs Clipper order signature (vs part)
                  /// @return returnAmount Amount of destination tokens received
                  function clipperSwap(
                      IClipperExchangeInterface clipperExchange,
                      IERC20 srcToken,
                      IERC20 dstToken,
                      uint256 inputAmount,
                      uint256 outputAmount,
                      uint256 goodUntil,
                      bytes32 r,
                      bytes32 vs
                  ) external payable returns(uint256 returnAmount) {
                      return clipperSwapTo(clipperExchange, payable(msg.sender), srcToken, dstToken, inputAmount, outputAmount, goodUntil, r, vs);
                  }
              
                  /// @notice Performs swap using Clipper exchange. Wraps and unwraps ETH if required.
                  /// Sending non-zero `msg.value` for anything but ETH swaps is prohibited
                  /// @param recipient Address that will receive swap funds
                  /// @param srcToken Source token
                  /// @param dstToken Destination token
                  /// @param inputAmount Amount of source tokens to swap
                  /// @param outputAmount Amount of destination tokens to receive
                  /// @param goodUntil Timestamp until the swap will be valid
                  /// @param r Clipper order signature (r part)
                  /// @param vs Clipper order signature (vs part)
                  /// @return returnAmount Amount of destination tokens received
                  function clipperSwapTo(
                      IClipperExchangeInterface clipperExchange,
                      address payable recipient,
                      IERC20 srcToken,
                      IERC20 dstToken,
                      uint256 inputAmount,
                      uint256 outputAmount,
                      uint256 goodUntil,
                      bytes32 r,
                      bytes32 vs
                  ) public payable returns(uint256 returnAmount) {
                      bool srcETH = srcToken == _ETH;
                      if (srcETH) {
                          if (msg.value != inputAmount) revert RouterErrors.InvalidMsgValue();
                      } else if (srcToken == _WETH) {
                          srcETH = true;
                          if (msg.value != 0) revert RouterErrors.InvalidMsgValue();
                          // _WETH.transferFrom(msg.sender, address(this), inputAmount);
                          // _WETH.withdraw(inputAmount);
                          address weth = address(_WETH);
                          bytes4 transferFromSelector = _WETH.transferFrom.selector;
                          bytes4 withdrawSelector = _WETH.withdraw.selector;
                          /// @solidity memory-safe-assembly
                          assembly { // solhint-disable-line no-inline-assembly
                              let ptr := mload(0x40)
              
                              mstore(ptr, transferFromSelector)
                              mstore(add(ptr, 0x04), caller())
                              mstore(add(ptr, 0x24), address())
                              mstore(add(ptr, 0x44), inputAmount)
                              if iszero(call(gas(), weth, 0, ptr, 0x64, 0, 0)) {
                                  returndatacopy(ptr, 0, returndatasize())
                                  revert(ptr, returndatasize())
                              }
              
                              mstore(ptr, withdrawSelector)
                              mstore(add(ptr, 0x04), inputAmount)
                              if iszero(call(gas(), weth, 0, ptr, 0x24, 0, 0)) {
                                  returndatacopy(ptr, 0, returndatasize())
                                  revert(ptr, returndatasize())
                              }
                          }
                      } else {
                          if (msg.value != 0) revert RouterErrors.InvalidMsgValue();
                          srcToken.safeTransferFrom(msg.sender, address(clipperExchange), inputAmount);
                      }
              
                      if (srcETH) {
                          // clipperExchange.sellEthForToken{value: inputAmount}(address(dstToken), inputAmount, outputAmount, goodUntil, recipient, signature, _INCH_TAG);
                          address clipper = address(clipperExchange);
                          bytes4 selector = clipperExchange.sellEthForToken.selector;
                          /// @solidity memory-safe-assembly
                          assembly { // solhint-disable-line no-inline-assembly
                              let ptr := mload(0x40)
              
                              mstore(ptr, selector)
                              mstore(add(ptr, 0x04), dstToken)
                              mstore(add(ptr, 0x24), inputAmount)
                              mstore(add(ptr, 0x44), outputAmount)
                              mstore(add(ptr, 0x64), goodUntil)
                              mstore(add(ptr, 0x84), recipient)
                              mstore(add(ptr, 0xa4), add(27, shr(_SIGNATURE_V_SHIFT, vs)))
                              mstore(add(ptr, 0xc4), r)
                              mstore(add(ptr, 0xe4), and(vs, _SIGNATURE_S_MASK))
                              mstore(add(ptr, 0x104), 0x120)
                              mstore(add(ptr, 0x143), _INCH_TAG_WITH_LENGTH_PREFIX)
                              if iszero(call(gas(), clipper, inputAmount, ptr, 0x149, 0, 0)) {
                                  returndatacopy(ptr, 0, returndatasize())
                                  revert(ptr, returndatasize())
                              }
                          }
                      } else if (dstToken == _ETH || dstToken == _WETH) {
                          // clipperExchange.sellTokenForEth(address(srcToken), inputAmount, outputAmount, goodUntil, recipient, signature, _INCH_TAG);
                          address clipper = address(clipperExchange);
                          bytes4 selector = clipperExchange.sellTokenForEth.selector;
                          /// @solidity memory-safe-assembly
                          assembly { // solhint-disable-line no-inline-assembly
                              let ptr := mload(0x40)
              
                              mstore(ptr, selector)
                              mstore(add(ptr, 0x04), srcToken)
                              mstore(add(ptr, 0x24), inputAmount)
                              mstore(add(ptr, 0x44), outputAmount)
                              mstore(add(ptr, 0x64), goodUntil)
                              switch iszero(dstToken)
                              case 1 {
                                  mstore(add(ptr, 0x84), recipient)
                              }
                              default {
                                  mstore(add(ptr, 0x84), address())
                              }
                              mstore(add(ptr, 0xa4), add(27, shr(_SIGNATURE_V_SHIFT, vs)))
                              mstore(add(ptr, 0xc4), r)
                              mstore(add(ptr, 0xe4), and(vs, _SIGNATURE_S_MASK))
                              mstore(add(ptr, 0x104), 0x120)
                              mstore(add(ptr, 0x143), _INCH_TAG_WITH_LENGTH_PREFIX)
                              if iszero(call(gas(), clipper, 0, ptr, 0x149, 0, 0)) {
                                  returndatacopy(ptr, 0, returndatasize())
                                  revert(ptr, returndatasize())
                              }
                          }
              
                          if (dstToken == _WETH) {
                              // _WETH.deposit{value: outputAmount}();
                              // _WETH.transfer(recipient, outputAmount);
                              address weth = address(_WETH);
                              bytes4 depositSelector = _WETH.deposit.selector;
                              bytes4 transferSelector = _WETH.transfer.selector;
                              /// @solidity memory-safe-assembly
                              assembly { // solhint-disable-line no-inline-assembly
                                  let ptr := mload(0x40)
              
                                  mstore(ptr, depositSelector)
                                  if iszero(call(gas(), weth, outputAmount, ptr, 0x04, 0, 0)) {
                                      returndatacopy(ptr, 0, returndatasize())
                                      revert(ptr, returndatasize())
                                  }
              
                                  mstore(ptr, transferSelector)
                                  mstore(add(ptr, 0x04), recipient)
                                  mstore(add(ptr, 0x24), outputAmount)
                                  if iszero(call(gas(), weth, 0, ptr, 0x44, 0, 0)) {
                                      returndatacopy(ptr, 0, returndatasize())
                                      revert(ptr, returndatasize())
                                  }
                              }
                          }
                      } else {
                          // clipperExchange.swap(address(srcToken), address(dstToken), inputAmount, outputAmount, goodUntil, recipient, signature, _INCH_TAG);
                          address clipper = address(clipperExchange);
                          bytes4 selector = clipperExchange.swap.selector;
                          /// @solidity memory-safe-assembly
                          assembly { // solhint-disable-line no-inline-assembly
                              let ptr := mload(0x40)
              
                              mstore(ptr, selector)
                              mstore(add(ptr, 0x04), srcToken)
                              mstore(add(ptr, 0x24), dstToken)
                              mstore(add(ptr, 0x44), inputAmount)
                              mstore(add(ptr, 0x64), outputAmount)
                              mstore(add(ptr, 0x84), goodUntil)
                              mstore(add(ptr, 0xa4), recipient)
                              mstore(add(ptr, 0xc4), add(27, shr(_SIGNATURE_V_SHIFT, vs)))
                              mstore(add(ptr, 0xe4), r)
                              mstore(add(ptr, 0x104), and(vs, _SIGNATURE_S_MASK))
                              mstore(add(ptr, 0x124), 0x140)
                              mstore(add(ptr, 0x163), _INCH_TAG_WITH_LENGTH_PREFIX)
                              if iszero(call(gas(), clipper, 0, ptr, 0x169, 0, 0)) {
                                  returndatacopy(ptr, 0, returndatasize())
                                  revert(ptr, returndatasize())
                              }
                          }
                      }
              
                      return outputAmount;
                  }
              }
              
              
              // File contracts/interfaces/IAggregationExecutor.sol
              
              
              pragma solidity 0.8.17;
              
              /// @title Interface for making arbitrary calls during swap
              interface IAggregationExecutor {
                  /// @notice propagates information about original msg.sender and executes arbitrary data
                  function execute(address msgSender) external payable;  // 0x4b64e492
              }
              
              
              // File @1inch/solidity-utils/contracts/interfaces/IERC20MetadataUppercase.sol@v2.1.1
              
              
              pragma solidity ^0.8.0;
              
              
              interface IERC20MetadataUppercase {
                  function NAME() external view returns (string memory);  // solhint-disable-line func-name-mixedcase
                  function SYMBOL() external view returns (string memory);  // solhint-disable-line func-name-mixedcase
              }
              
              
              // File @1inch/solidity-utils/contracts/libraries/StringUtil.sol@v2.1.1
              
              
              pragma solidity ^0.8.0;
              
              /// @title Library with gas-efficient string operations
              library StringUtil {
                  function toHex(uint256 value) internal pure returns (string memory) {
                      return toHex(abi.encodePacked(value));
                  }
              
                  function toHex(address value) internal pure returns (string memory) {
                      return toHex(abi.encodePacked(value));
                  }
              
                  function toHex(bytes memory data) internal pure returns (string memory result) {
                      /// @solidity memory-safe-assembly
                      assembly { // solhint-disable-line no-inline-assembly
                          function _toHex16(input) -> output {
                              output := or(
                                  and(input, 0xFFFFFFFFFFFFFFFF000000000000000000000000000000000000000000000000),
                                  shr(64, and(input, 0x0000000000000000FFFFFFFFFFFFFFFF00000000000000000000000000000000))
                              )
                              output := or(
                                  and(output, 0xFFFFFFFF000000000000000000000000FFFFFFFF000000000000000000000000),
                                  shr(32, and(output, 0x00000000FFFFFFFF000000000000000000000000FFFFFFFF0000000000000000))
                              )
                              output := or(
                                  and(output, 0xFFFF000000000000FFFF000000000000FFFF000000000000FFFF000000000000),
                                  shr(16, and(output, 0x0000FFFF000000000000FFFF000000000000FFFF000000000000FFFF00000000))
                              )
                              output := or(
                                  and(output, 0xFF000000FF000000FF000000FF000000FF000000FF000000FF000000FF000000),
                                  shr(8, and(output, 0x00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000))
                              )
                              output := or(
                                  shr(4, and(output, 0xF000F000F000F000F000F000F000F000F000F000F000F000F000F000F000F000)),
                                  shr(8, and(output, 0x0F000F000F000F000F000F000F000F000F000F000F000F000F000F000F000F00))
                              )
                              output := add(
                                  add(0x3030303030303030303030303030303030303030303030303030303030303030, output),
                                  mul(
                                      and(
                                          shr(4, add(output, 0x0606060606060606060606060606060606060606060606060606060606060606)),
                                          0x0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F
                                      ),
                                      7   // Change 7 to 39 for lower case output
                                  )
                              )
                          }
              
                          result := mload(0x40)
                          let length := mload(data)
                          let resultLength := shl(1, length)
                          let toPtr := add(result, 0x22)          // 32 bytes for length + 2 bytes for '0x'
                          mstore(0x40, add(toPtr, resultLength))  // move free memory pointer
                          mstore(add(result, 2), 0x3078)          // 0x3078 is right aligned so we write to `result + 2`
                                                                  // to store the last 2 bytes in the beginning of the string
                          mstore(result, add(resultLength, 2))    // extra 2 bytes for '0x'
              
                          for {
                              let fromPtr := add(data, 0x20)
                              let endPtr := add(fromPtr, length)
                          } lt(fromPtr, endPtr) {
                              fromPtr := add(fromPtr, 0x20)
                          } {
                              let rawData := mload(fromPtr)
                              let hexData := _toHex16(rawData)
                              mstore(toPtr, hexData)
                              toPtr := add(toPtr, 0x20)
                              hexData := _toHex16(shl(128, rawData))
                              mstore(toPtr, hexData)
                              toPtr := add(toPtr, 0x20)
                          }
                      }
                  }
              }
              
              
              // File @openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol@v4.7.3
              
              // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
              
              pragma solidity ^0.8.0;
              
              /**
               * @dev Interface for the optional metadata functions from the ERC20 standard.
               *
               * _Available since v4.1._
               */
              interface IERC20Metadata is IERC20 {
                  /**
                   * @dev Returns the name of the token.
                   */
                  function name() external view returns (string memory);
              
                  /**
                   * @dev Returns the symbol of the token.
                   */
                  function symbol() external view returns (string memory);
              
                  /**
                   * @dev Returns the decimals places of the token.
                   */
                  function decimals() external view returns (uint8);
              }
              
              
              // File @1inch/solidity-utils/contracts/libraries/UniERC20.sol@v2.1.1
              
              
              pragma solidity ^0.8.0;
              
              
              
              
              
              library UniERC20 {
                  using SafeERC20 for IERC20;
              
                  error InsufficientBalance();
                  error ApproveCalledOnETH();
                  error NotEnoughValue();
                  error FromIsNotSender();
                  error ToIsNotThis();
                  error ETHTransferFailed();
              
                  uint256 private constant _RAW_CALL_GAS_LIMIT = 5000;
                  IERC20 private constant _ETH_ADDRESS = IERC20(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
                  IERC20 private constant _ZERO_ADDRESS = IERC20(address(0));
              
                  function isETH(IERC20 token) internal pure returns (bool) {
                      return (token == _ZERO_ADDRESS || token == _ETH_ADDRESS);
                  }
              
                  function uniBalanceOf(IERC20 token, address account) internal view returns (uint256) {
                      if (isETH(token)) {
                          return account.balance;
                      } else {
                          return token.balanceOf(account);
                      }
                  }
              
                  /// @dev note that this function does nothing in case of zero amount
                  function uniTransfer(IERC20 token, address payable to, uint256 amount) internal {
                      if (amount > 0) {
                          if (isETH(token)) {
                              if (address(this).balance < amount) revert InsufficientBalance();
                              // solhint-disable-next-line avoid-low-level-calls
                              (bool success, ) = to.call{value: amount, gas: _RAW_CALL_GAS_LIMIT}("");
                              if (!success) revert ETHTransferFailed();
                          } else {
                              token.safeTransfer(to, amount);
                          }
                      }
                  }
              
                  /// @dev note that this function does nothing in case of zero amount
                  function uniTransferFrom(IERC20 token, address payable from, address to, uint256 amount) internal {
                      if (amount > 0) {
                          if (isETH(token)) {
                              if (msg.value < amount) revert NotEnoughValue();
                              if (from != msg.sender) revert FromIsNotSender();
                              if (to != address(this)) revert ToIsNotThis();
                              if (msg.value > amount) {
                                  // Return remainder if exist
                                  unchecked {
                                      // solhint-disable-next-line avoid-low-level-calls
                                      (bool success, ) = from.call{value: msg.value - amount, gas: _RAW_CALL_GAS_LIMIT}("");
                                      if (!success) revert ETHTransferFailed();
                                  }
                              }
                          } else {
                              token.safeTransferFrom(from, to, amount);
                          }
                      }
                  }
              
                  function uniSymbol(IERC20 token) internal view returns(string memory) {
                      return _uniDecode(token, IERC20Metadata.symbol.selector, IERC20MetadataUppercase.SYMBOL.selector);
                  }
              
                  function uniName(IERC20 token) internal view returns(string memory) {
                      return _uniDecode(token, IERC20Metadata.name.selector, IERC20MetadataUppercase.NAME.selector);
                  }
              
                  function uniApprove(IERC20 token, address to, uint256 amount) internal {
                      if (isETH(token)) revert ApproveCalledOnETH();
              
                      token.forceApprove(to, amount);
                  }
              
                  /// 20K gas is provided to account for possible implementations of name/symbol
                  /// (token implementation might be behind proxy or store the value in storage)
                  function _uniDecode(IERC20 token, bytes4 lowerCaseSelector, bytes4 upperCaseSelector) private view returns(string memory result) {
                      if (isETH(token)) {
                          return "ETH";
                      }
              
                      (bool success, bytes memory data) = address(token).staticcall{ gas: 20000 }(
                          abi.encodeWithSelector(lowerCaseSelector)
                      );
                      if (!success) {
                          (success, data) = address(token).staticcall{ gas: 20000 }(
                              abi.encodeWithSelector(upperCaseSelector)
                          );
                      }
              
                      if (success && data.length >= 0x40) {
                          (uint256 offset, uint256 len) = abi.decode(data, (uint256, uint256));
                          if (offset == 0x20 && len > 0 && data.length == 0x40 + len) {
                              /// @solidity memory-safe-assembly
                              assembly { // solhint-disable-line no-inline-assembly
                                  result := add(data, 0x20)
                              }
                              return result;
                          }
                      }
              
                      if (success && data.length == 32) {
                          uint256 len = 0;
                          while (len < data.length && data[len] >= 0x20 && data[len] <= 0x7E) {
                              unchecked {
                                  len++;
                              }
                          }
              
                          if (len > 0) {
                              /// @solidity memory-safe-assembly
                              assembly { // solhint-disable-line no-inline-assembly
                                  mstore(data, len)
                              }
                              return string(data);
                          }
                      }
              
                      return StringUtil.toHex(address(token));
                  }
              }
              
              
              // File contracts/routers/GenericRouter.sol
              
              
              pragma solidity 0.8.17;
              
              
              
              
              
              contract GenericRouter is EthReceiver {
                  using UniERC20 for IERC20;
                  using SafeERC20 for IERC20;
              
                  error ZeroMinReturn();
                  error ZeroReturnAmount();
              
                  uint256 private constant _PARTIAL_FILL = 1 << 0;
                  uint256 private constant _REQUIRES_EXTRA_ETH = 1 << 1;
              
                  struct SwapDescription {
                      IERC20 srcToken;
                      IERC20 dstToken;
                      address payable srcReceiver;
                      address payable dstReceiver;
                      uint256 amount;
                      uint256 minReturnAmount;
                      uint256 flags;
                  }
              
                  /// @notice Performs a swap, delegating all calls encoded in `data` to `executor`. See tests for usage examples
                  /// @dev router keeps 1 wei of every token on the contract balance for gas optimisations reasons. This affects first swap of every token by leaving 1 wei on the contract.
                  /// @param executor Aggregation executor that executes calls described in `data`
                  /// @param desc Swap description
                  /// @param permit Should contain valid permit that can be used in `IERC20Permit.permit` calls.
                  /// @param data Encoded calls that `caller` should execute in between of swaps
                  /// @return returnAmount Resulting token amount
                  /// @return spentAmount Source token amount
                  function swap(
                      IAggregationExecutor executor,
                      SwapDescription calldata desc,
                      bytes calldata permit,
                      bytes calldata data
                  )
                      external
                      payable
                      returns (
                          uint256 returnAmount,
                          uint256 spentAmount
                      )
                  {
                      if (desc.minReturnAmount == 0) revert ZeroMinReturn();
              
                      IERC20 srcToken = desc.srcToken;
                      IERC20 dstToken = desc.dstToken;
              
                      bool srcETH = srcToken.isETH();
                      if (desc.flags & _REQUIRES_EXTRA_ETH != 0) {
                          if (msg.value <= (srcETH ? desc.amount : 0)) revert RouterErrors.InvalidMsgValue();
                      } else {
                          if (msg.value != (srcETH ? desc.amount : 0)) revert RouterErrors.InvalidMsgValue();
                      }
              
                      if (!srcETH) {
                          if (permit.length > 0) {
                              srcToken.safePermit(permit);
                          }
                          srcToken.safeTransferFrom(msg.sender, desc.srcReceiver, desc.amount);
                      }
              
                      _execute(executor, msg.sender, desc.amount, data);
              
                      spentAmount = desc.amount;
                      // we leave 1 wei on the router for gas optimisations reasons
                      returnAmount = dstToken.uniBalanceOf(address(this));
                      if (returnAmount == 0) revert ZeroReturnAmount();
                      unchecked { returnAmount--; }
              
                      if (desc.flags & _PARTIAL_FILL != 0) {
                          uint256 unspentAmount = srcToken.uniBalanceOf(address(this));
                          if (unspentAmount > 1) {
                              // we leave 1 wei on the router for gas optimisations reasons
                              unchecked { unspentAmount--; }
                              spentAmount -= unspentAmount;
                              srcToken.uniTransfer(payable(msg.sender), unspentAmount);
                          }
                          if (returnAmount * desc.amount < desc.minReturnAmount * spentAmount) revert RouterErrors.ReturnAmountIsNotEnough();
                      } else {
                          if (returnAmount < desc.minReturnAmount) revert RouterErrors.ReturnAmountIsNotEnough();
                      }
              
                      address payable dstReceiver = (desc.dstReceiver == address(0)) ? payable(msg.sender) : desc.dstReceiver;
                      dstToken.uniTransfer(dstReceiver, returnAmount);
                  }
              
                  function _execute(
                      IAggregationExecutor executor,
                      address srcTokenOwner,
                      uint256 inputAmount,
                      bytes calldata data
                  ) private {
                      bytes4 executeSelector = executor.execute.selector;
                      /// @solidity memory-safe-assembly
                      assembly {  // solhint-disable-line no-inline-assembly
                          let ptr := mload(0x40)
              
                          mstore(ptr, executeSelector)
                          mstore(add(ptr, 0x04), srcTokenOwner)
                          calldatacopy(add(ptr, 0x24), data.offset, data.length)
                          mstore(add(add(ptr, 0x24), data.length), inputAmount)
              
                          if iszero(call(gas(), executor, callvalue(), ptr, add(0x44, data.length), 0, 0)) {
                              returndatacopy(ptr, 0, returndatasize())
                              revert(ptr, returndatasize())
                          }
                      }
                  }
              }
              
              
              // File contracts/routers/UnoswapRouter.sol
              
              
              pragma solidity 0.8.17;
              
              
              
              
              contract UnoswapRouter is EthReceiver {
                  using SafeERC20 for IERC20;
              
                  error ReservesCallFailed();
                  error SwapAmountTooLarge();
              
                  bytes4 private constant _TRANSFER_FROM_CALL_SELECTOR = 0x23b872dd;
                  bytes4 private constant _WETH_DEPOSIT_CALL_SELECTOR = 0xd0e30db0;
                  bytes4 private constant _WETH_WITHDRAW_CALL_SELECTOR = 0x2e1a7d4d;
                  bytes4 private constant _ERC20_TRANSFER_CALL_SELECTOR = 0xa9059cbb;
                  uint256 private constant _ADDRESS_MASK =   0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff;
                  uint256 private constant _REVERSE_MASK =   0x8000000000000000000000000000000000000000000000000000000000000000;
                  uint256 private constant _WETH_MASK =      0x4000000000000000000000000000000000000000000000000000000000000000;
                  uint256 private constant _NUMERATOR_MASK = 0x0000000000000000ffffffff0000000000000000000000000000000000000000;
                  /// @dev WETH address is network-specific and needs to be changed before deployment.
                  /// It can not be moved to immutable as immutables are not supported in assembly
                  address private constant _WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
                  bytes4 private constant _UNISWAP_PAIR_RESERVES_CALL_SELECTOR = 0x0902f1ac;
                  bytes4 private constant _UNISWAP_PAIR_SWAP_CALL_SELECTOR = 0x022c0d9f;
                  uint256 private constant _DENOMINATOR = 1e9;
                  uint256 private constant _NUMERATOR_OFFSET = 160;
                  uint256 private constant _MAX_SWAP_AMOUNT = (1 << 112) - 1;  // type(uint112).max;
              
                  /// @notice Same as `unoswapTo` but calls permit first,
                  /// allowing to approve token spending and make a swap in one transaction.
                  /// @param recipient Address that will receive swapped funds
                  /// @param srcToken Source token
                  /// @param amount Amount of source tokens to swap
                  /// @param minReturn Minimal allowed returnAmount to make transaction commit
                  /// @param pools Pools chain used for swaps. Pools src and dst tokens should match to make swap happen
                  /// @param permit Should contain valid permit that can be used in `IERC20Permit.permit` calls.
                  /// See tests for examples
                  function unoswapToWithPermit(
                      address payable recipient,
                      IERC20 srcToken,
                      uint256 amount,
                      uint256 minReturn,
                      uint256[] calldata pools,
                      bytes calldata permit
                  ) external returns(uint256 returnAmount) {
                      srcToken.safePermit(permit);
                      return _unoswap(recipient, srcToken, amount, minReturn, pools);
                  }
              
                  /// @notice Performs swap using Uniswap exchange. Wraps and unwraps ETH if required.
                  /// Sending non-zero `msg.value` for anything but ETH swaps is prohibited
                  /// @param recipient Address that will receive swapped funds
                  /// @param srcToken Source token
                  /// @param amount Amount of source tokens to swap
                  /// @param minReturn Minimal allowed returnAmount to make transaction commit
                  /// @param pools Pools chain used for swaps. Pools src and dst tokens should match to make swap happen
                  function unoswapTo(
                      address payable recipient,
                      IERC20 srcToken,
                      uint256 amount,
                      uint256 minReturn,
                      uint256[] calldata pools
                  ) external payable returns(uint256 returnAmount) {
                      return _unoswap(recipient, srcToken, amount, minReturn, pools);
                  }
              
                  /// @notice Performs swap using Uniswap exchange. Wraps and unwraps ETH if required.
                  /// Sending non-zero `msg.value` for anything but ETH swaps is prohibited
                  /// @param srcToken Source token
                  /// @param amount Amount of source tokens to swap
                  /// @param minReturn Minimal allowed returnAmount to make transaction commit
                  /// @param pools Pools chain used for swaps. Pools src and dst tokens should match to make swap happen
                  function unoswap(
                      IERC20 srcToken,
                      uint256 amount,
                      uint256 minReturn,
                      uint256[] calldata pools
                  ) external payable returns(uint256 returnAmount) {
                      return _unoswap(payable(msg.sender), srcToken, amount, minReturn, pools);
                  }
              
                  function _unoswap(
                      address payable recipient,
                      IERC20 srcToken,
                      uint256 amount,
                      uint256 minReturn,
                      uint256[] calldata pools
                  ) private returns(uint256 returnAmount) {
                      assembly {  // solhint-disable-line no-inline-assembly
                          function reRevert() {
                              returndatacopy(0, 0, returndatasize())
                              revert(0, returndatasize())
                          }
              
                          function validateERC20Transfer(status) {
                              if iszero(status) {
                                  reRevert()
                              }
                              let success := or(
                                  iszero(returndatasize()),                       // empty return data
                                  and(gt(returndatasize(), 31), eq(mload(0), 1))  // true in return data
                              )
                              if iszero(success) {
                                  mstore(0, 0xf27f64e400000000000000000000000000000000000000000000000000000000)  // ERC20TransferFailed()
                                  revert(0, 4)
                              }
                          }
              
                          function swap(emptyPtr, swapAmount, pair, reversed, numerator, to) -> ret {
                              mstore(emptyPtr, _UNISWAP_PAIR_RESERVES_CALL_SELECTOR)
                              if iszero(staticcall(gas(), pair, emptyPtr, 0x4, emptyPtr, 0x40)) {
                                  reRevert()
                              }
                              if iszero(eq(returndatasize(), 0x60)) {
                                  mstore(0, 0x85cd58dc00000000000000000000000000000000000000000000000000000000)  // ReservesCallFailed()
                                  revert(0, 4)
                              }
              
                              let reserve0 := mload(emptyPtr)
                              let reserve1 := mload(add(emptyPtr, 0x20))
                              if reversed {
                                  let tmp := reserve0
                                  reserve0 := reserve1
                                  reserve1 := tmp
                              }
                              // this will not overflow as reserve0, reserve1 and ret fit to 112 bit and numerator and _DENOMINATOR fit to 32 bit
                              ret := mul(swapAmount, numerator)
                              ret := div(mul(ret, reserve1), add(ret, mul(reserve0, _DENOMINATOR)))
              
                              mstore(emptyPtr, _UNISWAP_PAIR_SWAP_CALL_SELECTOR)
                              reversed := iszero(reversed)
                              mstore(add(emptyPtr, 0x04), mul(ret, iszero(reversed)))
                              mstore(add(emptyPtr, 0x24), mul(ret, reversed))
                              mstore(add(emptyPtr, 0x44), to)
                              mstore(add(emptyPtr, 0x64), 0x80)
                              mstore(add(emptyPtr, 0x84), 0)
                              if iszero(call(gas(), pair, 0, emptyPtr, 0xa4, 0, 0)) {
                                  reRevert()
                              }
                          }
              
                          // make sure that input amount fits in 112 bit
                          if gt(amount, _MAX_SWAP_AMOUNT) {
                              mstore(0, 0xcf0b4d3a00000000000000000000000000000000000000000000000000000000)  // SwapAmountTooLarge()
                              revert(0, 4)
                          }
              
                          let emptyPtr := mload(0x40)
                          mstore(0x40, add(emptyPtr, 0xc0))
              
                          let poolsEndOffset := add(pools.offset, shl(5, pools.length))
                          let rawPair := calldataload(pools.offset)
                          switch srcToken
                          case 0 {
                              if iszero(eq(amount, callvalue())) {
                                  mstore(0, 0x1841b4e100000000000000000000000000000000000000000000000000000000)  // InvalidMsgValue()
                                  revert(0, 4)
                              }
              
                              mstore(emptyPtr, _WETH_DEPOSIT_CALL_SELECTOR)
                              if iszero(call(gas(), _WETH, amount, emptyPtr, 0x4, 0, 0)) {
                                  reRevert()
                              }
              
                              mstore(emptyPtr, _ERC20_TRANSFER_CALL_SELECTOR)
                              mstore(add(emptyPtr, 0x4), and(rawPair, _ADDRESS_MASK))
                              mstore(add(emptyPtr, 0x24), amount)
                              if iszero(call(gas(), _WETH, 0, emptyPtr, 0x44, 0, 0)) {
                                  reRevert()
                              }
                          }
                          default {
                              if callvalue() {
                                  mstore(0, 0x1841b4e100000000000000000000000000000000000000000000000000000000)  // InvalidMsgValue()
                                  revert(0, 4)
                              }
              
                              mstore(emptyPtr, _TRANSFER_FROM_CALL_SELECTOR)
                              mstore(add(emptyPtr, 0x4), caller())
                              mstore(add(emptyPtr, 0x24), and(rawPair, _ADDRESS_MASK))
                              mstore(add(emptyPtr, 0x44), amount)
                              validateERC20Transfer(
                                  call(gas(), srcToken, 0, emptyPtr, 0x64, 0, 0x20)
                              )
                          }
              
                          returnAmount := amount
              
                          for {let i := add(pools.offset, 0x20)} lt(i, poolsEndOffset) {i := add(i, 0x20)} {
                              let nextRawPair := calldataload(i)
              
                              returnAmount := swap(
                                  emptyPtr,
                                  returnAmount,
                                  and(rawPair, _ADDRESS_MASK),
                                  and(rawPair, _REVERSE_MASK),
                                  shr(_NUMERATOR_OFFSET, and(rawPair, _NUMERATOR_MASK)),
                                  and(nextRawPair, _ADDRESS_MASK)
                              )
              
                              rawPair := nextRawPair
                          }
              
                          switch and(rawPair, _WETH_MASK)
                          case 0 {
                              returnAmount := swap(
                                  emptyPtr,
                                  returnAmount,
                                  and(rawPair, _ADDRESS_MASK),
                                  and(rawPair, _REVERSE_MASK),
                                  shr(_NUMERATOR_OFFSET, and(rawPair, _NUMERATOR_MASK)),
                                  recipient
                              )
                          }
                          default {
                              returnAmount := swap(
                                  emptyPtr,
                                  returnAmount,
                                  and(rawPair, _ADDRESS_MASK),
                                  and(rawPair, _REVERSE_MASK),
                                  shr(_NUMERATOR_OFFSET, and(rawPair, _NUMERATOR_MASK)),
                                  address()
                              )
              
                              mstore(emptyPtr, _WETH_WITHDRAW_CALL_SELECTOR)
                              mstore(add(emptyPtr, 0x04), returnAmount)
                              if iszero(call(gas(), _WETH, 0, emptyPtr, 0x24, 0, 0)) {
                                  reRevert()
                              }
              
                              if iszero(call(gas(), recipient, returnAmount, 0, 0, 0, 0)) {
                                  reRevert()
                              }
                          }
                      }
                      if (returnAmount < minReturn) revert RouterErrors.ReturnAmountIsNotEnough();
                  }
              }
              
              
              // File contracts/interfaces/IUniswapV3Pool.sol
              
              pragma solidity 0.8.17;
              
              interface IUniswapV3Pool {
                  /// @notice Swap token0 for token1, or token1 for token0
                  /// @dev The caller of this method receives a callback in the form of IUniswapV3SwapCallback#uniswapV3SwapCallback
                  /// @param recipient The address to receive the output of the swap
                  /// @param zeroForOne The direction of the swap, true for token0 to token1, false for token1 to token0
                  /// @param amountSpecified The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative)
                  /// @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this
                  /// value after the swap. If one for zero, the price cannot be greater than this value after the swap
                  /// @param data Any data to be passed through to the callback
                  /// @return amount0 The delta of the balance of token0 of the pool, exact when negative, minimum when positive
                  /// @return amount1 The delta of the balance of token1 of the pool, exact when negative, minimum when positive
                  function swap(
                      address recipient,
                      bool zeroForOne,
                      int256 amountSpecified,
                      uint160 sqrtPriceLimitX96,
                      bytes calldata data
                  ) external returns (int256 amount0, int256 amount1);
              
                  /// @notice The first of the two tokens of the pool, sorted by address
                  /// @return The token contract address
                  function token0() external view returns (address);
              
                  /// @notice The second of the two tokens of the pool, sorted by address
                  /// @return The token contract address
                  function token1() external view returns (address);
              
                  /// @notice The pool's fee in hundredths of a bip, i.e. 1e-6
                  /// @return The fee
                  function fee() external view returns (uint24);
              }
              
              
              // File contracts/interfaces/IUniswapV3SwapCallback.sol
              
              pragma solidity 0.8.17;
              
              /// @title Callback for IUniswapV3PoolActions#swap
              /// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface
              interface IUniswapV3SwapCallback {
                  /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap.
                  /// @dev In the implementation you must pay the pool tokens owed for the swap.
                  /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.
                  /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped.
                  /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by
                  /// the end of the swap. If positive, the callback must send that amount of token0 to the pool.
                  /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by
                  /// the end of the swap. If positive, the callback must send that amount of token1 to the pool.
                  /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call
                  function uniswapV3SwapCallback(
                      int256 amount0Delta,
                      int256 amount1Delta,
                      bytes calldata data
                  ) external;
              }
              
              
              // File @openzeppelin/contracts/utils/Address.sol@v4.7.3
              
              // OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol)
              
              pragma solidity ^0.8.1;
              
              /**
               * @dev Collection of functions related to the address type
               */
              library Address {
                  /**
                   * @dev Returns true if `account` is a contract.
                   *
                   * [IMPORTANT]
                   * ====
                   * It is unsafe to assume that an address for which this function returns
                   * false is an externally-owned account (EOA) and not a contract.
                   *
                   * Among others, `isContract` will return false for the following
                   * types of addresses:
                   *
                   *  - an externally-owned account
                   *  - a contract in construction
                   *  - an address where a contract will be created
                   *  - an address where a contract lived, but was destroyed
                   * ====
                   *
                   * [IMPORTANT]
                   * ====
                   * You shouldn't rely on `isContract` to protect against flash loan attacks!
                   *
                   * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
                   * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
                   * constructor.
                   * ====
                   */
                  function isContract(address account) internal view returns (bool) {
                      // This method relies on extcodesize/address.code.length, which returns 0
                      // for contracts in construction, since the code is only stored at the end
                      // of the constructor execution.
              
                      return account.code.length > 0;
                  }
              
                  /**
                   * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                   * `recipient`, forwarding all available gas and reverting on errors.
                   *
                   * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                   * of certain opcodes, possibly making contracts go over the 2300 gas limit
                   * imposed by `transfer`, making them unable to receive funds via
                   * `transfer`. {sendValue} removes this limitation.
                   *
                   * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                   *
                   * IMPORTANT: because control is transferred to `recipient`, care must be
                   * taken to not create reentrancy vulnerabilities. Consider using
                   * {ReentrancyGuard} or the
                   * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                   */
                  function sendValue(address payable recipient, uint256 amount) internal {
                      require(address(this).balance >= amount, "Address: insufficient balance");
              
                      (bool success, ) = recipient.call{value: amount}("");
                      require(success, "Address: unable to send value, recipient may have reverted");
                  }
              
                  /**
                   * @dev Performs a Solidity function call using a low level `call`. A
                   * plain `call` is an unsafe replacement for a function call: use this
                   * function instead.
                   *
                   * If `target` reverts with a revert reason, it is bubbled up by this
                   * function (like regular Solidity function calls).
                   *
                   * Returns the raw returned data. To convert to the expected return value,
                   * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
                   *
                   * Requirements:
                   *
                   * - `target` must be a contract.
                   * - calling `target` with `data` must not revert.
                   *
                   * _Available since v3.1._
                   */
                  function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                      return functionCall(target, data, "Address: low-level call failed");
                  }
              
                  /**
                   * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
                   * `errorMessage` as a fallback revert reason when `target` reverts.
                   *
                   * _Available since v3.1._
                   */
                  function functionCall(
                      address target,
                      bytes memory data,
                      string memory errorMessage
                  ) internal returns (bytes memory) {
                      return functionCallWithValue(target, data, 0, errorMessage);
                  }
              
                  /**
                   * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                   * but also transferring `value` wei to `target`.
                   *
                   * Requirements:
                   *
                   * - the calling contract must have an ETH balance of at least `value`.
                   * - the called Solidity function must be `payable`.
                   *
                   * _Available since v3.1._
                   */
                  function functionCallWithValue(
                      address target,
                      bytes memory data,
                      uint256 value
                  ) internal returns (bytes memory) {
                      return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
                  }
              
                  /**
                   * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
                   * with `errorMessage` as a fallback revert reason when `target` reverts.
                   *
                   * _Available since v3.1._
                   */
                  function functionCallWithValue(
                      address target,
                      bytes memory data,
                      uint256 value,
                      string memory errorMessage
                  ) internal returns (bytes memory) {
                      require(address(this).balance >= value, "Address: insufficient balance for call");
                      require(isContract(target), "Address: call to non-contract");
              
                      (bool success, bytes memory returndata) = target.call{value: value}(data);
                      return verifyCallResult(success, returndata, errorMessage);
                  }
              
                  /**
                   * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                   * but performing a static call.
                   *
                   * _Available since v3.3._
                   */
                  function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                      return functionStaticCall(target, data, "Address: low-level static call failed");
                  }
              
                  /**
                   * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                   * but performing a static call.
                   *
                   * _Available since v3.3._
                   */
                  function functionStaticCall(
                      address target,
                      bytes memory data,
                      string memory errorMessage
                  ) internal view returns (bytes memory) {
                      require(isContract(target), "Address: static call to non-contract");
              
                      (bool success, bytes memory returndata) = target.staticcall(data);
                      return verifyCallResult(success, returndata, errorMessage);
                  }
              
                  /**
                   * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                   * but performing a delegate call.
                   *
                   * _Available since v3.4._
                   */
                  function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                      return functionDelegateCall(target, data, "Address: low-level delegate call failed");
                  }
              
                  /**
                   * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                   * but performing a delegate call.
                   *
                   * _Available since v3.4._
                   */
                  function functionDelegateCall(
                      address target,
                      bytes memory data,
                      string memory errorMessage
                  ) internal returns (bytes memory) {
                      require(isContract(target), "Address: delegate call to non-contract");
              
                      (bool success, bytes memory returndata) = target.delegatecall(data);
                      return verifyCallResult(success, returndata, errorMessage);
                  }
              
                  /**
                   * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
                   * revert reason using the provided one.
                   *
                   * _Available since v4.3._
                   */
                  function verifyCallResult(
                      bool success,
                      bytes memory returndata,
                      string memory errorMessage
                  ) internal pure returns (bytes memory) {
                      if (success) {
                          return returndata;
                      } else {
                          // Look for revert reason and bubble it up if present
                          if (returndata.length > 0) {
                              // The easiest way to bubble the revert reason is using memory via assembly
                              /// @solidity memory-safe-assembly
                              assembly {
                                  let returndata_size := mload(returndata)
                                  revert(add(32, returndata), returndata_size)
                              }
                          } else {
                              revert(errorMessage);
                          }
                      }
                  }
              }
              
              
              // File @openzeppelin/contracts/utils/math/SafeCast.sol@v4.7.3
              
              // OpenZeppelin Contracts (last updated v4.7.0) (utils/math/SafeCast.sol)
              
              pragma solidity ^0.8.0;
              
              /**
               * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow
               * checks.
               *
               * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
               * easily result in undesired exploitation or bugs, since developers usually
               * assume that overflows raise errors. `SafeCast` restores this intuition by
               * reverting the transaction when such an operation overflows.
               *
               * Using this library instead of the unchecked operations eliminates an entire
               * class of bugs, so it's recommended to use it always.
               *
               * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing
               * all math on `uint256` and `int256` and then downcasting.
               */
              library SafeCast {
                  /**
                   * @dev Returns the downcasted uint248 from uint256, reverting on
                   * overflow (when the input is greater than largest uint248).
                   *
                   * Counterpart to Solidity's `uint248` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 248 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint248(uint256 value) internal pure returns (uint248) {
                      require(value <= type(uint248).max, "SafeCast: value doesn't fit in 248 bits");
                      return uint248(value);
                  }
              
                  /**
                   * @dev Returns the downcasted uint240 from uint256, reverting on
                   * overflow (when the input is greater than largest uint240).
                   *
                   * Counterpart to Solidity's `uint240` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 240 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint240(uint256 value) internal pure returns (uint240) {
                      require(value <= type(uint240).max, "SafeCast: value doesn't fit in 240 bits");
                      return uint240(value);
                  }
              
                  /**
                   * @dev Returns the downcasted uint232 from uint256, reverting on
                   * overflow (when the input is greater than largest uint232).
                   *
                   * Counterpart to Solidity's `uint232` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 232 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint232(uint256 value) internal pure returns (uint232) {
                      require(value <= type(uint232).max, "SafeCast: value doesn't fit in 232 bits");
                      return uint232(value);
                  }
              
                  /**
                   * @dev Returns the downcasted uint224 from uint256, reverting on
                   * overflow (when the input is greater than largest uint224).
                   *
                   * Counterpart to Solidity's `uint224` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 224 bits
                   *
                   * _Available since v4.2._
                   */
                  function toUint224(uint256 value) internal pure returns (uint224) {
                      require(value <= type(uint224).max, "SafeCast: value doesn't fit in 224 bits");
                      return uint224(value);
                  }
              
                  /**
                   * @dev Returns the downcasted uint216 from uint256, reverting on
                   * overflow (when the input is greater than largest uint216).
                   *
                   * Counterpart to Solidity's `uint216` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 216 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint216(uint256 value) internal pure returns (uint216) {
                      require(value <= type(uint216).max, "SafeCast: value doesn't fit in 216 bits");
                      return uint216(value);
                  }
              
                  /**
                   * @dev Returns the downcasted uint208 from uint256, reverting on
                   * overflow (when the input is greater than largest uint208).
                   *
                   * Counterpart to Solidity's `uint208` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 208 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint208(uint256 value) internal pure returns (uint208) {
                      require(value <= type(uint208).max, "SafeCast: value doesn't fit in 208 bits");
                      return uint208(value);
                  }
              
                  /**
                   * @dev Returns the downcasted uint200 from uint256, reverting on
                   * overflow (when the input is greater than largest uint200).
                   *
                   * Counterpart to Solidity's `uint200` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 200 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint200(uint256 value) internal pure returns (uint200) {
                      require(value <= type(uint200).max, "SafeCast: value doesn't fit in 200 bits");
                      return uint200(value);
                  }
              
                  /**
                   * @dev Returns the downcasted uint192 from uint256, reverting on
                   * overflow (when the input is greater than largest uint192).
                   *
                   * Counterpart to Solidity's `uint192` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 192 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint192(uint256 value) internal pure returns (uint192) {
                      require(value <= type(uint192).max, "SafeCast: value doesn't fit in 192 bits");
                      return uint192(value);
                  }
              
                  /**
                   * @dev Returns the downcasted uint184 from uint256, reverting on
                   * overflow (when the input is greater than largest uint184).
                   *
                   * Counterpart to Solidity's `uint184` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 184 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint184(uint256 value) internal pure returns (uint184) {
                      require(value <= type(uint184).max, "SafeCast: value doesn't fit in 184 bits");
                      return uint184(value);
                  }
              
                  /**
                   * @dev Returns the downcasted uint176 from uint256, reverting on
                   * overflow (when the input is greater than largest uint176).
                   *
                   * Counterpart to Solidity's `uint176` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 176 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint176(uint256 value) internal pure returns (uint176) {
                      require(value <= type(uint176).max, "SafeCast: value doesn't fit in 176 bits");
                      return uint176(value);
                  }
              
                  /**
                   * @dev Returns the downcasted uint168 from uint256, reverting on
                   * overflow (when the input is greater than largest uint168).
                   *
                   * Counterpart to Solidity's `uint168` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 168 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint168(uint256 value) internal pure returns (uint168) {
                      require(value <= type(uint168).max, "SafeCast: value doesn't fit in 168 bits");
                      return uint168(value);
                  }
              
                  /**
                   * @dev Returns the downcasted uint160 from uint256, reverting on
                   * overflow (when the input is greater than largest uint160).
                   *
                   * Counterpart to Solidity's `uint160` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 160 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint160(uint256 value) internal pure returns (uint160) {
                      require(value <= type(uint160).max, "SafeCast: value doesn't fit in 160 bits");
                      return uint160(value);
                  }
              
                  /**
                   * @dev Returns the downcasted uint152 from uint256, reverting on
                   * overflow (when the input is greater than largest uint152).
                   *
                   * Counterpart to Solidity's `uint152` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 152 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint152(uint256 value) internal pure returns (uint152) {
                      require(value <= type(uint152).max, "SafeCast: value doesn't fit in 152 bits");
                      return uint152(value);
                  }
              
                  /**
                   * @dev Returns the downcasted uint144 from uint256, reverting on
                   * overflow (when the input is greater than largest uint144).
                   *
                   * Counterpart to Solidity's `uint144` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 144 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint144(uint256 value) internal pure returns (uint144) {
                      require(value <= type(uint144).max, "SafeCast: value doesn't fit in 144 bits");
                      return uint144(value);
                  }
              
                  /**
                   * @dev Returns the downcasted uint136 from uint256, reverting on
                   * overflow (when the input is greater than largest uint136).
                   *
                   * Counterpart to Solidity's `uint136` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 136 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint136(uint256 value) internal pure returns (uint136) {
                      require(value <= type(uint136).max, "SafeCast: value doesn't fit in 136 bits");
                      return uint136(value);
                  }
              
                  /**
                   * @dev Returns the downcasted uint128 from uint256, reverting on
                   * overflow (when the input is greater than largest uint128).
                   *
                   * Counterpart to Solidity's `uint128` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 128 bits
                   *
                   * _Available since v2.5._
                   */
                  function toUint128(uint256 value) internal pure returns (uint128) {
                      require(value <= type(uint128).max, "SafeCast: value doesn't fit in 128 bits");
                      return uint128(value);
                  }
              
                  /**
                   * @dev Returns the downcasted uint120 from uint256, reverting on
                   * overflow (when the input is greater than largest uint120).
                   *
                   * Counterpart to Solidity's `uint120` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 120 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint120(uint256 value) internal pure returns (uint120) {
                      require(value <= type(uint120).max, "SafeCast: value doesn't fit in 120 bits");
                      return uint120(value);
                  }
              
                  /**
                   * @dev Returns the downcasted uint112 from uint256, reverting on
                   * overflow (when the input is greater than largest uint112).
                   *
                   * Counterpart to Solidity's `uint112` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 112 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint112(uint256 value) internal pure returns (uint112) {
                      require(value <= type(uint112).max, "SafeCast: value doesn't fit in 112 bits");
                      return uint112(value);
                  }
              
                  /**
                   * @dev Returns the downcasted uint104 from uint256, reverting on
                   * overflow (when the input is greater than largest uint104).
                   *
                   * Counterpart to Solidity's `uint104` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 104 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint104(uint256 value) internal pure returns (uint104) {
                      require(value <= type(uint104).max, "SafeCast: value doesn't fit in 104 bits");
                      return uint104(value);
                  }
              
                  /**
                   * @dev Returns the downcasted uint96 from uint256, reverting on
                   * overflow (when the input is greater than largest uint96).
                   *
                   * Counterpart to Solidity's `uint96` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 96 bits
                   *
                   * _Available since v4.2._
                   */
                  function toUint96(uint256 value) internal pure returns (uint96) {
                      require(value <= type(uint96).max, "SafeCast: value doesn't fit in 96 bits");
                      return uint96(value);
                  }
              
                  /**
                   * @dev Returns the downcasted uint88 from uint256, reverting on
                   * overflow (when the input is greater than largest uint88).
                   *
                   * Counterpart to Solidity's `uint88` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 88 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint88(uint256 value) internal pure returns (uint88) {
                      require(value <= type(uint88).max, "SafeCast: value doesn't fit in 88 bits");
                      return uint88(value);
                  }
              
                  /**
                   * @dev Returns the downcasted uint80 from uint256, reverting on
                   * overflow (when the input is greater than largest uint80).
                   *
                   * Counterpart to Solidity's `uint80` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 80 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint80(uint256 value) internal pure returns (uint80) {
                      require(value <= type(uint80).max, "SafeCast: value doesn't fit in 80 bits");
                      return uint80(value);
                  }
              
                  /**
                   * @dev Returns the downcasted uint72 from uint256, reverting on
                   * overflow (when the input is greater than largest uint72).
                   *
                   * Counterpart to Solidity's `uint72` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 72 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint72(uint256 value) internal pure returns (uint72) {
                      require(value <= type(uint72).max, "SafeCast: value doesn't fit in 72 bits");
                      return uint72(value);
                  }
              
                  /**
                   * @dev Returns the downcasted uint64 from uint256, reverting on
                   * overflow (when the input is greater than largest uint64).
                   *
                   * Counterpart to Solidity's `uint64` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 64 bits
                   *
                   * _Available since v2.5._
                   */
                  function toUint64(uint256 value) internal pure returns (uint64) {
                      require(value <= type(uint64).max, "SafeCast: value doesn't fit in 64 bits");
                      return uint64(value);
                  }
              
                  /**
                   * @dev Returns the downcasted uint56 from uint256, reverting on
                   * overflow (when the input is greater than largest uint56).
                   *
                   * Counterpart to Solidity's `uint56` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 56 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint56(uint256 value) internal pure returns (uint56) {
                      require(value <= type(uint56).max, "SafeCast: value doesn't fit in 56 bits");
                      return uint56(value);
                  }
              
                  /**
                   * @dev Returns the downcasted uint48 from uint256, reverting on
                   * overflow (when the input is greater than largest uint48).
                   *
                   * Counterpart to Solidity's `uint48` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 48 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint48(uint256 value) internal pure returns (uint48) {
                      require(value <= type(uint48).max, "SafeCast: value doesn't fit in 48 bits");
                      return uint48(value);
                  }
              
                  /**
                   * @dev Returns the downcasted uint40 from uint256, reverting on
                   * overflow (when the input is greater than largest uint40).
                   *
                   * Counterpart to Solidity's `uint40` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 40 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint40(uint256 value) internal pure returns (uint40) {
                      require(value <= type(uint40).max, "SafeCast: value doesn't fit in 40 bits");
                      return uint40(value);
                  }
              
                  /**
                   * @dev Returns the downcasted uint32 from uint256, reverting on
                   * overflow (when the input is greater than largest uint32).
                   *
                   * Counterpart to Solidity's `uint32` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 32 bits
                   *
                   * _Available since v2.5._
                   */
                  function toUint32(uint256 value) internal pure returns (uint32) {
                      require(value <= type(uint32).max, "SafeCast: value doesn't fit in 32 bits");
                      return uint32(value);
                  }
              
                  /**
                   * @dev Returns the downcasted uint24 from uint256, reverting on
                   * overflow (when the input is greater than largest uint24).
                   *
                   * Counterpart to Solidity's `uint24` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 24 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint24(uint256 value) internal pure returns (uint24) {
                      require(value <= type(uint24).max, "SafeCast: value doesn't fit in 24 bits");
                      return uint24(value);
                  }
              
                  /**
                   * @dev Returns the downcasted uint16 from uint256, reverting on
                   * overflow (when the input is greater than largest uint16).
                   *
                   * Counterpart to Solidity's `uint16` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 16 bits
                   *
                   * _Available since v2.5._
                   */
                  function toUint16(uint256 value) internal pure returns (uint16) {
                      require(value <= type(uint16).max, "SafeCast: value doesn't fit in 16 bits");
                      return uint16(value);
                  }
              
                  /**
                   * @dev Returns the downcasted uint8 from uint256, reverting on
                   * overflow (when the input is greater than largest uint8).
                   *
                   * Counterpart to Solidity's `uint8` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 8 bits
                   *
                   * _Available since v2.5._
                   */
                  function toUint8(uint256 value) internal pure returns (uint8) {
                      require(value <= type(uint8).max, "SafeCast: value doesn't fit in 8 bits");
                      return uint8(value);
                  }
              
                  /**
                   * @dev Converts a signed int256 into an unsigned uint256.
                   *
                   * Requirements:
                   *
                   * - input must be greater than or equal to 0.
                   *
                   * _Available since v3.0._
                   */
                  function toUint256(int256 value) internal pure returns (uint256) {
                      require(value >= 0, "SafeCast: value must be positive");
                      return uint256(value);
                  }
              
                  /**
                   * @dev Returns the downcasted int248 from int256, reverting on
                   * overflow (when the input is less than smallest int248 or
                   * greater than largest int248).
                   *
                   * Counterpart to Solidity's `int248` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 248 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt248(int256 value) internal pure returns (int248) {
                      require(value >= type(int248).min && value <= type(int248).max, "SafeCast: value doesn't fit in 248 bits");
                      return int248(value);
                  }
              
                  /**
                   * @dev Returns the downcasted int240 from int256, reverting on
                   * overflow (when the input is less than smallest int240 or
                   * greater than largest int240).
                   *
                   * Counterpart to Solidity's `int240` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 240 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt240(int256 value) internal pure returns (int240) {
                      require(value >= type(int240).min && value <= type(int240).max, "SafeCast: value doesn't fit in 240 bits");
                      return int240(value);
                  }
              
                  /**
                   * @dev Returns the downcasted int232 from int256, reverting on
                   * overflow (when the input is less than smallest int232 or
                   * greater than largest int232).
                   *
                   * Counterpart to Solidity's `int232` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 232 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt232(int256 value) internal pure returns (int232) {
                      require(value >= type(int232).min && value <= type(int232).max, "SafeCast: value doesn't fit in 232 bits");
                      return int232(value);
                  }
              
                  /**
                   * @dev Returns the downcasted int224 from int256, reverting on
                   * overflow (when the input is less than smallest int224 or
                   * greater than largest int224).
                   *
                   * Counterpart to Solidity's `int224` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 224 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt224(int256 value) internal pure returns (int224) {
                      require(value >= type(int224).min && value <= type(int224).max, "SafeCast: value doesn't fit in 224 bits");
                      return int224(value);
                  }
              
                  /**
                   * @dev Returns the downcasted int216 from int256, reverting on
                   * overflow (when the input is less than smallest int216 or
                   * greater than largest int216).
                   *
                   * Counterpart to Solidity's `int216` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 216 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt216(int256 value) internal pure returns (int216) {
                      require(value >= type(int216).min && value <= type(int216).max, "SafeCast: value doesn't fit in 216 bits");
                      return int216(value);
                  }
              
                  /**
                   * @dev Returns the downcasted int208 from int256, reverting on
                   * overflow (when the input is less than smallest int208 or
                   * greater than largest int208).
                   *
                   * Counterpart to Solidity's `int208` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 208 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt208(int256 value) internal pure returns (int208) {
                      require(value >= type(int208).min && value <= type(int208).max, "SafeCast: value doesn't fit in 208 bits");
                      return int208(value);
                  }
              
                  /**
                   * @dev Returns the downcasted int200 from int256, reverting on
                   * overflow (when the input is less than smallest int200 or
                   * greater than largest int200).
                   *
                   * Counterpart to Solidity's `int200` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 200 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt200(int256 value) internal pure returns (int200) {
                      require(value >= type(int200).min && value <= type(int200).max, "SafeCast: value doesn't fit in 200 bits");
                      return int200(value);
                  }
              
                  /**
                   * @dev Returns the downcasted int192 from int256, reverting on
                   * overflow (when the input is less than smallest int192 or
                   * greater than largest int192).
                   *
                   * Counterpart to Solidity's `int192` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 192 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt192(int256 value) internal pure returns (int192) {
                      require(value >= type(int192).min && value <= type(int192).max, "SafeCast: value doesn't fit in 192 bits");
                      return int192(value);
                  }
              
                  /**
                   * @dev Returns the downcasted int184 from int256, reverting on
                   * overflow (when the input is less than smallest int184 or
                   * greater than largest int184).
                   *
                   * Counterpart to Solidity's `int184` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 184 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt184(int256 value) internal pure returns (int184) {
                      require(value >= type(int184).min && value <= type(int184).max, "SafeCast: value doesn't fit in 184 bits");
                      return int184(value);
                  }
              
                  /**
                   * @dev Returns the downcasted int176 from int256, reverting on
                   * overflow (when the input is less than smallest int176 or
                   * greater than largest int176).
                   *
                   * Counterpart to Solidity's `int176` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 176 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt176(int256 value) internal pure returns (int176) {
                      require(value >= type(int176).min && value <= type(int176).max, "SafeCast: value doesn't fit in 176 bits");
                      return int176(value);
                  }
              
                  /**
                   * @dev Returns the downcasted int168 from int256, reverting on
                   * overflow (when the input is less than smallest int168 or
                   * greater than largest int168).
                   *
                   * Counterpart to Solidity's `int168` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 168 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt168(int256 value) internal pure returns (int168) {
                      require(value >= type(int168).min && value <= type(int168).max, "SafeCast: value doesn't fit in 168 bits");
                      return int168(value);
                  }
              
                  /**
                   * @dev Returns the downcasted int160 from int256, reverting on
                   * overflow (when the input is less than smallest int160 or
                   * greater than largest int160).
                   *
                   * Counterpart to Solidity's `int160` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 160 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt160(int256 value) internal pure returns (int160) {
                      require(value >= type(int160).min && value <= type(int160).max, "SafeCast: value doesn't fit in 160 bits");
                      return int160(value);
                  }
              
                  /**
                   * @dev Returns the downcasted int152 from int256, reverting on
                   * overflow (when the input is less than smallest int152 or
                   * greater than largest int152).
                   *
                   * Counterpart to Solidity's `int152` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 152 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt152(int256 value) internal pure returns (int152) {
                      require(value >= type(int152).min && value <= type(int152).max, "SafeCast: value doesn't fit in 152 bits");
                      return int152(value);
                  }
              
                  /**
                   * @dev Returns the downcasted int144 from int256, reverting on
                   * overflow (when the input is less than smallest int144 or
                   * greater than largest int144).
                   *
                   * Counterpart to Solidity's `int144` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 144 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt144(int256 value) internal pure returns (int144) {
                      require(value >= type(int144).min && value <= type(int144).max, "SafeCast: value doesn't fit in 144 bits");
                      return int144(value);
                  }
              
                  /**
                   * @dev Returns the downcasted int136 from int256, reverting on
                   * overflow (when the input is less than smallest int136 or
                   * greater than largest int136).
                   *
                   * Counterpart to Solidity's `int136` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 136 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt136(int256 value) internal pure returns (int136) {
                      require(value >= type(int136).min && value <= type(int136).max, "SafeCast: value doesn't fit in 136 bits");
                      return int136(value);
                  }
              
                  /**
                   * @dev Returns the downcasted int128 from int256, reverting on
                   * overflow (when the input is less than smallest int128 or
                   * greater than largest int128).
                   *
                   * Counterpart to Solidity's `int128` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 128 bits
                   *
                   * _Available since v3.1._
                   */
                  function toInt128(int256 value) internal pure returns (int128) {
                      require(value >= type(int128).min && value <= type(int128).max, "SafeCast: value doesn't fit in 128 bits");
                      return int128(value);
                  }
              
                  /**
                   * @dev Returns the downcasted int120 from int256, reverting on
                   * overflow (when the input is less than smallest int120 or
                   * greater than largest int120).
                   *
                   * Counterpart to Solidity's `int120` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 120 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt120(int256 value) internal pure returns (int120) {
                      require(value >= type(int120).min && value <= type(int120).max, "SafeCast: value doesn't fit in 120 bits");
                      return int120(value);
                  }
              
                  /**
                   * @dev Returns the downcasted int112 from int256, reverting on
                   * overflow (when the input is less than smallest int112 or
                   * greater than largest int112).
                   *
                   * Counterpart to Solidity's `int112` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 112 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt112(int256 value) internal pure returns (int112) {
                      require(value >= type(int112).min && value <= type(int112).max, "SafeCast: value doesn't fit in 112 bits");
                      return int112(value);
                  }
              
                  /**
                   * @dev Returns the downcasted int104 from int256, reverting on
                   * overflow (when the input is less than smallest int104 or
                   * greater than largest int104).
                   *
                   * Counterpart to Solidity's `int104` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 104 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt104(int256 value) internal pure returns (int104) {
                      require(value >= type(int104).min && value <= type(int104).max, "SafeCast: value doesn't fit in 104 bits");
                      return int104(value);
                  }
              
                  /**
                   * @dev Returns the downcasted int96 from int256, reverting on
                   * overflow (when the input is less than smallest int96 or
                   * greater than largest int96).
                   *
                   * Counterpart to Solidity's `int96` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 96 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt96(int256 value) internal pure returns (int96) {
                      require(value >= type(int96).min && value <= type(int96).max, "SafeCast: value doesn't fit in 96 bits");
                      return int96(value);
                  }
              
                  /**
                   * @dev Returns the downcasted int88 from int256, reverting on
                   * overflow (when the input is less than smallest int88 or
                   * greater than largest int88).
                   *
                   * Counterpart to Solidity's `int88` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 88 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt88(int256 value) internal pure returns (int88) {
                      require(value >= type(int88).min && value <= type(int88).max, "SafeCast: value doesn't fit in 88 bits");
                      return int88(value);
                  }
              
                  /**
                   * @dev Returns the downcasted int80 from int256, reverting on
                   * overflow (when the input is less than smallest int80 or
                   * greater than largest int80).
                   *
                   * Counterpart to Solidity's `int80` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 80 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt80(int256 value) internal pure returns (int80) {
                      require(value >= type(int80).min && value <= type(int80).max, "SafeCast: value doesn't fit in 80 bits");
                      return int80(value);
                  }
              
                  /**
                   * @dev Returns the downcasted int72 from int256, reverting on
                   * overflow (when the input is less than smallest int72 or
                   * greater than largest int72).
                   *
                   * Counterpart to Solidity's `int72` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 72 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt72(int256 value) internal pure returns (int72) {
                      require(value >= type(int72).min && value <= type(int72).max, "SafeCast: value doesn't fit in 72 bits");
                      return int72(value);
                  }
              
                  /**
                   * @dev Returns the downcasted int64 from int256, reverting on
                   * overflow (when the input is less than smallest int64 or
                   * greater than largest int64).
                   *
                   * Counterpart to Solidity's `int64` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 64 bits
                   *
                   * _Available since v3.1._
                   */
                  function toInt64(int256 value) internal pure returns (int64) {
                      require(value >= type(int64).min && value <= type(int64).max, "SafeCast: value doesn't fit in 64 bits");
                      return int64(value);
                  }
              
                  /**
                   * @dev Returns the downcasted int56 from int256, reverting on
                   * overflow (when the input is less than smallest int56 or
                   * greater than largest int56).
                   *
                   * Counterpart to Solidity's `int56` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 56 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt56(int256 value) internal pure returns (int56) {
                      require(value >= type(int56).min && value <= type(int56).max, "SafeCast: value doesn't fit in 56 bits");
                      return int56(value);
                  }
              
                  /**
                   * @dev Returns the downcasted int48 from int256, reverting on
                   * overflow (when the input is less than smallest int48 or
                   * greater than largest int48).
                   *
                   * Counterpart to Solidity's `int48` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 48 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt48(int256 value) internal pure returns (int48) {
                      require(value >= type(int48).min && value <= type(int48).max, "SafeCast: value doesn't fit in 48 bits");
                      return int48(value);
                  }
              
                  /**
                   * @dev Returns the downcasted int40 from int256, reverting on
                   * overflow (when the input is less than smallest int40 or
                   * greater than largest int40).
                   *
                   * Counterpart to Solidity's `int40` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 40 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt40(int256 value) internal pure returns (int40) {
                      require(value >= type(int40).min && value <= type(int40).max, "SafeCast: value doesn't fit in 40 bits");
                      return int40(value);
                  }
              
                  /**
                   * @dev Returns the downcasted int32 from int256, reverting on
                   * overflow (when the input is less than smallest int32 or
                   * greater than largest int32).
                   *
                   * Counterpart to Solidity's `int32` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 32 bits
                   *
                   * _Available since v3.1._
                   */
                  function toInt32(int256 value) internal pure returns (int32) {
                      require(value >= type(int32).min && value <= type(int32).max, "SafeCast: value doesn't fit in 32 bits");
                      return int32(value);
                  }
              
                  /**
                   * @dev Returns the downcasted int24 from int256, reverting on
                   * overflow (when the input is less than smallest int24 or
                   * greater than largest int24).
                   *
                   * Counterpart to Solidity's `int24` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 24 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt24(int256 value) internal pure returns (int24) {
                      require(value >= type(int24).min && value <= type(int24).max, "SafeCast: value doesn't fit in 24 bits");
                      return int24(value);
                  }
              
                  /**
                   * @dev Returns the downcasted int16 from int256, reverting on
                   * overflow (when the input is less than smallest int16 or
                   * greater than largest int16).
                   *
                   * Counterpart to Solidity's `int16` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 16 bits
                   *
                   * _Available since v3.1._
                   */
                  function toInt16(int256 value) internal pure returns (int16) {
                      require(value >= type(int16).min && value <= type(int16).max, "SafeCast: value doesn't fit in 16 bits");
                      return int16(value);
                  }
              
                  /**
                   * @dev Returns the downcasted int8 from int256, reverting on
                   * overflow (when the input is less than smallest int8 or
                   * greater than largest int8).
                   *
                   * Counterpart to Solidity's `int8` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 8 bits
                   *
                   * _Available since v3.1._
                   */
                  function toInt8(int256 value) internal pure returns (int8) {
                      require(value >= type(int8).min && value <= type(int8).max, "SafeCast: value doesn't fit in 8 bits");
                      return int8(value);
                  }
              
                  /**
                   * @dev Converts an unsigned uint256 into a signed int256.
                   *
                   * Requirements:
                   *
                   * - input must be less than or equal to maxInt256.
                   *
                   * _Available since v3.0._
                   */
                  function toInt256(uint256 value) internal pure returns (int256) {
                      // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
                      require(value <= uint256(type(int256).max), "SafeCast: value doesn't fit in an int256");
                      return int256(value);
                  }
              }
              
              
              // File contracts/routers/UnoswapV3Router.sol
              
              
              pragma solidity 0.8.17;
              
              
              
              
              
              
              
              
              
              contract UnoswapV3Router is EthReceiver, IUniswapV3SwapCallback {
                  using Address for address payable;
                  using SafeERC20 for IERC20;
              
                  error EmptyPools();
                  error BadPool();
              
                  uint256 private constant _ONE_FOR_ZERO_MASK = 1 << 255;
                  uint256 private constant _WETH_UNWRAP_MASK = 1 << 253;
                  bytes32 private constant _POOL_INIT_CODE_HASH = 0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54;
                  bytes32 private constant _FF_FACTORY = 0xff1F98431c8aD98523631AE4a59f267346ea31F9840000000000000000000000;
                  // concatenation of token0(), token1() fee(), transfer() and transferFrom() selectors
                  bytes32 private constant _SELECTORS = 0x0dfe1681d21220a7ddca3f43a9059cbb23b872dd000000000000000000000000;
                  uint256 private constant _ADDRESS_MASK =   0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff;
                  /// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK)
                  uint160 private constant _MIN_SQRT_RATIO = 4295128739 + 1;
                  /// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK)
                  uint160 private constant _MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342 - 1;
                  IWETH private immutable _WETH;  // solhint-disable-line var-name-mixedcase
              
                  constructor(IWETH weth) {
                      _WETH = weth;
                  }
              
                  /// @notice Same as `uniswapV3SwapTo` but calls permit first,
                  /// allowing to approve token spending and make a swap in one transaction.
                  /// @param recipient Address that will receive swap funds
                  /// @param srcToken Source token
                  /// @param amount Amount of source tokens to swap
                  /// @param minReturn Minimal allowed returnAmount to make transaction commit
                  /// @param pools Pools chain used for swaps. Pools src and dst tokens should match to make swap happen
                  /// @param permit Should contain valid permit that can be used in `IERC20Permit.permit` calls.
                  /// See tests for examples
                  function uniswapV3SwapToWithPermit(
                      address payable recipient,
                      IERC20 srcToken,
                      uint256 amount,
                      uint256 minReturn,
                      uint256[] calldata pools,
                      bytes calldata permit
                  ) external returns(uint256 returnAmount) {
                      srcToken.safePermit(permit);
                      return _uniswapV3Swap(recipient, amount, minReturn, pools);
                  }
              
                  /// @notice Same as `uniswapV3SwapTo` but uses `msg.sender` as recipient
                  /// @param amount Amount of source tokens to swap
                  /// @param minReturn Minimal allowed returnAmount to make transaction commit
                  /// @param pools Pools chain used for swaps. Pools src and dst tokens should match to make swap happen
                  function uniswapV3Swap(
                      uint256 amount,
                      uint256 minReturn,
                      uint256[] calldata pools
                  ) external payable returns(uint256 returnAmount) {
                      return _uniswapV3Swap(payable(msg.sender), amount, minReturn, pools);
                  }
              
                  /// @notice Performs swap using Uniswap V3 exchange. Wraps and unwraps ETH if required.
                  /// Sending non-zero `msg.value` for anything but ETH swaps is prohibited
                  /// @param recipient Address that will receive swap funds
                  /// @param amount Amount of source tokens to swap
                  /// @param minReturn Minimal allowed returnAmount to make transaction commit
                  /// @param pools Pools chain used for swaps. Pools src and dst tokens should match to make swap happen
                  function uniswapV3SwapTo(
                      address payable recipient,
                      uint256 amount,
                      uint256 minReturn,
                      uint256[] calldata pools
                  ) external payable returns(uint256 returnAmount) {
                      return _uniswapV3Swap(recipient, amount, minReturn, pools);
                  }
              
                  function _uniswapV3Swap(
                      address payable recipient,
                      uint256 amount,
                      uint256 minReturn,
                      uint256[] calldata pools
                  ) private returns(uint256 returnAmount) {
                      unchecked {
                          uint256 len = pools.length;
                          if (len == 0) revert EmptyPools();
                          uint256 lastIndex = len - 1;
                          returnAmount = amount;
                          bool wrapWeth = msg.value > 0;
                          bool unwrapWeth = pools[lastIndex] & _WETH_UNWRAP_MASK > 0;
                          if (wrapWeth) {
                              if (msg.value != amount) revert RouterErrors.InvalidMsgValue();
                              _WETH.deposit{value: amount}();
                          }
                          if (len > 1) {
                              returnAmount = _makeSwap(address(this), wrapWeth ? address(this) : msg.sender, pools[0], returnAmount);
              
                              for (uint256 i = 1; i < lastIndex; i++) {
                                  returnAmount = _makeSwap(address(this), address(this), pools[i], returnAmount);
                              }
                              returnAmount = _makeSwap(unwrapWeth ? address(this) : recipient, address(this), pools[lastIndex], returnAmount);
                          } else {
                              returnAmount = _makeSwap(unwrapWeth ? address(this) : recipient, wrapWeth ? address(this) : msg.sender, pools[0], returnAmount);
                          }
              
                          if (returnAmount < minReturn) revert RouterErrors.ReturnAmountIsNotEnough();
              
                          if (unwrapWeth) {
                              _WETH.withdraw(returnAmount);
                              recipient.sendValue(returnAmount);
                          }
                      }
                  }
              
                  /// @inheritdoc IUniswapV3SwapCallback
                  function uniswapV3SwapCallback(
                      int256 amount0Delta,
                      int256 amount1Delta,
                      bytes calldata /* data */
                  ) external override {
                      assembly {  // solhint-disable-line no-inline-assembly
                          function reRevert() {
                              returndatacopy(0, 0, returndatasize())
                              revert(0, returndatasize())
                          }
              
                          function validateERC20Transfer(status) {
                              if iszero(status) {
                                  reRevert()
                              }
                              let success := or(
                                  iszero(returndatasize()),                       // empty return data
                                  and(gt(returndatasize(), 31), eq(mload(0), 1))  // true in return data
                              )
                              if iszero(success) {
                                  mstore(0, 0xf27f64e400000000000000000000000000000000000000000000000000000000)  // ERC20TransferFailed()
                                  revert(0, 4)
                              }
                          }
              
                          let emptyPtr := mload(0x40)
                          let resultPtr := add(emptyPtr, 0x15)  // 0x15 = _FF_FACTORY size
              
                          mstore(emptyPtr, _SELECTORS)
                          if iszero(staticcall(gas(), caller(), emptyPtr, 0x4, resultPtr, 0x20)) {
                              reRevert()
                          }
                          if iszero(staticcall(gas(), caller(), add(emptyPtr, 0x4), 0x4, add(resultPtr, 0x20), 0x20)) {
                              reRevert()
                          }
                          if iszero(staticcall(gas(), caller(), add(emptyPtr, 0x8), 0x4, add(resultPtr, 0x40), 0x20)) {
                              reRevert()
                          }
              
                          let token
                          let amount
                          switch sgt(amount0Delta, 0)
                          case 1 {
                              token := mload(resultPtr)
                              amount := amount0Delta
                          }
                          default {
                              token := mload(add(resultPtr, 0x20))
                              amount := amount1Delta
                          }
              
                          mstore(emptyPtr, _FF_FACTORY)
                          mstore(resultPtr, keccak256(resultPtr, 0x60)) // Compute the inner hash in-place
                          mstore(add(resultPtr, 0x20), _POOL_INIT_CODE_HASH)
                          let pool := and(keccak256(emptyPtr, 0x55), _ADDRESS_MASK)
                          if xor(pool, caller()) {
                              mstore(0, 0xb2c0272200000000000000000000000000000000000000000000000000000000)  // BadPool()
                              revert(0, 4)
                          }
              
                          let payer := calldataload(0x84)
                          mstore(emptyPtr, _SELECTORS)
                          switch eq(payer, address())
                          case 1 {
                              // token.safeTransfer(msg.sender,amount)
                              mstore(add(emptyPtr, 0x10), caller())
                              mstore(add(emptyPtr, 0x30), amount)
                              validateERC20Transfer(
                                  call(gas(), token, 0, add(emptyPtr, 0x0c), 0x44, 0, 0x20)
                              )
                          }
                          default {
                              // token.safeTransferFrom(payer, msg.sender, amount);
                              mstore(add(emptyPtr, 0x14), payer)
                              mstore(add(emptyPtr, 0x34), caller())
                              mstore(add(emptyPtr, 0x54), amount)
                              validateERC20Transfer(
                                  call(gas(), token, 0, add(emptyPtr, 0x10), 0x64, 0, 0x20)
                              )
                          }
                      }
                  }
              
                  function _makeSwap(address recipient, address payer, uint256 pool, uint256 amount) private returns (uint256) {
                      bool zeroForOne = pool & _ONE_FOR_ZERO_MASK == 0;
                      if (zeroForOne) {
                          (, int256 amount1) = IUniswapV3Pool(address(uint160(pool))).swap(
                              recipient,
                              zeroForOne,
                              SafeCast.toInt256(amount),
                              _MIN_SQRT_RATIO,
                              abi.encode(payer)
                          );
                          return SafeCast.toUint256(-amount1);
                      } else {
                          (int256 amount0,) = IUniswapV3Pool(address(uint160(pool))).swap(
                              recipient,
                              zeroForOne,
                              SafeCast.toInt256(amount),
                              _MAX_SQRT_RATIO,
                              abi.encode(payer)
                          );
                          return SafeCast.toUint256(-amount0);
                      }
                  }
              }
              
              
              // File @1inch/solidity-utils/contracts/OnlyWethReceiver.sol@v2.1.1
              
              
              pragma solidity ^0.8.0;
              
              abstract contract OnlyWethReceiver is EthReceiver {
                  address private immutable _WETH;  // solhint-disable-line var-name-mixedcase
              
                  constructor(address weth) {
                      _WETH = address(weth);
                  }
              
                  function _receive() internal virtual override {
                      if (msg.sender != _WETH) revert EthDepositRejected();
                  }
              }
              
              
              // File @openzeppelin/contracts/interfaces/IERC1271.sol@v4.7.3
              
              // OpenZeppelin Contracts v4.4.1 (interfaces/IERC1271.sol)
              
              pragma solidity ^0.8.0;
              
              /**
               * @dev Interface of the ERC1271 standard signature validation method for
               * contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271].
               *
               * _Available since v4.1._
               */
              interface IERC1271 {
                  /**
                   * @dev Should return whether the signature provided is valid for the provided data
                   * @param hash      Hash of the data to be signed
                   * @param signature Signature byte array associated with _data
                   */
                  function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue);
              }
              
              
              // File @1inch/solidity-utils/contracts/libraries/ECDSA.sol@v2.1.1
              
              
              pragma solidity ^0.8.0;
              
              library ECDSA {
                  // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
                  // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
                  // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
                  // signatures from current libraries generate a unique signature with an s-value in the lower half order.
                  //
                  // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
                  // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
                  // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
                  // these malleable signatures as well.
                  uint256 private constant _S_BOUNDARY = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0 + 1;
                  uint256 private constant _COMPACT_S_MASK = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
                  uint256 private constant _COMPACT_V_SHIFT = 255;
              
                  function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal view returns(address signer) {
                      /// @solidity memory-safe-assembly
                      assembly { // solhint-disable-line no-inline-assembly
                          if lt(s, _S_BOUNDARY) {
                              let ptr := mload(0x40)
              
                              mstore(ptr, hash)
                              mstore(add(ptr, 0x20), v)
                              mstore(add(ptr, 0x40), r)
                              mstore(add(ptr, 0x60), s)
                              mstore(0, 0)
                              pop(staticcall(gas(), 0x1, ptr, 0x80, 0, 0x20))
                              signer := mload(0)
                          }
                      }
                  }
              
                  function recover(bytes32 hash, bytes32 r, bytes32 vs) internal view returns(address signer) {
                      /// @solidity memory-safe-assembly
                      assembly { // solhint-disable-line no-inline-assembly
                          let s := and(vs, _COMPACT_S_MASK)
                          if lt(s, _S_BOUNDARY) {
                              let ptr := mload(0x40)
              
                              mstore(ptr, hash)
                              mstore(add(ptr, 0x20), add(27, shr(_COMPACT_V_SHIFT, vs)))
                              mstore(add(ptr, 0x40), r)
                              mstore(add(ptr, 0x60), s)
                              mstore(0, 0)
                              pop(staticcall(gas(), 0x1, ptr, 0x80, 0, 0x20))
                              signer := mload(0)
                          }
                      }
                  }
              
                  /// WARNING!!!
                  /// There is a known signature malleability issue with two representations of signatures!
                  /// Even though this function is able to verify both standard 65-byte and compact 64-byte EIP-2098 signatures
                  /// one should never use raw signatures for any kind of invalidation logic in their code.
                  /// As the standard and compact representations are interchangeable any invalidation logic that relies on
                  /// signature uniqueness will get rekt.
                  /// More info: https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories/GHSA-4h98-2769-gh6h
                  function recover(bytes32 hash, bytes calldata signature) internal view returns(address signer) {
                      /// @solidity memory-safe-assembly
                      assembly { // solhint-disable-line no-inline-assembly
                          let ptr := mload(0x40)
              
                          // memory[ptr:ptr+0x80] = (hash, v, r, s)
                          switch signature.length
                          case 65 {
                              // memory[ptr+0x20:ptr+0x80] = (v, r, s)
                              mstore(add(ptr, 0x20), byte(0, calldataload(add(signature.offset, 0x40))))
                              calldatacopy(add(ptr, 0x40), signature.offset, 0x40)
                          }
                          case 64 {
                              // memory[ptr+0x20:ptr+0x80] = (v, r, s)
                              let vs := calldataload(add(signature.offset, 0x20))
                              mstore(add(ptr, 0x20), add(27, shr(_COMPACT_V_SHIFT, vs)))
                              calldatacopy(add(ptr, 0x40), signature.offset, 0x20)
                              mstore(add(ptr, 0x60), and(vs, _COMPACT_S_MASK))
                          }
                          default {
                              ptr := 0
                          }
              
                          if ptr {
                              if lt(mload(add(ptr, 0x60)), _S_BOUNDARY) {
                                  // memory[ptr:ptr+0x20] = (hash)
                                  mstore(ptr, hash)
              
                                  mstore(0, 0)
                                  pop(staticcall(gas(), 0x1, ptr, 0x80, 0, 0x20))
                                  signer := mload(0)
                              }
                          }
                      }
                  }
              
                  function recoverOrIsValidSignature(address signer, bytes32 hash, bytes calldata signature) internal view returns(bool success) {
                      if (signer == address(0)) return false;
                      if ((signature.length == 64 || signature.length == 65) && recover(hash, signature) == signer) {
                          return true;
                      }
                      return isValidSignature(signer, hash, signature);
                  }
              
                  function recoverOrIsValidSignature(address signer, bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal view returns(bool success) {
                      if (signer == address(0)) return false;
                      if (recover(hash, v, r, s) == signer) {
                          return true;
                      }
                      return isValidSignature(signer, hash, v, r, s);
                  }
              
                  function recoverOrIsValidSignature(address signer, bytes32 hash, bytes32 r, bytes32 vs) internal view returns(bool success) {
                      if (signer == address(0)) return false;
                      if (recover(hash, r, vs) == signer) {
                          return true;
                      }
                      return isValidSignature(signer, hash, r, vs);
                  }
              
                  function recoverOrIsValidSignature65(address signer, bytes32 hash, bytes32 r, bytes32 vs) internal view returns(bool success) {
                      if (signer == address(0)) return false;
                      if (recover(hash, r, vs) == signer) {
                          return true;
                      }
                      return isValidSignature65(signer, hash, r, vs);
                  }
              
                  function isValidSignature(address signer, bytes32 hash, bytes calldata signature) internal view returns(bool success) {
                      // (bool success, bytes memory data) = signer.staticcall(abi.encodeWithSelector(IERC1271.isValidSignature.selector, hash, signature));
                      // return success && data.length >= 4 && abi.decode(data, (bytes4)) == IERC1271.isValidSignature.selector;
                      bytes4 selector = IERC1271.isValidSignature.selector;
                      /// @solidity memory-safe-assembly
                      assembly { // solhint-disable-line no-inline-assembly
                          let ptr := mload(0x40)
              
                          mstore(ptr, selector)
                          mstore(add(ptr, 0x04), hash)
                          mstore(add(ptr, 0x24), 0x40)
                          mstore(add(ptr, 0x44), signature.length)
                          calldatacopy(add(ptr, 0x64), signature.offset, signature.length)
                          if staticcall(gas(), signer, ptr, add(0x64, signature.length), 0, 0x20) {
                              success := and(eq(selector, mload(0)), eq(returndatasize(), 0x20))
                          }
                      }
                  }
              
                  function isValidSignature(address signer, bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal view returns(bool success) {
                      bytes4 selector = IERC1271.isValidSignature.selector;
                      /// @solidity memory-safe-assembly
                      assembly { // solhint-disable-line no-inline-assembly
                          let ptr := mload(0x40)
              
                          mstore(ptr, selector)
                          mstore(add(ptr, 0x04), hash)
                          mstore(add(ptr, 0x24), 0x40)
                          mstore(add(ptr, 0x44), 65)
                          mstore(add(ptr, 0x64), r)
                          mstore(add(ptr, 0x84), s)
                          mstore8(add(ptr, 0xa4), v)
                          if staticcall(gas(), signer, ptr, 0xa5, 0, 0x20) {
                              success := and(eq(selector, mload(0)), eq(returndatasize(), 0x20))
                          }
                      }
                  }
              
                  function isValidSignature(address signer, bytes32 hash, bytes32 r, bytes32 vs) internal view returns(bool success) {
                      // (bool success, bytes memory data) = signer.staticcall(abi.encodeWithSelector(IERC1271.isValidSignature.selector, hash, abi.encodePacked(r, vs)));
                      // return success && data.length >= 4 && abi.decode(data, (bytes4)) == IERC1271.isValidSignature.selector;
                      bytes4 selector = IERC1271.isValidSignature.selector;
                      /// @solidity memory-safe-assembly
                      assembly { // solhint-disable-line no-inline-assembly
                          let ptr := mload(0x40)
              
                          mstore(ptr, selector)
                          mstore(add(ptr, 0x04), hash)
                          mstore(add(ptr, 0x24), 0x40)
                          mstore(add(ptr, 0x44), 64)
                          mstore(add(ptr, 0x64), r)
                          mstore(add(ptr, 0x84), vs)
                          if staticcall(gas(), signer, ptr, 0xa4, 0, 0x20) {
                              success := and(eq(selector, mload(0)), eq(returndatasize(), 0x20))
                          }
                      }
                  }
              
                  function isValidSignature65(address signer, bytes32 hash, bytes32 r, bytes32 vs) internal view returns(bool success) {
                      // (bool success, bytes memory data) = signer.staticcall(abi.encodeWithSelector(IERC1271.isValidSignature.selector, hash, abi.encodePacked(r, vs & ~uint256(1 << 255), uint8(vs >> 255))));
                      // return success && data.length >= 4 && abi.decode(data, (bytes4)) == IERC1271.isValidSignature.selector;
                      bytes4 selector = IERC1271.isValidSignature.selector;
                      /// @solidity memory-safe-assembly
                      assembly { // solhint-disable-line no-inline-assembly
                          let ptr := mload(0x40)
              
                          mstore(ptr, selector)
                          mstore(add(ptr, 0x04), hash)
                          mstore(add(ptr, 0x24), 0x40)
                          mstore(add(ptr, 0x44), 65)
                          mstore(add(ptr, 0x64), r)
                          mstore(add(ptr, 0x84), and(vs, _COMPACT_S_MASK))
                          mstore8(add(ptr, 0xa4), add(27, shr(_COMPACT_V_SHIFT, vs)))
                          if staticcall(gas(), signer, ptr, 0xa5, 0, 0x20) {
                              success := and(eq(selector, mload(0)), eq(returndatasize(), 0x20))
                          }
                      }
                  }
              
                  function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 res) {
                      // 32 is the length in bytes of hash, enforced by the type signature above
                      // return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
                      /// @solidity memory-safe-assembly
                      assembly { // solhint-disable-line no-inline-assembly
                          mstore(0, 0x19457468657265756d205369676e6564204d6573736167653a0a333200000000) // "\x19Ethereum Signed Message:\n32"
                          mstore(28, hash)
                          res := keccak256(0, 60)
                      }
                  }
              
                  function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 res) {
                      // return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
                      /// @solidity memory-safe-assembly
                      assembly { // solhint-disable-line no-inline-assembly
                          let ptr := mload(0x40)
                          mstore(ptr, 0x1901000000000000000000000000000000000000000000000000000000000000) // "\x19\x01"
                          mstore(add(ptr, 0x02), domainSeparator)
                          mstore(add(ptr, 0x22), structHash)
                          res := keccak256(ptr, 66)
                      }
                  }
              }
              
              
              // File @1inch/limit-order-protocol/contracts/OrderRFQLib.sol@v0.3.0-prerelease
              
              
              pragma solidity 0.8.17;
              
              library OrderRFQLib {
                  struct OrderRFQ {
                      uint256 info;  // lowest 64 bits is the order id, next 64 bits is the expiration timestamp
                      address makerAsset;
                      address takerAsset;
                      address maker;
                      address allowedSender;  // equals to Zero address on public orders
                      uint256 makingAmount;
                      uint256 takingAmount;
                  }
              
                  bytes32 constant internal _LIMIT_ORDER_RFQ_TYPEHASH = keccak256(
                      "OrderRFQ("
                          "uint256 info,"
                          "address makerAsset,"
                          "address takerAsset,"
                          "address maker,"
                          "address allowedSender,"
                          "uint256 makingAmount,"
                          "uint256 takingAmount"
                      ")"
                  );
              
                  function hash(OrderRFQ memory order, bytes32 domainSeparator) internal pure returns(bytes32 result) {
                      bytes32 typehash = _LIMIT_ORDER_RFQ_TYPEHASH;
                      bytes32 orderHash;
                      // this assembly is memory unsafe :(
                      assembly { // solhint-disable-line no-inline-assembly
                          let ptr := sub(order, 0x20)
              
                          // keccak256(abi.encode(_LIMIT_ORDER_RFQ_TYPEHASH, order));
                          let tmp := mload(ptr)
                          mstore(ptr, typehash)
                          orderHash := keccak256(ptr, 0x100)
                          mstore(ptr, tmp)
                      }
                      return ECDSA.toTypedDataHash(domainSeparator, orderHash);
                  }
              }
              
              
              // File @openzeppelin/contracts/utils/Strings.sol@v4.7.3
              
              // OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)
              
              pragma solidity ^0.8.0;
              
              /**
               * @dev String operations.
               */
              library Strings {
                  bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
                  uint8 private constant _ADDRESS_LENGTH = 20;
              
                  /**
                   * @dev Converts a `uint256` to its ASCII `string` decimal representation.
                   */
                  function toString(uint256 value) internal pure returns (string memory) {
                      // Inspired by OraclizeAPI's implementation - MIT licence
                      // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
              
                      if (value == 0) {
                          return "0";
                      }
                      uint256 temp = value;
                      uint256 digits;
                      while (temp != 0) {
                          digits++;
                          temp /= 10;
                      }
                      bytes memory buffer = new bytes(digits);
                      while (value != 0) {
                          digits -= 1;
                          buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
                          value /= 10;
                      }
                      return string(buffer);
                  }
              
                  /**
                   * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
                   */
                  function toHexString(uint256 value) internal pure returns (string memory) {
                      if (value == 0) {
                          return "0x00";
                      }
                      uint256 temp = value;
                      uint256 length = 0;
                      while (temp != 0) {
                          length++;
                          temp >>= 8;
                      }
                      return toHexString(value, length);
                  }
              
                  /**
                   * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
                   */
                  function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
                      bytes memory buffer = new bytes(2 * length + 2);
                      buffer[0] = "0";
                      buffer[1] = "x";
                      for (uint256 i = 2 * length + 1; i > 1; --i) {
                          buffer[i] = _HEX_SYMBOLS[value & 0xf];
                          value >>= 4;
                      }
                      require(value == 0, "Strings: hex length insufficient");
                      return string(buffer);
                  }
              
                  /**
                   * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
                   */
                  function toHexString(address addr) internal pure returns (string memory) {
                      return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
                  }
              }
              
              
              // File @openzeppelin/contracts/utils/cryptography/draft-EIP712.sol@v4.7.3
              
              // OpenZeppelin Contracts v4.4.1 (utils/cryptography/draft-EIP712.sol)
              
              pragma solidity ^0.8.0;
              
              /**
               * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
               *
               * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible,
               * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding
               * they need in their contracts using a combination of `abi.encode` and `keccak256`.
               *
               * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
               * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
               * ({_hashTypedDataV4}).
               *
               * The implementation of the domain separator was designed to be as efficient as possible while still properly updating
               * the chain id to protect against replay attacks on an eventual fork of the chain.
               *
               * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
               * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
               *
               * _Available since v3.4._
               */
              abstract contract EIP712 {
                  /* solhint-disable var-name-mixedcase */
                  // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
                  // invalidate the cached domain separator if the chain id changes.
                  bytes32 private immutable _CACHED_DOMAIN_SEPARATOR;
                  uint256 private immutable _CACHED_CHAIN_ID;
                  address private immutable _CACHED_THIS;
              
                  bytes32 private immutable _HASHED_NAME;
                  bytes32 private immutable _HASHED_VERSION;
                  bytes32 private immutable _TYPE_HASH;
              
                  /* solhint-enable var-name-mixedcase */
              
                  /**
                   * @dev Initializes the domain separator and parameter caches.
                   *
                   * The meaning of `name` and `version` is specified in
                   * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:
                   *
                   * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
                   * - `version`: the current major version of the signing domain.
                   *
                   * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
                   * contract upgrade].
                   */
                  constructor(string memory name, string memory version) {
                      bytes32 hashedName = keccak256(bytes(name));
                      bytes32 hashedVersion = keccak256(bytes(version));
                      bytes32 typeHash = keccak256(
                          "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
                      );
                      _HASHED_NAME = hashedName;
                      _HASHED_VERSION = hashedVersion;
                      _CACHED_CHAIN_ID = block.chainid;
                      _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(typeHash, hashedName, hashedVersion);
                      _CACHED_THIS = address(this);
                      _TYPE_HASH = typeHash;
                  }
              
                  /**
                   * @dev Returns the domain separator for the current chain.
                   */
                  function _domainSeparatorV4() internal view returns (bytes32) {
                      if (address(this) == _CACHED_THIS && block.chainid == _CACHED_CHAIN_ID) {
                          return _CACHED_DOMAIN_SEPARATOR;
                      } else {
                          return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION);
                      }
                  }
              
                  function _buildDomainSeparator(
                      bytes32 typeHash,
                      bytes32 nameHash,
                      bytes32 versionHash
                  ) private view returns (bytes32) {
                      return keccak256(abi.encode(typeHash, nameHash, versionHash, block.chainid, address(this)));
                  }
              
                  /**
                   * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
                   * function returns the hash of the fully encoded EIP712 message for this domain.
                   *
                   * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
                   *
                   * ```solidity
                   * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
                   *     keccak256("Mail(address to,string contents)"),
                   *     mailTo,
                   *     keccak256(bytes(mailContents))
                   * )));
                   * address signer = ECDSA.recover(digest, signature);
                   * ```
                   */
                  function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
                      return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash);
                  }
              }
              
              
              // File @1inch/limit-order-protocol/contracts/libraries/Errors.sol@v0.3.0-prerelease
              
              
              pragma solidity 0.8.17;
              
              library Errors {
                  error InvalidMsgValue();
                  error ETHTransferFailed();
              }
              
              
              // File @1inch/limit-order-protocol/contracts/helpers/AmountCalculator.sol@v0.3.0-prerelease
              
              
              pragma solidity 0.8.17;
              
              /// @title A helper contract for calculations related to order amounts
              library AmountCalculator {
                  /// @notice Calculates maker amount
                  /// @return Result Floored maker amount
                  function getMakingAmount(uint256 orderMakerAmount, uint256 orderTakerAmount, uint256 swapTakerAmount) internal pure returns(uint256) {
                      return swapTakerAmount * orderMakerAmount / orderTakerAmount;
                  }
              
                  /// @notice Calculates taker amount
                  /// @return Result Ceiled taker amount
                  function getTakingAmount(uint256 orderMakerAmount, uint256 orderTakerAmount, uint256 swapMakerAmount) internal pure returns(uint256) {
                      return (swapMakerAmount * orderTakerAmount + orderMakerAmount - 1) / orderMakerAmount;
                  }
              }
              
              
              // File @1inch/limit-order-protocol/contracts/OrderRFQMixin.sol@v0.3.0-prerelease
              
              
              pragma solidity 0.8.17;
              
              
              
              
              
              
              
              /// @title RFQ Limit Order mixin
              abstract contract OrderRFQMixin is EIP712, OnlyWethReceiver {
                  using SafeERC20 for IERC20;
                  using OrderRFQLib for OrderRFQLib.OrderRFQ;
              
                  error RFQZeroTargetIsForbidden();
                  error RFQPrivateOrder();
                  error RFQBadSignature();
                  error OrderExpired();
                  error MakingAmountExceeded();
                  error TakingAmountExceeded();
                  error RFQSwapWithZeroAmount();
                  error InvalidatedOrder();
              
                  /**
                   * @notice Emitted when RFQ gets filled
                   * @param orderHash Hash of the order
                   * @param makingAmount Amount of the maker asset that was transferred from maker to taker
                   */
                  event OrderFilledRFQ(
                      bytes32 orderHash,
                      uint256 makingAmount
                  );
              
                  uint256 private constant _RAW_CALL_GAS_LIMIT = 5000;
                  uint256 private constant _MAKER_AMOUNT_FLAG = 1 << 255;
                  uint256 private constant _SIGNER_SMART_CONTRACT_HINT = 1 << 254;
                  uint256 private constant _IS_VALID_SIGNATURE_65_BYTES = 1 << 253;
                  uint256 private constant _UNWRAP_WETH_FLAG = 1 << 252;
                  uint256 private constant _AMOUNT_MASK = ~(
                      _MAKER_AMOUNT_FLAG |
                      _SIGNER_SMART_CONTRACT_HINT |
                      _IS_VALID_SIGNATURE_65_BYTES |
                      _UNWRAP_WETH_FLAG
                  );
              
                  IWETH private immutable _WETH;  // solhint-disable-line var-name-mixedcase
                  mapping(address => mapping(uint256 => uint256)) private _invalidator;
              
                  constructor(IWETH weth) OnlyWethReceiver(address(weth)) {
                      _WETH = weth;
                  }
              
                  /**
                   * @notice Returns bitmask for double-spend invalidators based on lowest byte of order.info and filled quotes
                   * @param maker Maker address
                   * @param slot Slot number to return bitmask for
                   * @return result Each bit represents whether corresponding was already invalidated
                   */
                  function invalidatorForOrderRFQ(address maker, uint256 slot) external view returns(uint256 /* result */) {
                      return _invalidator[maker][slot];
                  }
              
                  /**
                   * @notice Cancels order's quote
                   * @param orderInfo Order info (only order id in lowest 64 bits is used)
                   */
                  function cancelOrderRFQ(uint256 orderInfo) external {
                      _invalidateOrder(msg.sender, orderInfo, 0);
                  }
              
                  /// @notice Cancels multiple order's quotes
                  function cancelOrderRFQ(uint256 orderInfo, uint256 additionalMask) external {
                      _invalidateOrder(msg.sender, orderInfo, additionalMask);
                  }
              
                  /**
                   * @notice Fills order's quote, fully or partially (whichever is possible)
                   * @param order Order quote to fill
                   * @param signature Signature to confirm quote ownership
                   * @param flagsAndAmount Fill configuration flags with amount packed in one slot
                   * @return filledMakingAmount Actual amount transferred from maker to taker
                   * @return filledTakingAmount Actual amount transferred from taker to maker
                   * @return orderHash Hash of the filled order
                   */
                  function fillOrderRFQ(
                      OrderRFQLib.OrderRFQ memory order,
                      bytes calldata signature,
                      uint256 flagsAndAmount
                  ) external payable returns(uint256 /* filledMakingAmount */, uint256 /* filledTakingAmount */, bytes32 /* orderHash */) {
                      return fillOrderRFQTo(order, signature, flagsAndAmount, msg.sender);
                  }
              
                  /**
                   * @notice Fills order's quote, fully or partially, with compact signature
                   * @param order Order quote to fill
                   * @param r R component of signature
                   * @param vs VS component of signature
                   * @param flagsAndAmount Fill configuration flags with amount packed in one slot
                   * - Bits 0-252 contain the amount to fill
                   * - Bit 253 is used to indicate whether signature is 64-bit (0) or 65-bit (1)
                   * - Bit 254 is used to indicate whether smart contract (1) signed the order or not (0)
                   * - Bit 255 is used to indicate whether maker (1) or taker amount (0) is given in the amount parameter
                   * @return filledMakingAmount Actual amount transferred from maker to taker
                   * @return filledTakingAmount Actual amount transferred from taker to maker
                   * @return orderHash Hash of the filled order
                   */
                  function fillOrderRFQCompact(
                      OrderRFQLib.OrderRFQ memory order,
                      bytes32 r,
                      bytes32 vs,
                      uint256 flagsAndAmount
                  ) external payable returns(uint256 filledMakingAmount, uint256 filledTakingAmount, bytes32 orderHash) {
                      orderHash = order.hash(_domainSeparatorV4());
                      if (flagsAndAmount & _SIGNER_SMART_CONTRACT_HINT != 0) {
                          if (flagsAndAmount & _IS_VALID_SIGNATURE_65_BYTES != 0) {
                              if (!ECDSA.isValidSignature65(order.maker, orderHash, r, vs)) revert RFQBadSignature();
                          } else {
                              if (!ECDSA.isValidSignature(order.maker, orderHash, r, vs)) revert RFQBadSignature();
                          }
                      } else {
                          if(!ECDSA.recoverOrIsValidSignature(order.maker, orderHash, r, vs)) revert RFQBadSignature();
                      }
              
                      (filledMakingAmount, filledTakingAmount) = _fillOrderRFQTo(order, flagsAndAmount, msg.sender);
                      emit OrderFilledRFQ(orderHash, filledMakingAmount);
                  }
              
                  /**
                   * @notice Same as `fillOrderRFQTo` but calls permit first.
                   * It allows to approve token spending and make a swap in one transaction.
                   * Also allows to specify funds destination instead of `msg.sender`
                   * @param order Order quote to fill
                   * @param signature Signature to confirm quote ownership
                   * @param flagsAndAmount Fill configuration flags with amount packed in one slot
                   * @param target Address that will receive swap funds
                   * @param permit Should consist of abiencoded token address and encoded `IERC20Permit.permit` call.
                   * @return filledMakingAmount Actual amount transferred from maker to taker
                   * @return filledTakingAmount Actual amount transferred from taker to maker
                   * @return orderHash Hash of the filled order
                   * @dev See tests for examples
                   */
                  function fillOrderRFQToWithPermit(
                      OrderRFQLib.OrderRFQ memory order,
                      bytes calldata signature,
                      uint256 flagsAndAmount,
                      address target,
                      bytes calldata permit
                  ) external returns(uint256 /* filledMakingAmount */, uint256 /* filledTakingAmount */, bytes32 /* orderHash */) {
                      IERC20(order.takerAsset).safePermit(permit);
                      return fillOrderRFQTo(order, signature, flagsAndAmount, target);
                  }
              
                  /**
                   * @notice Same as `fillOrderRFQ` but allows to specify funds destination instead of `msg.sender`
                   * @param order Order quote to fill
                   * @param signature Signature to confirm quote ownership
                   * @param flagsAndAmount Fill configuration flags with amount packed in one slot
                   * @param target Address that will receive swap funds
                   * @return filledMakingAmount Actual amount transferred from maker to taker
                   * @return filledTakingAmount Actual amount transferred from taker to maker
                   * @return orderHash Hash of the filled order
                   */
                  function fillOrderRFQTo(
                      OrderRFQLib.OrderRFQ memory order,
                      bytes calldata signature,
                      uint256 flagsAndAmount,
                      address target
                  ) public payable returns(uint256 filledMakingAmount, uint256 filledTakingAmount, bytes32 orderHash) {
                      orderHash = order.hash(_domainSeparatorV4());
                      if (flagsAndAmount & _SIGNER_SMART_CONTRACT_HINT != 0) {
                          if (flagsAndAmount & _IS_VALID_SIGNATURE_65_BYTES != 0 && signature.length != 65) revert RFQBadSignature();
                          if (!ECDSA.isValidSignature(order.maker, orderHash, signature)) revert RFQBadSignature();
                      } else {
                          if(!ECDSA.recoverOrIsValidSignature(order.maker, orderHash, signature)) revert RFQBadSignature();
                      }
                      (filledMakingAmount, filledTakingAmount) = _fillOrderRFQTo(order, flagsAndAmount, target);
                      emit OrderFilledRFQ(orderHash, filledMakingAmount);
                  }
              
                  function _fillOrderRFQTo(
                      OrderRFQLib.OrderRFQ memory order,
                      uint256 flagsAndAmount,
                      address target
                  ) private returns(uint256 makingAmount, uint256 takingAmount) {
                      if (target == address(0)) revert RFQZeroTargetIsForbidden();
              
                      address maker = order.maker;
              
                      // Validate order
                      if (order.allowedSender != address(0) && order.allowedSender != msg.sender) revert RFQPrivateOrder();
              
                      {  // Stack too deep
                          uint256 info = order.info;
                          // Check time expiration
                          uint256 expiration = uint128(info) >> 64;
                          if (expiration != 0 && block.timestamp > expiration) revert OrderExpired(); // solhint-disable-line not-rely-on-time
                          _invalidateOrder(maker, info, 0);
                      }
              
                      {  // Stack too deep
                          uint256 orderMakingAmount = order.makingAmount;
                          uint256 orderTakingAmount = order.takingAmount;
                          uint256 amount = flagsAndAmount & _AMOUNT_MASK;
                          // Compute partial fill if needed
                          if (amount == 0) {
                              // zero amount means whole order
                              makingAmount = orderMakingAmount;
                              takingAmount = orderTakingAmount;
                          }
                          else if (flagsAndAmount & _MAKER_AMOUNT_FLAG != 0) {
                              if (amount > orderMakingAmount) revert MakingAmountExceeded();
                              makingAmount = amount;
                              takingAmount = AmountCalculator.getTakingAmount(orderMakingAmount, orderTakingAmount, makingAmount);
                          }
                          else {
                              if (amount > orderTakingAmount) revert TakingAmountExceeded();
                              takingAmount = amount;
                              makingAmount = AmountCalculator.getMakingAmount(orderMakingAmount, orderTakingAmount, takingAmount);
                          }
                      }
              
                      if (makingAmount == 0 || takingAmount == 0) revert RFQSwapWithZeroAmount();
              
                      // Maker => Taker
                      if (order.makerAsset == address(_WETH) && flagsAndAmount & _UNWRAP_WETH_FLAG != 0) {
                          _WETH.transferFrom(maker, address(this), makingAmount);
                          _WETH.withdraw(makingAmount);
                          // solhint-disable-next-line avoid-low-level-calls
                          (bool success, ) = target.call{value: makingAmount, gas: _RAW_CALL_GAS_LIMIT}("");
                          if (!success) revert Errors.ETHTransferFailed();
                      } else {
                          IERC20(order.makerAsset).safeTransferFrom(maker, target, makingAmount);
                      }
              
                      // Taker => Maker
                      if (order.takerAsset == address(_WETH) && msg.value > 0) {
                          if (msg.value != takingAmount) revert Errors.InvalidMsgValue();
                          _WETH.deposit{ value: takingAmount }();
                          _WETH.transfer(maker, takingAmount);
                      } else {
                          if (msg.value != 0) revert Errors.InvalidMsgValue();
                          IERC20(order.takerAsset).safeTransferFrom(msg.sender, maker, takingAmount);
                      }
                  }
              
                  function _invalidateOrder(address maker, uint256 orderInfo, uint256 additionalMask) private {
                      uint256 invalidatorSlot = uint64(orderInfo) >> 8;
                      uint256 invalidatorBits = (1 << uint8(orderInfo)) | additionalMask;
                      mapping(uint256 => uint256) storage invalidatorStorage = _invalidator[maker];
                      uint256 invalidator = invalidatorStorage[invalidatorSlot];
                      if (invalidator & invalidatorBits == invalidatorBits) revert InvalidatedOrder();
                      invalidatorStorage[invalidatorSlot] = invalidator | invalidatorBits;
                  }
              }
              
              
              // File @1inch/limit-order-protocol/contracts/OrderLib.sol@v0.3.0-prerelease
              
              
              pragma solidity 0.8.17;
              
              library OrderLib {
                  struct Order {
                      uint256 salt;
                      address makerAsset;
                      address takerAsset;
                      address maker;
                      address receiver;
                      address allowedSender;  // equals to Zero address on public orders
                      uint256 makingAmount;
                      uint256 takingAmount;
                      uint256 offsets;
                      // bytes makerAssetData;
                      // bytes takerAssetData;
                      // bytes getMakingAmount; // this.staticcall(abi.encodePacked(bytes, swapTakerAmount)) => (swapMakerAmount)
                      // bytes getTakingAmount; // this.staticcall(abi.encodePacked(bytes, swapMakerAmount)) => (swapTakerAmount)
                      // bytes predicate;       // this.staticcall(bytes) => (bool)
                      // bytes permit;          // On first fill: permit.1.call(abi.encodePacked(permit.selector, permit.2))
                      // bytes preInteraction;
                      // bytes postInteraction;
                      bytes interactions; // concat(makerAssetData, takerAssetData, getMakingAmount, getTakingAmount, predicate, permit, preIntercation, postInteraction)
                  }
              
                  bytes32 constant internal _LIMIT_ORDER_TYPEHASH = keccak256(
                      "Order("
                          "uint256 salt,"
                          "address makerAsset,"
                          "address takerAsset,"
                          "address maker,"
                          "address receiver,"
                          "address allowedSender,"
                          "uint256 makingAmount,"
                          "uint256 takingAmount,"
                          "uint256 offsets,"
                          "bytes interactions"
                      ")"
                  );
              
                  enum DynamicField {
                      MakerAssetData,
                      TakerAssetData,
                      GetMakingAmount,
                      GetTakingAmount,
                      Predicate,
                      Permit,
                      PreInteraction,
                      PostInteraction
                  }
              
                  function getterIsFrozen(bytes calldata getter) internal pure returns(bool) {
                      return getter.length == 1 && getter[0] == "x";
                  }
              
                  function _get(Order calldata order, DynamicField field) private pure returns(bytes calldata) {
                      uint256 bitShift = uint256(field) << 5; // field * 32
                      return order.interactions[
                          uint32((order.offsets << 32) >> bitShift):
                          uint32(order.offsets >> bitShift)
                      ];
                  }
              
                  function makerAssetData(Order calldata order) internal pure returns(bytes calldata) {
                      return _get(order, DynamicField.MakerAssetData);
                  }
              
                  function takerAssetData(Order calldata order) internal pure returns(bytes calldata) {
                      return _get(order, DynamicField.TakerAssetData);
                  }
              
                  function getMakingAmount(Order calldata order) internal pure returns(bytes calldata) {
                      return _get(order, DynamicField.GetMakingAmount);
                  }
              
                  function getTakingAmount(Order calldata order) internal pure returns(bytes calldata) {
                      return _get(order, DynamicField.GetTakingAmount);
                  }
              
                  function predicate(Order calldata order) internal pure returns(bytes calldata) {
                      return _get(order, DynamicField.Predicate);
                  }
              
                  function permit(Order calldata order) internal pure returns(bytes calldata) {
                      return _get(order, DynamicField.Permit);
                  }
              
                  function preInteraction(Order calldata order) internal pure returns(bytes calldata) {
                      return _get(order, DynamicField.PreInteraction);
                  }
              
                  function postInteraction(Order calldata order) internal pure returns(bytes calldata) {
                      return _get(order, DynamicField.PostInteraction);
                  }
              
                  function hash(Order calldata order, bytes32 domainSeparator) internal pure returns(bytes32 result) {
                      bytes calldata interactions = order.interactions;
                      bytes32 typehash = _LIMIT_ORDER_TYPEHASH;
                      /// @solidity memory-safe-assembly
                      assembly { // solhint-disable-line no-inline-assembly
                          let ptr := mload(0x40)
              
                          // keccak256(abi.encode(_LIMIT_ORDER_TYPEHASH, orderWithoutInteractions, keccak256(order.interactions)));
                          calldatacopy(ptr, interactions.offset, interactions.length)
                          mstore(add(ptr, 0x140), keccak256(ptr, interactions.length))
                          calldatacopy(add(ptr, 0x20), order, 0x120)
                          mstore(ptr, typehash)
                          result := keccak256(ptr, 0x160)
                      }
                      result = ECDSA.toTypedDataHash(domainSeparator, result);
                  }
              }
              
              
              // File @1inch/limit-order-protocol/contracts/libraries/ArgumentsDecoder.sol@v0.3.0-prerelease
              
              
              pragma solidity 0.8.17;
              
              /// @title Library with gas efficient alternatives to `abi.decode`
              library ArgumentsDecoder {
                  error IncorrectDataLength();
              
                  function decodeUint256(bytes calldata data, uint256 offset) internal pure returns(uint256 value) {
                      unchecked { if (data.length < offset + 32) revert IncorrectDataLength(); }
                      // no memory ops inside so this insertion is automatically memory safe
                      assembly { // solhint-disable-line no-inline-assembly
                          value := calldataload(add(data.offset, offset))
                      }
                  }
              
                  function decodeSelector(bytes calldata data) internal pure returns(bytes4 value) {
                      if (data.length < 4) revert IncorrectDataLength();
                      // no memory ops inside so this insertion is automatically memory safe
                      assembly { // solhint-disable-line no-inline-assembly
                          value := calldataload(data.offset)
                      }
                  }
              
                  function decodeTailCalldata(bytes calldata data, uint256 tailOffset) internal pure returns(bytes calldata args) {
                      if (data.length < tailOffset) revert IncorrectDataLength();
                      // no memory ops inside so this insertion is automatically memory safe
                      assembly {  // solhint-disable-line no-inline-assembly
                          args.offset := add(data.offset, tailOffset)
                          args.length := sub(data.length, tailOffset)
                      }
                  }
              
                  function decodeTargetAndCalldata(bytes calldata data) internal pure returns(address target, bytes calldata args) {
                      if (data.length < 20) revert IncorrectDataLength();
                      // no memory ops inside so this insertion is automatically memory safe
                      assembly {  // solhint-disable-line no-inline-assembly
                          target := shr(96, calldataload(data.offset))
                          args.offset := add(data.offset, 20)
                          args.length := sub(data.length, 20)
                      }
                  }
              }
              
              
              // File @1inch/limit-order-protocol/contracts/helpers/NonceManager.sol@v0.3.0-prerelease
              
              
              pragma solidity 0.8.17;
              
              /// @title A helper contract for managing nonce of tx sender
              contract NonceManager {
                  error AdvanceNonceFailed();
                  event NonceIncreased(address indexed maker, uint256 newNonce);
              
                  mapping(address => uint256) public nonce;
              
                  /// @notice Advances nonce by one
                  function increaseNonce() external {
                      advanceNonce(1);
                  }
              
                  /// @notice Advances nonce by specified amount
                  function advanceNonce(uint8 amount) public {
                      if (amount == 0) revert AdvanceNonceFailed();
                      uint256 newNonce = nonce[msg.sender] + amount;
                      nonce[msg.sender] = newNonce;
                      emit NonceIncreased(msg.sender, newNonce);
                  }
              
                  /// @notice Checks if `makerAddress` has specified `makerNonce`
                  /// @return Result True if `makerAddress` has specified nonce. Otherwise, false
                  function nonceEquals(address makerAddress, uint256 makerNonce) public view returns(bool) {
                      return nonce[makerAddress] == makerNonce;
                  }
              }
              
              
              // File @1inch/limit-order-protocol/contracts/helpers/PredicateHelper.sol@v0.3.0-prerelease
              
              
              pragma solidity 0.8.17;
              
              
              /// @title A helper contract for executing boolean functions on arbitrary target call results
              contract PredicateHelper is NonceManager {
                  using ArgumentsDecoder for bytes;
              
                  error ArbitraryStaticCallFailed();
              
                  /// @notice Calls every target with corresponding data
                  /// @return Result True if call to any target returned True. Otherwise, false
                  function or(uint256 offsets, bytes calldata data) public view returns(bool) {
                      uint256 current;
                      uint256 previous;
                      for (uint256 i = 0; (current = uint32(offsets >> i)) != 0; i += 32) {
                          (bool success, uint256 res) = _selfStaticCall(data[previous:current]);
                          if (success && res == 1) {
                              return true;
                          }
                          previous = current;
                      }
                      return false;
                  }
              
                  /// @notice Calls every target with corresponding data
                  /// @return Result True if calls to all targets returned True. Otherwise, false
                  function and(uint256 offsets, bytes calldata data) public view returns(bool) {
                      uint256 current;
                      uint256 previous;
                      for (uint256 i = 0; (current = uint32(offsets >> i)) != 0; i += 32) {
                          (bool success, uint256 res) = _selfStaticCall(data[previous:current]);
                          if (!success || res != 1) {
                              return false;
                          }
                          previous = current;
                      }
                      return true;
                  }
              
                  /// @notice Calls target with specified data and tests if it's equal to the value
                  /// @param value Value to test
                  /// @return Result True if call to target returns the same value as `value`. Otherwise, false
                  function eq(uint256 value, bytes calldata data) public view returns(bool) {
                      (bool success, uint256 res) = _selfStaticCall(data);
                      return success && res == value;
                  }
              
                  /// @notice Calls target with specified data and tests if it's lower than value
                  /// @param value Value to test
                  /// @return Result True if call to target returns value which is lower than `value`. Otherwise, false
                  function lt(uint256 value, bytes calldata data) public view returns(bool) {
                      (bool success, uint256 res) = _selfStaticCall(data);
                      return success && res < value;
                  }
              
                  /// @notice Calls target with specified data and tests if it's bigger than value
                  /// @param value Value to test
                  /// @return Result True if call to target returns value which is bigger than `value`. Otherwise, false
                  function gt(uint256 value, bytes calldata data) public view returns(bool) {
                      (bool success, uint256 res) = _selfStaticCall(data);
                      return success && res > value;
                  }
              
                  /// @notice Checks passed time against block timestamp
                  /// @return Result True if current block timestamp is lower than `time`. Otherwise, false
                  function timestampBelow(uint256 time) public view returns(bool) {
                      return block.timestamp < time;  // solhint-disable-line not-rely-on-time
                  }
              
                  /// @notice Performs an arbitrary call to target with data
                  /// @return Result Bytes transmuted to uint256
                  function arbitraryStaticCall(address target, bytes calldata data) public view returns(uint256) {
                      (bool success, uint256 res) = _staticcallForUint(target, data);
                      if (!success) revert ArbitraryStaticCallFailed();
                      return res;
                  }
              
                  function timestampBelowAndNonceEquals(uint256 timeNonceAccount) public view returns(bool) {
                      uint256 _time = uint48(timeNonceAccount >> 208);
                      uint256 _nonce = uint48(timeNonceAccount >> 160);
                      address _account = address(uint160(timeNonceAccount));
                      return timestampBelow(_time) && nonceEquals(_account, _nonce);
                  }
              
                  function _selfStaticCall(bytes calldata data) internal view returns(bool, uint256) {
                      uint256 selector = uint32(data.decodeSelector());
                      uint256 arg = data.decodeUint256(4);
              
                      // special case for the most often used predicate
                      if (selector == uint32(this.timestampBelowAndNonceEquals.selector)) {  // 0x2cc2878d
                          return (true, timestampBelowAndNonceEquals(arg) ? 1 : 0);
                      }
              
                      if (selector < uint32(this.arbitraryStaticCall.selector)) {  // 0xbf15fcd8
                          if (selector < uint32(this.eq.selector)) {  // 0x6fe7b0ba
                              if (selector == uint32(this.gt.selector)) {  // 0x4f38e2b8
                                  return (true, gt(arg, data.decodeTailCalldata(100)) ? 1 : 0);
                              } else if (selector == uint32(this.timestampBelow.selector)) {  // 0x63592c2b
                                  return (true, timestampBelow(arg) ? 1 : 0);
                              }
                          } else {
                              if (selector == uint32(this.eq.selector)) {  // 0x6fe7b0ba
                                  return (true, eq(arg, data.decodeTailCalldata(100)) ? 1 : 0);
                              } else if (selector == uint32(this.or.selector)) {  // 0x74261145
                                  return (true, or(arg, data.decodeTailCalldata(100)) ? 1 : 0);
                              }
                          }
                      } else {
                          if (selector < uint32(this.lt.selector)) {  // 0xca4ece22
                              if (selector == uint32(this.arbitraryStaticCall.selector)) {  // 0xbf15fcd8
                                  return (true, arbitraryStaticCall(address(uint160(arg)), data.decodeTailCalldata(100)));
                              } else if (selector == uint32(this.and.selector)) {  // 0xbfa75143
                                  return (true, and(arg, data.decodeTailCalldata(100)) ? 1 : 0);
                              }
                          } else {
                              if (selector == uint32(this.lt.selector)) {  // 0xca4ece22
                                  return (true, lt(arg, data.decodeTailCalldata(100)) ? 1 : 0);
                              } else if (selector == uint32(this.nonceEquals.selector)) {  // 0xcf6fc6e3
                                  return (true, nonceEquals(address(uint160(arg)), data.decodeUint256(0x24)) ? 1 : 0);
                              }
                          }
                      }
              
                      return _staticcallForUint(address(this), data);
                  }
              
                  function _staticcallForUint(address target, bytes calldata input) private view returns(bool success, uint256 res) {
                      /// @solidity memory-safe-assembly
                      assembly { // solhint-disable-line no-inline-assembly
                          let data := mload(0x40)
              
                          calldatacopy(data, input.offset, input.length)
                          success := staticcall(gas(), target, data, input.length, 0x0, 0x20)
                          success := and(success, eq(returndatasize(), 32))
                          if success {
                              res := mload(0)
                          }
                      }
                  }
              }
              
              
              // File @1inch/limit-order-protocol/contracts/interfaces/IOrderMixin.sol@v0.3.0-prerelease
              
              
              pragma solidity 0.8.17;
              
              interface IOrderMixin {
                  /**
                   * @notice Returns unfilled amount for order. Throws if order does not exist
                   * @param orderHash Order's hash. Can be obtained by the `hashOrder` function
                   * @return amount Unfilled amount
                   */
                  function remaining(bytes32 orderHash) external view returns(uint256 amount);
              
                  /**
                   * @notice Returns unfilled amount for order
                   * @param orderHash Order's hash. Can be obtained by the `hashOrder` function
                   * @return rawAmount Unfilled amount of order plus one if order exists. Otherwise 0
                   */
                  function remainingRaw(bytes32 orderHash) external view returns(uint256 rawAmount);
              
                  /**
                   * @notice Same as `remainingRaw` but for multiple orders
                   * @param orderHashes Array of hashes
                   * @return rawAmounts Array of amounts for each order plus one if order exists or 0 otherwise
                   */
                  function remainingsRaw(bytes32[] memory orderHashes) external view returns(uint256[] memory rawAmounts);
              
                  /**
                   * @notice Checks order predicate
                   * @param order Order to check predicate for
                   * @return result Predicate evaluation result. True if predicate allows to fill the order, false otherwise
                   */
                  function checkPredicate(OrderLib.Order calldata order) external view returns(bool result);
              
                  /**
                   * @notice Returns order hash according to EIP712 standard
                   * @param order Order to get hash for
                   * @return orderHash Hash of the order
                   */
                  function hashOrder(OrderLib.Order calldata order) external view returns(bytes32);
              
                  /**
                   * @notice Delegates execution to custom implementation. Could be used to validate if `transferFrom` works properly
                   * @dev The function always reverts and returns the simulation results in revert data.
                   * @param target Addresses that will be delegated
                   * @param data Data that will be passed to delegatee
                   */
                  function simulate(address target, bytes calldata data) external;
              
                  /**
                   * @notice Cancels order.
                   * @dev Order is cancelled by setting remaining amount to _ORDER_FILLED value
                   * @param order Order quote to cancel
                   * @return orderRemaining Unfilled amount of order before cancellation
                   * @return orderHash Hash of the filled order
                   */
                  function cancelOrder(OrderLib.Order calldata order) external returns(uint256 orderRemaining, bytes32 orderHash);
              
                  /**
                   * @notice Fills an order. If one doesn't exist (first fill) it will be created using order.makerAssetData
                   * @param order Order quote to fill
                   * @param signature Signature to confirm quote ownership
                   * @param interaction A call data for InteractiveNotificationReceiver. Taker may execute interaction after getting maker assets and before sending taker assets.
                   * @param makingAmount Making amount
                   * @param takingAmount Taking amount
                   * @param skipPermitAndThresholdAmount Specifies maximum allowed takingAmount when takingAmount is zero, otherwise specifies minimum allowed makingAmount. Top-most bit specifies whether taker wants to skip maker's permit.
                   * @return actualMakingAmount Actual amount transferred from maker to taker
                   * @return actualTakingAmount Actual amount transferred from taker to maker
                   * @return orderHash Hash of the filled order
                   */
                  function fillOrder(
                      OrderLib.Order calldata order,
                      bytes calldata signature,
                      bytes calldata interaction,
                      uint256 makingAmount,
                      uint256 takingAmount,
                      uint256 skipPermitAndThresholdAmount
                  ) external payable returns(uint256 actualMakingAmount, uint256 actualTakingAmount, bytes32 orderHash);
              
                  /**
                   * @notice Same as `fillOrderTo` but calls permit first,
                   * allowing to approve token spending and make a swap in one transaction.
                   * Also allows to specify funds destination instead of `msg.sender`
                   * @dev See tests for examples
                   * @param order Order quote to fill
                   * @param signature Signature to confirm quote ownership
                   * @param interaction A call data for InteractiveNotificationReceiver. Taker may execute interaction after getting maker assets and before sending taker assets.
                   * @param makingAmount Making amount
                   * @param takingAmount Taking amount
                   * @param skipPermitAndThresholdAmount Specifies maximum allowed takingAmount when takingAmount is zero, otherwise specifies minimum allowed makingAmount. Top-most bit specifies whether taker wants to skip maker's permit.
                   * @param target Address that will receive swap funds
                   * @param permit Should consist of abiencoded token address and encoded `IERC20Permit.permit` call.
                   * @return actualMakingAmount Actual amount transferred from maker to taker
                   * @return actualTakingAmount Actual amount transferred from taker to maker
                   * @return orderHash Hash of the filled order
                   */
                  function fillOrderToWithPermit(
                      OrderLib.Order calldata order,
                      bytes calldata signature,
                      bytes calldata interaction,
                      uint256 makingAmount,
                      uint256 takingAmount,
                      uint256 skipPermitAndThresholdAmount,
                      address target,
                      bytes calldata permit
                  ) external returns(uint256 actualMakingAmount, uint256 actualTakingAmount, bytes32 orderHash);
              
                  /**
                   * @notice Same as `fillOrder` but allows to specify funds destination instead of `msg.sender`
                   * @param order_ Order quote to fill
                   * @param signature Signature to confirm quote ownership
                   * @param interaction A call data for InteractiveNotificationReceiver. Taker may execute interaction after getting maker assets and before sending taker assets.
                   * @param makingAmount Making amount
                   * @param takingAmount Taking amount
                   * @param skipPermitAndThresholdAmount Specifies maximum allowed takingAmount when takingAmount is zero, otherwise specifies minimum allowed makingAmount. Top-most bit specifies whether taker wants to skip maker's permit.
                   * @param target Address that will receive swap funds
                   * @return actualMakingAmount Actual amount transferred from maker to taker
                   * @return actualTakingAmount Actual amount transferred from taker to maker
                   * @return orderHash Hash of the filled order
                   */
                  function fillOrderTo(
                      OrderLib.Order calldata order_,
                      bytes calldata signature,
                      bytes calldata interaction,
                      uint256 makingAmount,
                      uint256 takingAmount,
                      uint256 skipPermitAndThresholdAmount,
                      address target
                  ) external payable returns(uint256 actualMakingAmount, uint256 actualTakingAmount, bytes32 orderHash);
              }
              
              
              // File @1inch/limit-order-protocol/contracts/interfaces/NotificationReceiver.sol@v0.3.0-prerelease
              
              
              pragma solidity 0.8.17;
              
              /// @title Interface for interactor which acts between `maker => taker` and `taker => maker` transfers.
              interface PreInteractionNotificationReceiver {
                  function fillOrderPreInteraction(
                      bytes32 orderHash,
                      address maker,
                      address taker,
                      uint256 makingAmount,
                      uint256 takingAmount,
                      uint256 remainingAmount,
                      bytes memory interactiveData
                  ) external;
              }
              
              interface PostInteractionNotificationReceiver {
                  /// @notice Callback method that gets called after taker transferred funds to maker but before
                  /// the opposite transfer happened
                  function fillOrderPostInteraction(
                      bytes32 orderHash,
                      address maker,
                      address taker,
                      uint256 makingAmount,
                      uint256 takingAmount,
                      uint256 remainingAmount,
                      bytes memory interactiveData
                  ) external;
              }
              
              interface InteractionNotificationReceiver {
                  function fillOrderInteraction(
                      address taker,
                      uint256 makingAmount,
                      uint256 takingAmount,
                      bytes memory interactiveData
                  ) external returns(uint256 offeredTakingAmount);
              }
              
              
              // File @1inch/limit-order-protocol/contracts/OrderMixin.sol@v0.3.0-prerelease
              
              
              pragma solidity 0.8.17;
              
              
              
              
              
              
              
              
              
              
              
              /// @title Regular Limit Order mixin
              abstract contract OrderMixin is IOrderMixin, EIP712, PredicateHelper {
                  using SafeERC20 for IERC20;
                  using ArgumentsDecoder for bytes;
                  using OrderLib for OrderLib.Order;
              
                  error UnknownOrder();
                  error AccessDenied();
                  error AlreadyFilled();
                  error PermitLengthTooLow();
                  error ZeroTargetIsForbidden();
                  error RemainingAmountIsZero();
                  error PrivateOrder();
                  error BadSignature();
                  error ReentrancyDetected();
                  error PredicateIsNotTrue();
                  error OnlyOneAmountShouldBeZero();
                  error TakingAmountTooHigh();
                  error MakingAmountTooLow();
                  error SwapWithZeroAmount();
                  error TransferFromMakerToTakerFailed();
                  error TransferFromTakerToMakerFailed();
                  error WrongAmount();
                  error WrongGetter();
                  error GetAmountCallFailed();
                  error TakingAmountIncreased();
                  error SimulationResults(bool success, bytes res);
              
                  /// @notice Emitted every time order gets filled, including partial fills
                  event OrderFilled(
                      address indexed maker,
                      bytes32 orderHash,
                      uint256 remaining
                  );
              
                  /// @notice Emitted when order gets cancelled
                  event OrderCanceled(
                      address indexed maker,
                      bytes32 orderHash,
                      uint256 remainingRaw
                  );
              
                  uint256 constant private _ORDER_DOES_NOT_EXIST = 0;
                  uint256 constant private _ORDER_FILLED = 1;
                  uint256 constant private _SKIP_PERMIT_FLAG = 1 << 255;
                  uint256 constant private _THRESHOLD_MASK = ~_SKIP_PERMIT_FLAG;
              
                  IWETH private immutable _WETH;  // solhint-disable-line var-name-mixedcase
                  /// @notice Stores unfilled amounts for each order plus one.
                  /// Therefore 0 means order doesn't exist and 1 means order was filled
                  mapping(bytes32 => uint256) private _remaining;
              
                  constructor(IWETH weth) {
                      _WETH = weth;
                  }
              
                  /**
                   * @notice See {IOrderMixin-remaining}.
                   */
                  function remaining(bytes32 orderHash) external view returns(uint256 /* amount */) {
                      uint256 amount = _remaining[orderHash];
                      if (amount == _ORDER_DOES_NOT_EXIST) revert UnknownOrder();
                      unchecked { return amount - 1; }
                  }
              
                  /**
                   * @notice See {IOrderMixin-remainingRaw}.
                   */
                  function remainingRaw(bytes32 orderHash) external view returns(uint256 /* rawAmount */) {
                      return _remaining[orderHash];
                  }
              
                  /**
                   * @notice See {IOrderMixin-remainingsRaw}.
                   */
                  function remainingsRaw(bytes32[] memory orderHashes) external view returns(uint256[] memory /* rawAmounts */) {
                      uint256[] memory results = new uint256[](orderHashes.length);
                      for (uint256 i = 0; i < orderHashes.length; i++) {
                          results[i] = _remaining[orderHashes[i]];
                      }
                      return results;
                  }
              
                  /**
                   * @notice See {IOrderMixin-simulate}.
                   */
                  function simulate(address target, bytes calldata data) external {
                      // solhint-disable-next-line avoid-low-level-calls
                      (bool success, bytes memory result) = target.delegatecall(data);
                      revert SimulationResults(success, result);
                  }
              
                  /**
                   * @notice See {IOrderMixin-cancelOrder}.
                   */
                  function cancelOrder(OrderLib.Order calldata order) external returns(uint256 orderRemaining, bytes32 orderHash) {
                      if (order.maker != msg.sender) revert AccessDenied();
              
                      orderHash = hashOrder(order);
                      orderRemaining = _remaining[orderHash];
                      if (orderRemaining == _ORDER_FILLED) revert AlreadyFilled();
                      emit OrderCanceled(msg.sender, orderHash, orderRemaining);
                      _remaining[orderHash] = _ORDER_FILLED;
                  }
              
                  /**
                   * @notice See {IOrderMixin-fillOrder}.
                   */
                  function fillOrder(
                      OrderLib.Order calldata order,
                      bytes calldata signature,
                      bytes calldata interaction,
                      uint256 makingAmount,
                      uint256 takingAmount,
                      uint256 skipPermitAndThresholdAmount
                  ) external payable returns(uint256 /* actualMakingAmount */, uint256 /* actualTakingAmount */, bytes32 /* orderHash */) {
                      return fillOrderTo(order, signature, interaction, makingAmount, takingAmount, skipPermitAndThresholdAmount, msg.sender);
                  }
              
                  /**
                   * @notice See {IOrderMixin-fillOrderToWithPermit}.
                   */
                  function fillOrderToWithPermit(
                      OrderLib.Order calldata order,
                      bytes calldata signature,
                      bytes calldata interaction,
                      uint256 makingAmount,
                      uint256 takingAmount,
                      uint256 skipPermitAndThresholdAmount,
                      address target,
                      bytes calldata permit
                  ) external returns(uint256 /* actualMakingAmount */, uint256 /* actualTakingAmount */, bytes32 /* orderHash */) {
                      if (permit.length < 20) revert PermitLengthTooLow();
                      {  // Stack too deep
                          (address token, bytes calldata permitData) = permit.decodeTargetAndCalldata();
                          IERC20(token).safePermit(permitData);
                      }
                      return fillOrderTo(order, signature, interaction, makingAmount, takingAmount, skipPermitAndThresholdAmount, target);
                  }
              
                  /**
                   * @notice See {IOrderMixin-fillOrderTo}.
                   */
                  function fillOrderTo(
                      OrderLib.Order calldata order_,
                      bytes calldata signature,
                      bytes calldata interaction,
                      uint256 makingAmount,
                      uint256 takingAmount,
                      uint256 skipPermitAndThresholdAmount,
                      address target
                  ) public payable returns(uint256 actualMakingAmount, uint256 actualTakingAmount, bytes32 orderHash) {
                      if (target == address(0)) revert ZeroTargetIsForbidden();
                      orderHash = hashOrder(order_);
              
                      OrderLib.Order calldata order = order_; // Helps with "Stack too deep"
                      actualMakingAmount = makingAmount;
                      actualTakingAmount = takingAmount;
              
                      uint256 remainingMakingAmount = _remaining[orderHash];
                      if (remainingMakingAmount == _ORDER_FILLED) revert RemainingAmountIsZero();
                      if (order.allowedSender != address(0) && order.allowedSender != msg.sender) revert PrivateOrder();
                      if (remainingMakingAmount == _ORDER_DOES_NOT_EXIST) {
                          // First fill: validate order and permit maker asset
                          if (!ECDSA.recoverOrIsValidSignature(order.maker, orderHash, signature)) revert BadSignature();
                          remainingMakingAmount = order.makingAmount;
              
                          bytes calldata permit = order.permit();
                          if (skipPermitAndThresholdAmount & _SKIP_PERMIT_FLAG == 0 && permit.length >= 20) {
                              // proceed only if taker is willing to execute permit and its length is enough to store address
                              (address token, bytes calldata permitCalldata) = permit.decodeTargetAndCalldata();
                              IERC20(token).safePermit(permitCalldata);
                              if (_remaining[orderHash] != _ORDER_DOES_NOT_EXIST) revert ReentrancyDetected();
                          }
                      } else {
                          unchecked { remainingMakingAmount -= 1; }
                      }
              
                      // Check if order is valid
                      if (order.predicate().length > 0) {
                          if (!checkPredicate(order)) revert PredicateIsNotTrue();
                      }
              
                      // Compute maker and taker assets amount
                      if ((actualTakingAmount == 0) == (actualMakingAmount == 0)) {
                          revert OnlyOneAmountShouldBeZero();
                      } else if (actualTakingAmount == 0) {
                          if (actualMakingAmount > remainingMakingAmount) {
                              actualMakingAmount = remainingMakingAmount;
                          }
                          actualTakingAmount = _getTakingAmount(order.getTakingAmount(), order.makingAmount, actualMakingAmount, order.takingAmount, remainingMakingAmount, orderHash);
                          uint256 thresholdAmount = skipPermitAndThresholdAmount & _THRESHOLD_MASK;
                          // check that actual rate is not worse than what was expected
                          // actualTakingAmount / actualMakingAmount <= thresholdAmount / makingAmount
                          if (actualTakingAmount * makingAmount > thresholdAmount * actualMakingAmount) revert TakingAmountTooHigh();
                      } else {
                          actualMakingAmount = _getMakingAmount(order.getMakingAmount(), order.takingAmount, actualTakingAmount, order.makingAmount, remainingMakingAmount, orderHash);
                          if (actualMakingAmount > remainingMakingAmount) {
                              actualMakingAmount = remainingMakingAmount;
                              actualTakingAmount = _getTakingAmount(order.getTakingAmount(), order.makingAmount, actualMakingAmount, order.takingAmount, remainingMakingAmount, orderHash);
                              if (actualTakingAmount > takingAmount) revert TakingAmountIncreased();
                          }
                          uint256 thresholdAmount = skipPermitAndThresholdAmount & _THRESHOLD_MASK;
                          // check that actual rate is not worse than what was expected
                          // actualMakingAmount / actualTakingAmount >= thresholdAmount / takingAmount
                          if (actualMakingAmount * takingAmount < thresholdAmount * actualTakingAmount) revert MakingAmountTooLow();
                      }
              
                      if (actualMakingAmount == 0 || actualTakingAmount == 0) revert SwapWithZeroAmount();
              
                      // Update remaining amount in storage
                      unchecked {
                          remainingMakingAmount = remainingMakingAmount - actualMakingAmount;
                          _remaining[orderHash] = remainingMakingAmount + 1;
                      }
                      emit OrderFilled(order_.maker, orderHash, remainingMakingAmount);
              
                      // Maker can handle funds interactively
                      if (order.preInteraction().length >= 20) {
                          // proceed only if interaction length is enough to store address
                          (address interactionTarget, bytes calldata interactionData) = order.preInteraction().decodeTargetAndCalldata();
                          PreInteractionNotificationReceiver(interactionTarget).fillOrderPreInteraction(
                              orderHash, order.maker, msg.sender, actualMakingAmount, actualTakingAmount, remainingMakingAmount, interactionData
                          );
                      }
              
                      // Maker => Taker
                      if (!_callTransferFrom(
                          order.makerAsset,
                          order.maker,
                          target,
                          actualMakingAmount,
                          order.makerAssetData()
                      )) revert TransferFromMakerToTakerFailed();
              
                      if (interaction.length >= 20) {
                          // proceed only if interaction length is enough to store address
                          (address interactionTarget, bytes calldata interactionData) = interaction.decodeTargetAndCalldata();
                          uint256 offeredTakingAmount = InteractionNotificationReceiver(interactionTarget).fillOrderInteraction(
                              msg.sender, actualMakingAmount, actualTakingAmount, interactionData
                          );
              
                          if (offeredTakingAmount > actualTakingAmount &&
                              !OrderLib.getterIsFrozen(order.getMakingAmount()) &&
                              !OrderLib.getterIsFrozen(order.getTakingAmount()))
                          {
                              actualTakingAmount = offeredTakingAmount;
                          }
                      }
              
                      // Taker => Maker
                      if (order.takerAsset == address(_WETH) && msg.value > 0) {
                          if (msg.value < actualTakingAmount) revert Errors.InvalidMsgValue();
                          if (msg.value > actualTakingAmount) {
                              unchecked {
                                  (bool success, ) = msg.sender.call{value: msg.value - actualTakingAmount}("");  // solhint-disable-line avoid-low-level-calls
                                  if (!success) revert Errors.ETHTransferFailed();
                              }
                          }
                          _WETH.deposit{ value: actualTakingAmount }();
                          _WETH.transfer(order.receiver == address(0) ? order.maker : order.receiver, actualTakingAmount);
                      } else {
                          if (msg.value != 0) revert Errors.InvalidMsgValue();
                          if (!_callTransferFrom(
                              order.takerAsset,
                              msg.sender,
                              order.receiver == address(0) ? order.maker : order.receiver,
                              actualTakingAmount,
                              order.takerAssetData()
                          )) revert TransferFromTakerToMakerFailed();
                      }
              
                      // Maker can handle funds interactively
                      if (order.postInteraction().length >= 20) {
                          // proceed only if interaction length is enough to store address
                          (address interactionTarget, bytes calldata interactionData) = order.postInteraction().decodeTargetAndCalldata();
                          PostInteractionNotificationReceiver(interactionTarget).fillOrderPostInteraction(
                               orderHash, order.maker, msg.sender, actualMakingAmount, actualTakingAmount, remainingMakingAmount, interactionData
                          );
                      }
                  }
              
                  /**
                   * @notice See {IOrderMixin-checkPredicate}.
                   */
                  function checkPredicate(OrderLib.Order calldata order) public view returns(bool) {
                      (bool success, uint256 res) = _selfStaticCall(order.predicate());
                      return success && res == 1;
                  }
              
                  /**
                   * @notice See {IOrderMixin-hashOrder}.
                   */
                  function hashOrder(OrderLib.Order calldata order) public view returns(bytes32) {
                      return order.hash(_domainSeparatorV4());
                  }
              
                  function _callTransferFrom(address asset, address from, address to, uint256 amount, bytes calldata input) private returns(bool success) {
                      bytes4 selector = IERC20.transferFrom.selector;
                      /// @solidity memory-safe-assembly
                      assembly { // solhint-disable-line no-inline-assembly
                          let data := mload(0x40)
              
                          mstore(data, selector)
                          mstore(add(data, 0x04), from)
                          mstore(add(data, 0x24), to)
                          mstore(add(data, 0x44), amount)
                          calldatacopy(add(data, 0x64), input.offset, input.length)
                          let status := call(gas(), asset, 0, data, add(0x64, input.length), 0x0, 0x20)
                          success := and(status, or(iszero(returndatasize()), and(gt(returndatasize(), 31), eq(mload(0), 1))))
                      }
                  }
              
                  function _getMakingAmount(
                      bytes calldata getter,
                      uint256 orderTakingAmount,
                      uint256 requestedTakingAmount,
                      uint256 orderMakingAmount,
                      uint256 remainingMakingAmount,
                      bytes32 orderHash
                  ) private view returns(uint256) {
                      if (getter.length == 0) {
                          // Linear proportion
                          return AmountCalculator.getMakingAmount(orderMakingAmount, orderTakingAmount, requestedTakingAmount);
                      }
                      return _callGetter(getter, orderTakingAmount, requestedTakingAmount, orderMakingAmount, remainingMakingAmount, orderHash);
                  }
              
                  function _getTakingAmount(
                      bytes calldata getter,
                      uint256 orderMakingAmount,
                      uint256 requestedMakingAmount,
                      uint256 orderTakingAmount,
                      uint256 remainingMakingAmount,
                      bytes32 orderHash
                  ) private view returns(uint256) {
                      if (getter.length == 0) {
                          // Linear proportion
                          return AmountCalculator.getTakingAmount(orderMakingAmount, orderTakingAmount, requestedMakingAmount);
                      }
                      return _callGetter(getter, orderMakingAmount, requestedMakingAmount, orderTakingAmount, remainingMakingAmount, orderHash);
                  }
              
                  function _callGetter(
                      bytes calldata getter,
                      uint256 orderExpectedAmount,
                      uint256 requestedAmount,
                      uint256 orderResultAmount,
                      uint256 remainingMakingAmount,
                      bytes32 orderHash
                  ) private view returns(uint256) {
                      if (getter.length == 1) {
                          if (OrderLib.getterIsFrozen(getter)) {
                              // On "x" getter calldata only exact amount is allowed
                              if (requestedAmount != orderExpectedAmount) revert WrongAmount();
                              return orderResultAmount;
                          } else {
                              revert WrongGetter();
                          }
                      } else {
                          (address target, bytes calldata data) = getter.decodeTargetAndCalldata();
                          (bool success, bytes memory result) = target.staticcall(abi.encodePacked(data, requestedAmount, remainingMakingAmount, orderHash));
                          if (!success || result.length != 32) revert GetAmountCallFailed();
                          return abi.decode(result, (uint256));
                      }
                  }
              }
              
              
              // File @openzeppelin/contracts/utils/Context.sol@v4.7.3
              
              // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
              
              pragma solidity ^0.8.0;
              
              /**
               * @dev Provides information about the current execution context, including the
               * sender of the transaction and its data. While these are generally available
               * via msg.sender and msg.data, they should not be accessed in such a direct
               * manner, since when dealing with meta-transactions the account sending and
               * paying for execution may not be the actual sender (as far as an application
               * is concerned).
               *
               * This contract is only required for intermediate, library-like contracts.
               */
              abstract contract Context {
                  function _msgSender() internal view virtual returns (address) {
                      return msg.sender;
                  }
              
                  function _msgData() internal view virtual returns (bytes calldata) {
                      return msg.data;
                  }
              }
              
              
              // File @openzeppelin/contracts/access/Ownable.sol@v4.7.3
              
              // OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)
              
              pragma solidity ^0.8.0;
              
              /**
               * @dev Contract module which provides a basic access control mechanism, where
               * there is an account (an owner) that can be granted exclusive access to
               * specific functions.
               *
               * By default, the owner account will be the one that deploys the contract. This
               * can later be changed with {transferOwnership}.
               *
               * This module is used through inheritance. It will make available the modifier
               * `onlyOwner`, which can be applied to your functions to restrict their use to
               * the owner.
               */
              abstract contract Ownable is Context {
                  address private _owner;
              
                  event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
              
                  /**
                   * @dev Initializes the contract setting the deployer as the initial owner.
                   */
                  constructor() {
                      _transferOwnership(_msgSender());
                  }
              
                  /**
                   * @dev Throws if called by any account other than the owner.
                   */
                  modifier onlyOwner() {
                      _checkOwner();
                      _;
                  }
              
                  /**
                   * @dev Returns the address of the current owner.
                   */
                  function owner() public view virtual returns (address) {
                      return _owner;
                  }
              
                  /**
                   * @dev Throws if the sender is not the owner.
                   */
                  function _checkOwner() internal view virtual {
                      require(owner() == _msgSender(), "Ownable: caller is not the owner");
                  }
              
                  /**
                   * @dev Leaves the contract without owner. It will not be possible to call
                   * `onlyOwner` functions anymore. Can only be called by the current owner.
                   *
                   * NOTE: Renouncing ownership will leave the contract without an owner,
                   * thereby removing any functionality that is only available to the owner.
                   */
                  function renounceOwnership() public virtual onlyOwner {
                      _transferOwnership(address(0));
                  }
              
                  /**
                   * @dev Transfers ownership of the contract to a new account (`newOwner`).
                   * Can only be called by the current owner.
                   */
                  function transferOwnership(address newOwner) public virtual onlyOwner {
                      require(newOwner != address(0), "Ownable: new owner is the zero address");
                      _transferOwnership(newOwner);
                  }
              
                  /**
                   * @dev Transfers ownership of the contract to a new account (`newOwner`).
                   * Internal function without access restriction.
                   */
                  function _transferOwnership(address newOwner) internal virtual {
                      address oldOwner = _owner;
                      _owner = newOwner;
                      emit OwnershipTransferred(oldOwner, newOwner);
                  }
              }
              
              
              // File contracts/AggregationRouterV5.sol
              
              
              pragma solidity 0.8.17;
              
              
              
              
              
              
              
              
              
              /// @notice Main contract incorporates a number of routers to perform swaps and limit orders protocol to fill limit orders
              contract AggregationRouterV5 is EIP712("1inch Aggregation Router", "5"), Ownable,
                  ClipperRouter, GenericRouter, UnoswapRouter, UnoswapV3Router, OrderMixin, OrderRFQMixin
              {
                  using UniERC20 for IERC20;
              
                  error ZeroAddress();
              
                  /**
                   * @dev Sets the wrapped eth token and clipper exhange interface
                   * Both values are immutable: they can only be set once during
                   * construction.
                   */
                  constructor(IWETH weth)
                      UnoswapV3Router(weth)
                      ClipperRouter(weth)
                      OrderMixin(weth)
                      OrderRFQMixin(weth)
                  {
                      if (address(weth) == address(0)) revert ZeroAddress();
                  }
              
                  /**
                   * @notice Retrieves funds accidently sent directly to the contract address
                   * @param token ERC20 token to retrieve
                   * @param amount amount to retrieve
                   */
                  function rescueFunds(IERC20 token, uint256 amount) external onlyOwner {
                      token.uniTransfer(payable(msg.sender), amount);
                  }
              
                  /**
                   * @notice Destroys the contract and sends eth to sender. Use with caution.
                   * The only case when the use of the method is justified is if there is an exploit found.
                   * And the damage from the exploit is greater than from just an urgent contract change.
                   */
                  function destroy() external onlyOwner {
                      selfdestruct(payable(msg.sender));
                  }
              
                  function _receive() internal override(EthReceiver, OnlyWethReceiver) {
                      EthReceiver._receive();
                  }
              }

              File 2 of 4: WETH9
              // Copyright (C) 2015, 2016, 2017 Dapphub
              
              // This program is free software: you can redistribute it and/or modify
              // it under the terms of the GNU General Public License as published by
              // the Free Software Foundation, either version 3 of the License, or
              // (at your option) any later version.
              
              // This program is distributed in the hope that it will be useful,
              // but WITHOUT ANY WARRANTY; without even the implied warranty of
              // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
              // GNU General Public License for more details.
              
              // You should have received a copy of the GNU General Public License
              // along with this program.  If not, see <http://www.gnu.org/licenses/>.
              
              pragma solidity ^0.4.18;
              
              contract WETH9 {
                  string public name     = "Wrapped Ether";
                  string public symbol   = "WETH";
                  uint8  public decimals = 18;
              
                  event  Approval(address indexed src, address indexed guy, uint wad);
                  event  Transfer(address indexed src, address indexed dst, uint wad);
                  event  Deposit(address indexed dst, uint wad);
                  event  Withdrawal(address indexed src, uint wad);
              
                  mapping (address => uint)                       public  balanceOf;
                  mapping (address => mapping (address => uint))  public  allowance;
              
                  function() public payable {
                      deposit();
                  }
                  function deposit() public payable {
                      balanceOf[msg.sender] += msg.value;
                      Deposit(msg.sender, msg.value);
                  }
                  function withdraw(uint wad) public {
                      require(balanceOf[msg.sender] >= wad);
                      balanceOf[msg.sender] -= wad;
                      msg.sender.transfer(wad);
                      Withdrawal(msg.sender, wad);
                  }
              
                  function totalSupply() public view returns (uint) {
                      return this.balance;
                  }
              
                  function approve(address guy, uint wad) public returns (bool) {
                      allowance[msg.sender][guy] = wad;
                      Approval(msg.sender, guy, wad);
                      return true;
                  }
              
                  function transfer(address dst, uint wad) public returns (bool) {
                      return transferFrom(msg.sender, dst, wad);
                  }
              
                  function transferFrom(address src, address dst, uint wad)
                      public
                      returns (bool)
                  {
                      require(balanceOf[src] >= wad);
              
                      if (src != msg.sender && allowance[src][msg.sender] != uint(-1)) {
                          require(allowance[src][msg.sender] >= wad);
                          allowance[src][msg.sender] -= wad;
                      }
              
                      balanceOf[src] -= wad;
                      balanceOf[dst] += wad;
              
                      Transfer(src, dst, wad);
              
                      return true;
                  }
              }
              
              
              /*
                                  GNU GENERAL PUBLIC LICENSE
                                     Version 3, 29 June 2007
              
               Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
               Everyone is permitted to copy and distribute verbatim copies
               of this license document, but changing it is not allowed.
              
                                          Preamble
              
                The GNU General Public License is a free, copyleft license for
              software and other kinds of works.
              
                The licenses for most software and other practical works are designed
              to take away your freedom to share and change the works.  By contrast,
              the GNU General Public License is intended to guarantee your freedom to
              share and change all versions of a program--to make sure it remains free
              software for all its users.  We, the Free Software Foundation, use the
              GNU General Public License for most of our software; it applies also to
              any other work released this way by its authors.  You can apply it to
              your programs, too.
              
                When we speak of free software, we are referring to freedom, not
              price.  Our General Public Licenses are designed to make sure that you
              have the freedom to distribute copies of free software (and charge for
              them if you wish), that you receive source code or can get it if you
              want it, that you can change the software or use pieces of it in new
              free programs, and that you know you can do these things.
              
                To protect your rights, we need to prevent others from denying you
              these rights or asking you to surrender the rights.  Therefore, you have
              certain responsibilities if you distribute copies of the software, or if
              you modify it: responsibilities to respect the freedom of others.
              
                For example, if you distribute copies of such a program, whether
              gratis or for a fee, you must pass on to the recipients the same
              freedoms that you received.  You must make sure that they, too, receive
              or can get the source code.  And you must show them these terms so they
              know their rights.
              
                Developers that use the GNU GPL protect your rights with two steps:
              (1) assert copyright on the software, and (2) offer you this License
              giving you legal permission to copy, distribute and/or modify it.
              
                For the developers' and authors' protection, the GPL clearly explains
              that there is no warranty for this free software.  For both users' and
              authors' sake, the GPL requires that modified versions be marked as
              changed, so that their problems will not be attributed erroneously to
              authors of previous versions.
              
                Some devices are designed to deny users access to install or run
              modified versions of the software inside them, although the manufacturer
              can do so.  This is fundamentally incompatible with the aim of
              protecting users' freedom to change the software.  The systematic
              pattern of such abuse occurs in the area of products for individuals to
              use, which is precisely where it is most unacceptable.  Therefore, we
              have designed this version of the GPL to prohibit the practice for those
              products.  If such problems arise substantially in other domains, we
              stand ready to extend this provision to those domains in future versions
              of the GPL, as needed to protect the freedom of users.
              
                Finally, every program is threatened constantly by software patents.
              States should not allow patents to restrict development and use of
              software on general-purpose computers, but in those that do, we wish to
              avoid the special danger that patents applied to a free program could
              make it effectively proprietary.  To prevent this, the GPL assures that
              patents cannot be used to render the program non-free.
              
                The precise terms and conditions for copying, distribution and
              modification follow.
              
                                     TERMS AND CONDITIONS
              
                0. Definitions.
              
                "This License" refers to version 3 of the GNU General Public License.
              
                "Copyright" also means copyright-like laws that apply to other kinds of
              works, such as semiconductor masks.
              
                "The Program" refers to any copyrightable work licensed under this
              License.  Each licensee is addressed as "you".  "Licensees" and
              "recipients" may be individuals or organizations.
              
                To "modify" a work means to copy from or adapt all or part of the work
              in a fashion requiring copyright permission, other than the making of an
              exact copy.  The resulting work is called a "modified version" of the
              earlier work or a work "based on" the earlier work.
              
                A "covered work" means either the unmodified Program or a work based
              on the Program.
              
                To "propagate" a work means to do anything with it that, without
              permission, would make you directly or secondarily liable for
              infringement under applicable copyright law, except executing it on a
              computer or modifying a private copy.  Propagation includes copying,
              distribution (with or without modification), making available to the
              public, and in some countries other activities as well.
              
                To "convey" a work means any kind of propagation that enables other
              parties to make or receive copies.  Mere interaction with a user through
              a computer network, with no transfer of a copy, is not conveying.
              
                An interactive user interface displays "Appropriate Legal Notices"
              to the extent that it includes a convenient and prominently visible
              feature that (1) displays an appropriate copyright notice, and (2)
              tells the user that there is no warranty for the work (except to the
              extent that warranties are provided), that licensees may convey the
              work under this License, and how to view a copy of this License.  If
              the interface presents a list of user commands or options, such as a
              menu, a prominent item in the list meets this criterion.
              
                1. Source Code.
              
                The "source code" for a work means the preferred form of the work
              for making modifications to it.  "Object code" means any non-source
              form of a work.
              
                A "Standard Interface" means an interface that either is an official
              standard defined by a recognized standards body, or, in the case of
              interfaces specified for a particular programming language, one that
              is widely used among developers working in that language.
              
                The "System Libraries" of an executable work include anything, other
              than the work as a whole, that (a) is included in the normal form of
              packaging a Major Component, but which is not part of that Major
              Component, and (b) serves only to enable use of the work with that
              Major Component, or to implement a Standard Interface for which an
              implementation is available to the public in source code form.  A
              "Major Component", in this context, means a major essential component
              (kernel, window system, and so on) of the specific operating system
              (if any) on which the executable work runs, or a compiler used to
              produce the work, or an object code interpreter used to run it.
              
                The "Corresponding Source" for a work in object code form means all
              the source code needed to generate, install, and (for an executable
              work) run the object code and to modify the work, including scripts to
              control those activities.  However, it does not include the work's
              System Libraries, or general-purpose tools or generally available free
              programs which are used unmodified in performing those activities but
              which are not part of the work.  For example, Corresponding Source
              includes interface definition files associated with source files for
              the work, and the source code for shared libraries and dynamically
              linked subprograms that the work is specifically designed to require,
              such as by intimate data communication or control flow between those
              subprograms and other parts of the work.
              
                The Corresponding Source need not include anything that users
              can regenerate automatically from other parts of the Corresponding
              Source.
              
                The Corresponding Source for a work in source code form is that
              same work.
              
                2. Basic Permissions.
              
                All rights granted under this License are granted for the term of
              copyright on the Program, and are irrevocable provided the stated
              conditions are met.  This License explicitly affirms your unlimited
              permission to run the unmodified Program.  The output from running a
              covered work is covered by this License only if the output, given its
              content, constitutes a covered work.  This License acknowledges your
              rights of fair use or other equivalent, as provided by copyright law.
              
                You may make, run and propagate covered works that you do not
              convey, without conditions so long as your license otherwise remains
              in force.  You may convey covered works to others for the sole purpose
              of having them make modifications exclusively for you, or provide you
              with facilities for running those works, provided that you comply with
              the terms of this License in conveying all material for which you do
              not control copyright.  Those thus making or running the covered works
              for you must do so exclusively on your behalf, under your direction
              and control, on terms that prohibit them from making any copies of
              your copyrighted material outside their relationship with you.
              
                Conveying under any other circumstances is permitted solely under
              the conditions stated below.  Sublicensing is not allowed; section 10
              makes it unnecessary.
              
                3. Protecting Users' Legal Rights From Anti-Circumvention Law.
              
                No covered work shall be deemed part of an effective technological
              measure under any applicable law fulfilling obligations under article
              11 of the WIPO copyright treaty adopted on 20 December 1996, or
              similar laws prohibiting or restricting circumvention of such
              measures.
              
                When you convey a covered work, you waive any legal power to forbid
              circumvention of technological measures to the extent such circumvention
              is effected by exercising rights under this License with respect to
              the covered work, and you disclaim any intention to limit operation or
              modification of the work as a means of enforcing, against the work's
              users, your or third parties' legal rights to forbid circumvention of
              technological measures.
              
                4. Conveying Verbatim Copies.
              
                You may convey verbatim copies of the Program's source code as you
              receive it, in any medium, provided that you conspicuously and
              appropriately publish on each copy an appropriate copyright notice;
              keep intact all notices stating that this License and any
              non-permissive terms added in accord with section 7 apply to the code;
              keep intact all notices of the absence of any warranty; and give all
              recipients a copy of this License along with the Program.
              
                You may charge any price or no price for each copy that you convey,
              and you may offer support or warranty protection for a fee.
              
                5. Conveying Modified Source Versions.
              
                You may convey a work based on the Program, or the modifications to
              produce it from the Program, in the form of source code under the
              terms of section 4, provided that you also meet all of these conditions:
              
                  a) The work must carry prominent notices stating that you modified
                  it, and giving a relevant date.
              
                  b) The work must carry prominent notices stating that it is
                  released under this License and any conditions added under section
                  7.  This requirement modifies the requirement in section 4 to
                  "keep intact all notices".
              
                  c) You must license the entire work, as a whole, under this
                  License to anyone who comes into possession of a copy.  This
                  License will therefore apply, along with any applicable section 7
                  additional terms, to the whole of the work, and all its parts,
                  regardless of how they are packaged.  This License gives no
                  permission to license the work in any other way, but it does not
                  invalidate such permission if you have separately received it.
              
                  d) If the work has interactive user interfaces, each must display
                  Appropriate Legal Notices; however, if the Program has interactive
                  interfaces that do not display Appropriate Legal Notices, your
                  work need not make them do so.
              
                A compilation of a covered work with other separate and independent
              works, which are not by their nature extensions of the covered work,
              and which are not combined with it such as to form a larger program,
              in or on a volume of a storage or distribution medium, is called an
              "aggregate" if the compilation and its resulting copyright are not
              used to limit the access or legal rights of the compilation's users
              beyond what the individual works permit.  Inclusion of a covered work
              in an aggregate does not cause this License to apply to the other
              parts of the aggregate.
              
                6. Conveying Non-Source Forms.
              
                You may convey a covered work in object code form under the terms
              of sections 4 and 5, provided that you also convey the
              machine-readable Corresponding Source under the terms of this License,
              in one of these ways:
              
                  a) Convey the object code in, or embodied in, a physical product
                  (including a physical distribution medium), accompanied by the
                  Corresponding Source fixed on a durable physical medium
                  customarily used for software interchange.
              
                  b) Convey the object code in, or embodied in, a physical product
                  (including a physical distribution medium), accompanied by a
                  written offer, valid for at least three years and valid for as
                  long as you offer spare parts or customer support for that product
                  model, to give anyone who possesses the object code either (1) a
                  copy of the Corresponding Source for all the software in the
                  product that is covered by this License, on a durable physical
                  medium customarily used for software interchange, for a price no
                  more than your reasonable cost of physically performing this
                  conveying of source, or (2) access to copy the
                  Corresponding Source from a network server at no charge.
              
                  c) Convey individual copies of the object code with a copy of the
                  written offer to provide the Corresponding Source.  This
                  alternative is allowed only occasionally and noncommercially, and
                  only if you received the object code with such an offer, in accord
                  with subsection 6b.
              
                  d) Convey the object code by offering access from a designated
                  place (gratis or for a charge), and offer equivalent access to the
                  Corresponding Source in the same way through the same place at no
                  further charge.  You need not require recipients to copy the
                  Corresponding Source along with the object code.  If the place to
                  copy the object code is a network server, the Corresponding Source
                  may be on a different server (operated by you or a third party)
                  that supports equivalent copying facilities, provided you maintain
                  clear directions next to the object code saying where to find the
                  Corresponding Source.  Regardless of what server hosts the
                  Corresponding Source, you remain obligated to ensure that it is
                  available for as long as needed to satisfy these requirements.
              
                  e) Convey the object code using peer-to-peer transmission, provided
                  you inform other peers where the object code and Corresponding
                  Source of the work are being offered to the general public at no
                  charge under subsection 6d.
              
                A separable portion of the object code, whose source code is excluded
              from the Corresponding Source as a System Library, need not be
              included in conveying the object code work.
              
                A "User Product" is either (1) a "consumer product", which means any
              tangible personal property which is normally used for personal, family,
              or household purposes, or (2) anything designed or sold for incorporation
              into a dwelling.  In determining whether a product is a consumer product,
              doubtful cases shall be resolved in favor of coverage.  For a particular
              product received by a particular user, "normally used" refers to a
              typical or common use of that class of product, regardless of the status
              of the particular user or of the way in which the particular user
              actually uses, or expects or is expected to use, the product.  A product
              is a consumer product regardless of whether the product has substantial
              commercial, industrial or non-consumer uses, unless such uses represent
              the only significant mode of use of the product.
              
                "Installation Information" for a User Product means any methods,
              procedures, authorization keys, or other information required to install
              and execute modified versions of a covered work in that User Product from
              a modified version of its Corresponding Source.  The information must
              suffice to ensure that the continued functioning of the modified object
              code is in no case prevented or interfered with solely because
              modification has been made.
              
                If you convey an object code work under this section in, or with, or
              specifically for use in, a User Product, and the conveying occurs as
              part of a transaction in which the right of possession and use of the
              User Product is transferred to the recipient in perpetuity or for a
              fixed term (regardless of how the transaction is characterized), the
              Corresponding Source conveyed under this section must be accompanied
              by the Installation Information.  But this requirement does not apply
              if neither you nor any third party retains the ability to install
              modified object code on the User Product (for example, the work has
              been installed in ROM).
              
                The requirement to provide Installation Information does not include a
              requirement to continue to provide support service, warranty, or updates
              for a work that has been modified or installed by the recipient, or for
              the User Product in which it has been modified or installed.  Access to a
              network may be denied when the modification itself materially and
              adversely affects the operation of the network or violates the rules and
              protocols for communication across the network.
              
                Corresponding Source conveyed, and Installation Information provided,
              in accord with this section must be in a format that is publicly
              documented (and with an implementation available to the public in
              source code form), and must require no special password or key for
              unpacking, reading or copying.
              
                7. Additional Terms.
              
                "Additional permissions" are terms that supplement the terms of this
              License by making exceptions from one or more of its conditions.
              Additional permissions that are applicable to the entire Program shall
              be treated as though they were included in this License, to the extent
              that they are valid under applicable law.  If additional permissions
              apply only to part of the Program, that part may be used separately
              under those permissions, but the entire Program remains governed by
              this License without regard to the additional permissions.
              
                When you convey a copy of a covered work, you may at your option
              remove any additional permissions from that copy, or from any part of
              it.  (Additional permissions may be written to require their own
              removal in certain cases when you modify the work.)  You may place
              additional permissions on material, added by you to a covered work,
              for which you have or can give appropriate copyright permission.
              
                Notwithstanding any other provision of this License, for material you
              add to a covered work, you may (if authorized by the copyright holders of
              that material) supplement the terms of this License with terms:
              
                  a) Disclaiming warranty or limiting liability differently from the
                  terms of sections 15 and 16 of this License; or
              
                  b) Requiring preservation of specified reasonable legal notices or
                  author attributions in that material or in the Appropriate Legal
                  Notices displayed by works containing it; or
              
                  c) Prohibiting misrepresentation of the origin of that material, or
                  requiring that modified versions of such material be marked in
                  reasonable ways as different from the original version; or
              
                  d) Limiting the use for publicity purposes of names of licensors or
                  authors of the material; or
              
                  e) Declining to grant rights under trademark law for use of some
                  trade names, trademarks, or service marks; or
              
                  f) Requiring indemnification of licensors and authors of that
                  material by anyone who conveys the material (or modified versions of
                  it) with contractual assumptions of liability to the recipient, for
                  any liability that these contractual assumptions directly impose on
                  those licensors and authors.
              
                All other non-permissive additional terms are considered "further
              restrictions" within the meaning of section 10.  If the Program as you
              received it, or any part of it, contains a notice stating that it is
              governed by this License along with a term that is a further
              restriction, you may remove that term.  If a license document contains
              a further restriction but permits relicensing or conveying under this
              License, you may add to a covered work material governed by the terms
              of that license document, provided that the further restriction does
              not survive such relicensing or conveying.
              
                If you add terms to a covered work in accord with this section, you
              must place, in the relevant source files, a statement of the
              additional terms that apply to those files, or a notice indicating
              where to find the applicable terms.
              
                Additional terms, permissive or non-permissive, may be stated in the
              form of a separately written license, or stated as exceptions;
              the above requirements apply either way.
              
                8. Termination.
              
                You may not propagate or modify a covered work except as expressly
              provided under this License.  Any attempt otherwise to propagate or
              modify it is void, and will automatically terminate your rights under
              this License (including any patent licenses granted under the third
              paragraph of section 11).
              
                However, if you cease all violation of this License, then your
              license from a particular copyright holder is reinstated (a)
              provisionally, unless and until the copyright holder explicitly and
              finally terminates your license, and (b) permanently, if the copyright
              holder fails to notify you of the violation by some reasonable means
              prior to 60 days after the cessation.
              
                Moreover, your license from a particular copyright holder is
              reinstated permanently if the copyright holder notifies you of the
              violation by some reasonable means, this is the first time you have
              received notice of violation of this License (for any work) from that
              copyright holder, and you cure the violation prior to 30 days after
              your receipt of the notice.
              
                Termination of your rights under this section does not terminate the
              licenses of parties who have received copies or rights from you under
              this License.  If your rights have been terminated and not permanently
              reinstated, you do not qualify to receive new licenses for the same
              material under section 10.
              
                9. Acceptance Not Required for Having Copies.
              
                You are not required to accept this License in order to receive or
              run a copy of the Program.  Ancillary propagation of a covered work
              occurring solely as a consequence of using peer-to-peer transmission
              to receive a copy likewise does not require acceptance.  However,
              nothing other than this License grants you permission to propagate or
              modify any covered work.  These actions infringe copyright if you do
              not accept this License.  Therefore, by modifying or propagating a
              covered work, you indicate your acceptance of this License to do so.
              
                10. Automatic Licensing of Downstream Recipients.
              
                Each time you convey a covered work, the recipient automatically
              receives a license from the original licensors, to run, modify and
              propagate that work, subject to this License.  You are not responsible
              for enforcing compliance by third parties with this License.
              
                An "entity transaction" is a transaction transferring control of an
              organization, or substantially all assets of one, or subdividing an
              organization, or merging organizations.  If propagation of a covered
              work results from an entity transaction, each party to that
              transaction who receives a copy of the work also receives whatever
              licenses to the work the party's predecessor in interest had or could
              give under the previous paragraph, plus a right to possession of the
              Corresponding Source of the work from the predecessor in interest, if
              the predecessor has it or can get it with reasonable efforts.
              
                You may not impose any further restrictions on the exercise of the
              rights granted or affirmed under this License.  For example, you may
              not impose a license fee, royalty, or other charge for exercise of
              rights granted under this License, and you may not initiate litigation
              (including a cross-claim or counterclaim in a lawsuit) alleging that
              any patent claim is infringed by making, using, selling, offering for
              sale, or importing the Program or any portion of it.
              
                11. Patents.
              
                A "contributor" is a copyright holder who authorizes use under this
              License of the Program or a work on which the Program is based.  The
              work thus licensed is called the contributor's "contributor version".
              
                A contributor's "essential patent claims" are all patent claims
              owned or controlled by the contributor, whether already acquired or
              hereafter acquired, that would be infringed by some manner, permitted
              by this License, of making, using, or selling its contributor version,
              but do not include claims that would be infringed only as a
              consequence of further modification of the contributor version.  For
              purposes of this definition, "control" includes the right to grant
              patent sublicenses in a manner consistent with the requirements of
              this License.
              
                Each contributor grants you a non-exclusive, worldwide, royalty-free
              patent license under the contributor's essential patent claims, to
              make, use, sell, offer for sale, import and otherwise run, modify and
              propagate the contents of its contributor version.
              
                In the following three paragraphs, a "patent license" is any express
              agreement or commitment, however denominated, not to enforce a patent
              (such as an express permission to practice a patent or covenant not to
              sue for patent infringement).  To "grant" such a patent license to a
              party means to make such an agreement or commitment not to enforce a
              patent against the party.
              
                If you convey a covered work, knowingly relying on a patent license,
              and the Corresponding Source of the work is not available for anyone
              to copy, free of charge and under the terms of this License, through a
              publicly available network server or other readily accessible means,
              then you must either (1) cause the Corresponding Source to be so
              available, or (2) arrange to deprive yourself of the benefit of the
              patent license for this particular work, or (3) arrange, in a manner
              consistent with the requirements of this License, to extend the patent
              license to downstream recipients.  "Knowingly relying" means you have
              actual knowledge that, but for the patent license, your conveying the
              covered work in a country, or your recipient's use of the covered work
              in a country, would infringe one or more identifiable patents in that
              country that you have reason to believe are valid.
              
                If, pursuant to or in connection with a single transaction or
              arrangement, you convey, or propagate by procuring conveyance of, a
              covered work, and grant a patent license to some of the parties
              receiving the covered work authorizing them to use, propagate, modify
              or convey a specific copy of the covered work, then the patent license
              you grant is automatically extended to all recipients of the covered
              work and works based on it.
              
                A patent license is "discriminatory" if it does not include within
              the scope of its coverage, prohibits the exercise of, or is
              conditioned on the non-exercise of one or more of the rights that are
              specifically granted under this License.  You may not convey a covered
              work if you are a party to an arrangement with a third party that is
              in the business of distributing software, under which you make payment
              to the third party based on the extent of your activity of conveying
              the work, and under which the third party grants, to any of the
              parties who would receive the covered work from you, a discriminatory
              patent license (a) in connection with copies of the covered work
              conveyed by you (or copies made from those copies), or (b) primarily
              for and in connection with specific products or compilations that
              contain the covered work, unless you entered into that arrangement,
              or that patent license was granted, prior to 28 March 2007.
              
                Nothing in this License shall be construed as excluding or limiting
              any implied license or other defenses to infringement that may
              otherwise be available to you under applicable patent law.
              
                12. No Surrender of Others' Freedom.
              
                If conditions are imposed on you (whether by court order, agreement or
              otherwise) that contradict the conditions of this License, they do not
              excuse you from the conditions of this License.  If you cannot convey a
              covered work so as to satisfy simultaneously your obligations under this
              License and any other pertinent obligations, then as a consequence you may
              not convey it at all.  For example, if you agree to terms that obligate you
              to collect a royalty for further conveying from those to whom you convey
              the Program, the only way you could satisfy both those terms and this
              License would be to refrain entirely from conveying the Program.
              
                13. Use with the GNU Affero General Public License.
              
                Notwithstanding any other provision of this License, you have
              permission to link or combine any covered work with a work licensed
              under version 3 of the GNU Affero General Public License into a single
              combined work, and to convey the resulting work.  The terms of this
              License will continue to apply to the part which is the covered work,
              but the special requirements of the GNU Affero General Public License,
              section 13, concerning interaction through a network will apply to the
              combination as such.
              
                14. Revised Versions of this License.
              
                The Free Software Foundation may publish revised and/or new versions of
              the GNU General Public License from time to time.  Such new versions will
              be similar in spirit to the present version, but may differ in detail to
              address new problems or concerns.
              
                Each version is given a distinguishing version number.  If the
              Program specifies that a certain numbered version of the GNU General
              Public License "or any later version" applies to it, you have the
              option of following the terms and conditions either of that numbered
              version or of any later version published by the Free Software
              Foundation.  If the Program does not specify a version number of the
              GNU General Public License, you may choose any version ever published
              by the Free Software Foundation.
              
                If the Program specifies that a proxy can decide which future
              versions of the GNU General Public License can be used, that proxy's
              public statement of acceptance of a version permanently authorizes you
              to choose that version for the Program.
              
                Later license versions may give you additional or different
              permissions.  However, no additional obligations are imposed on any
              author or copyright holder as a result of your choosing to follow a
              later version.
              
                15. Disclaimer of Warranty.
              
                THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
              APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
              HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
              OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
              THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
              PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
              IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
              ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
              
                16. Limitation of Liability.
              
                IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
              WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
              THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
              GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
              USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
              DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
              PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
              EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
              SUCH DAMAGES.
              
                17. Interpretation of Sections 15 and 16.
              
                If the disclaimer of warranty and limitation of liability provided
              above cannot be given local legal effect according to their terms,
              reviewing courts shall apply local law that most closely approximates
              an absolute waiver of all civil liability in connection with the
              Program, unless a warranty or assumption of liability accompanies a
              copy of the Program in return for a fee.
              
                                   END OF TERMS AND CONDITIONS
              
                          How to Apply These Terms to Your New Programs
              
                If you develop a new program, and you want it to be of the greatest
              possible use to the public, the best way to achieve this is to make it
              free software which everyone can redistribute and change under these terms.
              
                To do so, attach the following notices to the program.  It is safest
              to attach them to the start of each source file to most effectively
              state the exclusion of warranty; and each file should have at least
              the "copyright" line and a pointer to where the full notice is found.
              
                  <one line to give the program's name and a brief idea of what it does.>
                  Copyright (C) <year>  <name of author>
              
                  This program is free software: you can redistribute it and/or modify
                  it under the terms of the GNU General Public License as published by
                  the Free Software Foundation, either version 3 of the License, or
                  (at your option) any later version.
              
                  This program is distributed in the hope that it will be useful,
                  but WITHOUT ANY WARRANTY; without even the implied warranty of
                  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
                  GNU General Public License for more details.
              
                  You should have received a copy of the GNU General Public License
                  along with this program.  If not, see <http://www.gnu.org/licenses/>.
              
              Also add information on how to contact you by electronic and paper mail.
              
                If the program does terminal interaction, make it output a short
              notice like this when it starts in an interactive mode:
              
                  <program>  Copyright (C) <year>  <name of author>
                  This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
                  This is free software, and you are welcome to redistribute it
                  under certain conditions; type `show c' for details.
              
              The hypothetical commands `show w' and `show c' should show the appropriate
              parts of the General Public License.  Of course, your program's commands
              might be different; for a GUI interface, you would use an "about box".
              
                You should also get your employer (if you work as a programmer) or school,
              if any, to sign a "copyright disclaimer" for the program, if necessary.
              For more information on this, and how to apply and follow the GNU GPL, see
              <http://www.gnu.org/licenses/>.
              
                The GNU General Public License does not permit incorporating your program
              into proprietary programs.  If your program is a subroutine library, you
              may consider it more useful to permit linking proprietary applications with
              the library.  If this is what you want to do, use the GNU Lesser General
              Public License instead of this License.  But first, please read
              <http://www.gnu.org/philosophy/why-not-lgpl.html>.
              
              */

              File 3 of 4: UniswapV3Pool
              // SPDX-License-Identifier: BUSL-1.1
              pragma solidity =0.7.6;
              import './interfaces/IUniswapV3Pool.sol';
              import './NoDelegateCall.sol';
              import './libraries/LowGasSafeMath.sol';
              import './libraries/SafeCast.sol';
              import './libraries/Tick.sol';
              import './libraries/TickBitmap.sol';
              import './libraries/Position.sol';
              import './libraries/Oracle.sol';
              import './libraries/FullMath.sol';
              import './libraries/FixedPoint128.sol';
              import './libraries/TransferHelper.sol';
              import './libraries/TickMath.sol';
              import './libraries/LiquidityMath.sol';
              import './libraries/SqrtPriceMath.sol';
              import './libraries/SwapMath.sol';
              import './interfaces/IUniswapV3PoolDeployer.sol';
              import './interfaces/IUniswapV3Factory.sol';
              import './interfaces/IERC20Minimal.sol';
              import './interfaces/callback/IUniswapV3MintCallback.sol';
              import './interfaces/callback/IUniswapV3SwapCallback.sol';
              import './interfaces/callback/IUniswapV3FlashCallback.sol';
              contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall {
                  using LowGasSafeMath for uint256;
                  using LowGasSafeMath for int256;
                  using SafeCast for uint256;
                  using SafeCast for int256;
                  using Tick for mapping(int24 => Tick.Info);
                  using TickBitmap for mapping(int16 => uint256);
                  using Position for mapping(bytes32 => Position.Info);
                  using Position for Position.Info;
                  using Oracle for Oracle.Observation[65535];
                  /// @inheritdoc IUniswapV3PoolImmutables
                  address public immutable override factory;
                  /// @inheritdoc IUniswapV3PoolImmutables
                  address public immutable override token0;
                  /// @inheritdoc IUniswapV3PoolImmutables
                  address public immutable override token1;
                  /// @inheritdoc IUniswapV3PoolImmutables
                  uint24 public immutable override fee;
                  /// @inheritdoc IUniswapV3PoolImmutables
                  int24 public immutable override tickSpacing;
                  /// @inheritdoc IUniswapV3PoolImmutables
                  uint128 public immutable override maxLiquidityPerTick;
                  struct Slot0 {
                      // the current price
                      uint160 sqrtPriceX96;
                      // the current tick
                      int24 tick;
                      // the most-recently updated index of the observations array
                      uint16 observationIndex;
                      // the current maximum number of observations that are being stored
                      uint16 observationCardinality;
                      // the next maximum number of observations to store, triggered in observations.write
                      uint16 observationCardinalityNext;
                      // the current protocol fee as a percentage of the swap fee taken on withdrawal
                      // represented as an integer denominator (1/x)%
                      uint8 feeProtocol;
                      // whether the pool is locked
                      bool unlocked;
                  }
                  /// @inheritdoc IUniswapV3PoolState
                  Slot0 public override slot0;
                  /// @inheritdoc IUniswapV3PoolState
                  uint256 public override feeGrowthGlobal0X128;
                  /// @inheritdoc IUniswapV3PoolState
                  uint256 public override feeGrowthGlobal1X128;
                  // accumulated protocol fees in token0/token1 units
                  struct ProtocolFees {
                      uint128 token0;
                      uint128 token1;
                  }
                  /// @inheritdoc IUniswapV3PoolState
                  ProtocolFees public override protocolFees;
                  /// @inheritdoc IUniswapV3PoolState
                  uint128 public override liquidity;
                  /// @inheritdoc IUniswapV3PoolState
                  mapping(int24 => Tick.Info) public override ticks;
                  /// @inheritdoc IUniswapV3PoolState
                  mapping(int16 => uint256) public override tickBitmap;
                  /// @inheritdoc IUniswapV3PoolState
                  mapping(bytes32 => Position.Info) public override positions;
                  /// @inheritdoc IUniswapV3PoolState
                  Oracle.Observation[65535] public override observations;
                  /// @dev Mutually exclusive reentrancy protection into the pool to/from a method. This method also prevents entrance
                  /// to a function before the pool is initialized. The reentrancy guard is required throughout the contract because
                  /// we use balance checks to determine the payment status of interactions such as mint, swap and flash.
                  modifier lock() {
                      require(slot0.unlocked, 'LOK');
                      slot0.unlocked = false;
                      _;
                      slot0.unlocked = true;
                  }
                  /// @dev Prevents calling a function from anyone except the address returned by IUniswapV3Factory#owner()
                  modifier onlyFactoryOwner() {
                      require(msg.sender == IUniswapV3Factory(factory).owner());
                      _;
                  }
                  constructor() {
                      int24 _tickSpacing;
                      (factory, token0, token1, fee, _tickSpacing) = IUniswapV3PoolDeployer(msg.sender).parameters();
                      tickSpacing = _tickSpacing;
                      maxLiquidityPerTick = Tick.tickSpacingToMaxLiquidityPerTick(_tickSpacing);
                  }
                  /// @dev Common checks for valid tick inputs.
                  function checkTicks(int24 tickLower, int24 tickUpper) private pure {
                      require(tickLower < tickUpper, 'TLU');
                      require(tickLower >= TickMath.MIN_TICK, 'TLM');
                      require(tickUpper <= TickMath.MAX_TICK, 'TUM');
                  }
                  /// @dev Returns the block timestamp truncated to 32 bits, i.e. mod 2**32. This method is overridden in tests.
                  function _blockTimestamp() internal view virtual returns (uint32) {
                      return uint32(block.timestamp); // truncation is desired
                  }
                  /// @dev Get the pool's balance of token0
                  /// @dev This function is gas optimized to avoid a redundant extcodesize check in addition to the returndatasize
                  /// check
                  function balance0() private view returns (uint256) {
                      (bool success, bytes memory data) =
                          token0.staticcall(abi.encodeWithSelector(IERC20Minimal.balanceOf.selector, address(this)));
                      require(success && data.length >= 32);
                      return abi.decode(data, (uint256));
                  }
                  /// @dev Get the pool's balance of token1
                  /// @dev This function is gas optimized to avoid a redundant extcodesize check in addition to the returndatasize
                  /// check
                  function balance1() private view returns (uint256) {
                      (bool success, bytes memory data) =
                          token1.staticcall(abi.encodeWithSelector(IERC20Minimal.balanceOf.selector, address(this)));
                      require(success && data.length >= 32);
                      return abi.decode(data, (uint256));
                  }
                  /// @inheritdoc IUniswapV3PoolDerivedState
                  function snapshotCumulativesInside(int24 tickLower, int24 tickUpper)
                      external
                      view
                      override
                      noDelegateCall
                      returns (
                          int56 tickCumulativeInside,
                          uint160 secondsPerLiquidityInsideX128,
                          uint32 secondsInside
                      )
                  {
                      checkTicks(tickLower, tickUpper);
                      int56 tickCumulativeLower;
                      int56 tickCumulativeUpper;
                      uint160 secondsPerLiquidityOutsideLowerX128;
                      uint160 secondsPerLiquidityOutsideUpperX128;
                      uint32 secondsOutsideLower;
                      uint32 secondsOutsideUpper;
                      {
                          Tick.Info storage lower = ticks[tickLower];
                          Tick.Info storage upper = ticks[tickUpper];
                          bool initializedLower;
                          (tickCumulativeLower, secondsPerLiquidityOutsideLowerX128, secondsOutsideLower, initializedLower) = (
                              lower.tickCumulativeOutside,
                              lower.secondsPerLiquidityOutsideX128,
                              lower.secondsOutside,
                              lower.initialized
                          );
                          require(initializedLower);
                          bool initializedUpper;
                          (tickCumulativeUpper, secondsPerLiquidityOutsideUpperX128, secondsOutsideUpper, initializedUpper) = (
                              upper.tickCumulativeOutside,
                              upper.secondsPerLiquidityOutsideX128,
                              upper.secondsOutside,
                              upper.initialized
                          );
                          require(initializedUpper);
                      }
                      Slot0 memory _slot0 = slot0;
                      if (_slot0.tick < tickLower) {
                          return (
                              tickCumulativeLower - tickCumulativeUpper,
                              secondsPerLiquidityOutsideLowerX128 - secondsPerLiquidityOutsideUpperX128,
                              secondsOutsideLower - secondsOutsideUpper
                          );
                      } else if (_slot0.tick < tickUpper) {
                          uint32 time = _blockTimestamp();
                          (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) =
                              observations.observeSingle(
                                  time,
                                  0,
                                  _slot0.tick,
                                  _slot0.observationIndex,
                                  liquidity,
                                  _slot0.observationCardinality
                              );
                          return (
                              tickCumulative - tickCumulativeLower - tickCumulativeUpper,
                              secondsPerLiquidityCumulativeX128 -
                                  secondsPerLiquidityOutsideLowerX128 -
                                  secondsPerLiquidityOutsideUpperX128,
                              time - secondsOutsideLower - secondsOutsideUpper
                          );
                      } else {
                          return (
                              tickCumulativeUpper - tickCumulativeLower,
                              secondsPerLiquidityOutsideUpperX128 - secondsPerLiquidityOutsideLowerX128,
                              secondsOutsideUpper - secondsOutsideLower
                          );
                      }
                  }
                  /// @inheritdoc IUniswapV3PoolDerivedState
                  function observe(uint32[] calldata secondsAgos)
                      external
                      view
                      override
                      noDelegateCall
                      returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s)
                  {
                      return
                          observations.observe(
                              _blockTimestamp(),
                              secondsAgos,
                              slot0.tick,
                              slot0.observationIndex,
                              liquidity,
                              slot0.observationCardinality
                          );
                  }
                  /// @inheritdoc IUniswapV3PoolActions
                  function increaseObservationCardinalityNext(uint16 observationCardinalityNext)
                      external
                      override
                      lock
                      noDelegateCall
                  {
                      uint16 observationCardinalityNextOld = slot0.observationCardinalityNext; // for the event
                      uint16 observationCardinalityNextNew =
                          observations.grow(observationCardinalityNextOld, observationCardinalityNext);
                      slot0.observationCardinalityNext = observationCardinalityNextNew;
                      if (observationCardinalityNextOld != observationCardinalityNextNew)
                          emit IncreaseObservationCardinalityNext(observationCardinalityNextOld, observationCardinalityNextNew);
                  }
                  /// @inheritdoc IUniswapV3PoolActions
                  /// @dev not locked because it initializes unlocked
                  function initialize(uint160 sqrtPriceX96) external override {
                      require(slot0.sqrtPriceX96 == 0, 'AI');
                      int24 tick = TickMath.getTickAtSqrtRatio(sqrtPriceX96);
                      (uint16 cardinality, uint16 cardinalityNext) = observations.initialize(_blockTimestamp());
                      slot0 = Slot0({
                          sqrtPriceX96: sqrtPriceX96,
                          tick: tick,
                          observationIndex: 0,
                          observationCardinality: cardinality,
                          observationCardinalityNext: cardinalityNext,
                          feeProtocol: 0,
                          unlocked: true
                      });
                      emit Initialize(sqrtPriceX96, tick);
                  }
                  struct ModifyPositionParams {
                      // the address that owns the position
                      address owner;
                      // the lower and upper tick of the position
                      int24 tickLower;
                      int24 tickUpper;
                      // any change in liquidity
                      int128 liquidityDelta;
                  }
                  /// @dev Effect some changes to a position
                  /// @param params the position details and the change to the position's liquidity to effect
                  /// @return position a storage pointer referencing the position with the given owner and tick range
                  /// @return amount0 the amount of token0 owed to the pool, negative if the pool should pay the recipient
                  /// @return amount1 the amount of token1 owed to the pool, negative if the pool should pay the recipient
                  function _modifyPosition(ModifyPositionParams memory params)
                      private
                      noDelegateCall
                      returns (
                          Position.Info storage position,
                          int256 amount0,
                          int256 amount1
                      )
                  {
                      checkTicks(params.tickLower, params.tickUpper);
                      Slot0 memory _slot0 = slot0; // SLOAD for gas optimization
                      position = _updatePosition(
                          params.owner,
                          params.tickLower,
                          params.tickUpper,
                          params.liquidityDelta,
                          _slot0.tick
                      );
                      if (params.liquidityDelta != 0) {
                          if (_slot0.tick < params.tickLower) {
                              // current tick is below the passed range; liquidity can only become in range by crossing from left to
                              // right, when we'll need _more_ token0 (it's becoming more valuable) so user must provide it
                              amount0 = SqrtPriceMath.getAmount0Delta(
                                  TickMath.getSqrtRatioAtTick(params.tickLower),
                                  TickMath.getSqrtRatioAtTick(params.tickUpper),
                                  params.liquidityDelta
                              );
                          } else if (_slot0.tick < params.tickUpper) {
                              // current tick is inside the passed range
                              uint128 liquidityBefore = liquidity; // SLOAD for gas optimization
                              // write an oracle entry
                              (slot0.observationIndex, slot0.observationCardinality) = observations.write(
                                  _slot0.observationIndex,
                                  _blockTimestamp(),
                                  _slot0.tick,
                                  liquidityBefore,
                                  _slot0.observationCardinality,
                                  _slot0.observationCardinalityNext
                              );
                              amount0 = SqrtPriceMath.getAmount0Delta(
                                  _slot0.sqrtPriceX96,
                                  TickMath.getSqrtRatioAtTick(params.tickUpper),
                                  params.liquidityDelta
                              );
                              amount1 = SqrtPriceMath.getAmount1Delta(
                                  TickMath.getSqrtRatioAtTick(params.tickLower),
                                  _slot0.sqrtPriceX96,
                                  params.liquidityDelta
                              );
                              liquidity = LiquidityMath.addDelta(liquidityBefore, params.liquidityDelta);
                          } else {
                              // current tick is above the passed range; liquidity can only become in range by crossing from right to
                              // left, when we'll need _more_ token1 (it's becoming more valuable) so user must provide it
                              amount1 = SqrtPriceMath.getAmount1Delta(
                                  TickMath.getSqrtRatioAtTick(params.tickLower),
                                  TickMath.getSqrtRatioAtTick(params.tickUpper),
                                  params.liquidityDelta
                              );
                          }
                      }
                  }
                  /// @dev Gets and updates a position with the given liquidity delta
                  /// @param owner the owner of the position
                  /// @param tickLower the lower tick of the position's tick range
                  /// @param tickUpper the upper tick of the position's tick range
                  /// @param tick the current tick, passed to avoid sloads
                  function _updatePosition(
                      address owner,
                      int24 tickLower,
                      int24 tickUpper,
                      int128 liquidityDelta,
                      int24 tick
                  ) private returns (Position.Info storage position) {
                      position = positions.get(owner, tickLower, tickUpper);
                      uint256 _feeGrowthGlobal0X128 = feeGrowthGlobal0X128; // SLOAD for gas optimization
                      uint256 _feeGrowthGlobal1X128 = feeGrowthGlobal1X128; // SLOAD for gas optimization
                      // if we need to update the ticks, do it
                      bool flippedLower;
                      bool flippedUpper;
                      if (liquidityDelta != 0) {
                          uint32 time = _blockTimestamp();
                          (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) =
                              observations.observeSingle(
                                  time,
                                  0,
                                  slot0.tick,
                                  slot0.observationIndex,
                                  liquidity,
                                  slot0.observationCardinality
                              );
                          flippedLower = ticks.update(
                              tickLower,
                              tick,
                              liquidityDelta,
                              _feeGrowthGlobal0X128,
                              _feeGrowthGlobal1X128,
                              secondsPerLiquidityCumulativeX128,
                              tickCumulative,
                              time,
                              false,
                              maxLiquidityPerTick
                          );
                          flippedUpper = ticks.update(
                              tickUpper,
                              tick,
                              liquidityDelta,
                              _feeGrowthGlobal0X128,
                              _feeGrowthGlobal1X128,
                              secondsPerLiquidityCumulativeX128,
                              tickCumulative,
                              time,
                              true,
                              maxLiquidityPerTick
                          );
                          if (flippedLower) {
                              tickBitmap.flipTick(tickLower, tickSpacing);
                          }
                          if (flippedUpper) {
                              tickBitmap.flipTick(tickUpper, tickSpacing);
                          }
                      }
                      (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) =
                          ticks.getFeeGrowthInside(tickLower, tickUpper, tick, _feeGrowthGlobal0X128, _feeGrowthGlobal1X128);
                      position.update(liquidityDelta, feeGrowthInside0X128, feeGrowthInside1X128);
                      // clear any tick data that is no longer needed
                      if (liquidityDelta < 0) {
                          if (flippedLower) {
                              ticks.clear(tickLower);
                          }
                          if (flippedUpper) {
                              ticks.clear(tickUpper);
                          }
                      }
                  }
                  /// @inheritdoc IUniswapV3PoolActions
                  /// @dev noDelegateCall is applied indirectly via _modifyPosition
                  function mint(
                      address recipient,
                      int24 tickLower,
                      int24 tickUpper,
                      uint128 amount,
                      bytes calldata data
                  ) external override lock returns (uint256 amount0, uint256 amount1) {
                      require(amount > 0);
                      (, int256 amount0Int, int256 amount1Int) =
                          _modifyPosition(
                              ModifyPositionParams({
                                  owner: recipient,
                                  tickLower: tickLower,
                                  tickUpper: tickUpper,
                                  liquidityDelta: int256(amount).toInt128()
                              })
                          );
                      amount0 = uint256(amount0Int);
                      amount1 = uint256(amount1Int);
                      uint256 balance0Before;
                      uint256 balance1Before;
                      if (amount0 > 0) balance0Before = balance0();
                      if (amount1 > 0) balance1Before = balance1();
                      IUniswapV3MintCallback(msg.sender).uniswapV3MintCallback(amount0, amount1, data);
                      if (amount0 > 0) require(balance0Before.add(amount0) <= balance0(), 'M0');
                      if (amount1 > 0) require(balance1Before.add(amount1) <= balance1(), 'M1');
                      emit Mint(msg.sender, recipient, tickLower, tickUpper, amount, amount0, amount1);
                  }
                  /// @inheritdoc IUniswapV3PoolActions
                  function collect(
                      address recipient,
                      int24 tickLower,
                      int24 tickUpper,
                      uint128 amount0Requested,
                      uint128 amount1Requested
                  ) external override lock returns (uint128 amount0, uint128 amount1) {
                      // we don't need to checkTicks here, because invalid positions will never have non-zero tokensOwed{0,1}
                      Position.Info storage position = positions.get(msg.sender, tickLower, tickUpper);
                      amount0 = amount0Requested > position.tokensOwed0 ? position.tokensOwed0 : amount0Requested;
                      amount1 = amount1Requested > position.tokensOwed1 ? position.tokensOwed1 : amount1Requested;
                      if (amount0 > 0) {
                          position.tokensOwed0 -= amount0;
                          TransferHelper.safeTransfer(token0, recipient, amount0);
                      }
                      if (amount1 > 0) {
                          position.tokensOwed1 -= amount1;
                          TransferHelper.safeTransfer(token1, recipient, amount1);
                      }
                      emit Collect(msg.sender, recipient, tickLower, tickUpper, amount0, amount1);
                  }
                  /// @inheritdoc IUniswapV3PoolActions
                  /// @dev noDelegateCall is applied indirectly via _modifyPosition
                  function burn(
                      int24 tickLower,
                      int24 tickUpper,
                      uint128 amount
                  ) external override lock returns (uint256 amount0, uint256 amount1) {
                      (Position.Info storage position, int256 amount0Int, int256 amount1Int) =
                          _modifyPosition(
                              ModifyPositionParams({
                                  owner: msg.sender,
                                  tickLower: tickLower,
                                  tickUpper: tickUpper,
                                  liquidityDelta: -int256(amount).toInt128()
                              })
                          );
                      amount0 = uint256(-amount0Int);
                      amount1 = uint256(-amount1Int);
                      if (amount0 > 0 || amount1 > 0) {
                          (position.tokensOwed0, position.tokensOwed1) = (
                              position.tokensOwed0 + uint128(amount0),
                              position.tokensOwed1 + uint128(amount1)
                          );
                      }
                      emit Burn(msg.sender, tickLower, tickUpper, amount, amount0, amount1);
                  }
                  struct SwapCache {
                      // the protocol fee for the input token
                      uint8 feeProtocol;
                      // liquidity at the beginning of the swap
                      uint128 liquidityStart;
                      // the timestamp of the current block
                      uint32 blockTimestamp;
                      // the current value of the tick accumulator, computed only if we cross an initialized tick
                      int56 tickCumulative;
                      // the current value of seconds per liquidity accumulator, computed only if we cross an initialized tick
                      uint160 secondsPerLiquidityCumulativeX128;
                      // whether we've computed and cached the above two accumulators
                      bool computedLatestObservation;
                  }
                  // the top level state of the swap, the results of which are recorded in storage at the end
                  struct SwapState {
                      // the amount remaining to be swapped in/out of the input/output asset
                      int256 amountSpecifiedRemaining;
                      // the amount already swapped out/in of the output/input asset
                      int256 amountCalculated;
                      // current sqrt(price)
                      uint160 sqrtPriceX96;
                      // the tick associated with the current price
                      int24 tick;
                      // the global fee growth of the input token
                      uint256 feeGrowthGlobalX128;
                      // amount of input token paid as protocol fee
                      uint128 protocolFee;
                      // the current liquidity in range
                      uint128 liquidity;
                  }
                  struct StepComputations {
                      // the price at the beginning of the step
                      uint160 sqrtPriceStartX96;
                      // the next tick to swap to from the current tick in the swap direction
                      int24 tickNext;
                      // whether tickNext is initialized or not
                      bool initialized;
                      // sqrt(price) for the next tick (1/0)
                      uint160 sqrtPriceNextX96;
                      // how much is being swapped in in this step
                      uint256 amountIn;
                      // how much is being swapped out
                      uint256 amountOut;
                      // how much fee is being paid in
                      uint256 feeAmount;
                  }
                  /// @inheritdoc IUniswapV3PoolActions
                  function swap(
                      address recipient,
                      bool zeroForOne,
                      int256 amountSpecified,
                      uint160 sqrtPriceLimitX96,
                      bytes calldata data
                  ) external override noDelegateCall returns (int256 amount0, int256 amount1) {
                      require(amountSpecified != 0, 'AS');
                      Slot0 memory slot0Start = slot0;
                      require(slot0Start.unlocked, 'LOK');
                      require(
                          zeroForOne
                              ? sqrtPriceLimitX96 < slot0Start.sqrtPriceX96 && sqrtPriceLimitX96 > TickMath.MIN_SQRT_RATIO
                              : sqrtPriceLimitX96 > slot0Start.sqrtPriceX96 && sqrtPriceLimitX96 < TickMath.MAX_SQRT_RATIO,
                          'SPL'
                      );
                      slot0.unlocked = false;
                      SwapCache memory cache =
                          SwapCache({
                              liquidityStart: liquidity,
                              blockTimestamp: _blockTimestamp(),
                              feeProtocol: zeroForOne ? (slot0Start.feeProtocol % 16) : (slot0Start.feeProtocol >> 4),
                              secondsPerLiquidityCumulativeX128: 0,
                              tickCumulative: 0,
                              computedLatestObservation: false
                          });
                      bool exactInput = amountSpecified > 0;
                      SwapState memory state =
                          SwapState({
                              amountSpecifiedRemaining: amountSpecified,
                              amountCalculated: 0,
                              sqrtPriceX96: slot0Start.sqrtPriceX96,
                              tick: slot0Start.tick,
                              feeGrowthGlobalX128: zeroForOne ? feeGrowthGlobal0X128 : feeGrowthGlobal1X128,
                              protocolFee: 0,
                              liquidity: cache.liquidityStart
                          });
                      // continue swapping as long as we haven't used the entire input/output and haven't reached the price limit
                      while (state.amountSpecifiedRemaining != 0 && state.sqrtPriceX96 != sqrtPriceLimitX96) {
                          StepComputations memory step;
                          step.sqrtPriceStartX96 = state.sqrtPriceX96;
                          (step.tickNext, step.initialized) = tickBitmap.nextInitializedTickWithinOneWord(
                              state.tick,
                              tickSpacing,
                              zeroForOne
                          );
                          // ensure that we do not overshoot the min/max tick, as the tick bitmap is not aware of these bounds
                          if (step.tickNext < TickMath.MIN_TICK) {
                              step.tickNext = TickMath.MIN_TICK;
                          } else if (step.tickNext > TickMath.MAX_TICK) {
                              step.tickNext = TickMath.MAX_TICK;
                          }
                          // get the price for the next tick
                          step.sqrtPriceNextX96 = TickMath.getSqrtRatioAtTick(step.tickNext);
                          // compute values to swap to the target tick, price limit, or point where input/output amount is exhausted
                          (state.sqrtPriceX96, step.amountIn, step.amountOut, step.feeAmount) = SwapMath.computeSwapStep(
                              state.sqrtPriceX96,
                              (zeroForOne ? step.sqrtPriceNextX96 < sqrtPriceLimitX96 : step.sqrtPriceNextX96 > sqrtPriceLimitX96)
                                  ? sqrtPriceLimitX96
                                  : step.sqrtPriceNextX96,
                              state.liquidity,
                              state.amountSpecifiedRemaining,
                              fee
                          );
                          if (exactInput) {
                              state.amountSpecifiedRemaining -= (step.amountIn + step.feeAmount).toInt256();
                              state.amountCalculated = state.amountCalculated.sub(step.amountOut.toInt256());
                          } else {
                              state.amountSpecifiedRemaining += step.amountOut.toInt256();
                              state.amountCalculated = state.amountCalculated.add((step.amountIn + step.feeAmount).toInt256());
                          }
                          // if the protocol fee is on, calculate how much is owed, decrement feeAmount, and increment protocolFee
                          if (cache.feeProtocol > 0) {
                              uint256 delta = step.feeAmount / cache.feeProtocol;
                              step.feeAmount -= delta;
                              state.protocolFee += uint128(delta);
                          }
                          // update global fee tracker
                          if (state.liquidity > 0)
                              state.feeGrowthGlobalX128 += FullMath.mulDiv(step.feeAmount, FixedPoint128.Q128, state.liquidity);
                          // shift tick if we reached the next price
                          if (state.sqrtPriceX96 == step.sqrtPriceNextX96) {
                              // if the tick is initialized, run the tick transition
                              if (step.initialized) {
                                  // check for the placeholder value, which we replace with the actual value the first time the swap
                                  // crosses an initialized tick
                                  if (!cache.computedLatestObservation) {
                                      (cache.tickCumulative, cache.secondsPerLiquidityCumulativeX128) = observations.observeSingle(
                                          cache.blockTimestamp,
                                          0,
                                          slot0Start.tick,
                                          slot0Start.observationIndex,
                                          cache.liquidityStart,
                                          slot0Start.observationCardinality
                                      );
                                      cache.computedLatestObservation = true;
                                  }
                                  int128 liquidityNet =
                                      ticks.cross(
                                          step.tickNext,
                                          (zeroForOne ? state.feeGrowthGlobalX128 : feeGrowthGlobal0X128),
                                          (zeroForOne ? feeGrowthGlobal1X128 : state.feeGrowthGlobalX128),
                                          cache.secondsPerLiquidityCumulativeX128,
                                          cache.tickCumulative,
                                          cache.blockTimestamp
                                      );
                                  // if we're moving leftward, we interpret liquidityNet as the opposite sign
                                  // safe because liquidityNet cannot be type(int128).min
                                  if (zeroForOne) liquidityNet = -liquidityNet;
                                  state.liquidity = LiquidityMath.addDelta(state.liquidity, liquidityNet);
                              }
                              state.tick = zeroForOne ? step.tickNext - 1 : step.tickNext;
                          } else if (state.sqrtPriceX96 != step.sqrtPriceStartX96) {
                              // recompute unless we're on a lower tick boundary (i.e. already transitioned ticks), and haven't moved
                              state.tick = TickMath.getTickAtSqrtRatio(state.sqrtPriceX96);
                          }
                      }
                      // update tick and write an oracle entry if the tick change
                      if (state.tick != slot0Start.tick) {
                          (uint16 observationIndex, uint16 observationCardinality) =
                              observations.write(
                                  slot0Start.observationIndex,
                                  cache.blockTimestamp,
                                  slot0Start.tick,
                                  cache.liquidityStart,
                                  slot0Start.observationCardinality,
                                  slot0Start.observationCardinalityNext
                              );
                          (slot0.sqrtPriceX96, slot0.tick, slot0.observationIndex, slot0.observationCardinality) = (
                              state.sqrtPriceX96,
                              state.tick,
                              observationIndex,
                              observationCardinality
                          );
                      } else {
                          // otherwise just update the price
                          slot0.sqrtPriceX96 = state.sqrtPriceX96;
                      }
                      // update liquidity if it changed
                      if (cache.liquidityStart != state.liquidity) liquidity = state.liquidity;
                      // update fee growth global and, if necessary, protocol fees
                      // overflow is acceptable, protocol has to withdraw before it hits type(uint128).max fees
                      if (zeroForOne) {
                          feeGrowthGlobal0X128 = state.feeGrowthGlobalX128;
                          if (state.protocolFee > 0) protocolFees.token0 += state.protocolFee;
                      } else {
                          feeGrowthGlobal1X128 = state.feeGrowthGlobalX128;
                          if (state.protocolFee > 0) protocolFees.token1 += state.protocolFee;
                      }
                      (amount0, amount1) = zeroForOne == exactInput
                          ? (amountSpecified - state.amountSpecifiedRemaining, state.amountCalculated)
                          : (state.amountCalculated, amountSpecified - state.amountSpecifiedRemaining);
                      // do the transfers and collect payment
                      if (zeroForOne) {
                          if (amount1 < 0) TransferHelper.safeTransfer(token1, recipient, uint256(-amount1));
                          uint256 balance0Before = balance0();
                          IUniswapV3SwapCallback(msg.sender).uniswapV3SwapCallback(amount0, amount1, data);
                          require(balance0Before.add(uint256(amount0)) <= balance0(), 'IIA');
                      } else {
                          if (amount0 < 0) TransferHelper.safeTransfer(token0, recipient, uint256(-amount0));
                          uint256 balance1Before = balance1();
                          IUniswapV3SwapCallback(msg.sender).uniswapV3SwapCallback(amount0, amount1, data);
                          require(balance1Before.add(uint256(amount1)) <= balance1(), 'IIA');
                      }
                      emit Swap(msg.sender, recipient, amount0, amount1, state.sqrtPriceX96, state.liquidity, state.tick);
                      slot0.unlocked = true;
                  }
                  /// @inheritdoc IUniswapV3PoolActions
                  function flash(
                      address recipient,
                      uint256 amount0,
                      uint256 amount1,
                      bytes calldata data
                  ) external override lock noDelegateCall {
                      uint128 _liquidity = liquidity;
                      require(_liquidity > 0, 'L');
                      uint256 fee0 = FullMath.mulDivRoundingUp(amount0, fee, 1e6);
                      uint256 fee1 = FullMath.mulDivRoundingUp(amount1, fee, 1e6);
                      uint256 balance0Before = balance0();
                      uint256 balance1Before = balance1();
                      if (amount0 > 0) TransferHelper.safeTransfer(token0, recipient, amount0);
                      if (amount1 > 0) TransferHelper.safeTransfer(token1, recipient, amount1);
                      IUniswapV3FlashCallback(msg.sender).uniswapV3FlashCallback(fee0, fee1, data);
                      uint256 balance0After = balance0();
                      uint256 balance1After = balance1();
                      require(balance0Before.add(fee0) <= balance0After, 'F0');
                      require(balance1Before.add(fee1) <= balance1After, 'F1');
                      // sub is safe because we know balanceAfter is gt balanceBefore by at least fee
                      uint256 paid0 = balance0After - balance0Before;
                      uint256 paid1 = balance1After - balance1Before;
                      if (paid0 > 0) {
                          uint8 feeProtocol0 = slot0.feeProtocol % 16;
                          uint256 fees0 = feeProtocol0 == 0 ? 0 : paid0 / feeProtocol0;
                          if (uint128(fees0) > 0) protocolFees.token0 += uint128(fees0);
                          feeGrowthGlobal0X128 += FullMath.mulDiv(paid0 - fees0, FixedPoint128.Q128, _liquidity);
                      }
                      if (paid1 > 0) {
                          uint8 feeProtocol1 = slot0.feeProtocol >> 4;
                          uint256 fees1 = feeProtocol1 == 0 ? 0 : paid1 / feeProtocol1;
                          if (uint128(fees1) > 0) protocolFees.token1 += uint128(fees1);
                          feeGrowthGlobal1X128 += FullMath.mulDiv(paid1 - fees1, FixedPoint128.Q128, _liquidity);
                      }
                      emit Flash(msg.sender, recipient, amount0, amount1, paid0, paid1);
                  }
                  /// @inheritdoc IUniswapV3PoolOwnerActions
                  function setFeeProtocol(uint8 feeProtocol0, uint8 feeProtocol1) external override lock onlyFactoryOwner {
                      require(
                          (feeProtocol0 == 0 || (feeProtocol0 >= 4 && feeProtocol0 <= 10)) &&
                              (feeProtocol1 == 0 || (feeProtocol1 >= 4 && feeProtocol1 <= 10))
                      );
                      uint8 feeProtocolOld = slot0.feeProtocol;
                      slot0.feeProtocol = feeProtocol0 + (feeProtocol1 << 4);
                      emit SetFeeProtocol(feeProtocolOld % 16, feeProtocolOld >> 4, feeProtocol0, feeProtocol1);
                  }
                  /// @inheritdoc IUniswapV3PoolOwnerActions
                  function collectProtocol(
                      address recipient,
                      uint128 amount0Requested,
                      uint128 amount1Requested
                  ) external override lock onlyFactoryOwner returns (uint128 amount0, uint128 amount1) {
                      amount0 = amount0Requested > protocolFees.token0 ? protocolFees.token0 : amount0Requested;
                      amount1 = amount1Requested > protocolFees.token1 ? protocolFees.token1 : amount1Requested;
                      if (amount0 > 0) {
                          if (amount0 == protocolFees.token0) amount0--; // ensure that the slot is not cleared, for gas savings
                          protocolFees.token0 -= amount0;
                          TransferHelper.safeTransfer(token0, recipient, amount0);
                      }
                      if (amount1 > 0) {
                          if (amount1 == protocolFees.token1) amount1--; // ensure that the slot is not cleared, for gas savings
                          protocolFees.token1 -= amount1;
                          TransferHelper.safeTransfer(token1, recipient, amount1);
                      }
                      emit CollectProtocol(msg.sender, recipient, amount0, amount1);
                  }
              }
              // SPDX-License-Identifier: GPL-2.0-or-later
              pragma solidity >=0.5.0;
              import './pool/IUniswapV3PoolImmutables.sol';
              import './pool/IUniswapV3PoolState.sol';
              import './pool/IUniswapV3PoolDerivedState.sol';
              import './pool/IUniswapV3PoolActions.sol';
              import './pool/IUniswapV3PoolOwnerActions.sol';
              import './pool/IUniswapV3PoolEvents.sol';
              /// @title The interface for a Uniswap V3 Pool
              /// @notice A Uniswap pool facilitates swapping and automated market making between any two assets that strictly conform
              /// to the ERC20 specification
              /// @dev The pool interface is broken up into many smaller pieces
              interface IUniswapV3Pool is
                  IUniswapV3PoolImmutables,
                  IUniswapV3PoolState,
                  IUniswapV3PoolDerivedState,
                  IUniswapV3PoolActions,
                  IUniswapV3PoolOwnerActions,
                  IUniswapV3PoolEvents
              {
              }
              // SPDX-License-Identifier: BUSL-1.1
              pragma solidity =0.7.6;
              /// @title Prevents delegatecall to a contract
              /// @notice Base contract that provides a modifier for preventing delegatecall to methods in a child contract
              abstract contract NoDelegateCall {
                  /// @dev The original address of this contract
                  address private immutable original;
                  constructor() {
                      // Immutables are computed in the init code of the contract, and then inlined into the deployed bytecode.
                      // In other words, this variable won't change when it's checked at runtime.
                      original = address(this);
                  }
                  /// @dev Private method is used instead of inlining into modifier because modifiers are copied into each method,
                  ///     and the use of immutable means the address bytes are copied in every place the modifier is used.
                  function checkNotDelegateCall() private view {
                      require(address(this) == original);
                  }
                  /// @notice Prevents delegatecall into the modified method
                  modifier noDelegateCall() {
                      checkNotDelegateCall();
                      _;
                  }
              }
              // SPDX-License-Identifier: GPL-2.0-or-later
              pragma solidity >=0.7.0;
              /// @title Optimized overflow and underflow safe math operations
              /// @notice Contains methods for doing math operations that revert on overflow or underflow for minimal gas cost
              library LowGasSafeMath {
                  /// @notice Returns x + y, reverts if sum overflows uint256
                  /// @param x The augend
                  /// @param y The addend
                  /// @return z The sum of x and y
                  function add(uint256 x, uint256 y) internal pure returns (uint256 z) {
                      require((z = x + y) >= x);
                  }
                  /// @notice Returns x - y, reverts if underflows
                  /// @param x The minuend
                  /// @param y The subtrahend
                  /// @return z The difference of x and y
                  function sub(uint256 x, uint256 y) internal pure returns (uint256 z) {
                      require((z = x - y) <= x);
                  }
                  /// @notice Returns x * y, reverts if overflows
                  /// @param x The multiplicand
                  /// @param y The multiplier
                  /// @return z The product of x and y
                  function mul(uint256 x, uint256 y) internal pure returns (uint256 z) {
                      require(x == 0 || (z = x * y) / x == y);
                  }
                  /// @notice Returns x + y, reverts if overflows or underflows
                  /// @param x The augend
                  /// @param y The addend
                  /// @return z The sum of x and y
                  function add(int256 x, int256 y) internal pure returns (int256 z) {
                      require((z = x + y) >= x == (y >= 0));
                  }
                  /// @notice Returns x - y, reverts if overflows or underflows
                  /// @param x The minuend
                  /// @param y The subtrahend
                  /// @return z The difference of x and y
                  function sub(int256 x, int256 y) internal pure returns (int256 z) {
                      require((z = x - y) <= x == (y >= 0));
                  }
              }
              // SPDX-License-Identifier: GPL-2.0-or-later
              pragma solidity >=0.5.0;
              /// @title Safe casting methods
              /// @notice Contains methods for safely casting between types
              library SafeCast {
                  /// @notice Cast a uint256 to a uint160, revert on overflow
                  /// @param y The uint256 to be downcasted
                  /// @return z The downcasted integer, now type uint160
                  function toUint160(uint256 y) internal pure returns (uint160 z) {
                      require((z = uint160(y)) == y);
                  }
                  /// @notice Cast a int256 to a int128, revert on overflow or underflow
                  /// @param y The int256 to be downcasted
                  /// @return z The downcasted integer, now type int128
                  function toInt128(int256 y) internal pure returns (int128 z) {
                      require((z = int128(y)) == y);
                  }
                  /// @notice Cast a uint256 to a int256, revert on overflow
                  /// @param y The uint256 to be casted
                  /// @return z The casted integer, now type int256
                  function toInt256(uint256 y) internal pure returns (int256 z) {
                      require(y < 2**255);
                      z = int256(y);
                  }
              }
              // SPDX-License-Identifier: BUSL-1.1
              pragma solidity >=0.5.0;
              import './LowGasSafeMath.sol';
              import './SafeCast.sol';
              import './TickMath.sol';
              import './LiquidityMath.sol';
              /// @title Tick
              /// @notice Contains functions for managing tick processes and relevant calculations
              library Tick {
                  using LowGasSafeMath for int256;
                  using SafeCast for int256;
                  // info stored for each initialized individual tick
                  struct Info {
                      // the total position liquidity that references this tick
                      uint128 liquidityGross;
                      // amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left),
                      int128 liquidityNet;
                      // fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick)
                      // only has relative meaning, not absolute — the value depends on when the tick is initialized
                      uint256 feeGrowthOutside0X128;
                      uint256 feeGrowthOutside1X128;
                      // the cumulative tick value on the other side of the tick
                      int56 tickCumulativeOutside;
                      // the seconds per unit of liquidity on the _other_ side of this tick (relative to the current tick)
                      // only has relative meaning, not absolute — the value depends on when the tick is initialized
                      uint160 secondsPerLiquidityOutsideX128;
                      // the seconds spent on the other side of the tick (relative to the current tick)
                      // only has relative meaning, not absolute — the value depends on when the tick is initialized
                      uint32 secondsOutside;
                      // true iff the tick is initialized, i.e. the value is exactly equivalent to the expression liquidityGross != 0
                      // these 8 bits are set to prevent fresh sstores when crossing newly initialized ticks
                      bool initialized;
                  }
                  /// @notice Derives max liquidity per tick from given tick spacing
                  /// @dev Executed within the pool constructor
                  /// @param tickSpacing The amount of required tick separation, realized in multiples of `tickSpacing`
                  ///     e.g., a tickSpacing of 3 requires ticks to be initialized every 3rd tick i.e., ..., -6, -3, 0, 3, 6, ...
                  /// @return The max liquidity per tick
                  function tickSpacingToMaxLiquidityPerTick(int24 tickSpacing) internal pure returns (uint128) {
                      int24 minTick = (TickMath.MIN_TICK / tickSpacing) * tickSpacing;
                      int24 maxTick = (TickMath.MAX_TICK / tickSpacing) * tickSpacing;
                      uint24 numTicks = uint24((maxTick - minTick) / tickSpacing) + 1;
                      return type(uint128).max / numTicks;
                  }
                  /// @notice Retrieves fee growth data
                  /// @param self The mapping containing all tick information for initialized ticks
                  /// @param tickLower The lower tick boundary of the position
                  /// @param tickUpper The upper tick boundary of the position
                  /// @param tickCurrent The current tick
                  /// @param feeGrowthGlobal0X128 The all-time global fee growth, per unit of liquidity, in token0
                  /// @param feeGrowthGlobal1X128 The all-time global fee growth, per unit of liquidity, in token1
                  /// @return feeGrowthInside0X128 The all-time fee growth in token0, per unit of liquidity, inside the position's tick boundaries
                  /// @return feeGrowthInside1X128 The all-time fee growth in token1, per unit of liquidity, inside the position's tick boundaries
                  function getFeeGrowthInside(
                      mapping(int24 => Tick.Info) storage self,
                      int24 tickLower,
                      int24 tickUpper,
                      int24 tickCurrent,
                      uint256 feeGrowthGlobal0X128,
                      uint256 feeGrowthGlobal1X128
                  ) internal view returns (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) {
                      Info storage lower = self[tickLower];
                      Info storage upper = self[tickUpper];
                      // calculate fee growth below
                      uint256 feeGrowthBelow0X128;
                      uint256 feeGrowthBelow1X128;
                      if (tickCurrent >= tickLower) {
                          feeGrowthBelow0X128 = lower.feeGrowthOutside0X128;
                          feeGrowthBelow1X128 = lower.feeGrowthOutside1X128;
                      } else {
                          feeGrowthBelow0X128 = feeGrowthGlobal0X128 - lower.feeGrowthOutside0X128;
                          feeGrowthBelow1X128 = feeGrowthGlobal1X128 - lower.feeGrowthOutside1X128;
                      }
                      // calculate fee growth above
                      uint256 feeGrowthAbove0X128;
                      uint256 feeGrowthAbove1X128;
                      if (tickCurrent < tickUpper) {
                          feeGrowthAbove0X128 = upper.feeGrowthOutside0X128;
                          feeGrowthAbove1X128 = upper.feeGrowthOutside1X128;
                      } else {
                          feeGrowthAbove0X128 = feeGrowthGlobal0X128 - upper.feeGrowthOutside0X128;
                          feeGrowthAbove1X128 = feeGrowthGlobal1X128 - upper.feeGrowthOutside1X128;
                      }
                      feeGrowthInside0X128 = feeGrowthGlobal0X128 - feeGrowthBelow0X128 - feeGrowthAbove0X128;
                      feeGrowthInside1X128 = feeGrowthGlobal1X128 - feeGrowthBelow1X128 - feeGrowthAbove1X128;
                  }
                  /// @notice Updates a tick and returns true if the tick was flipped from initialized to uninitialized, or vice versa
                  /// @param self The mapping containing all tick information for initialized ticks
                  /// @param tick The tick that will be updated
                  /// @param tickCurrent The current tick
                  /// @param liquidityDelta A new amount of liquidity to be added (subtracted) when tick is crossed from left to right (right to left)
                  /// @param feeGrowthGlobal0X128 The all-time global fee growth, per unit of liquidity, in token0
                  /// @param feeGrowthGlobal1X128 The all-time global fee growth, per unit of liquidity, in token1
                  /// @param secondsPerLiquidityCumulativeX128 The all-time seconds per max(1, liquidity) of the pool
                  /// @param time The current block timestamp cast to a uint32
                  /// @param upper true for updating a position's upper tick, or false for updating a position's lower tick
                  /// @param maxLiquidity The maximum liquidity allocation for a single tick
                  /// @return flipped Whether the tick was flipped from initialized to uninitialized, or vice versa
                  function update(
                      mapping(int24 => Tick.Info) storage self,
                      int24 tick,
                      int24 tickCurrent,
                      int128 liquidityDelta,
                      uint256 feeGrowthGlobal0X128,
                      uint256 feeGrowthGlobal1X128,
                      uint160 secondsPerLiquidityCumulativeX128,
                      int56 tickCumulative,
                      uint32 time,
                      bool upper,
                      uint128 maxLiquidity
                  ) internal returns (bool flipped) {
                      Tick.Info storage info = self[tick];
                      uint128 liquidityGrossBefore = info.liquidityGross;
                      uint128 liquidityGrossAfter = LiquidityMath.addDelta(liquidityGrossBefore, liquidityDelta);
                      require(liquidityGrossAfter <= maxLiquidity, 'LO');
                      flipped = (liquidityGrossAfter == 0) != (liquidityGrossBefore == 0);
                      if (liquidityGrossBefore == 0) {
                          // by convention, we assume that all growth before a tick was initialized happened _below_ the tick
                          if (tick <= tickCurrent) {
                              info.feeGrowthOutside0X128 = feeGrowthGlobal0X128;
                              info.feeGrowthOutside1X128 = feeGrowthGlobal1X128;
                              info.secondsPerLiquidityOutsideX128 = secondsPerLiquidityCumulativeX128;
                              info.tickCumulativeOutside = tickCumulative;
                              info.secondsOutside = time;
                          }
                          info.initialized = true;
                      }
                      info.liquidityGross = liquidityGrossAfter;
                      // when the lower (upper) tick is crossed left to right (right to left), liquidity must be added (removed)
                      info.liquidityNet = upper
                          ? int256(info.liquidityNet).sub(liquidityDelta).toInt128()
                          : int256(info.liquidityNet).add(liquidityDelta).toInt128();
                  }
                  /// @notice Clears tick data
                  /// @param self The mapping containing all initialized tick information for initialized ticks
                  /// @param tick The tick that will be cleared
                  function clear(mapping(int24 => Tick.Info) storage self, int24 tick) internal {
                      delete self[tick];
                  }
                  /// @notice Transitions to next tick as needed by price movement
                  /// @param self The mapping containing all tick information for initialized ticks
                  /// @param tick The destination tick of the transition
                  /// @param feeGrowthGlobal0X128 The all-time global fee growth, per unit of liquidity, in token0
                  /// @param feeGrowthGlobal1X128 The all-time global fee growth, per unit of liquidity, in token1
                  /// @param secondsPerLiquidityCumulativeX128 The current seconds per liquidity
                  /// @param time The current block.timestamp
                  /// @return liquidityNet The amount of liquidity added (subtracted) when tick is crossed from left to right (right to left)
                  function cross(
                      mapping(int24 => Tick.Info) storage self,
                      int24 tick,
                      uint256 feeGrowthGlobal0X128,
                      uint256 feeGrowthGlobal1X128,
                      uint160 secondsPerLiquidityCumulativeX128,
                      int56 tickCumulative,
                      uint32 time
                  ) internal returns (int128 liquidityNet) {
                      Tick.Info storage info = self[tick];
                      info.feeGrowthOutside0X128 = feeGrowthGlobal0X128 - info.feeGrowthOutside0X128;
                      info.feeGrowthOutside1X128 = feeGrowthGlobal1X128 - info.feeGrowthOutside1X128;
                      info.secondsPerLiquidityOutsideX128 = secondsPerLiquidityCumulativeX128 - info.secondsPerLiquidityOutsideX128;
                      info.tickCumulativeOutside = tickCumulative - info.tickCumulativeOutside;
                      info.secondsOutside = time - info.secondsOutside;
                      liquidityNet = info.liquidityNet;
                  }
              }
              // SPDX-License-Identifier: BUSL-1.1
              pragma solidity >=0.5.0;
              import './BitMath.sol';
              /// @title Packed tick initialized state library
              /// @notice Stores a packed mapping of tick index to its initialized state
              /// @dev The mapping uses int16 for keys since ticks are represented as int24 and there are 256 (2^8) values per word.
              library TickBitmap {
                  /// @notice Computes the position in the mapping where the initialized bit for a tick lives
                  /// @param tick The tick for which to compute the position
                  /// @return wordPos The key in the mapping containing the word in which the bit is stored
                  /// @return bitPos The bit position in the word where the flag is stored
                  function position(int24 tick) private pure returns (int16 wordPos, uint8 bitPos) {
                      wordPos = int16(tick >> 8);
                      bitPos = uint8(tick % 256);
                  }
                  /// @notice Flips the initialized state for a given tick from false to true, or vice versa
                  /// @param self The mapping in which to flip the tick
                  /// @param tick The tick to flip
                  /// @param tickSpacing The spacing between usable ticks
                  function flipTick(
                      mapping(int16 => uint256) storage self,
                      int24 tick,
                      int24 tickSpacing
                  ) internal {
                      require(tick % tickSpacing == 0); // ensure that the tick is spaced
                      (int16 wordPos, uint8 bitPos) = position(tick / tickSpacing);
                      uint256 mask = 1 << bitPos;
                      self[wordPos] ^= mask;
                  }
                  /// @notice Returns the next initialized tick contained in the same word (or adjacent word) as the tick that is either
                  /// to the left (less than or equal to) or right (greater than) of the given tick
                  /// @param self The mapping in which to compute the next initialized tick
                  /// @param tick The starting tick
                  /// @param tickSpacing The spacing between usable ticks
                  /// @param lte Whether to search for the next initialized tick to the left (less than or equal to the starting tick)
                  /// @return next The next initialized or uninitialized tick up to 256 ticks away from the current tick
                  /// @return initialized Whether the next tick is initialized, as the function only searches within up to 256 ticks
                  function nextInitializedTickWithinOneWord(
                      mapping(int16 => uint256) storage self,
                      int24 tick,
                      int24 tickSpacing,
                      bool lte
                  ) internal view returns (int24 next, bool initialized) {
                      int24 compressed = tick / tickSpacing;
                      if (tick < 0 && tick % tickSpacing != 0) compressed--; // round towards negative infinity
                      if (lte) {
                          (int16 wordPos, uint8 bitPos) = position(compressed);
                          // all the 1s at or to the right of the current bitPos
                          uint256 mask = (1 << bitPos) - 1 + (1 << bitPos);
                          uint256 masked = self[wordPos] & mask;
                          // if there are no initialized ticks to the right of or at the current tick, return rightmost in the word
                          initialized = masked != 0;
                          // overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick
                          next = initialized
                              ? (compressed - int24(bitPos - BitMath.mostSignificantBit(masked))) * tickSpacing
                              : (compressed - int24(bitPos)) * tickSpacing;
                      } else {
                          // start from the word of the next tick, since the current tick state doesn't matter
                          (int16 wordPos, uint8 bitPos) = position(compressed + 1);
                          // all the 1s at or to the left of the bitPos
                          uint256 mask = ~((1 << bitPos) - 1);
                          uint256 masked = self[wordPos] & mask;
                          // if there are no initialized ticks to the left of the current tick, return leftmost in the word
                          initialized = masked != 0;
                          // overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick
                          next = initialized
                              ? (compressed + 1 + int24(BitMath.leastSignificantBit(masked) - bitPos)) * tickSpacing
                              : (compressed + 1 + int24(type(uint8).max - bitPos)) * tickSpacing;
                      }
                  }
              }
              // SPDX-License-Identifier: BUSL-1.1
              pragma solidity >=0.5.0;
              import './FullMath.sol';
              import './FixedPoint128.sol';
              import './LiquidityMath.sol';
              /// @title Position
              /// @notice Positions represent an owner address' liquidity between a lower and upper tick boundary
              /// @dev Positions store additional state for tracking fees owed to the position
              library Position {
                  // info stored for each user's position
                  struct Info {
                      // the amount of liquidity owned by this position
                      uint128 liquidity;
                      // fee growth per unit of liquidity as of the last update to liquidity or fees owed
                      uint256 feeGrowthInside0LastX128;
                      uint256 feeGrowthInside1LastX128;
                      // the fees owed to the position owner in token0/token1
                      uint128 tokensOwed0;
                      uint128 tokensOwed1;
                  }
                  /// @notice Returns the Info struct of a position, given an owner and position boundaries
                  /// @param self The mapping containing all user positions
                  /// @param owner The address of the position owner
                  /// @param tickLower The lower tick boundary of the position
                  /// @param tickUpper The upper tick boundary of the position
                  /// @return position The position info struct of the given owners' position
                  function get(
                      mapping(bytes32 => Info) storage self,
                      address owner,
                      int24 tickLower,
                      int24 tickUpper
                  ) internal view returns (Position.Info storage position) {
                      position = self[keccak256(abi.encodePacked(owner, tickLower, tickUpper))];
                  }
                  /// @notice Credits accumulated fees to a user's position
                  /// @param self The individual position to update
                  /// @param liquidityDelta The change in pool liquidity as a result of the position update
                  /// @param feeGrowthInside0X128 The all-time fee growth in token0, per unit of liquidity, inside the position's tick boundaries
                  /// @param feeGrowthInside1X128 The all-time fee growth in token1, per unit of liquidity, inside the position's tick boundaries
                  function update(
                      Info storage self,
                      int128 liquidityDelta,
                      uint256 feeGrowthInside0X128,
                      uint256 feeGrowthInside1X128
                  ) internal {
                      Info memory _self = self;
                      uint128 liquidityNext;
                      if (liquidityDelta == 0) {
                          require(_self.liquidity > 0, 'NP'); // disallow pokes for 0 liquidity positions
                          liquidityNext = _self.liquidity;
                      } else {
                          liquidityNext = LiquidityMath.addDelta(_self.liquidity, liquidityDelta);
                      }
                      // calculate accumulated fees
                      uint128 tokensOwed0 =
                          uint128(
                              FullMath.mulDiv(
                                  feeGrowthInside0X128 - _self.feeGrowthInside0LastX128,
                                  _self.liquidity,
                                  FixedPoint128.Q128
                              )
                          );
                      uint128 tokensOwed1 =
                          uint128(
                              FullMath.mulDiv(
                                  feeGrowthInside1X128 - _self.feeGrowthInside1LastX128,
                                  _self.liquidity,
                                  FixedPoint128.Q128
                              )
                          );
                      // update the position
                      if (liquidityDelta != 0) self.liquidity = liquidityNext;
                      self.feeGrowthInside0LastX128 = feeGrowthInside0X128;
                      self.feeGrowthInside1LastX128 = feeGrowthInside1X128;
                      if (tokensOwed0 > 0 || tokensOwed1 > 0) {
                          // overflow is acceptable, have to withdraw before you hit type(uint128).max fees
                          self.tokensOwed0 += tokensOwed0;
                          self.tokensOwed1 += tokensOwed1;
                      }
                  }
              }
              // SPDX-License-Identifier: BUSL-1.1
              pragma solidity >=0.5.0;
              /// @title Oracle
              /// @notice Provides price and liquidity data useful for a wide variety of system designs
              /// @dev Instances of stored oracle data, "observations", are collected in the oracle array
              /// Every pool is initialized with an oracle array length of 1. Anyone can pay the SSTOREs to increase the
              /// maximum length of the oracle array. New slots will be added when the array is fully populated.
              /// Observations are overwritten when the full length of the oracle array is populated.
              /// The most recent observation is available, independent of the length of the oracle array, by passing 0 to observe()
              library Oracle {
                  struct Observation {
                      // the block timestamp of the observation
                      uint32 blockTimestamp;
                      // the tick accumulator, i.e. tick * time elapsed since the pool was first initialized
                      int56 tickCumulative;
                      // the seconds per liquidity, i.e. seconds elapsed / max(1, liquidity) since the pool was first initialized
                      uint160 secondsPerLiquidityCumulativeX128;
                      // whether or not the observation is initialized
                      bool initialized;
                  }
                  /// @notice Transforms a previous observation into a new observation, given the passage of time and the current tick and liquidity values
                  /// @dev blockTimestamp _must_ be chronologically equal to or greater than last.blockTimestamp, safe for 0 or 1 overflows
                  /// @param last The specified observation to be transformed
                  /// @param blockTimestamp The timestamp of the new observation
                  /// @param tick The active tick at the time of the new observation
                  /// @param liquidity The total in-range liquidity at the time of the new observation
                  /// @return Observation The newly populated observation
                  function transform(
                      Observation memory last,
                      uint32 blockTimestamp,
                      int24 tick,
                      uint128 liquidity
                  ) private pure returns (Observation memory) {
                      uint32 delta = blockTimestamp - last.blockTimestamp;
                      return
                          Observation({
                              blockTimestamp: blockTimestamp,
                              tickCumulative: last.tickCumulative + int56(tick) * delta,
                              secondsPerLiquidityCumulativeX128: last.secondsPerLiquidityCumulativeX128 +
                                  ((uint160(delta) << 128) / (liquidity > 0 ? liquidity : 1)),
                              initialized: true
                          });
                  }
                  /// @notice Initialize the oracle array by writing the first slot. Called once for the lifecycle of the observations array
                  /// @param self The stored oracle array
                  /// @param time The time of the oracle initialization, via block.timestamp truncated to uint32
                  /// @return cardinality The number of populated elements in the oracle array
                  /// @return cardinalityNext The new length of the oracle array, independent of population
                  function initialize(Observation[65535] storage self, uint32 time)
                      internal
                      returns (uint16 cardinality, uint16 cardinalityNext)
                  {
                      self[0] = Observation({
                          blockTimestamp: time,
                          tickCumulative: 0,
                          secondsPerLiquidityCumulativeX128: 0,
                          initialized: true
                      });
                      return (1, 1);
                  }
                  /// @notice Writes an oracle observation to the array
                  /// @dev Writable at most once per block. Index represents the most recently written element. cardinality and index must be tracked externally.
                  /// If the index is at the end of the allowable array length (according to cardinality), and the next cardinality
                  /// is greater than the current one, cardinality may be increased. This restriction is created to preserve ordering.
                  /// @param self The stored oracle array
                  /// @param index The index of the observation that was most recently written to the observations array
                  /// @param blockTimestamp The timestamp of the new observation
                  /// @param tick The active tick at the time of the new observation
                  /// @param liquidity The total in-range liquidity at the time of the new observation
                  /// @param cardinality The number of populated elements in the oracle array
                  /// @param cardinalityNext The new length of the oracle array, independent of population
                  /// @return indexUpdated The new index of the most recently written element in the oracle array
                  /// @return cardinalityUpdated The new cardinality of the oracle array
                  function write(
                      Observation[65535] storage self,
                      uint16 index,
                      uint32 blockTimestamp,
                      int24 tick,
                      uint128 liquidity,
                      uint16 cardinality,
                      uint16 cardinalityNext
                  ) internal returns (uint16 indexUpdated, uint16 cardinalityUpdated) {
                      Observation memory last = self[index];
                      // early return if we've already written an observation this block
                      if (last.blockTimestamp == blockTimestamp) return (index, cardinality);
                      // if the conditions are right, we can bump the cardinality
                      if (cardinalityNext > cardinality && index == (cardinality - 1)) {
                          cardinalityUpdated = cardinalityNext;
                      } else {
                          cardinalityUpdated = cardinality;
                      }
                      indexUpdated = (index + 1) % cardinalityUpdated;
                      self[indexUpdated] = transform(last, blockTimestamp, tick, liquidity);
                  }
                  /// @notice Prepares the oracle array to store up to `next` observations
                  /// @param self The stored oracle array
                  /// @param current The current next cardinality of the oracle array
                  /// @param next The proposed next cardinality which will be populated in the oracle array
                  /// @return next The next cardinality which will be populated in the oracle array
                  function grow(
                      Observation[65535] storage self,
                      uint16 current,
                      uint16 next
                  ) internal returns (uint16) {
                      require(current > 0, 'I');
                      // no-op if the passed next value isn't greater than the current next value
                      if (next <= current) return current;
                      // store in each slot to prevent fresh SSTOREs in swaps
                      // this data will not be used because the initialized boolean is still false
                      for (uint16 i = current; i < next; i++) self[i].blockTimestamp = 1;
                      return next;
                  }
                  /// @notice comparator for 32-bit timestamps
                  /// @dev safe for 0 or 1 overflows, a and b _must_ be chronologically before or equal to time
                  /// @param time A timestamp truncated to 32 bits
                  /// @param a A comparison timestamp from which to determine the relative position of `time`
                  /// @param b From which to determine the relative position of `time`
                  /// @return bool Whether `a` is chronologically <= `b`
                  function lte(
                      uint32 time,
                      uint32 a,
                      uint32 b
                  ) private pure returns (bool) {
                      // if there hasn't been overflow, no need to adjust
                      if (a <= time && b <= time) return a <= b;
                      uint256 aAdjusted = a > time ? a : a + 2**32;
                      uint256 bAdjusted = b > time ? b : b + 2**32;
                      return aAdjusted <= bAdjusted;
                  }
                  /// @notice Fetches the observations beforeOrAt and atOrAfter a target, i.e. where [beforeOrAt, atOrAfter] is satisfied.
                  /// The result may be the same observation, or adjacent observations.
                  /// @dev The answer must be contained in the array, used when the target is located within the stored observation
                  /// boundaries: older than the most recent observation and younger, or the same age as, the oldest observation
                  /// @param self The stored oracle array
                  /// @param time The current block.timestamp
                  /// @param target The timestamp at which the reserved observation should be for
                  /// @param index The index of the observation that was most recently written to the observations array
                  /// @param cardinality The number of populated elements in the oracle array
                  /// @return beforeOrAt The observation recorded before, or at, the target
                  /// @return atOrAfter The observation recorded at, or after, the target
                  function binarySearch(
                      Observation[65535] storage self,
                      uint32 time,
                      uint32 target,
                      uint16 index,
                      uint16 cardinality
                  ) private view returns (Observation memory beforeOrAt, Observation memory atOrAfter) {
                      uint256 l = (index + 1) % cardinality; // oldest observation
                      uint256 r = l + cardinality - 1; // newest observation
                      uint256 i;
                      while (true) {
                          i = (l + r) / 2;
                          beforeOrAt = self[i % cardinality];
                          // we've landed on an uninitialized tick, keep searching higher (more recently)
                          if (!beforeOrAt.initialized) {
                              l = i + 1;
                              continue;
                          }
                          atOrAfter = self[(i + 1) % cardinality];
                          bool targetAtOrAfter = lte(time, beforeOrAt.blockTimestamp, target);
                          // check if we've found the answer!
                          if (targetAtOrAfter && lte(time, target, atOrAfter.blockTimestamp)) break;
                          if (!targetAtOrAfter) r = i - 1;
                          else l = i + 1;
                      }
                  }
                  /// @notice Fetches the observations beforeOrAt and atOrAfter a given target, i.e. where [beforeOrAt, atOrAfter] is satisfied
                  /// @dev Assumes there is at least 1 initialized observation.
                  /// Used by observeSingle() to compute the counterfactual accumulator values as of a given block timestamp.
                  /// @param self The stored oracle array
                  /// @param time The current block.timestamp
                  /// @param target The timestamp at which the reserved observation should be for
                  /// @param tick The active tick at the time of the returned or simulated observation
                  /// @param index The index of the observation that was most recently written to the observations array
                  /// @param liquidity The total pool liquidity at the time of the call
                  /// @param cardinality The number of populated elements in the oracle array
                  /// @return beforeOrAt The observation which occurred at, or before, the given timestamp
                  /// @return atOrAfter The observation which occurred at, or after, the given timestamp
                  function getSurroundingObservations(
                      Observation[65535] storage self,
                      uint32 time,
                      uint32 target,
                      int24 tick,
                      uint16 index,
                      uint128 liquidity,
                      uint16 cardinality
                  ) private view returns (Observation memory beforeOrAt, Observation memory atOrAfter) {
                      // optimistically set before to the newest observation
                      beforeOrAt = self[index];
                      // if the target is chronologically at or after the newest observation, we can early return
                      if (lte(time, beforeOrAt.blockTimestamp, target)) {
                          if (beforeOrAt.blockTimestamp == target) {
                              // if newest observation equals target, we're in the same block, so we can ignore atOrAfter
                              return (beforeOrAt, atOrAfter);
                          } else {
                              // otherwise, we need to transform
                              return (beforeOrAt, transform(beforeOrAt, target, tick, liquidity));
                          }
                      }
                      // now, set before to the oldest observation
                      beforeOrAt = self[(index + 1) % cardinality];
                      if (!beforeOrAt.initialized) beforeOrAt = self[0];
                      // ensure that the target is chronologically at or after the oldest observation
                      require(lte(time, beforeOrAt.blockTimestamp, target), 'OLD');
                      // if we've reached this point, we have to binary search
                      return binarySearch(self, time, target, index, cardinality);
                  }
                  /// @dev Reverts if an observation at or before the desired observation timestamp does not exist.
                  /// 0 may be passed as `secondsAgo' to return the current cumulative values.
                  /// If called with a timestamp falling between two observations, returns the counterfactual accumulator values
                  /// at exactly the timestamp between the two observations.
                  /// @param self The stored oracle array
                  /// @param time The current block timestamp
                  /// @param secondsAgo The amount of time to look back, in seconds, at which point to return an observation
                  /// @param tick The current tick
                  /// @param index The index of the observation that was most recently written to the observations array
                  /// @param liquidity The current in-range pool liquidity
                  /// @param cardinality The number of populated elements in the oracle array
                  /// @return tickCumulative The tick * time elapsed since the pool was first initialized, as of `secondsAgo`
                  /// @return secondsPerLiquidityCumulativeX128 The time elapsed / max(1, liquidity) since the pool was first initialized, as of `secondsAgo`
                  function observeSingle(
                      Observation[65535] storage self,
                      uint32 time,
                      uint32 secondsAgo,
                      int24 tick,
                      uint16 index,
                      uint128 liquidity,
                      uint16 cardinality
                  ) internal view returns (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) {
                      if (secondsAgo == 0) {
                          Observation memory last = self[index];
                          if (last.blockTimestamp != time) last = transform(last, time, tick, liquidity);
                          return (last.tickCumulative, last.secondsPerLiquidityCumulativeX128);
                      }
                      uint32 target = time - secondsAgo;
                      (Observation memory beforeOrAt, Observation memory atOrAfter) =
                          getSurroundingObservations(self, time, target, tick, index, liquidity, cardinality);
                      if (target == beforeOrAt.blockTimestamp) {
                          // we're at the left boundary
                          return (beforeOrAt.tickCumulative, beforeOrAt.secondsPerLiquidityCumulativeX128);
                      } else if (target == atOrAfter.blockTimestamp) {
                          // we're at the right boundary
                          return (atOrAfter.tickCumulative, atOrAfter.secondsPerLiquidityCumulativeX128);
                      } else {
                          // we're in the middle
                          uint32 observationTimeDelta = atOrAfter.blockTimestamp - beforeOrAt.blockTimestamp;
                          uint32 targetDelta = target - beforeOrAt.blockTimestamp;
                          return (
                              beforeOrAt.tickCumulative +
                                  ((atOrAfter.tickCumulative - beforeOrAt.tickCumulative) / observationTimeDelta) *
                                  targetDelta,
                              beforeOrAt.secondsPerLiquidityCumulativeX128 +
                                  uint160(
                                      (uint256(
                                          atOrAfter.secondsPerLiquidityCumulativeX128 - beforeOrAt.secondsPerLiquidityCumulativeX128
                                      ) * targetDelta) / observationTimeDelta
                                  )
                          );
                      }
                  }
                  /// @notice Returns the accumulator values as of each time seconds ago from the given time in the array of `secondsAgos`
                  /// @dev Reverts if `secondsAgos` > oldest observation
                  /// @param self The stored oracle array
                  /// @param time The current block.timestamp
                  /// @param secondsAgos Each amount of time to look back, in seconds, at which point to return an observation
                  /// @param tick The current tick
                  /// @param index The index of the observation that was most recently written to the observations array
                  /// @param liquidity The current in-range pool liquidity
                  /// @param cardinality The number of populated elements in the oracle array
                  /// @return tickCumulatives The tick * time elapsed since the pool was first initialized, as of each `secondsAgo`
                  /// @return secondsPerLiquidityCumulativeX128s The cumulative seconds / max(1, liquidity) since the pool was first initialized, as of each `secondsAgo`
                  function observe(
                      Observation[65535] storage self,
                      uint32 time,
                      uint32[] memory secondsAgos,
                      int24 tick,
                      uint16 index,
                      uint128 liquidity,
                      uint16 cardinality
                  ) internal view returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) {
                      require(cardinality > 0, 'I');
                      tickCumulatives = new int56[](secondsAgos.length);
                      secondsPerLiquidityCumulativeX128s = new uint160[](secondsAgos.length);
                      for (uint256 i = 0; i < secondsAgos.length; i++) {
                          (tickCumulatives[i], secondsPerLiquidityCumulativeX128s[i]) = observeSingle(
                              self,
                              time,
                              secondsAgos[i],
                              tick,
                              index,
                              liquidity,
                              cardinality
                          );
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity >=0.4.0;
              /// @title Contains 512-bit math functions
              /// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision
              /// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits
              library FullMath {
                  /// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
                  /// @param a The multiplicand
                  /// @param b The multiplier
                  /// @param denominator The divisor
                  /// @return result The 256-bit result
                  /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv
                  function mulDiv(
                      uint256 a,
                      uint256 b,
                      uint256 denominator
                  ) internal pure returns (uint256 result) {
                      // 512-bit multiply [prod1 prod0] = a * b
                      // Compute the product mod 2**256 and mod 2**256 - 1
                      // then use the Chinese Remainder Theorem to reconstruct
                      // the 512 bit result. The result is stored in two 256
                      // variables such that product = prod1 * 2**256 + prod0
                      uint256 prod0; // Least significant 256 bits of the product
                      uint256 prod1; // Most significant 256 bits of the product
                      assembly {
                          let mm := mulmod(a, b, not(0))
                          prod0 := mul(a, b)
                          prod1 := sub(sub(mm, prod0), lt(mm, prod0))
                      }
                      // Handle non-overflow cases, 256 by 256 division
                      if (prod1 == 0) {
                          require(denominator > 0);
                          assembly {
                              result := div(prod0, denominator)
                          }
                          return result;
                      }
                      // Make sure the result is less than 2**256.
                      // Also prevents denominator == 0
                      require(denominator > prod1);
                      ///////////////////////////////////////////////
                      // 512 by 256 division.
                      ///////////////////////////////////////////////
                      // Make division exact by subtracting the remainder from [prod1 prod0]
                      // Compute remainder using mulmod
                      uint256 remainder;
                      assembly {
                          remainder := mulmod(a, b, denominator)
                      }
                      // Subtract 256 bit number from 512 bit number
                      assembly {
                          prod1 := sub(prod1, gt(remainder, prod0))
                          prod0 := sub(prod0, remainder)
                      }
                      // Factor powers of two out of denominator
                      // Compute largest power of two divisor of denominator.
                      // Always >= 1.
                      uint256 twos = -denominator & denominator;
                      // Divide denominator by power of two
                      assembly {
                          denominator := div(denominator, twos)
                      }
                      // Divide [prod1 prod0] by the factors of two
                      assembly {
                          prod0 := div(prod0, twos)
                      }
                      // Shift in bits from prod1 into prod0. For this we need
                      // to flip `twos` such that it is 2**256 / twos.
                      // If twos is zero, then it becomes one
                      assembly {
                          twos := add(div(sub(0, twos), twos), 1)
                      }
                      prod0 |= prod1 * twos;
                      // Invert denominator mod 2**256
                      // Now that denominator is an odd number, it has an inverse
                      // modulo 2**256 such that denominator * inv = 1 mod 2**256.
                      // Compute the inverse by starting with a seed that is correct
                      // correct for four bits. That is, denominator * inv = 1 mod 2**4
                      uint256 inv = (3 * denominator) ^ 2;
                      // Now use Newton-Raphson iteration to improve the precision.
                      // Thanks to Hensel's lifting lemma, this also works in modular
                      // arithmetic, doubling the correct bits in each step.
                      inv *= 2 - denominator * inv; // inverse mod 2**8
                      inv *= 2 - denominator * inv; // inverse mod 2**16
                      inv *= 2 - denominator * inv; // inverse mod 2**32
                      inv *= 2 - denominator * inv; // inverse mod 2**64
                      inv *= 2 - denominator * inv; // inverse mod 2**128
                      inv *= 2 - denominator * inv; // inverse mod 2**256
                      // Because the division is now exact we can divide by multiplying
                      // with the modular inverse of denominator. This will give us the
                      // correct result modulo 2**256. Since the precoditions guarantee
                      // that the outcome is less than 2**256, this is the final result.
                      // We don't need to compute the high bits of the result and prod1
                      // is no longer required.
                      result = prod0 * inv;
                      return result;
                  }
                  /// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
                  /// @param a The multiplicand
                  /// @param b The multiplier
                  /// @param denominator The divisor
                  /// @return result The 256-bit result
                  function mulDivRoundingUp(
                      uint256 a,
                      uint256 b,
                      uint256 denominator
                  ) internal pure returns (uint256 result) {
                      result = mulDiv(a, b, denominator);
                      if (mulmod(a, b, denominator) > 0) {
                          require(result < type(uint256).max);
                          result++;
                      }
                  }
              }
              // SPDX-License-Identifier: GPL-2.0-or-later
              pragma solidity >=0.4.0;
              /// @title FixedPoint128
              /// @notice A library for handling binary fixed point numbers, see https://en.wikipedia.org/wiki/Q_(number_format)
              library FixedPoint128 {
                  uint256 internal constant Q128 = 0x100000000000000000000000000000000;
              }
              // SPDX-License-Identifier: GPL-2.0-or-later
              pragma solidity >=0.6.0;
              import '../interfaces/IERC20Minimal.sol';
              /// @title TransferHelper
              /// @notice Contains helper methods for interacting with ERC20 tokens that do not consistently return true/false
              library TransferHelper {
                  /// @notice Transfers tokens from msg.sender to a recipient
                  /// @dev Calls transfer on token contract, errors with TF if transfer fails
                  /// @param token The contract address of the token which will be transferred
                  /// @param to The recipient of the transfer
                  /// @param value The value of the transfer
                  function safeTransfer(
                      address token,
                      address to,
                      uint256 value
                  ) internal {
                      (bool success, bytes memory data) =
                          token.call(abi.encodeWithSelector(IERC20Minimal.transfer.selector, to, value));
                      require(success && (data.length == 0 || abi.decode(data, (bool))), 'TF');
                  }
              }
              // SPDX-License-Identifier: GPL-2.0-or-later
              pragma solidity >=0.5.0;
              /// @title Math library for computing sqrt prices from ticks and vice versa
              /// @notice Computes sqrt price for ticks of size 1.0001, i.e. sqrt(1.0001^tick) as fixed point Q64.96 numbers. Supports
              /// prices between 2**-128 and 2**128
              library TickMath {
                  /// @dev The minimum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**-128
                  int24 internal constant MIN_TICK = -887272;
                  /// @dev The maximum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**128
                  int24 internal constant MAX_TICK = -MIN_TICK;
                  /// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK)
                  uint160 internal constant MIN_SQRT_RATIO = 4295128739;
                  /// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK)
                  uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342;
                  /// @notice Calculates sqrt(1.0001^tick) * 2^96
                  /// @dev Throws if |tick| > max tick
                  /// @param tick The input tick for the above formula
                  /// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the ratio of the two assets (token1/token0)
                  /// at the given tick
                  function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) {
                      uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick));
                      require(absTick <= uint256(MAX_TICK), 'T');
                      uint256 ratio = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000;
                      if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128;
                      if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
                      if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
                      if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128;
                      if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
                      if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
                      if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
                      if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
                      if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
                      if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
                      if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
                      if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
                      if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
                      if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
                      if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128;
                      if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
                      if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128;
                      if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128;
                      if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128;
                      if (tick > 0) ratio = type(uint256).max / ratio;
                      // this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96.
                      // we then downcast because we know the result always fits within 160 bits due to our tick input constraint
                      // we round up in the division so getTickAtSqrtRatio of the output price is always consistent
                      sqrtPriceX96 = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1));
                  }
                  /// @notice Calculates the greatest tick value such that getRatioAtTick(tick) <= ratio
                  /// @dev Throws in case sqrtPriceX96 < MIN_SQRT_RATIO, as MIN_SQRT_RATIO is the lowest value getRatioAtTick may
                  /// ever return.
                  /// @param sqrtPriceX96 The sqrt ratio for which to compute the tick as a Q64.96
                  /// @return tick The greatest tick for which the ratio is less than or equal to the input ratio
                  function getTickAtSqrtRatio(uint160 sqrtPriceX96) internal pure returns (int24 tick) {
                      // second inequality must be < because the price can never reach the price at the max tick
                      require(sqrtPriceX96 >= MIN_SQRT_RATIO && sqrtPriceX96 < MAX_SQRT_RATIO, 'R');
                      uint256 ratio = uint256(sqrtPriceX96) << 32;
                      uint256 r = ratio;
                      uint256 msb = 0;
                      assembly {
                          let f := shl(7, gt(r, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))
                          msb := or(msb, f)
                          r := shr(f, r)
                      }
                      assembly {
                          let f := shl(6, gt(r, 0xFFFFFFFFFFFFFFFF))
                          msb := or(msb, f)
                          r := shr(f, r)
                      }
                      assembly {
                          let f := shl(5, gt(r, 0xFFFFFFFF))
                          msb := or(msb, f)
                          r := shr(f, r)
                      }
                      assembly {
                          let f := shl(4, gt(r, 0xFFFF))
                          msb := or(msb, f)
                          r := shr(f, r)
                      }
                      assembly {
                          let f := shl(3, gt(r, 0xFF))
                          msb := or(msb, f)
                          r := shr(f, r)
                      }
                      assembly {
                          let f := shl(2, gt(r, 0xF))
                          msb := or(msb, f)
                          r := shr(f, r)
                      }
                      assembly {
                          let f := shl(1, gt(r, 0x3))
                          msb := or(msb, f)
                          r := shr(f, r)
                      }
                      assembly {
                          let f := gt(r, 0x1)
                          msb := or(msb, f)
                      }
                      if (msb >= 128) r = ratio >> (msb - 127);
                      else r = ratio << (127 - msb);
                      int256 log_2 = (int256(msb) - 128) << 64;
                      assembly {
                          r := shr(127, mul(r, r))
                          let f := shr(128, r)
                          log_2 := or(log_2, shl(63, f))
                          r := shr(f, r)
                      }
                      assembly {
                          r := shr(127, mul(r, r))
                          let f := shr(128, r)
                          log_2 := or(log_2, shl(62, f))
                          r := shr(f, r)
                      }
                      assembly {
                          r := shr(127, mul(r, r))
                          let f := shr(128, r)
                          log_2 := or(log_2, shl(61, f))
                          r := shr(f, r)
                      }
                      assembly {
                          r := shr(127, mul(r, r))
                          let f := shr(128, r)
                          log_2 := or(log_2, shl(60, f))
                          r := shr(f, r)
                      }
                      assembly {
                          r := shr(127, mul(r, r))
                          let f := shr(128, r)
                          log_2 := or(log_2, shl(59, f))
                          r := shr(f, r)
                      }
                      assembly {
                          r := shr(127, mul(r, r))
                          let f := shr(128, r)
                          log_2 := or(log_2, shl(58, f))
                          r := shr(f, r)
                      }
                      assembly {
                          r := shr(127, mul(r, r))
                          let f := shr(128, r)
                          log_2 := or(log_2, shl(57, f))
                          r := shr(f, r)
                      }
                      assembly {
                          r := shr(127, mul(r, r))
                          let f := shr(128, r)
                          log_2 := or(log_2, shl(56, f))
                          r := shr(f, r)
                      }
                      assembly {
                          r := shr(127, mul(r, r))
                          let f := shr(128, r)
                          log_2 := or(log_2, shl(55, f))
                          r := shr(f, r)
                      }
                      assembly {
                          r := shr(127, mul(r, r))
                          let f := shr(128, r)
                          log_2 := or(log_2, shl(54, f))
                          r := shr(f, r)
                      }
                      assembly {
                          r := shr(127, mul(r, r))
                          let f := shr(128, r)
                          log_2 := or(log_2, shl(53, f))
                          r := shr(f, r)
                      }
                      assembly {
                          r := shr(127, mul(r, r))
                          let f := shr(128, r)
                          log_2 := or(log_2, shl(52, f))
                          r := shr(f, r)
                      }
                      assembly {
                          r := shr(127, mul(r, r))
                          let f := shr(128, r)
                          log_2 := or(log_2, shl(51, f))
                          r := shr(f, r)
                      }
                      assembly {
                          r := shr(127, mul(r, r))
                          let f := shr(128, r)
                          log_2 := or(log_2, shl(50, f))
                      }
                      int256 log_sqrt10001 = log_2 * 255738958999603826347141; // 128.128 number
                      int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128);
                      int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128);
                      tick = tickLow == tickHi ? tickLow : getSqrtRatioAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow;
                  }
              }
              // SPDX-License-Identifier: GPL-2.0-or-later
              pragma solidity >=0.5.0;
              /// @title Math library for liquidity
              library LiquidityMath {
                  /// @notice Add a signed liquidity delta to liquidity and revert if it overflows or underflows
                  /// @param x The liquidity before change
                  /// @param y The delta by which liquidity should be changed
                  /// @return z The liquidity delta
                  function addDelta(uint128 x, int128 y) internal pure returns (uint128 z) {
                      if (y < 0) {
                          require((z = x - uint128(-y)) < x, 'LS');
                      } else {
                          require((z = x + uint128(y)) >= x, 'LA');
                      }
                  }
              }
              // SPDX-License-Identifier: BUSL-1.1
              pragma solidity >=0.5.0;
              import './LowGasSafeMath.sol';
              import './SafeCast.sol';
              import './FullMath.sol';
              import './UnsafeMath.sol';
              import './FixedPoint96.sol';
              /// @title Functions based on Q64.96 sqrt price and liquidity
              /// @notice Contains the math that uses square root of price as a Q64.96 and liquidity to compute deltas
              library SqrtPriceMath {
                  using LowGasSafeMath for uint256;
                  using SafeCast for uint256;
                  /// @notice Gets the next sqrt price given a delta of token0
                  /// @dev Always rounds up, because in the exact output case (increasing price) we need to move the price at least
                  /// far enough to get the desired output amount, and in the exact input case (decreasing price) we need to move the
                  /// price less in order to not send too much output.
                  /// The most precise formula for this is liquidity * sqrtPX96 / (liquidity +- amount * sqrtPX96),
                  /// if this is impossible because of overflow, we calculate liquidity / (liquidity / sqrtPX96 +- amount).
                  /// @param sqrtPX96 The starting price, i.e. before accounting for the token0 delta
                  /// @param liquidity The amount of usable liquidity
                  /// @param amount How much of token0 to add or remove from virtual reserves
                  /// @param add Whether to add or remove the amount of token0
                  /// @return The price after adding or removing amount, depending on add
                  function getNextSqrtPriceFromAmount0RoundingUp(
                      uint160 sqrtPX96,
                      uint128 liquidity,
                      uint256 amount,
                      bool add
                  ) internal pure returns (uint160) {
                      // we short circuit amount == 0 because the result is otherwise not guaranteed to equal the input price
                      if (amount == 0) return sqrtPX96;
                      uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION;
                      if (add) {
                          uint256 product;
                          if ((product = amount * sqrtPX96) / amount == sqrtPX96) {
                              uint256 denominator = numerator1 + product;
                              if (denominator >= numerator1)
                                  // always fits in 160 bits
                                  return uint160(FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator));
                          }
                          return uint160(UnsafeMath.divRoundingUp(numerator1, (numerator1 / sqrtPX96).add(amount)));
                      } else {
                          uint256 product;
                          // if the product overflows, we know the denominator underflows
                          // in addition, we must check that the denominator does not underflow
                          require((product = amount * sqrtPX96) / amount == sqrtPX96 && numerator1 > product);
                          uint256 denominator = numerator1 - product;
                          return FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator).toUint160();
                      }
                  }
                  /// @notice Gets the next sqrt price given a delta of token1
                  /// @dev Always rounds down, because in the exact output case (decreasing price) we need to move the price at least
                  /// far enough to get the desired output amount, and in the exact input case (increasing price) we need to move the
                  /// price less in order to not send too much output.
                  /// The formula we compute is within <1 wei of the lossless version: sqrtPX96 +- amount / liquidity
                  /// @param sqrtPX96 The starting price, i.e., before accounting for the token1 delta
                  /// @param liquidity The amount of usable liquidity
                  /// @param amount How much of token1 to add, or remove, from virtual reserves
                  /// @param add Whether to add, or remove, the amount of token1
                  /// @return The price after adding or removing `amount`
                  function getNextSqrtPriceFromAmount1RoundingDown(
                      uint160 sqrtPX96,
                      uint128 liquidity,
                      uint256 amount,
                      bool add
                  ) internal pure returns (uint160) {
                      // if we're adding (subtracting), rounding down requires rounding the quotient down (up)
                      // in both cases, avoid a mulDiv for most inputs
                      if (add) {
                          uint256 quotient =
                              (
                                  amount <= type(uint160).max
                                      ? (amount << FixedPoint96.RESOLUTION) / liquidity
                                      : FullMath.mulDiv(amount, FixedPoint96.Q96, liquidity)
                              );
                          return uint256(sqrtPX96).add(quotient).toUint160();
                      } else {
                          uint256 quotient =
                              (
                                  amount <= type(uint160).max
                                      ? UnsafeMath.divRoundingUp(amount << FixedPoint96.RESOLUTION, liquidity)
                                      : FullMath.mulDivRoundingUp(amount, FixedPoint96.Q96, liquidity)
                              );
                          require(sqrtPX96 > quotient);
                          // always fits 160 bits
                          return uint160(sqrtPX96 - quotient);
                      }
                  }
                  /// @notice Gets the next sqrt price given an input amount of token0 or token1
                  /// @dev Throws if price or liquidity are 0, or if the next price is out of bounds
                  /// @param sqrtPX96 The starting price, i.e., before accounting for the input amount
                  /// @param liquidity The amount of usable liquidity
                  /// @param amountIn How much of token0, or token1, is being swapped in
                  /// @param zeroForOne Whether the amount in is token0 or token1
                  /// @return sqrtQX96 The price after adding the input amount to token0 or token1
                  function getNextSqrtPriceFromInput(
                      uint160 sqrtPX96,
                      uint128 liquidity,
                      uint256 amountIn,
                      bool zeroForOne
                  ) internal pure returns (uint160 sqrtQX96) {
                      require(sqrtPX96 > 0);
                      require(liquidity > 0);
                      // round to make sure that we don't pass the target price
                      return
                          zeroForOne
                              ? getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountIn, true)
                              : getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountIn, true);
                  }
                  /// @notice Gets the next sqrt price given an output amount of token0 or token1
                  /// @dev Throws if price or liquidity are 0 or the next price is out of bounds
                  /// @param sqrtPX96 The starting price before accounting for the output amount
                  /// @param liquidity The amount of usable liquidity
                  /// @param amountOut How much of token0, or token1, is being swapped out
                  /// @param zeroForOne Whether the amount out is token0 or token1
                  /// @return sqrtQX96 The price after removing the output amount of token0 or token1
                  function getNextSqrtPriceFromOutput(
                      uint160 sqrtPX96,
                      uint128 liquidity,
                      uint256 amountOut,
                      bool zeroForOne
                  ) internal pure returns (uint160 sqrtQX96) {
                      require(sqrtPX96 > 0);
                      require(liquidity > 0);
                      // round to make sure that we pass the target price
                      return
                          zeroForOne
                              ? getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountOut, false)
                              : getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountOut, false);
                  }
                  /// @notice Gets the amount0 delta between two prices
                  /// @dev Calculates liquidity / sqrt(lower) - liquidity / sqrt(upper),
                  /// i.e. liquidity * (sqrt(upper) - sqrt(lower)) / (sqrt(upper) * sqrt(lower))
                  /// @param sqrtRatioAX96 A sqrt price
                  /// @param sqrtRatioBX96 Another sqrt price
                  /// @param liquidity The amount of usable liquidity
                  /// @param roundUp Whether to round the amount up or down
                  /// @return amount0 Amount of token0 required to cover a position of size liquidity between the two passed prices
                  function getAmount0Delta(
                      uint160 sqrtRatioAX96,
                      uint160 sqrtRatioBX96,
                      uint128 liquidity,
                      bool roundUp
                  ) internal pure returns (uint256 amount0) {
                      if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
                      uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION;
                      uint256 numerator2 = sqrtRatioBX96 - sqrtRatioAX96;
                      require(sqrtRatioAX96 > 0);
                      return
                          roundUp
                              ? UnsafeMath.divRoundingUp(
                                  FullMath.mulDivRoundingUp(numerator1, numerator2, sqrtRatioBX96),
                                  sqrtRatioAX96
                              )
                              : FullMath.mulDiv(numerator1, numerator2, sqrtRatioBX96) / sqrtRatioAX96;
                  }
                  /// @notice Gets the amount1 delta between two prices
                  /// @dev Calculates liquidity * (sqrt(upper) - sqrt(lower))
                  /// @param sqrtRatioAX96 A sqrt price
                  /// @param sqrtRatioBX96 Another sqrt price
                  /// @param liquidity The amount of usable liquidity
                  /// @param roundUp Whether to round the amount up, or down
                  /// @return amount1 Amount of token1 required to cover a position of size liquidity between the two passed prices
                  function getAmount1Delta(
                      uint160 sqrtRatioAX96,
                      uint160 sqrtRatioBX96,
                      uint128 liquidity,
                      bool roundUp
                  ) internal pure returns (uint256 amount1) {
                      if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
                      return
                          roundUp
                              ? FullMath.mulDivRoundingUp(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96)
                              : FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96);
                  }
                  /// @notice Helper that gets signed token0 delta
                  /// @param sqrtRatioAX96 A sqrt price
                  /// @param sqrtRatioBX96 Another sqrt price
                  /// @param liquidity The change in liquidity for which to compute the amount0 delta
                  /// @return amount0 Amount of token0 corresponding to the passed liquidityDelta between the two prices
                  function getAmount0Delta(
                      uint160 sqrtRatioAX96,
                      uint160 sqrtRatioBX96,
                      int128 liquidity
                  ) internal pure returns (int256 amount0) {
                      return
                          liquidity < 0
                              ? -getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256()
                              : getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256();
                  }
                  /// @notice Helper that gets signed token1 delta
                  /// @param sqrtRatioAX96 A sqrt price
                  /// @param sqrtRatioBX96 Another sqrt price
                  /// @param liquidity The change in liquidity for which to compute the amount1 delta
                  /// @return amount1 Amount of token1 corresponding to the passed liquidityDelta between the two prices
                  function getAmount1Delta(
                      uint160 sqrtRatioAX96,
                      uint160 sqrtRatioBX96,
                      int128 liquidity
                  ) internal pure returns (int256 amount1) {
                      return
                          liquidity < 0
                              ? -getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256()
                              : getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256();
                  }
              }
              // SPDX-License-Identifier: BUSL-1.1
              pragma solidity >=0.5.0;
              import './FullMath.sol';
              import './SqrtPriceMath.sol';
              /// @title Computes the result of a swap within ticks
              /// @notice Contains methods for computing the result of a swap within a single tick price range, i.e., a single tick.
              library SwapMath {
                  /// @notice Computes the result of swapping some amount in, or amount out, given the parameters of the swap
                  /// @dev The fee, plus the amount in, will never exceed the amount remaining if the swap's `amountSpecified` is positive
                  /// @param sqrtRatioCurrentX96 The current sqrt price of the pool
                  /// @param sqrtRatioTargetX96 The price that cannot be exceeded, from which the direction of the swap is inferred
                  /// @param liquidity The usable liquidity
                  /// @param amountRemaining How much input or output amount is remaining to be swapped in/out
                  /// @param feePips The fee taken from the input amount, expressed in hundredths of a bip
                  /// @return sqrtRatioNextX96 The price after swapping the amount in/out, not to exceed the price target
                  /// @return amountIn The amount to be swapped in, of either token0 or token1, based on the direction of the swap
                  /// @return amountOut The amount to be received, of either token0 or token1, based on the direction of the swap
                  /// @return feeAmount The amount of input that will be taken as a fee
                  function computeSwapStep(
                      uint160 sqrtRatioCurrentX96,
                      uint160 sqrtRatioTargetX96,
                      uint128 liquidity,
                      int256 amountRemaining,
                      uint24 feePips
                  )
                      internal
                      pure
                      returns (
                          uint160 sqrtRatioNextX96,
                          uint256 amountIn,
                          uint256 amountOut,
                          uint256 feeAmount
                      )
                  {
                      bool zeroForOne = sqrtRatioCurrentX96 >= sqrtRatioTargetX96;
                      bool exactIn = amountRemaining >= 0;
                      if (exactIn) {
                          uint256 amountRemainingLessFee = FullMath.mulDiv(uint256(amountRemaining), 1e6 - feePips, 1e6);
                          amountIn = zeroForOne
                              ? SqrtPriceMath.getAmount0Delta(sqrtRatioTargetX96, sqrtRatioCurrentX96, liquidity, true)
                              : SqrtPriceMath.getAmount1Delta(sqrtRatioCurrentX96, sqrtRatioTargetX96, liquidity, true);
                          if (amountRemainingLessFee >= amountIn) sqrtRatioNextX96 = sqrtRatioTargetX96;
                          else
                              sqrtRatioNextX96 = SqrtPriceMath.getNextSqrtPriceFromInput(
                                  sqrtRatioCurrentX96,
                                  liquidity,
                                  amountRemainingLessFee,
                                  zeroForOne
                              );
                      } else {
                          amountOut = zeroForOne
                              ? SqrtPriceMath.getAmount1Delta(sqrtRatioTargetX96, sqrtRatioCurrentX96, liquidity, false)
                              : SqrtPriceMath.getAmount0Delta(sqrtRatioCurrentX96, sqrtRatioTargetX96, liquidity, false);
                          if (uint256(-amountRemaining) >= amountOut) sqrtRatioNextX96 = sqrtRatioTargetX96;
                          else
                              sqrtRatioNextX96 = SqrtPriceMath.getNextSqrtPriceFromOutput(
                                  sqrtRatioCurrentX96,
                                  liquidity,
                                  uint256(-amountRemaining),
                                  zeroForOne
                              );
                      }
                      bool max = sqrtRatioTargetX96 == sqrtRatioNextX96;
                      // get the input/output amounts
                      if (zeroForOne) {
                          amountIn = max && exactIn
                              ? amountIn
                              : SqrtPriceMath.getAmount0Delta(sqrtRatioNextX96, sqrtRatioCurrentX96, liquidity, true);
                          amountOut = max && !exactIn
                              ? amountOut
                              : SqrtPriceMath.getAmount1Delta(sqrtRatioNextX96, sqrtRatioCurrentX96, liquidity, false);
                      } else {
                          amountIn = max && exactIn
                              ? amountIn
                              : SqrtPriceMath.getAmount1Delta(sqrtRatioCurrentX96, sqrtRatioNextX96, liquidity, true);
                          amountOut = max && !exactIn
                              ? amountOut
                              : SqrtPriceMath.getAmount0Delta(sqrtRatioCurrentX96, sqrtRatioNextX96, liquidity, false);
                      }
                      // cap the output amount to not exceed the remaining output amount
                      if (!exactIn && amountOut > uint256(-amountRemaining)) {
                          amountOut = uint256(-amountRemaining);
                      }
                      if (exactIn && sqrtRatioNextX96 != sqrtRatioTargetX96) {
                          // we didn't reach the target, so take the remainder of the maximum input as fee
                          feeAmount = uint256(amountRemaining) - amountIn;
                      } else {
                          feeAmount = FullMath.mulDivRoundingUp(amountIn, feePips, 1e6 - feePips);
                      }
                  }
              }
              // SPDX-License-Identifier: GPL-2.0-or-later
              pragma solidity >=0.5.0;
              /// @title An interface for a contract that is capable of deploying Uniswap V3 Pools
              /// @notice A contract that constructs a pool must implement this to pass arguments to the pool
              /// @dev This is used to avoid having constructor arguments in the pool contract, which results in the init code hash
              /// of the pool being constant allowing the CREATE2 address of the pool to be cheaply computed on-chain
              interface IUniswapV3PoolDeployer {
                  /// @notice Get the parameters to be used in constructing the pool, set transiently during pool creation.
                  /// @dev Called by the pool constructor to fetch the parameters of the pool
                  /// Returns factory The factory address
                  /// Returns token0 The first token of the pool by address sort order
                  /// Returns token1 The second token of the pool by address sort order
                  /// Returns fee The fee collected upon every swap in the pool, denominated in hundredths of a bip
                  /// Returns tickSpacing The minimum number of ticks between initialized ticks
                  function parameters()
                      external
                      view
                      returns (
                          address factory,
                          address token0,
                          address token1,
                          uint24 fee,
                          int24 tickSpacing
                      );
              }
              // SPDX-License-Identifier: GPL-2.0-or-later
              pragma solidity >=0.5.0;
              /// @title The interface for the Uniswap V3 Factory
              /// @notice The Uniswap V3 Factory facilitates creation of Uniswap V3 pools and control over the protocol fees
              interface IUniswapV3Factory {
                  /// @notice Emitted when the owner of the factory is changed
                  /// @param oldOwner The owner before the owner was changed
                  /// @param newOwner The owner after the owner was changed
                  event OwnerChanged(address indexed oldOwner, address indexed newOwner);
                  /// @notice Emitted when a pool is created
                  /// @param token0 The first token of the pool by address sort order
                  /// @param token1 The second token of the pool by address sort order
                  /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip
                  /// @param tickSpacing The minimum number of ticks between initialized ticks
                  /// @param pool The address of the created pool
                  event PoolCreated(
                      address indexed token0,
                      address indexed token1,
                      uint24 indexed fee,
                      int24 tickSpacing,
                      address pool
                  );
                  /// @notice Emitted when a new fee amount is enabled for pool creation via the factory
                  /// @param fee The enabled fee, denominated in hundredths of a bip
                  /// @param tickSpacing The minimum number of ticks between initialized ticks for pools created with the given fee
                  event FeeAmountEnabled(uint24 indexed fee, int24 indexed tickSpacing);
                  /// @notice Returns the current owner of the factory
                  /// @dev Can be changed by the current owner via setOwner
                  /// @return The address of the factory owner
                  function owner() external view returns (address);
                  /// @notice Returns the tick spacing for a given fee amount, if enabled, or 0 if not enabled
                  /// @dev A fee amount can never be removed, so this value should be hard coded or cached in the calling context
                  /// @param fee The enabled fee, denominated in hundredths of a bip. Returns 0 in case of unenabled fee
                  /// @return The tick spacing
                  function feeAmountTickSpacing(uint24 fee) external view returns (int24);
                  /// @notice Returns the pool address for a given pair of tokens and a fee, or address 0 if it does not exist
                  /// @dev tokenA and tokenB may be passed in either token0/token1 or token1/token0 order
                  /// @param tokenA The contract address of either token0 or token1
                  /// @param tokenB The contract address of the other token
                  /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip
                  /// @return pool The pool address
                  function getPool(
                      address tokenA,
                      address tokenB,
                      uint24 fee
                  ) external view returns (address pool);
                  /// @notice Creates a pool for the given two tokens and fee
                  /// @param tokenA One of the two tokens in the desired pool
                  /// @param tokenB The other of the two tokens in the desired pool
                  /// @param fee The desired fee for the pool
                  /// @dev tokenA and tokenB may be passed in either order: token0/token1 or token1/token0. tickSpacing is retrieved
                  /// from the fee. The call will revert if the pool already exists, the fee is invalid, or the token arguments
                  /// are invalid.
                  /// @return pool The address of the newly created pool
                  function createPool(
                      address tokenA,
                      address tokenB,
                      uint24 fee
                  ) external returns (address pool);
                  /// @notice Updates the owner of the factory
                  /// @dev Must be called by the current owner
                  /// @param _owner The new owner of the factory
                  function setOwner(address _owner) external;
                  /// @notice Enables a fee amount with the given tickSpacing
                  /// @dev Fee amounts may never be removed once enabled
                  /// @param fee The fee amount to enable, denominated in hundredths of a bip (i.e. 1e-6)
                  /// @param tickSpacing The spacing between ticks to be enforced for all pools created with the given fee amount
                  function enableFeeAmount(uint24 fee, int24 tickSpacing) external;
              }
              // SPDX-License-Identifier: GPL-2.0-or-later
              pragma solidity >=0.5.0;
              /// @title Minimal ERC20 interface for Uniswap
              /// @notice Contains a subset of the full ERC20 interface that is used in Uniswap V3
              interface IERC20Minimal {
                  /// @notice Returns the balance of a token
                  /// @param account The account for which to look up the number of tokens it has, i.e. its balance
                  /// @return The number of tokens held by the account
                  function balanceOf(address account) external view returns (uint256);
                  /// @notice Transfers the amount of token from the `msg.sender` to the recipient
                  /// @param recipient The account that will receive the amount transferred
                  /// @param amount The number of tokens to send from the sender to the recipient
                  /// @return Returns true for a successful transfer, false for an unsuccessful transfer
                  function transfer(address recipient, uint256 amount) external returns (bool);
                  /// @notice Returns the current allowance given to a spender by an owner
                  /// @param owner The account of the token owner
                  /// @param spender The account of the token spender
                  /// @return The current allowance granted by `owner` to `spender`
                  function allowance(address owner, address spender) external view returns (uint256);
                  /// @notice Sets the allowance of a spender from the `msg.sender` to the value `amount`
                  /// @param spender The account which will be allowed to spend a given amount of the owners tokens
                  /// @param amount The amount of tokens allowed to be used by `spender`
                  /// @return Returns true for a successful approval, false for unsuccessful
                  function approve(address spender, uint256 amount) external returns (bool);
                  /// @notice Transfers `amount` tokens from `sender` to `recipient` up to the allowance given to the `msg.sender`
                  /// @param sender The account from which the transfer will be initiated
                  /// @param recipient The recipient of the transfer
                  /// @param amount The amount of the transfer
                  /// @return Returns true for a successful transfer, false for unsuccessful
                  function transferFrom(
                      address sender,
                      address recipient,
                      uint256 amount
                  ) external returns (bool);
                  /// @notice Event emitted when tokens are transferred from one address to another, either via `#transfer` or `#transferFrom`.
                  /// @param from The account from which the tokens were sent, i.e. the balance decreased
                  /// @param to The account to which the tokens were sent, i.e. the balance increased
                  /// @param value The amount of tokens that were transferred
                  event Transfer(address indexed from, address indexed to, uint256 value);
                  /// @notice Event emitted when the approval amount for the spender of a given owner's tokens changes.
                  /// @param owner The account that approved spending of its tokens
                  /// @param spender The account for which the spending allowance was modified
                  /// @param value The new allowance from the owner to the spender
                  event Approval(address indexed owner, address indexed spender, uint256 value);
              }
              // SPDX-License-Identifier: GPL-2.0-or-later
              pragma solidity >=0.5.0;
              /// @title Callback for IUniswapV3PoolActions#mint
              /// @notice Any contract that calls IUniswapV3PoolActions#mint must implement this interface
              interface IUniswapV3MintCallback {
                  /// @notice Called to `msg.sender` after minting liquidity to a position from IUniswapV3Pool#mint.
                  /// @dev In the implementation you must pay the pool tokens owed for the minted liquidity.
                  /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.
                  /// @param amount0Owed The amount of token0 due to the pool for the minted liquidity
                  /// @param amount1Owed The amount of token1 due to the pool for the minted liquidity
                  /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#mint call
                  function uniswapV3MintCallback(
                      uint256 amount0Owed,
                      uint256 amount1Owed,
                      bytes calldata data
                  ) external;
              }
              // SPDX-License-Identifier: GPL-2.0-or-later
              pragma solidity >=0.5.0;
              /// @title Callback for IUniswapV3PoolActions#swap
              /// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface
              interface IUniswapV3SwapCallback {
                  /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap.
                  /// @dev In the implementation you must pay the pool tokens owed for the swap.
                  /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.
                  /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped.
                  /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by
                  /// the end of the swap. If positive, the callback must send that amount of token0 to the pool.
                  /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by
                  /// the end of the swap. If positive, the callback must send that amount of token1 to the pool.
                  /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call
                  function uniswapV3SwapCallback(
                      int256 amount0Delta,
                      int256 amount1Delta,
                      bytes calldata data
                  ) external;
              }
              // SPDX-License-Identifier: GPL-2.0-or-later
              pragma solidity >=0.5.0;
              /// @title Callback for IUniswapV3PoolActions#flash
              /// @notice Any contract that calls IUniswapV3PoolActions#flash must implement this interface
              interface IUniswapV3FlashCallback {
                  /// @notice Called to `msg.sender` after transferring to the recipient from IUniswapV3Pool#flash.
                  /// @dev In the implementation you must repay the pool the tokens sent by flash plus the computed fee amounts.
                  /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.
                  /// @param fee0 The fee amount in token0 due to the pool by the end of the flash
                  /// @param fee1 The fee amount in token1 due to the pool by the end of the flash
                  /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#flash call
                  function uniswapV3FlashCallback(
                      uint256 fee0,
                      uint256 fee1,
                      bytes calldata data
                  ) external;
              }
              // SPDX-License-Identifier: GPL-2.0-or-later
              pragma solidity >=0.5.0;
              /// @title Pool state that never changes
              /// @notice These parameters are fixed for a pool forever, i.e., the methods will always return the same values
              interface IUniswapV3PoolImmutables {
                  /// @notice The contract that deployed the pool, which must adhere to the IUniswapV3Factory interface
                  /// @return The contract address
                  function factory() external view returns (address);
                  /// @notice The first of the two tokens of the pool, sorted by address
                  /// @return The token contract address
                  function token0() external view returns (address);
                  /// @notice The second of the two tokens of the pool, sorted by address
                  /// @return The token contract address
                  function token1() external view returns (address);
                  /// @notice The pool's fee in hundredths of a bip, i.e. 1e-6
                  /// @return The fee
                  function fee() external view returns (uint24);
                  /// @notice The pool tick spacing
                  /// @dev Ticks can only be used at multiples of this value, minimum of 1 and always positive
                  /// e.g.: a tickSpacing of 3 means ticks can be initialized every 3rd tick, i.e., ..., -6, -3, 0, 3, 6, ...
                  /// This value is an int24 to avoid casting even though it is always positive.
                  /// @return The tick spacing
                  function tickSpacing() external view returns (int24);
                  /// @notice The maximum amount of position liquidity that can use any tick in the range
                  /// @dev This parameter is enforced per tick to prevent liquidity from overflowing a uint128 at any point, and
                  /// also prevents out-of-range liquidity from being used to prevent adding in-range liquidity to a pool
                  /// @return The max amount of liquidity per tick
                  function maxLiquidityPerTick() external view returns (uint128);
              }
              // SPDX-License-Identifier: GPL-2.0-or-later
              pragma solidity >=0.5.0;
              /// @title Pool state that can change
              /// @notice These methods compose the pool's state, and can change with any frequency including multiple times
              /// per transaction
              interface IUniswapV3PoolState {
                  /// @notice The 0th storage slot in the pool stores many values, and is exposed as a single method to save gas
                  /// when accessed externally.
                  /// @return sqrtPriceX96 The current price of the pool as a sqrt(token1/token0) Q64.96 value
                  /// tick The current tick of the pool, i.e. according to the last tick transition that was run.
                  /// This value may not always be equal to SqrtTickMath.getTickAtSqrtRatio(sqrtPriceX96) if the price is on a tick
                  /// boundary.
                  /// observationIndex The index of the last oracle observation that was written,
                  /// observationCardinality The current maximum number of observations stored in the pool,
                  /// observationCardinalityNext The next maximum number of observations, to be updated when the observation.
                  /// feeProtocol The protocol fee for both tokens of the pool.
                  /// Encoded as two 4 bit values, where the protocol fee of token1 is shifted 4 bits and the protocol fee of token0
                  /// is the lower 4 bits. Used as the denominator of a fraction of the swap fee, e.g. 4 means 1/4th of the swap fee.
                  /// unlocked Whether the pool is currently locked to reentrancy
                  function slot0()
                      external
                      view
                      returns (
                          uint160 sqrtPriceX96,
                          int24 tick,
                          uint16 observationIndex,
                          uint16 observationCardinality,
                          uint16 observationCardinalityNext,
                          uint8 feeProtocol,
                          bool unlocked
                      );
                  /// @notice The fee growth as a Q128.128 fees of token0 collected per unit of liquidity for the entire life of the pool
                  /// @dev This value can overflow the uint256
                  function feeGrowthGlobal0X128() external view returns (uint256);
                  /// @notice The fee growth as a Q128.128 fees of token1 collected per unit of liquidity for the entire life of the pool
                  /// @dev This value can overflow the uint256
                  function feeGrowthGlobal1X128() external view returns (uint256);
                  /// @notice The amounts of token0 and token1 that are owed to the protocol
                  /// @dev Protocol fees will never exceed uint128 max in either token
                  function protocolFees() external view returns (uint128 token0, uint128 token1);
                  /// @notice The currently in range liquidity available to the pool
                  /// @dev This value has no relationship to the total liquidity across all ticks
                  function liquidity() external view returns (uint128);
                  /// @notice Look up information about a specific tick in the pool
                  /// @param tick The tick to look up
                  /// @return liquidityGross the total amount of position liquidity that uses the pool either as tick lower or
                  /// tick upper,
                  /// liquidityNet how much liquidity changes when the pool price crosses the tick,
                  /// feeGrowthOutside0X128 the fee growth on the other side of the tick from the current tick in token0,
                  /// feeGrowthOutside1X128 the fee growth on the other side of the tick from the current tick in token1,
                  /// tickCumulativeOutside the cumulative tick value on the other side of the tick from the current tick
                  /// secondsPerLiquidityOutsideX128 the seconds spent per liquidity on the other side of the tick from the current tick,
                  /// secondsOutside the seconds spent on the other side of the tick from the current tick,
                  /// initialized Set to true if the tick is initialized, i.e. liquidityGross is greater than 0, otherwise equal to false.
                  /// Outside values can only be used if the tick is initialized, i.e. if liquidityGross is greater than 0.
                  /// In addition, these values are only relative and must be used only in comparison to previous snapshots for
                  /// a specific position.
                  function ticks(int24 tick)
                      external
                      view
                      returns (
                          uint128 liquidityGross,
                          int128 liquidityNet,
                          uint256 feeGrowthOutside0X128,
                          uint256 feeGrowthOutside1X128,
                          int56 tickCumulativeOutside,
                          uint160 secondsPerLiquidityOutsideX128,
                          uint32 secondsOutside,
                          bool initialized
                      );
                  /// @notice Returns 256 packed tick initialized boolean values. See TickBitmap for more information
                  function tickBitmap(int16 wordPosition) external view returns (uint256);
                  /// @notice Returns the information about a position by the position's key
                  /// @param key The position's key is a hash of a preimage composed by the owner, tickLower and tickUpper
                  /// @return _liquidity The amount of liquidity in the position,
                  /// Returns feeGrowthInside0LastX128 fee growth of token0 inside the tick range as of the last mint/burn/poke,
                  /// Returns feeGrowthInside1LastX128 fee growth of token1 inside the tick range as of the last mint/burn/poke,
                  /// Returns tokensOwed0 the computed amount of token0 owed to the position as of the last mint/burn/poke,
                  /// Returns tokensOwed1 the computed amount of token1 owed to the position as of the last mint/burn/poke
                  function positions(bytes32 key)
                      external
                      view
                      returns (
                          uint128 _liquidity,
                          uint256 feeGrowthInside0LastX128,
                          uint256 feeGrowthInside1LastX128,
                          uint128 tokensOwed0,
                          uint128 tokensOwed1
                      );
                  /// @notice Returns data about a specific observation index
                  /// @param index The element of the observations array to fetch
                  /// @dev You most likely want to use #observe() instead of this method to get an observation as of some amount of time
                  /// ago, rather than at a specific index in the array.
                  /// @return blockTimestamp The timestamp of the observation,
                  /// Returns tickCumulative the tick multiplied by seconds elapsed for the life of the pool as of the observation timestamp,
                  /// Returns secondsPerLiquidityCumulativeX128 the seconds per in range liquidity for the life of the pool as of the observation timestamp,
                  /// Returns initialized whether the observation has been initialized and the values are safe to use
                  function observations(uint256 index)
                      external
                      view
                      returns (
                          uint32 blockTimestamp,
                          int56 tickCumulative,
                          uint160 secondsPerLiquidityCumulativeX128,
                          bool initialized
                      );
              }
              // SPDX-License-Identifier: GPL-2.0-or-later
              pragma solidity >=0.5.0;
              /// @title Pool state that is not stored
              /// @notice Contains view functions to provide information about the pool that is computed rather than stored on the
              /// blockchain. The functions here may have variable gas costs.
              interface IUniswapV3PoolDerivedState {
                  /// @notice Returns the cumulative tick and liquidity as of each timestamp `secondsAgo` from the current block timestamp
                  /// @dev To get a time weighted average tick or liquidity-in-range, you must call this with two values, one representing
                  /// the beginning of the period and another for the end of the period. E.g., to get the last hour time-weighted average tick,
                  /// you must call it with secondsAgos = [3600, 0].
                  /// @dev The time weighted average tick represents the geometric time weighted average price of the pool, in
                  /// log base sqrt(1.0001) of token1 / token0. The TickMath library can be used to go from a tick value to a ratio.
                  /// @param secondsAgos From how long ago each cumulative tick and liquidity value should be returned
                  /// @return tickCumulatives Cumulative tick values as of each `secondsAgos` from the current block timestamp
                  /// @return secondsPerLiquidityCumulativeX128s Cumulative seconds per liquidity-in-range value as of each `secondsAgos` from the current block
                  /// timestamp
                  function observe(uint32[] calldata secondsAgos)
                      external
                      view
                      returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s);
                  /// @notice Returns a snapshot of the tick cumulative, seconds per liquidity and seconds inside a tick range
                  /// @dev Snapshots must only be compared to other snapshots, taken over a period for which a position existed.
                  /// I.e., snapshots cannot be compared if a position is not held for the entire period between when the first
                  /// snapshot is taken and the second snapshot is taken.
                  /// @param tickLower The lower tick of the range
                  /// @param tickUpper The upper tick of the range
                  /// @return tickCumulativeInside The snapshot of the tick accumulator for the range
                  /// @return secondsPerLiquidityInsideX128 The snapshot of seconds per liquidity for the range
                  /// @return secondsInside The snapshot of seconds per liquidity for the range
                  function snapshotCumulativesInside(int24 tickLower, int24 tickUpper)
                      external
                      view
                      returns (
                          int56 tickCumulativeInside,
                          uint160 secondsPerLiquidityInsideX128,
                          uint32 secondsInside
                      );
              }
              // SPDX-License-Identifier: GPL-2.0-or-later
              pragma solidity >=0.5.0;
              /// @title Permissionless pool actions
              /// @notice Contains pool methods that can be called by anyone
              interface IUniswapV3PoolActions {
                  /// @notice Sets the initial price for the pool
                  /// @dev Price is represented as a sqrt(amountToken1/amountToken0) Q64.96 value
                  /// @param sqrtPriceX96 the initial sqrt price of the pool as a Q64.96
                  function initialize(uint160 sqrtPriceX96) external;
                  /// @notice Adds liquidity for the given recipient/tickLower/tickUpper position
                  /// @dev The caller of this method receives a callback in the form of IUniswapV3MintCallback#uniswapV3MintCallback
                  /// in which they must pay any token0 or token1 owed for the liquidity. The amount of token0/token1 due depends
                  /// on tickLower, tickUpper, the amount of liquidity, and the current price.
                  /// @param recipient The address for which the liquidity will be created
                  /// @param tickLower The lower tick of the position in which to add liquidity
                  /// @param tickUpper The upper tick of the position in which to add liquidity
                  /// @param amount The amount of liquidity to mint
                  /// @param data Any data that should be passed through to the callback
                  /// @return amount0 The amount of token0 that was paid to mint the given amount of liquidity. Matches the value in the callback
                  /// @return amount1 The amount of token1 that was paid to mint the given amount of liquidity. Matches the value in the callback
                  function mint(
                      address recipient,
                      int24 tickLower,
                      int24 tickUpper,
                      uint128 amount,
                      bytes calldata data
                  ) external returns (uint256 amount0, uint256 amount1);
                  /// @notice Collects tokens owed to a position
                  /// @dev Does not recompute fees earned, which must be done either via mint or burn of any amount of liquidity.
                  /// Collect must be called by the position owner. To withdraw only token0 or only token1, amount0Requested or
                  /// amount1Requested may be set to zero. To withdraw all tokens owed, caller may pass any value greater than the
                  /// actual tokens owed, e.g. type(uint128).max. Tokens owed may be from accumulated swap fees or burned liquidity.
                  /// @param recipient The address which should receive the fees collected
                  /// @param tickLower The lower tick of the position for which to collect fees
                  /// @param tickUpper The upper tick of the position for which to collect fees
                  /// @param amount0Requested How much token0 should be withdrawn from the fees owed
                  /// @param amount1Requested How much token1 should be withdrawn from the fees owed
                  /// @return amount0 The amount of fees collected in token0
                  /// @return amount1 The amount of fees collected in token1
                  function collect(
                      address recipient,
                      int24 tickLower,
                      int24 tickUpper,
                      uint128 amount0Requested,
                      uint128 amount1Requested
                  ) external returns (uint128 amount0, uint128 amount1);
                  /// @notice Burn liquidity from the sender and account tokens owed for the liquidity to the position
                  /// @dev Can be used to trigger a recalculation of fees owed to a position by calling with an amount of 0
                  /// @dev Fees must be collected separately via a call to #collect
                  /// @param tickLower The lower tick of the position for which to burn liquidity
                  /// @param tickUpper The upper tick of the position for which to burn liquidity
                  /// @param amount How much liquidity to burn
                  /// @return amount0 The amount of token0 sent to the recipient
                  /// @return amount1 The amount of token1 sent to the recipient
                  function burn(
                      int24 tickLower,
                      int24 tickUpper,
                      uint128 amount
                  ) external returns (uint256 amount0, uint256 amount1);
                  /// @notice Swap token0 for token1, or token1 for token0
                  /// @dev The caller of this method receives a callback in the form of IUniswapV3SwapCallback#uniswapV3SwapCallback
                  /// @param recipient The address to receive the output of the swap
                  /// @param zeroForOne The direction of the swap, true for token0 to token1, false for token1 to token0
                  /// @param amountSpecified The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative)
                  /// @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this
                  /// value after the swap. If one for zero, the price cannot be greater than this value after the swap
                  /// @param data Any data to be passed through to the callback
                  /// @return amount0 The delta of the balance of token0 of the pool, exact when negative, minimum when positive
                  /// @return amount1 The delta of the balance of token1 of the pool, exact when negative, minimum when positive
                  function swap(
                      address recipient,
                      bool zeroForOne,
                      int256 amountSpecified,
                      uint160 sqrtPriceLimitX96,
                      bytes calldata data
                  ) external returns (int256 amount0, int256 amount1);
                  /// @notice Receive token0 and/or token1 and pay it back, plus a fee, in the callback
                  /// @dev The caller of this method receives a callback in the form of IUniswapV3FlashCallback#uniswapV3FlashCallback
                  /// @dev Can be used to donate underlying tokens pro-rata to currently in-range liquidity providers by calling
                  /// with 0 amount{0,1} and sending the donation amount(s) from the callback
                  /// @param recipient The address which will receive the token0 and token1 amounts
                  /// @param amount0 The amount of token0 to send
                  /// @param amount1 The amount of token1 to send
                  /// @param data Any data to be passed through to the callback
                  function flash(
                      address recipient,
                      uint256 amount0,
                      uint256 amount1,
                      bytes calldata data
                  ) external;
                  /// @notice Increase the maximum number of price and liquidity observations that this pool will store
                  /// @dev This method is no-op if the pool already has an observationCardinalityNext greater than or equal to
                  /// the input observationCardinalityNext.
                  /// @param observationCardinalityNext The desired minimum number of observations for the pool to store
                  function increaseObservationCardinalityNext(uint16 observationCardinalityNext) external;
              }
              // SPDX-License-Identifier: GPL-2.0-or-later
              pragma solidity >=0.5.0;
              /// @title Permissioned pool actions
              /// @notice Contains pool methods that may only be called by the factory owner
              interface IUniswapV3PoolOwnerActions {
                  /// @notice Set the denominator of the protocol's % share of the fees
                  /// @param feeProtocol0 new protocol fee for token0 of the pool
                  /// @param feeProtocol1 new protocol fee for token1 of the pool
                  function setFeeProtocol(uint8 feeProtocol0, uint8 feeProtocol1) external;
                  /// @notice Collect the protocol fee accrued to the pool
                  /// @param recipient The address to which collected protocol fees should be sent
                  /// @param amount0Requested The maximum amount of token0 to send, can be 0 to collect fees in only token1
                  /// @param amount1Requested The maximum amount of token1 to send, can be 0 to collect fees in only token0
                  /// @return amount0 The protocol fee collected in token0
                  /// @return amount1 The protocol fee collected in token1
                  function collectProtocol(
                      address recipient,
                      uint128 amount0Requested,
                      uint128 amount1Requested
                  ) external returns (uint128 amount0, uint128 amount1);
              }
              // SPDX-License-Identifier: GPL-2.0-or-later
              pragma solidity >=0.5.0;
              /// @title Events emitted by a pool
              /// @notice Contains all events emitted by the pool
              interface IUniswapV3PoolEvents {
                  /// @notice Emitted exactly once by a pool when #initialize is first called on the pool
                  /// @dev Mint/Burn/Swap cannot be emitted by the pool before Initialize
                  /// @param sqrtPriceX96 The initial sqrt price of the pool, as a Q64.96
                  /// @param tick The initial tick of the pool, i.e. log base 1.0001 of the starting price of the pool
                  event Initialize(uint160 sqrtPriceX96, int24 tick);
                  /// @notice Emitted when liquidity is minted for a given position
                  /// @param sender The address that minted the liquidity
                  /// @param owner The owner of the position and recipient of any minted liquidity
                  /// @param tickLower The lower tick of the position
                  /// @param tickUpper The upper tick of the position
                  /// @param amount The amount of liquidity minted to the position range
                  /// @param amount0 How much token0 was required for the minted liquidity
                  /// @param amount1 How much token1 was required for the minted liquidity
                  event Mint(
                      address sender,
                      address indexed owner,
                      int24 indexed tickLower,
                      int24 indexed tickUpper,
                      uint128 amount,
                      uint256 amount0,
                      uint256 amount1
                  );
                  /// @notice Emitted when fees are collected by the owner of a position
                  /// @dev Collect events may be emitted with zero amount0 and amount1 when the caller chooses not to collect fees
                  /// @param owner The owner of the position for which fees are collected
                  /// @param tickLower The lower tick of the position
                  /// @param tickUpper The upper tick of the position
                  /// @param amount0 The amount of token0 fees collected
                  /// @param amount1 The amount of token1 fees collected
                  event Collect(
                      address indexed owner,
                      address recipient,
                      int24 indexed tickLower,
                      int24 indexed tickUpper,
                      uint128 amount0,
                      uint128 amount1
                  );
                  /// @notice Emitted when a position's liquidity is removed
                  /// @dev Does not withdraw any fees earned by the liquidity position, which must be withdrawn via #collect
                  /// @param owner The owner of the position for which liquidity is removed
                  /// @param tickLower The lower tick of the position
                  /// @param tickUpper The upper tick of the position
                  /// @param amount The amount of liquidity to remove
                  /// @param amount0 The amount of token0 withdrawn
                  /// @param amount1 The amount of token1 withdrawn
                  event Burn(
                      address indexed owner,
                      int24 indexed tickLower,
                      int24 indexed tickUpper,
                      uint128 amount,
                      uint256 amount0,
                      uint256 amount1
                  );
                  /// @notice Emitted by the pool for any swaps between token0 and token1
                  /// @param sender The address that initiated the swap call, and that received the callback
                  /// @param recipient The address that received the output of the swap
                  /// @param amount0 The delta of the token0 balance of the pool
                  /// @param amount1 The delta of the token1 balance of the pool
                  /// @param sqrtPriceX96 The sqrt(price) of the pool after the swap, as a Q64.96
                  /// @param liquidity The liquidity of the pool after the swap
                  /// @param tick The log base 1.0001 of price of the pool after the swap
                  event Swap(
                      address indexed sender,
                      address indexed recipient,
                      int256 amount0,
                      int256 amount1,
                      uint160 sqrtPriceX96,
                      uint128 liquidity,
                      int24 tick
                  );
                  /// @notice Emitted by the pool for any flashes of token0/token1
                  /// @param sender The address that initiated the swap call, and that received the callback
                  /// @param recipient The address that received the tokens from flash
                  /// @param amount0 The amount of token0 that was flashed
                  /// @param amount1 The amount of token1 that was flashed
                  /// @param paid0 The amount of token0 paid for the flash, which can exceed the amount0 plus the fee
                  /// @param paid1 The amount of token1 paid for the flash, which can exceed the amount1 plus the fee
                  event Flash(
                      address indexed sender,
                      address indexed recipient,
                      uint256 amount0,
                      uint256 amount1,
                      uint256 paid0,
                      uint256 paid1
                  );
                  /// @notice Emitted by the pool for increases to the number of observations that can be stored
                  /// @dev observationCardinalityNext is not the observation cardinality until an observation is written at the index
                  /// just before a mint/swap/burn.
                  /// @param observationCardinalityNextOld The previous value of the next observation cardinality
                  /// @param observationCardinalityNextNew The updated value of the next observation cardinality
                  event IncreaseObservationCardinalityNext(
                      uint16 observationCardinalityNextOld,
                      uint16 observationCardinalityNextNew
                  );
                  /// @notice Emitted when the protocol fee is changed by the pool
                  /// @param feeProtocol0Old The previous value of the token0 protocol fee
                  /// @param feeProtocol1Old The previous value of the token1 protocol fee
                  /// @param feeProtocol0New The updated value of the token0 protocol fee
                  /// @param feeProtocol1New The updated value of the token1 protocol fee
                  event SetFeeProtocol(uint8 feeProtocol0Old, uint8 feeProtocol1Old, uint8 feeProtocol0New, uint8 feeProtocol1New);
                  /// @notice Emitted when the collected protocol fees are withdrawn by the factory owner
                  /// @param sender The address that collects the protocol fees
                  /// @param recipient The address that receives the collected protocol fees
                  /// @param amount0 The amount of token0 protocol fees that is withdrawn
                  /// @param amount0 The amount of token1 protocol fees that is withdrawn
                  event CollectProtocol(address indexed sender, address indexed recipient, uint128 amount0, uint128 amount1);
              }
              // SPDX-License-Identifier: GPL-2.0-or-later
              pragma solidity >=0.5.0;
              /// @title BitMath
              /// @dev This library provides functionality for computing bit properties of an unsigned integer
              library BitMath {
                  /// @notice Returns the index of the most significant bit of the number,
                  ///     where the least significant bit is at index 0 and the most significant bit is at index 255
                  /// @dev The function satisfies the property:
                  ///     x >= 2**mostSignificantBit(x) and x < 2**(mostSignificantBit(x)+1)
                  /// @param x the value for which to compute the most significant bit, must be greater than 0
                  /// @return r the index of the most significant bit
                  function mostSignificantBit(uint256 x) internal pure returns (uint8 r) {
                      require(x > 0);
                      if (x >= 0x100000000000000000000000000000000) {
                          x >>= 128;
                          r += 128;
                      }
                      if (x >= 0x10000000000000000) {
                          x >>= 64;
                          r += 64;
                      }
                      if (x >= 0x100000000) {
                          x >>= 32;
                          r += 32;
                      }
                      if (x >= 0x10000) {
                          x >>= 16;
                          r += 16;
                      }
                      if (x >= 0x100) {
                          x >>= 8;
                          r += 8;
                      }
                      if (x >= 0x10) {
                          x >>= 4;
                          r += 4;
                      }
                      if (x >= 0x4) {
                          x >>= 2;
                          r += 2;
                      }
                      if (x >= 0x2) r += 1;
                  }
                  /// @notice Returns the index of the least significant bit of the number,
                  ///     where the least significant bit is at index 0 and the most significant bit is at index 255
                  /// @dev The function satisfies the property:
                  ///     (x & 2**leastSignificantBit(x)) != 0 and (x & (2**(leastSignificantBit(x)) - 1)) == 0)
                  /// @param x the value for which to compute the least significant bit, must be greater than 0
                  /// @return r the index of the least significant bit
                  function leastSignificantBit(uint256 x) internal pure returns (uint8 r) {
                      require(x > 0);
                      r = 255;
                      if (x & type(uint128).max > 0) {
                          r -= 128;
                      } else {
                          x >>= 128;
                      }
                      if (x & type(uint64).max > 0) {
                          r -= 64;
                      } else {
                          x >>= 64;
                      }
                      if (x & type(uint32).max > 0) {
                          r -= 32;
                      } else {
                          x >>= 32;
                      }
                      if (x & type(uint16).max > 0) {
                          r -= 16;
                      } else {
                          x >>= 16;
                      }
                      if (x & type(uint8).max > 0) {
                          r -= 8;
                      } else {
                          x >>= 8;
                      }
                      if (x & 0xf > 0) {
                          r -= 4;
                      } else {
                          x >>= 4;
                      }
                      if (x & 0x3 > 0) {
                          r -= 2;
                      } else {
                          x >>= 2;
                      }
                      if (x & 0x1 > 0) r -= 1;
                  }
              }
              // SPDX-License-Identifier: GPL-2.0-or-later
              pragma solidity >=0.5.0;
              /// @title Math functions that do not check inputs or outputs
              /// @notice Contains methods that perform common math functions but do not do any overflow or underflow checks
              library UnsafeMath {
                  /// @notice Returns ceil(x / y)
                  /// @dev division by 0 has unspecified behavior, and must be checked externally
                  /// @param x The dividend
                  /// @param y The divisor
                  /// @return z The quotient, ceil(x / y)
                  function divRoundingUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
                      assembly {
                          z := add(div(x, y), gt(mod(x, y), 0))
                      }
                  }
              }
              // SPDX-License-Identifier: GPL-2.0-or-later
              pragma solidity >=0.4.0;
              /// @title FixedPoint96
              /// @notice A library for handling binary fixed point numbers, see https://en.wikipedia.org/wiki/Q_(number_format)
              /// @dev Used in SqrtPriceMath.sol
              library FixedPoint96 {
                  uint8 internal constant RESOLUTION = 96;
                  uint256 internal constant Q96 = 0x1000000000000000000000000;
              }
              

              File 4 of 4: PunkStrategy
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.20;
              import {Ownable} from "solady/auth/Ownable.sol";
              import {ERC20} from "solady/tokens/ERC20.sol";
              import {ReentrancyGuard} from "solady/utils/ReentrancyGuard.sol";
              import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
              import {IPositionManager} from "@uniswap/v4-periphery/src/interfaces/IPositionManager.sol";
              import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol";
              import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
              import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
              import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
              import {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol";
              import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
              import {IUniswapV4Router04} from "v4-router/interfaces/IUniswapV4Router04.sol";
              import "./Interfaces.sol";
              /// @title PunkStrategy - The Perpetual Punk Machine
              /// @author TokenWorks (https://token.works/)
              contract PunkStrategy is ERC20, Ownable, ReentrancyGuard {
                  /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™                ™™™™™™™™™™™                ™™™™™™™™™™™ */
                  /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™               ™™™™™™™™™™™™™              ™™™™™™™™™™  */
                  /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™              ™™™™™™™™™™™™™              ™™™™™™™™™™™  */
                  /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™             ™™™™™™™™™™™™™™            ™™™™™™™™™™™   */
                  /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™            ™™™™™™™™™™™™™™™            ™™™™™™™™™™™   */
                  /*                ™™™™™™™™™™™            ™™™™™™™™™™™           ™™™™™™™™™™™™™™™           ™™™™™™™™™™™    */
                  /*                ™™™™™™™™™™™             ™™™™™™™™™™          ™™™™™™™™™™™™™™™™™          ™™™™™™™™™™™    */
                  /*                ™™™™™™™™™™™             ™™™™™™™™™™          ™™™™™™™™™™™™™™™™™          ™™™™™™™™™™     */
                  /*                ™™™™™™™™™™™              ™™™™™™™™™™        ™™™™™™™™™™™™™™™™™™™        ™™™™™™™™™™™     */
                  /*                ™™™™™™™™™™™              ™™™™™™™™™™™       ™™™™™™™™™ ™™™™™™™™™       ™™™™™™™™™™™      */
                  /*                ™™™™™™™™™™™               ™™™™™™™™™™      ™™™™™™™™™™ ™™™™™™™™™™      ™™™™™™™™™™™      */
                  /*                ™™™™™™™™™™™               ™™™™™™™™™™      ™™™™™™™™™   ™™™™™™™™™      ™™™™™™™™™™       */
                  /*                ™™™™™™™™™™™                ™™™™™™™™™™    ™™™™™™™™™™    ™™™™™™™™™    ™™™™™™™™™™        */
                  /*                ™™™™™™™™™™™                 ™™™™™™™™™™   ™™™™™™™™™     ™™™™™™™™™™  ™™™™™™™™™™™        */
                  /*                ™™™™™™™™™™™                 ™™™™™™™™™™  ™™™™™™™™™™     ™™™™™™™™™™  ™™™™™™™™™™         */
                  /*                ™™™™™™™™™™™                  ™™™™™™™™™™™™™™™™™™™™       ™™™™™™™™™™™™™™™™™™™™          */
                  /*                ™™™™™™™™™™™                   ™™™™™™™™™™™™™™™™™™         ™™™™™™™™™™™™™™™™™™           */
                  /*                ™™™™™™™™™™™                   ™™™™™™™™™™™™™™™™™™         ™™™™™™™™™™™™™™™™™™           */
                  /*                ™™™™™™™™™™™                    ™™™™™™™™™™™™™™™™           ™™™™™™™™™™™™™™™™            */
                  /*                ™™™™™™™™™™™                     ™™™™™™™™™™™™™™             ™™™™™™™™™™™™™™             */
                  /*                ™™™™™™™™™™™                     ™™™™™™™™™™™™™™             ™™™™™™™™™™™™™™             */
                  /*                ™™™™™™™™™™™                      ™™™™™™™™™™™™               ™™™™™™™™™™™™              */
                  /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
                  /*                      CONSTANTS                      */
                  /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
                  
                  IPositionManager private immutable posm;
                  IAllowanceTransfer private immutable permit2;
                  IUniswapV4Router04 private immutable router;
                  address public hookAddress;
                  uint256 public constant MAX_SUPPLY = 1_000_000_000 * 1e18;
                  address payable constant PUNKS = payable(0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB);
                  address public constant DEADADDRESS = 0x000000000000000000000000000000000000dEaD;
                  /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
                  /*                   STATE VARIABLES                   */
                  /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
                  bool public loadingLiquidity;
                  uint256 public currentFees;
                  uint256 public reward;
                  uint256 public lastPunkSalePrice;
                  uint256 public priceMultiplier;
                  /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
                  /*                    CUSTOM EVENTS                    */
                  /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
                  event ProtocolFeesFromSales(uint256 ethFees);
                  /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
                  /*                    CUSTOM ERRORS                    */
                  /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
                  error WrongEthAmount();
                  error OnlyHook();
                  error PunkNotForSale();
                  error InsufficientContractBalance();
                  error NoSaleToProcess();
                  error PunkNotOwned();
                  error InvalidMultiplier();
                  error NoPunksBoughtYet();
                  /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
                  /*                     CONSTRUCTOR                     */
                  /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
                  /// @notice Initializes the contract with required addresses and permissions
                  /// @param _owner Address that becomes the owner of the contract
                  /// @param _posm Uniswap V4 position manager address 
                  /// @param _permit2 Permit2 contract address
                  constructor(
                      address _owner,
                      address _posm,
                      address _permit2,
                      address payable _router
                  ) {
                      permit2 = IAllowanceTransfer(_permit2);
                      posm = IPositionManager(_posm);
                      router = IUniswapV4Router04(_router);
                      _initializeOwner(_owner);
                      _mint(address(this), MAX_SUPPLY);
                      
                      reward = 0.01 ether; // 0.01e prize for calling mechanism functions
                      priceMultiplier = 2000; // 2.0x in basis points
                  }
                  /// @notice Returns the name of the token
                  /// @return The token name as a string
                  function name() public pure override returns (string memory)   { 
                      return "PunkStrategy"; 
                  }
                  /// @notice Returns the symbol of the token
                  /// @return The token symbol as a string
                  function symbol() public pure override returns (string memory) { 
                      return "PNKSTR";     
                  }
                  /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
                  /*                    ADMIN FUNCTIONS                  */
                  /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
                  /// @notice Loads initial liquidity into the pool and starts the game
                  /// @param _hook Address of the PunkStrategyHook contract
                  /// @dev Must send exactly 2 wei. Can only be called once by owner.
                  function loadLiquidity(address _hook) external payable onlyOwner {
                      if (msg.value != 2 wei) revert WrongEthAmount();
                      hookAddress = _hook;
                      _loadLiquidity(_hook);
                  }
                  /// @notice Emergency function to withdraw ETH
                  /// @param _to Address to send ETH to
                  /// @param _amount Amount of ETH to send
                  /// @dev Only callable by owner
                  function transferEther(
                      address _to,
                      uint256 _amount
                  ) external payable onlyOwner {
                      SafeTransferLib.forceSafeTransferETH(_to, _amount);
                  }
                  /// @notice Updates the reward amount for calling buyPunkAndRelist and processPunkSale
                  /// @param _newReward New reward amount in wei
                  /// @dev Only callable by owner
                  function setReward(uint256 _newReward) external onlyOwner {
                      reward = _newReward;
                  }
                  /// @notice Updates the price multiplier for relisting punks
                  /// @param _newMultiplier New multiplier in basis points (1100 = 1.1x, 10000 = 10.0x)
                  /// @dev Only callable by owner. Must be between 1.1x (1100) and 10.0x (10000)
                  function setPriceMultiplier(uint256 _newMultiplier) external onlyOwner {
                      if (_newMultiplier < 1100 || _newMultiplier > 10000) revert InvalidMultiplier();
                      priceMultiplier = _newMultiplier;
                  }
                  /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
                  /*                 MECHANISM FUNCTIONS                 */
                  /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
                  /// @notice Allows the hook (or the Owner) to deposit trading fees into the contract
                  /// @dev Only callable by the hook contract or owner
                  /// @dev Fees are added to currentFees balance
                  function addFees() external payable {
                      if (msg.sender != hookAddress && msg.sender != owner()) revert OnlyHook();
                      currentFees += msg.value;
                  }
                  /// @notice Buys a punk from the market and relists it, paying a reward to the caller
                  /// @param punkId The ID of the punk to buy and relist
                  /// @dev Requires the punk to be for sale to anyone and sufficient fees (price + reward)
                  function buyPunkAndRelist(uint256 punkId) external nonReentrant returns (uint256) {
                      IPunks punksContract = IPunks(PUNKS);
                      
                      // Fetch punk offer details
                      (bool isForSale, , , uint256 minValue, address onlySellTo) = punksContract.punksOfferedForSale(punkId);
                      
                      // Validate punk is for sale and available to anyone
                      if (!isForSale) revert PunkNotForSale();
                      if (onlySellTo != address(0)) revert PunkNotForSale();
                      
                      // Calculate required ETH (punk price + reward)
                      uint256 totalRequired = minValue + reward;
                      
                      // Check currentFees has sufficient balance
                      if (currentFees < totalRequired) revert InsufficientContractBalance();
                      
                      // Buy the punk
                      punksContract.buyPunk{value: minValue}(punkId);
                      // Make sure we own it
                      if (punksContract.punkIndexToAddress(punkId) != address(this)) revert PunkNotOwned();
                      
                      // Relist the punk at the configured multiplier price
                      punksContract.offerPunkForSale(punkId, minValue * priceMultiplier / 1000);
                      
                      // Send reward to caller
                      SafeTransferLib.forceSafeTransferETH(msg.sender, reward);
                      
                      // Deduct spent fees from currentFees
                      currentFees -= totalRequired;
                      // If this is the first punk purchase, update hook state
                      if (lastPunkSalePrice == 0) {
                          IPunkStrategyHook(hookAddress).punksAreAccumulating();
                      }
                      // Set last sale price to the punk price
                      lastPunkSalePrice = minValue;
                      return lastPunkSalePrice;
                  }
                  /// @notice Processes a punk sale by checking for new ETH, rewarding caller, and burning tokens
                  /// @dev Verifies excess ETH above currentFees matches a punk sale, rewards caller, burns tokens with remaining ETH
                  /// @return The amount of ETH processed from the sale
                  function processPunkSale() external nonReentrant returns (uint256) {
                      if (lastPunkSalePrice == 0) revert NoPunksBoughtYet();
                      // Calculate excess ETH in contract beyond currentFees
                      uint256 excessEth = address(this).balance - currentFees;
                      
                      // Verify excess matches a punk sale
                      if (excessEth <= lastPunkSalePrice) revert NoSaleToProcess();
                      
                      // Use remaining ETH to buy and burn tokens
                      uint256 burnAmount = excessEth - reward;
                      _buyAndBurnTokens(burnAmount);
                      // Set fee cooldown on hook
                      IPunkStrategyHook(hookAddress).feeCooldown();
                      
                      // Send reward to caller after burn completes
                      SafeTransferLib.forceSafeTransferETH(msg.sender, reward);
                      
                      emit ProtocolFeesFromSales(excessEth);
                      return excessEth;
                  }
                  /// @notice Checks if there is ETH from a punk sale
                  /// @dev Returns true if excess ETH is less than or equal to the last punk sale price
                  /// @return bool True if there is insufficient ETH, false otherwise
                  function canProcessPunkSale() external view returns (bool) {
                      uint256 excessEth = address(this).balance - currentFees;
                      return excessEth > lastPunkSalePrice;
                  }
                  /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
                  /*                  INTERNAL FUNCTIONS                 */
                  /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
                  /// @notice Internal function to load liquidity into the Uniswap V4 pool
                  /// @param _hook Address of the CabalcoinHook
                  function _loadLiquidity(address _hook) internal {
                      loadingLiquidity = true;
                      // Create the pool with ETH (currency0) and TOKEN (currency1)
                      Currency currency0 = Currency.wrap(address(0)); // ETH
                      Currency currency1 = Currency.wrap(address(this)); // TOKEN
                      uint24 lpFee = 0;
                      int24 tickSpacing = 60;
                      uint256 token0Amount = 1; // 1 wei
                      uint256 token1Amount = 1_000_000_000 * 10**18; // 1B TOKEN
                      // 10e18 ETH = 1_000_000_000e18 TOKEN 
                      uint160 startingPrice = 501082896750095888663770159906816;
                      int24 tickLower = TickMath.minUsableTick(tickSpacing);
                      int24 tickUpper = int24(175020);
                      PoolKey memory key = PoolKey(currency0, currency1, lpFee, tickSpacing, IHooks(_hook));
                      bytes memory hookData = new bytes(0);
                      // Hardcoded from LiquidityAmounts.getLiquidityForAmounts
                      uint128 liquidity = 158372218983990412488087;
                      uint256 amount0Max = token0Amount + 1 wei;
                      uint256 amount1Max = token1Amount + 1 wei;
                      (bytes memory actions, bytes[] memory mintParams) =
                          _mintLiquidityParams(key, tickLower, tickUpper, liquidity, amount0Max, amount1Max, address(this), hookData);
                      bytes[] memory params = new bytes[](2);
                      params[0] = abi.encodeWithSelector(posm.initializePool.selector, key, startingPrice, hookData);
                      params[1] = abi.encodeWithSelector(
                          posm.modifyLiquidities.selector, abi.encode(actions, mintParams), block.timestamp + 60
                      );
                      uint256 valueToPass = amount0Max;
                      permit2.approve(address(this), address(posm), type(uint160).max, type(uint48).max);
                      posm.multicall{value: valueToPass}(params);
                      loadingLiquidity = false;
                  }
                  /// @notice Creates parameters for minting liquidity in Uniswap V4
                  function _mintLiquidityParams(
                      PoolKey memory poolKey,
                      int24 _tickLower,
                      int24 _tickUpper,
                      uint256 liquidity,
                      uint256 amount0Max,
                      uint256 amount1Max,
                      address recipient,
                      bytes memory hookData
                  ) internal pure returns (bytes memory, bytes[] memory) {
                      bytes memory actions = abi.encodePacked(uint8(Actions.MINT_POSITION), uint8(Actions.SETTLE_PAIR));
                      bytes[] memory params = new bytes[](2);
                      params[0] = abi.encode(poolKey, _tickLower, _tickUpper, liquidity, amount0Max, amount1Max, recipient, hookData);
                      params[1] = abi.encode(poolKey.currency0, poolKey.currency1);
                      return (actions, params);
                  }
                  /// @notice Buys tokens with ETH and burns them by sending to dead address
                  /// @param amountIn The amount of ETH to spend on tokens that will be burned
                  function _buyAndBurnTokens(uint256 amountIn) internal {
                      PoolKey memory key = PoolKey(
                          Currency.wrap(address(0)),
                          Currency.wrap(address(this)),
                          0,
                          60,
                          IHooks(hookAddress)
                      );
                      router.swapExactTokensForTokens{value: amountIn}(
                          amountIn,
                          0,
                          true,
                          key,
                          "",
                          DEADADDRESS,
                          block.timestamp
                      );
                  }
                  /// @notice Allows the contract to receive ETH
                  receive() external payable {}
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.4;
              /// @notice Simple single owner authorization mixin.
              /// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/Ownable.sol)
              ///
              /// @dev Note:
              /// This implementation does NOT auto-initialize the owner to `msg.sender`.
              /// You MUST call the `_initializeOwner` in the constructor / initializer.
              ///
              /// While the ownable portion follows
              /// [EIP-173](https://eips.ethereum.org/EIPS/eip-173) for compatibility,
              /// the nomenclature for the 2-step ownership handover may be unique to this codebase.
              abstract contract Ownable {
                  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
                  /*                       CUSTOM ERRORS                        */
                  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
                  /// @dev The caller is not authorized to call the function.
                  error Unauthorized();
                  /// @dev The `newOwner` cannot be the zero address.
                  error NewOwnerIsZeroAddress();
                  /// @dev The `pendingOwner` does not have a valid handover request.
                  error NoHandoverRequest();
                  /// @dev Cannot double-initialize.
                  error AlreadyInitialized();
                  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
                  /*                           EVENTS                           */
                  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
                  /// @dev The ownership is transferred from `oldOwner` to `newOwner`.
                  /// This event is intentionally kept the same as OpenZeppelin's Ownable to be
                  /// compatible with indexers and [EIP-173](https://eips.ethereum.org/EIPS/eip-173),
                  /// despite it not being as lightweight as a single argument event.
                  event OwnershipTransferred(address indexed oldOwner, address indexed newOwner);
                  /// @dev An ownership handover to `pendingOwner` has been requested.
                  event OwnershipHandoverRequested(address indexed pendingOwner);
                  /// @dev The ownership handover to `pendingOwner` has been canceled.
                  event OwnershipHandoverCanceled(address indexed pendingOwner);
                  /// @dev `keccak256(bytes("OwnershipTransferred(address,address)"))`.
                  uint256 private constant _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE =
                      0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0;
                  /// @dev `keccak256(bytes("OwnershipHandoverRequested(address)"))`.
                  uint256 private constant _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE =
                      0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d;
                  /// @dev `keccak256(bytes("OwnershipHandoverCanceled(address)"))`.
                  uint256 private constant _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE =
                      0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92;
                  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
                  /*                          STORAGE                           */
                  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
                  /// @dev The owner slot is given by:
                  /// `bytes32(~uint256(uint32(bytes4(keccak256("_OWNER_SLOT_NOT")))))`.
                  /// It is intentionally chosen to be a high value
                  /// to avoid collision with lower slots.
                  /// The choice of manual storage layout is to enable compatibility
                  /// with both regular and upgradeable contracts.
                  bytes32 internal constant _OWNER_SLOT =
                      0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927;
                  /// The ownership handover slot of `newOwner` is given by:
                  /// ```
                  ///     mstore(0x00, or(shl(96, user), _HANDOVER_SLOT_SEED))
                  ///     let handoverSlot := keccak256(0x00, 0x20)
                  /// ```
                  /// It stores the expiry timestamp of the two-step ownership handover.
                  uint256 private constant _HANDOVER_SLOT_SEED = 0x389a75e1;
                  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
                  /*                     INTERNAL FUNCTIONS                     */
                  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
                  /// @dev Override to return true to make `_initializeOwner` prevent double-initialization.
                  function _guardInitializeOwner() internal pure virtual returns (bool guard) {}
                  /// @dev Initializes the owner directly without authorization guard.
                  /// This function must be called upon initialization,
                  /// regardless of whether the contract is upgradeable or not.
                  /// This is to enable generalization to both regular and upgradeable contracts,
                  /// and to save gas in case the initial owner is not the caller.
                  /// For performance reasons, this function will not check if there
                  /// is an existing owner.
                  function _initializeOwner(address newOwner) internal virtual {
                      if (_guardInitializeOwner()) {
                          /// @solidity memory-safe-assembly
                          assembly {
                              let ownerSlot := _OWNER_SLOT
                              if sload(ownerSlot) {
                                  mstore(0x00, 0x0dc149f0) // `AlreadyInitialized()`.
                                  revert(0x1c, 0x04)
                              }
                              // Clean the upper 96 bits.
                              newOwner := shr(96, shl(96, newOwner))
                              // Store the new value.
                              sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
                              // Emit the {OwnershipTransferred} event.
                              log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
                          }
                      } else {
                          /// @solidity memory-safe-assembly
                          assembly {
                              // Clean the upper 96 bits.
                              newOwner := shr(96, shl(96, newOwner))
                              // Store the new value.
                              sstore(_OWNER_SLOT, newOwner)
                              // Emit the {OwnershipTransferred} event.
                              log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
                          }
                      }
                  }
                  /// @dev Sets the owner directly without authorization guard.
                  function _setOwner(address newOwner) internal virtual {
                      if (_guardInitializeOwner()) {
                          /// @solidity memory-safe-assembly
                          assembly {
                              let ownerSlot := _OWNER_SLOT
                              // Clean the upper 96 bits.
                              newOwner := shr(96, shl(96, newOwner))
                              // Emit the {OwnershipTransferred} event.
                              log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
                              // Store the new value.
                              sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
                          }
                      } else {
                          /// @solidity memory-safe-assembly
                          assembly {
                              let ownerSlot := _OWNER_SLOT
                              // Clean the upper 96 bits.
                              newOwner := shr(96, shl(96, newOwner))
                              // Emit the {OwnershipTransferred} event.
                              log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
                              // Store the new value.
                              sstore(ownerSlot, newOwner)
                          }
                      }
                  }
                  /// @dev Throws if the sender is not the owner.
                  function _checkOwner() internal view virtual {
                      /// @solidity memory-safe-assembly
                      assembly {
                          // If the caller is not the stored owner, revert.
                          if iszero(eq(caller(), sload(_OWNER_SLOT))) {
                              mstore(0x00, 0x82b42900) // `Unauthorized()`.
                              revert(0x1c, 0x04)
                          }
                      }
                  }
                  /// @dev Returns how long a two-step ownership handover is valid for in seconds.
                  /// Override to return a different value if needed.
                  /// Made internal to conserve bytecode. Wrap it in a public function if needed.
                  function _ownershipHandoverValidFor() internal view virtual returns (uint64) {
                      return 48 * 3600;
                  }
                  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
                  /*                  PUBLIC UPDATE FUNCTIONS                   */
                  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
                  /// @dev Allows the owner to transfer the ownership to `newOwner`.
                  function transferOwnership(address newOwner) public payable virtual onlyOwner {
                      /// @solidity memory-safe-assembly
                      assembly {
                          if iszero(shl(96, newOwner)) {
                              mstore(0x00, 0x7448fbae) // `NewOwnerIsZeroAddress()`.
                              revert(0x1c, 0x04)
                          }
                      }
                      _setOwner(newOwner);
                  }
                  /// @dev Allows the owner to renounce their ownership.
                  function renounceOwnership() public payable virtual onlyOwner {
                      _setOwner(address(0));
                  }
                  /// @dev Request a two-step ownership handover to the caller.
                  /// The request will automatically expire in 48 hours (172800 seconds) by default.
                  function requestOwnershipHandover() public payable virtual {
                      unchecked {
                          uint256 expires = block.timestamp + _ownershipHandoverValidFor();
                          /// @solidity memory-safe-assembly
                          assembly {
                              // Compute and set the handover slot to `expires`.
                              mstore(0x0c, _HANDOVER_SLOT_SEED)
                              mstore(0x00, caller())
                              sstore(keccak256(0x0c, 0x20), expires)
                              // Emit the {OwnershipHandoverRequested} event.
                              log2(0, 0, _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE, caller())
                          }
                      }
                  }
                  /// @dev Cancels the two-step ownership handover to the caller, if any.
                  function cancelOwnershipHandover() public payable virtual {
                      /// @solidity memory-safe-assembly
                      assembly {
                          // Compute and set the handover slot to 0.
                          mstore(0x0c, _HANDOVER_SLOT_SEED)
                          mstore(0x00, caller())
                          sstore(keccak256(0x0c, 0x20), 0)
                          // Emit the {OwnershipHandoverCanceled} event.
                          log2(0, 0, _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE, caller())
                      }
                  }
                  /// @dev Allows the owner to complete the two-step ownership handover to `pendingOwner`.
                  /// Reverts if there is no existing ownership handover requested by `pendingOwner`.
                  function completeOwnershipHandover(address pendingOwner) public payable virtual onlyOwner {
                      /// @solidity memory-safe-assembly
                      assembly {
                          // Compute and set the handover slot to 0.
                          mstore(0x0c, _HANDOVER_SLOT_SEED)
                          mstore(0x00, pendingOwner)
                          let handoverSlot := keccak256(0x0c, 0x20)
                          // If the handover does not exist, or has expired.
                          if gt(timestamp(), sload(handoverSlot)) {
                              mstore(0x00, 0x6f5e8818) // `NoHandoverRequest()`.
                              revert(0x1c, 0x04)
                          }
                          // Set the handover slot to 0.
                          sstore(handoverSlot, 0)
                      }
                      _setOwner(pendingOwner);
                  }
                  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
                  /*                   PUBLIC READ FUNCTIONS                    */
                  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
                  /// @dev Returns the owner of the contract.
                  function owner() public view virtual returns (address result) {
                      /// @solidity memory-safe-assembly
                      assembly {
                          result := sload(_OWNER_SLOT)
                      }
                  }
                  /// @dev Returns the expiry timestamp for the two-step ownership handover to `pendingOwner`.
                  function ownershipHandoverExpiresAt(address pendingOwner)
                      public
                      view
                      virtual
                      returns (uint256 result)
                  {
                      /// @solidity memory-safe-assembly
                      assembly {
                          // Compute the handover slot.
                          mstore(0x0c, _HANDOVER_SLOT_SEED)
                          mstore(0x00, pendingOwner)
                          // Load the handover slot.
                          result := sload(keccak256(0x0c, 0x20))
                      }
                  }
                  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
                  /*                         MODIFIERS                          */
                  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
                  /// @dev Marks a function as only callable by the owner.
                  modifier onlyOwner() virtual {
                      _checkOwner();
                      _;
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.4;
              /// @notice Simple ERC20 + EIP-2612 implementation.
              /// @author Solady (https://github.com/vectorized/solady/blob/main/src/tokens/ERC20.sol)
              /// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
              /// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol)
              ///
              /// @dev Note:
              /// - The ERC20 standard allows minting and transferring to and from the zero address,
              ///   minting and transferring zero tokens, as well as self-approvals.
              ///   For performance, this implementation WILL NOT revert for such actions.
              ///   Please add any checks with overrides if desired.
              /// - The `permit` function uses the ecrecover precompile (0x1).
              ///
              /// If you are overriding:
              /// - NEVER violate the ERC20 invariant:
              ///   the total sum of all balances must be equal to `totalSupply()`.
              /// - Check that the overridden function is actually used in the function you want to
              ///   change the behavior of. Much of the code has been manually inlined for performance.
              abstract contract ERC20 {
                  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
                  /*                       CUSTOM ERRORS                        */
                  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
                  /// @dev The total supply has overflowed.
                  error TotalSupplyOverflow();
                  /// @dev The allowance has overflowed.
                  error AllowanceOverflow();
                  /// @dev The allowance has underflowed.
                  error AllowanceUnderflow();
                  /// @dev Insufficient balance.
                  error InsufficientBalance();
                  /// @dev Insufficient allowance.
                  error InsufficientAllowance();
                  /// @dev The permit is invalid.
                  error InvalidPermit();
                  /// @dev The permit has expired.
                  error PermitExpired();
                  /// @dev The allowance of Permit2 is fixed at infinity.
                  error Permit2AllowanceIsFixedAtInfinity();
                  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
                  /*                           EVENTS                           */
                  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
                  /// @dev Emitted when `amount` tokens is transferred from `from` to `to`.
                  event Transfer(address indexed from, address indexed to, uint256 amount);
                  /// @dev Emitted when `amount` tokens is approved by `owner` to be used by `spender`.
                  event Approval(address indexed owner, address indexed spender, uint256 amount);
                  /// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
                  uint256 private constant _TRANSFER_EVENT_SIGNATURE =
                      0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
                  /// @dev `keccak256(bytes("Approval(address,address,uint256)"))`.
                  uint256 private constant _APPROVAL_EVENT_SIGNATURE =
                      0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925;
                  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
                  /*                          STORAGE                           */
                  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
                  /// @dev The storage slot for the total supply.
                  uint256 private constant _TOTAL_SUPPLY_SLOT = 0x05345cdf77eb68f44c;
                  /// @dev The balance slot of `owner` is given by:
                  /// ```
                  ///     mstore(0x0c, _BALANCE_SLOT_SEED)
                  ///     mstore(0x00, owner)
                  ///     let balanceSlot := keccak256(0x0c, 0x20)
                  /// ```
                  uint256 private constant _BALANCE_SLOT_SEED = 0x87a211a2;
                  /// @dev The allowance slot of (`owner`, `spender`) is given by:
                  /// ```
                  ///     mstore(0x20, spender)
                  ///     mstore(0x0c, _ALLOWANCE_SLOT_SEED)
                  ///     mstore(0x00, owner)
                  ///     let allowanceSlot := keccak256(0x0c, 0x34)
                  /// ```
                  uint256 private constant _ALLOWANCE_SLOT_SEED = 0x7f5e9f20;
                  /// @dev The nonce slot of `owner` is given by:
                  /// ```
                  ///     mstore(0x0c, _NONCES_SLOT_SEED)
                  ///     mstore(0x00, owner)
                  ///     let nonceSlot := keccak256(0x0c, 0x20)
                  /// ```
                  uint256 private constant _NONCES_SLOT_SEED = 0x38377508;
                  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
                  /*                         CONSTANTS                          */
                  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
                  /// @dev `(_NONCES_SLOT_SEED << 16) | 0x1901`.
                  uint256 private constant _NONCES_SLOT_SEED_WITH_SIGNATURE_PREFIX = 0x383775081901;
                  /// @dev `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`.
                  bytes32 private constant _DOMAIN_TYPEHASH =
                      0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f;
                  /// @dev `keccak256("1")`.
                  /// If you need to use a different version, override `_versionHash`.
                  bytes32 private constant _DEFAULT_VERSION_HASH =
                      0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6;
                  /// @dev `keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")`.
                  bytes32 private constant _PERMIT_TYPEHASH =
                      0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
                  /// @dev The canonical Permit2 address.
                  /// For signature-based allowance granting for single transaction ERC20 `transferFrom`.
                  /// Enabled by default. To disable, override `_givePermit2InfiniteAllowance()`.
                  /// [Github](https://github.com/Uniswap/permit2)
                  /// [Etherscan](https://etherscan.io/address/0x000000000022D473030F116dDEE9F6B43aC78BA3)
                  address internal constant _PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;
                  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
                  /*                       ERC20 METADATA                       */
                  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
                  /// @dev Returns the name of the token.
                  function name() public view virtual returns (string memory);
                  /// @dev Returns the symbol of the token.
                  function symbol() public view virtual returns (string memory);
                  /// @dev Returns the decimals places of the token.
                  function decimals() public view virtual returns (uint8) {
                      return 18;
                  }
                  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
                  /*                           ERC20                            */
                  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
                  /// @dev Returns the amount of tokens in existence.
                  function totalSupply() public view virtual returns (uint256 result) {
                      /// @solidity memory-safe-assembly
                      assembly {
                          result := sload(_TOTAL_SUPPLY_SLOT)
                      }
                  }
                  /// @dev Returns the amount of tokens owned by `owner`.
                  function balanceOf(address owner) public view virtual returns (uint256 result) {
                      /// @solidity memory-safe-assembly
                      assembly {
                          mstore(0x0c, _BALANCE_SLOT_SEED)
                          mstore(0x00, owner)
                          result := sload(keccak256(0x0c, 0x20))
                      }
                  }
                  /// @dev Returns the amount of tokens that `spender` can spend on behalf of `owner`.
                  function allowance(address owner, address spender)
                      public
                      view
                      virtual
                      returns (uint256 result)
                  {
                      if (_givePermit2InfiniteAllowance()) {
                          if (spender == _PERMIT2) return type(uint256).max;
                      }
                      /// @solidity memory-safe-assembly
                      assembly {
                          mstore(0x20, spender)
                          mstore(0x0c, _ALLOWANCE_SLOT_SEED)
                          mstore(0x00, owner)
                          result := sload(keccak256(0x0c, 0x34))
                      }
                  }
                  /// @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
                  ///
                  /// Emits a {Approval} event.
                  function approve(address spender, uint256 amount) public virtual returns (bool) {
                      if (_givePermit2InfiniteAllowance()) {
                          /// @solidity memory-safe-assembly
                          assembly {
                              // If `spender == _PERMIT2 && amount != type(uint256).max`.
                              if iszero(or(xor(shr(96, shl(96, spender)), _PERMIT2), iszero(not(amount)))) {
                                  mstore(0x00, 0x3f68539a) // `Permit2AllowanceIsFixedAtInfinity()`.
                                  revert(0x1c, 0x04)
                              }
                          }
                      }
                      /// @solidity memory-safe-assembly
                      assembly {
                          // Compute the allowance slot and store the amount.
                          mstore(0x20, spender)
                          mstore(0x0c, _ALLOWANCE_SLOT_SEED)
                          mstore(0x00, caller())
                          sstore(keccak256(0x0c, 0x34), amount)
                          // Emit the {Approval} event.
                          mstore(0x00, amount)
                          log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c)))
                      }
                      return true;
                  }
                  /// @dev Transfer `amount` tokens from the caller to `to`.
                  ///
                  /// Requirements:
                  /// - `from` must at least have `amount`.
                  ///
                  /// Emits a {Transfer} event.
                  function transfer(address to, uint256 amount) public virtual returns (bool) {
                      _beforeTokenTransfer(msg.sender, to, amount);
                      /// @solidity memory-safe-assembly
                      assembly {
                          // Compute the balance slot and load its value.
                          mstore(0x0c, _BALANCE_SLOT_SEED)
                          mstore(0x00, caller())
                          let fromBalanceSlot := keccak256(0x0c, 0x20)
                          let fromBalance := sload(fromBalanceSlot)
                          // Revert if insufficient balance.
                          if gt(amount, fromBalance) {
                              mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
                              revert(0x1c, 0x04)
                          }
                          // Subtract and store the updated balance.
                          sstore(fromBalanceSlot, sub(fromBalance, amount))
                          // Compute the balance slot of `to`.
                          mstore(0x00, to)
                          let toBalanceSlot := keccak256(0x0c, 0x20)
                          // Add and store the updated balance of `to`.
                          // Will not overflow because the sum of all user balances
                          // cannot exceed the maximum uint256 value.
                          sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
                          // Emit the {Transfer} event.
                          mstore(0x20, amount)
                          log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, caller(), shr(96, mload(0x0c)))
                      }
                      _afterTokenTransfer(msg.sender, to, amount);
                      return true;
                  }
                  /// @dev Transfers `amount` tokens from `from` to `to`.
                  ///
                  /// Note: Does not update the allowance if it is the maximum uint256 value.
                  ///
                  /// Requirements:
                  /// - `from` must at least have `amount`.
                  /// - The caller must have at least `amount` of allowance to transfer the tokens of `from`.
                  ///
                  /// Emits a {Transfer} event.
                  function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {
                      _beforeTokenTransfer(from, to, amount);
                      // Code duplication is for zero-cost abstraction if possible.
                      if (_givePermit2InfiniteAllowance()) {
                          /// @solidity memory-safe-assembly
                          assembly {
                              let from_ := shl(96, from)
                              if iszero(eq(caller(), _PERMIT2)) {
                                  // Compute the allowance slot and load its value.
                                  mstore(0x20, caller())
                                  mstore(0x0c, or(from_, _ALLOWANCE_SLOT_SEED))
                                  let allowanceSlot := keccak256(0x0c, 0x34)
                                  let allowance_ := sload(allowanceSlot)
                                  // If the allowance is not the maximum uint256 value.
                                  if not(allowance_) {
                                      // Revert if the amount to be transferred exceeds the allowance.
                                      if gt(amount, allowance_) {
                                          mstore(0x00, 0x13be252b) // `InsufficientAllowance()`.
                                          revert(0x1c, 0x04)
                                      }
                                      // Subtract and store the updated allowance.
                                      sstore(allowanceSlot, sub(allowance_, amount))
                                  }
                              }
                              // Compute the balance slot and load its value.
                              mstore(0x0c, or(from_, _BALANCE_SLOT_SEED))
                              let fromBalanceSlot := keccak256(0x0c, 0x20)
                              let fromBalance := sload(fromBalanceSlot)
                              // Revert if insufficient balance.
                              if gt(amount, fromBalance) {
                                  mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
                                  revert(0x1c, 0x04)
                              }
                              // Subtract and store the updated balance.
                              sstore(fromBalanceSlot, sub(fromBalance, amount))
                              // Compute the balance slot of `to`.
                              mstore(0x00, to)
                              let toBalanceSlot := keccak256(0x0c, 0x20)
                              // Add and store the updated balance of `to`.
                              // Will not overflow because the sum of all user balances
                              // cannot exceed the maximum uint256 value.
                              sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
                              // Emit the {Transfer} event.
                              mstore(0x20, amount)
                              log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c)))
                          }
                      } else {
                          /// @solidity memory-safe-assembly
                          assembly {
                              let from_ := shl(96, from)
                              // Compute the allowance slot and load its value.
                              mstore(0x20, caller())
                              mstore(0x0c, or(from_, _ALLOWANCE_SLOT_SEED))
                              let allowanceSlot := keccak256(0x0c, 0x34)
                              let allowance_ := sload(allowanceSlot)
                              // If the allowance is not the maximum uint256 value.
                              if not(allowance_) {
                                  // Revert if the amount to be transferred exceeds the allowance.
                                  if gt(amount, allowance_) {
                                      mstore(0x00, 0x13be252b) // `InsufficientAllowance()`.
                                      revert(0x1c, 0x04)
                                  }
                                  // Subtract and store the updated allowance.
                                  sstore(allowanceSlot, sub(allowance_, amount))
                              }
                              // Compute the balance slot and load its value.
                              mstore(0x0c, or(from_, _BALANCE_SLOT_SEED))
                              let fromBalanceSlot := keccak256(0x0c, 0x20)
                              let fromBalance := sload(fromBalanceSlot)
                              // Revert if insufficient balance.
                              if gt(amount, fromBalance) {
                                  mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
                                  revert(0x1c, 0x04)
                              }
                              // Subtract and store the updated balance.
                              sstore(fromBalanceSlot, sub(fromBalance, amount))
                              // Compute the balance slot of `to`.
                              mstore(0x00, to)
                              let toBalanceSlot := keccak256(0x0c, 0x20)
                              // Add and store the updated balance of `to`.
                              // Will not overflow because the sum of all user balances
                              // cannot exceed the maximum uint256 value.
                              sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
                              // Emit the {Transfer} event.
                              mstore(0x20, amount)
                              log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c)))
                          }
                      }
                      _afterTokenTransfer(from, to, amount);
                      return true;
                  }
                  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
                  /*                          EIP-2612                          */
                  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
                  /// @dev For more performance, override to return the constant value
                  /// of `keccak256(bytes(name()))` if `name()` will never change.
                  function _constantNameHash() internal view virtual returns (bytes32 result) {}
                  /// @dev If you need a different value, override this function.
                  function _versionHash() internal view virtual returns (bytes32 result) {
                      result = _DEFAULT_VERSION_HASH;
                  }
                  /// @dev For inheriting contracts to increment the nonce.
                  function _incrementNonce(address owner) internal virtual {
                      /// @solidity memory-safe-assembly
                      assembly {
                          mstore(0x0c, _NONCES_SLOT_SEED)
                          mstore(0x00, owner)
                          let nonceSlot := keccak256(0x0c, 0x20)
                          sstore(nonceSlot, add(1, sload(nonceSlot)))
                      }
                  }
                  /// @dev Returns the current nonce for `owner`.
                  /// This value is used to compute the signature for EIP-2612 permit.
                  function nonces(address owner) public view virtual returns (uint256 result) {
                      /// @solidity memory-safe-assembly
                      assembly {
                          // Compute the nonce slot and load its value.
                          mstore(0x0c, _NONCES_SLOT_SEED)
                          mstore(0x00, owner)
                          result := sload(keccak256(0x0c, 0x20))
                      }
                  }
                  /// @dev Sets `value` as the allowance of `spender` over the tokens of `owner`,
                  /// authorized by a signed approval by `owner`.
                  ///
                  /// Emits a {Approval} event.
                  function permit(
                      address owner,
                      address spender,
                      uint256 value,
                      uint256 deadline,
                      uint8 v,
                      bytes32 r,
                      bytes32 s
                  ) public virtual {
                      if (_givePermit2InfiniteAllowance()) {
                          /// @solidity memory-safe-assembly
                          assembly {
                              // If `spender == _PERMIT2 && value != type(uint256).max`.
                              if iszero(or(xor(shr(96, shl(96, spender)), _PERMIT2), iszero(not(value)))) {
                                  mstore(0x00, 0x3f68539a) // `Permit2AllowanceIsFixedAtInfinity()`.
                                  revert(0x1c, 0x04)
                              }
                          }
                      }
                      bytes32 nameHash = _constantNameHash();
                      //  We simply calculate it on-the-fly to allow for cases where the `name` may change.
                      if (nameHash == bytes32(0)) nameHash = keccak256(bytes(name()));
                      bytes32 versionHash = _versionHash();
                      /// @solidity memory-safe-assembly
                      assembly {
                          // Revert if the block timestamp is greater than `deadline`.
                          if gt(timestamp(), deadline) {
                              mstore(0x00, 0x1a15a3cc) // `PermitExpired()`.
                              revert(0x1c, 0x04)
                          }
                          let m := mload(0x40) // Grab the free memory pointer.
                          // Clean the upper 96 bits.
                          owner := shr(96, shl(96, owner))
                          spender := shr(96, shl(96, spender))
                          // Compute the nonce slot and load its value.
                          mstore(0x0e, _NONCES_SLOT_SEED_WITH_SIGNATURE_PREFIX)
                          mstore(0x00, owner)
                          let nonceSlot := keccak256(0x0c, 0x20)
                          let nonceValue := sload(nonceSlot)
                          // Prepare the domain separator.
                          mstore(m, _DOMAIN_TYPEHASH)
                          mstore(add(m, 0x20), nameHash)
                          mstore(add(m, 0x40), versionHash)
                          mstore(add(m, 0x60), chainid())
                          mstore(add(m, 0x80), address())
                          mstore(0x2e, keccak256(m, 0xa0))
                          // Prepare the struct hash.
                          mstore(m, _PERMIT_TYPEHASH)
                          mstore(add(m, 0x20), owner)
                          mstore(add(m, 0x40), spender)
                          mstore(add(m, 0x60), value)
                          mstore(add(m, 0x80), nonceValue)
                          mstore(add(m, 0xa0), deadline)
                          mstore(0x4e, keccak256(m, 0xc0))
                          // Prepare the ecrecover calldata.
                          mstore(0x00, keccak256(0x2c, 0x42))
                          mstore(0x20, and(0xff, v))
                          mstore(0x40, r)
                          mstore(0x60, s)
                          let t := staticcall(gas(), 1, 0x00, 0x80, 0x20, 0x20)
                          // If the ecrecover fails, the returndatasize will be 0x00,
                          // `owner` will be checked if it equals the hash at 0x00,
                          // which evaluates to false (i.e. 0), and we will revert.
                          // If the ecrecover succeeds, the returndatasize will be 0x20,
                          // `owner` will be compared against the returned address at 0x20.
                          if iszero(eq(mload(returndatasize()), owner)) {
                              mstore(0x00, 0xddafbaef) // `InvalidPermit()`.
                              revert(0x1c, 0x04)
                          }
                          // Increment and store the updated nonce.
                          sstore(nonceSlot, add(nonceValue, t)) // `t` is 1 if ecrecover succeeds.
                          // Compute the allowance slot and store the value.
                          // The `owner` is already at slot 0x20.
                          mstore(0x40, or(shl(160, _ALLOWANCE_SLOT_SEED), spender))
                          sstore(keccak256(0x2c, 0x34), value)
                          // Emit the {Approval} event.
                          log3(add(m, 0x60), 0x20, _APPROVAL_EVENT_SIGNATURE, owner, spender)
                          mstore(0x40, m) // Restore the free memory pointer.
                          mstore(0x60, 0) // Restore the zero pointer.
                      }
                  }
                  /// @dev Returns the EIP-712 domain separator for the EIP-2612 permit.
                  function DOMAIN_SEPARATOR() public view virtual returns (bytes32 result) {
                      bytes32 nameHash = _constantNameHash();
                      //  We simply calculate it on-the-fly to allow for cases where the `name` may change.
                      if (nameHash == bytes32(0)) nameHash = keccak256(bytes(name()));
                      bytes32 versionHash = _versionHash();
                      /// @solidity memory-safe-assembly
                      assembly {
                          let m := mload(0x40) // Grab the free memory pointer.
                          mstore(m, _DOMAIN_TYPEHASH)
                          mstore(add(m, 0x20), nameHash)
                          mstore(add(m, 0x40), versionHash)
                          mstore(add(m, 0x60), chainid())
                          mstore(add(m, 0x80), address())
                          result := keccak256(m, 0xa0)
                      }
                  }
                  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
                  /*                  INTERNAL MINT FUNCTIONS                   */
                  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
                  /// @dev Mints `amount` tokens to `to`, increasing the total supply.
                  ///
                  /// Emits a {Transfer} event.
                  function _mint(address to, uint256 amount) internal virtual {
                      _beforeTokenTransfer(address(0), to, amount);
                      /// @solidity memory-safe-assembly
                      assembly {
                          let totalSupplyBefore := sload(_TOTAL_SUPPLY_SLOT)
                          let totalSupplyAfter := add(totalSupplyBefore, amount)
                          // Revert if the total supply overflows.
                          if lt(totalSupplyAfter, totalSupplyBefore) {
                              mstore(0x00, 0xe5cfe957) // `TotalSupplyOverflow()`.
                              revert(0x1c, 0x04)
                          }
                          // Store the updated total supply.
                          sstore(_TOTAL_SUPPLY_SLOT, totalSupplyAfter)
                          // Compute the balance slot and load its value.
                          mstore(0x0c, _BALANCE_SLOT_SEED)
                          mstore(0x00, to)
                          let toBalanceSlot := keccak256(0x0c, 0x20)
                          // Add and store the updated balance.
                          sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
                          // Emit the {Transfer} event.
                          mstore(0x20, amount)
                          log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, 0, shr(96, mload(0x0c)))
                      }
                      _afterTokenTransfer(address(0), to, amount);
                  }
                  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
                  /*                  INTERNAL BURN FUNCTIONS                   */
                  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
                  /// @dev Burns `amount` tokens from `from`, reducing the total supply.
                  ///
                  /// Emits a {Transfer} event.
                  function _burn(address from, uint256 amount) internal virtual {
                      _beforeTokenTransfer(from, address(0), amount);
                      /// @solidity memory-safe-assembly
                      assembly {
                          // Compute the balance slot and load its value.
                          mstore(0x0c, _BALANCE_SLOT_SEED)
                          mstore(0x00, from)
                          let fromBalanceSlot := keccak256(0x0c, 0x20)
                          let fromBalance := sload(fromBalanceSlot)
                          // Revert if insufficient balance.
                          if gt(amount, fromBalance) {
                              mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
                              revert(0x1c, 0x04)
                          }
                          // Subtract and store the updated balance.
                          sstore(fromBalanceSlot, sub(fromBalance, amount))
                          // Subtract and store the updated total supply.
                          sstore(_TOTAL_SUPPLY_SLOT, sub(sload(_TOTAL_SUPPLY_SLOT), amount))
                          // Emit the {Transfer} event.
                          mstore(0x00, amount)
                          log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), 0)
                      }
                      _afterTokenTransfer(from, address(0), amount);
                  }
                  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
                  /*                INTERNAL TRANSFER FUNCTIONS                 */
                  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
                  /// @dev Moves `amount` of tokens from `from` to `to`.
                  function _transfer(address from, address to, uint256 amount) internal virtual {
                      _beforeTokenTransfer(from, to, amount);
                      /// @solidity memory-safe-assembly
                      assembly {
                          let from_ := shl(96, from)
                          // Compute the balance slot and load its value.
                          mstore(0x0c, or(from_, _BALANCE_SLOT_SEED))
                          let fromBalanceSlot := keccak256(0x0c, 0x20)
                          let fromBalance := sload(fromBalanceSlot)
                          // Revert if insufficient balance.
                          if gt(amount, fromBalance) {
                              mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
                              revert(0x1c, 0x04)
                          }
                          // Subtract and store the updated balance.
                          sstore(fromBalanceSlot, sub(fromBalance, amount))
                          // Compute the balance slot of `to`.
                          mstore(0x00, to)
                          let toBalanceSlot := keccak256(0x0c, 0x20)
                          // Add and store the updated balance of `to`.
                          // Will not overflow because the sum of all user balances
                          // cannot exceed the maximum uint256 value.
                          sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
                          // Emit the {Transfer} event.
                          mstore(0x20, amount)
                          log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c)))
                      }
                      _afterTokenTransfer(from, to, amount);
                  }
                  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
                  /*                INTERNAL ALLOWANCE FUNCTIONS                */
                  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
                  /// @dev Updates the allowance of `owner` for `spender` based on spent `amount`.
                  function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {
                      if (_givePermit2InfiniteAllowance()) {
                          if (spender == _PERMIT2) return; // Do nothing, as allowance is infinite.
                      }
                      /// @solidity memory-safe-assembly
                      assembly {
                          // Compute the allowance slot and load its value.
                          mstore(0x20, spender)
                          mstore(0x0c, _ALLOWANCE_SLOT_SEED)
                          mstore(0x00, owner)
                          let allowanceSlot := keccak256(0x0c, 0x34)
                          let allowance_ := sload(allowanceSlot)
                          // If the allowance is not the maximum uint256 value.
                          if not(allowance_) {
                              // Revert if the amount to be transferred exceeds the allowance.
                              if gt(amount, allowance_) {
                                  mstore(0x00, 0x13be252b) // `InsufficientAllowance()`.
                                  revert(0x1c, 0x04)
                              }
                              // Subtract and store the updated allowance.
                              sstore(allowanceSlot, sub(allowance_, amount))
                          }
                      }
                  }
                  /// @dev Sets `amount` as the allowance of `spender` over the tokens of `owner`.
                  ///
                  /// Emits a {Approval} event.
                  function _approve(address owner, address spender, uint256 amount) internal virtual {
                      if (_givePermit2InfiniteAllowance()) {
                          /// @solidity memory-safe-assembly
                          assembly {
                              // If `spender == _PERMIT2 && amount != type(uint256).max`.
                              if iszero(or(xor(shr(96, shl(96, spender)), _PERMIT2), iszero(not(amount)))) {
                                  mstore(0x00, 0x3f68539a) // `Permit2AllowanceIsFixedAtInfinity()`.
                                  revert(0x1c, 0x04)
                              }
                          }
                      }
                      /// @solidity memory-safe-assembly
                      assembly {
                          let owner_ := shl(96, owner)
                          // Compute the allowance slot and store the amount.
                          mstore(0x20, spender)
                          mstore(0x0c, or(owner_, _ALLOWANCE_SLOT_SEED))
                          sstore(keccak256(0x0c, 0x34), amount)
                          // Emit the {Approval} event.
                          mstore(0x00, amount)
                          log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, shr(96, owner_), shr(96, mload(0x2c)))
                      }
                  }
                  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
                  /*                     HOOKS TO OVERRIDE                      */
                  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
                  /// @dev Hook that is called before any transfer of tokens.
                  /// This includes minting and burning.
                  function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}
                  /// @dev Hook that is called after any transfer of tokens.
                  /// This includes minting and burning.
                  function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}
                  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
                  /*                          PERMIT2                           */
                  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
                  /// @dev Returns whether to fix the Permit2 contract's allowance at infinity.
                  ///
                  /// This value should be kept constant after contract initialization,
                  /// or else the actual allowance values may not match with the {Approval} events.
                  /// For best performance, return a compile-time constant for zero-cost abstraction.
                  function _givePermit2InfiniteAllowance() internal view virtual returns (bool) {
                      return true;
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.4;
              /// @notice Reentrancy guard mixin.
              /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/ReentrancyGuard.sol)
              abstract contract ReentrancyGuard {
                  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
                  /*                       CUSTOM ERRORS                        */
                  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
                  /// @dev Unauthorized reentrant call.
                  error Reentrancy();
                  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
                  /*                          STORAGE                           */
                  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
                  /// @dev Equivalent to: `uint72(bytes9(keccak256("_REENTRANCY_GUARD_SLOT")))`.
                  /// 9 bytes is large enough to avoid collisions with lower slots,
                  /// but not too large to result in excessive bytecode bloat.
                  uint256 private constant _REENTRANCY_GUARD_SLOT = 0x929eee149b4bd21268;
                  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
                  /*                      REENTRANCY GUARD                      */
                  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
                  /// @dev Guards a function from reentrancy.
                  modifier nonReentrant() virtual {
                      /// @solidity memory-safe-assembly
                      assembly {
                          if eq(sload(_REENTRANCY_GUARD_SLOT), address()) {
                              mstore(0x00, 0xab143c06) // `Reentrancy()`.
                              revert(0x1c, 0x04)
                          }
                          sstore(_REENTRANCY_GUARD_SLOT, address())
                      }
                      _;
                      /// @solidity memory-safe-assembly
                      assembly {
                          sstore(_REENTRANCY_GUARD_SLOT, codesize())
                      }
                  }
                  /// @dev Guards a view function from read-only reentrancy.
                  modifier nonReadReentrant() virtual {
                      /// @solidity memory-safe-assembly
                      assembly {
                          if eq(sload(_REENTRANCY_GUARD_SLOT), address()) {
                              mstore(0x00, 0xab143c06) // `Reentrancy()`.
                              revert(0x1c, 0x04)
                          }
                      }
                      _;
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.4;
              /// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
              /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol)
              /// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
              /// @author Permit2 operations from (https://github.com/Uniswap/permit2/blob/main/src/libraries/Permit2Lib.sol)
              ///
              /// @dev Note:
              /// - For ETH transfers, please use `forceSafeTransferETH` for DoS protection.
              library SafeTransferLib {
                  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
                  /*                       CUSTOM ERRORS                        */
                  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
                  /// @dev The ETH transfer has failed.
                  error ETHTransferFailed();
                  /// @dev The ERC20 `transferFrom` has failed.
                  error TransferFromFailed();
                  /// @dev The ERC20 `transfer` has failed.
                  error TransferFailed();
                  /// @dev The ERC20 `approve` has failed.
                  error ApproveFailed();
                  /// @dev The ERC20 `totalSupply` query has failed.
                  error TotalSupplyQueryFailed();
                  /// @dev The Permit2 operation has failed.
                  error Permit2Failed();
                  /// @dev The Permit2 amount must be less than `2**160 - 1`.
                  error Permit2AmountOverflow();
                  /// @dev The Permit2 approve operation has failed.
                  error Permit2ApproveFailed();
                  /// @dev The Permit2 lockdown operation has failed.
                  error Permit2LockdownFailed();
                  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
                  /*                         CONSTANTS                          */
                  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
                  /// @dev Suggested gas stipend for contract receiving ETH that disallows any storage writes.
                  uint256 internal constant GAS_STIPEND_NO_STORAGE_WRITES = 2300;
                  /// @dev Suggested gas stipend for contract receiving ETH to perform a few
                  /// storage reads and writes, but low enough to prevent griefing.
                  uint256 internal constant GAS_STIPEND_NO_GRIEF = 100000;
                  /// @dev The unique EIP-712 domain separator for the DAI token contract.
                  bytes32 internal constant DAI_DOMAIN_SEPARATOR =
                      0xdbb8cf42e1ecb028be3f3dbc922e1d878b963f411dc388ced501601c60f7c6f7;
                  /// @dev The address for the WETH9 contract on Ethereum mainnet.
                  address internal constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
                  /// @dev The canonical Permit2 address.
                  /// [Github](https://github.com/Uniswap/permit2)
                  /// [Etherscan](https://etherscan.io/address/0x000000000022D473030F116dDEE9F6B43aC78BA3)
                  address internal constant PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;
                  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
                  /*                       ETH OPERATIONS                       */
                  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
                  // If the ETH transfer MUST succeed with a reasonable gas budget, use the force variants.
                  //
                  // The regular variants:
                  // - Forwards all remaining gas to the target.
                  // - Reverts if the target reverts.
                  // - Reverts if the current contract has insufficient balance.
                  //
                  // The force variants:
                  // - Forwards with an optional gas stipend
                  //   (defaults to `GAS_STIPEND_NO_GRIEF`, which is sufficient for most cases).
                  // - If the target reverts, or if the gas stipend is exhausted,
                  //   creates a temporary contract to force send the ETH via `SELFDESTRUCT`.
                  //   Future compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758.
                  // - Reverts if the current contract has insufficient balance.
                  //
                  // The try variants:
                  // - Forwards with a mandatory gas stipend.
                  // - Instead of reverting, returns whether the transfer succeeded.
                  /// @dev Sends `amount` (in wei) ETH to `to`.
                  function safeTransferETH(address to, uint256 amount) internal {
                      /// @solidity memory-safe-assembly
                      assembly {
                          if iszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) {
                              mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                              revert(0x1c, 0x04)
                          }
                      }
                  }
                  /// @dev Sends all the ETH in the current contract to `to`.
                  function safeTransferAllETH(address to) internal {
                      /// @solidity memory-safe-assembly
                      assembly {
                          // Transfer all the ETH and check if it succeeded or not.
                          if iszero(call(gas(), to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
                              mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                              revert(0x1c, 0x04)
                          }
                      }
                  }
                  /// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
                  function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal {
                      /// @solidity memory-safe-assembly
                      assembly {
                          if lt(selfbalance(), amount) {
                              mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                              revert(0x1c, 0x04)
                          }
                          if iszero(call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)) {
                              mstore(0x00, to) // Store the address in scratch space.
                              mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                              mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                              if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
                          }
                      }
                  }
                  /// @dev Force sends all the ETH in the current contract to `to`, with a `gasStipend`.
                  function forceSafeTransferAllETH(address to, uint256 gasStipend) internal {
                      /// @solidity memory-safe-assembly
                      assembly {
                          if iszero(call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
                              mstore(0x00, to) // Store the address in scratch space.
                              mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                              mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                              if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
                          }
                      }
                  }
                  /// @dev Force sends `amount` (in wei) ETH to `to`, with `GAS_STIPEND_NO_GRIEF`.
                  function forceSafeTransferETH(address to, uint256 amount) internal {
                      /// @solidity memory-safe-assembly
                      assembly {
                          if lt(selfbalance(), amount) {
                              mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                              revert(0x1c, 0x04)
                          }
                          if iszero(call(GAS_STIPEND_NO_GRIEF, to, amount, codesize(), 0x00, codesize(), 0x00)) {
                              mstore(0x00, to) // Store the address in scratch space.
                              mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                              mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                              if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
                          }
                      }
                  }
                  /// @dev Force sends all the ETH in the current contract to `to`, with `GAS_STIPEND_NO_GRIEF`.
                  function forceSafeTransferAllETH(address to) internal {
                      /// @solidity memory-safe-assembly
                      assembly {
                          // forgefmt: disable-next-item
                          if iszero(call(GAS_STIPEND_NO_GRIEF, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
                              mstore(0x00, to) // Store the address in scratch space.
                              mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                              mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                              if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
                          }
                      }
                  }
                  /// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
                  function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend)
                      internal
                      returns (bool success)
                  {
                      /// @solidity memory-safe-assembly
                      assembly {
                          success := call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)
                      }
                  }
                  /// @dev Sends all the ETH in the current contract to `to`, with a `gasStipend`.
                  function trySafeTransferAllETH(address to, uint256 gasStipend)
                      internal
                      returns (bool success)
                  {
                      /// @solidity memory-safe-assembly
                      assembly {
                          success := call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)
                      }
                  }
                  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
                  /*                      ERC20 OPERATIONS                      */
                  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
                  /// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
                  /// Reverts upon failure.
                  ///
                  /// The `from` account must have at least `amount` approved for
                  /// the current contract to manage.
                  function safeTransferFrom(address token, address from, address to, uint256 amount) internal {
                      /// @solidity memory-safe-assembly
                      assembly {
                          let m := mload(0x40) // Cache the free memory pointer.
                          mstore(0x60, amount) // Store the `amount` argument.
                          mstore(0x40, to) // Store the `to` argument.
                          mstore(0x2c, shl(96, from)) // Store the `from` argument.
                          mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
                          let success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
                          if iszero(and(eq(mload(0x00), 1), success)) {
                              if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                                  mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
                                  revert(0x1c, 0x04)
                              }
                          }
                          mstore(0x60, 0) // Restore the zero slot to zero.
                          mstore(0x40, m) // Restore the free memory pointer.
                      }
                  }
                  /// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
                  ///
                  /// The `from` account must have at least `amount` approved for the current contract to manage.
                  function trySafeTransferFrom(address token, address from, address to, uint256 amount)
                      internal
                      returns (bool success)
                  {
                      /// @solidity memory-safe-assembly
                      assembly {
                          let m := mload(0x40) // Cache the free memory pointer.
                          mstore(0x60, amount) // Store the `amount` argument.
                          mstore(0x40, to) // Store the `to` argument.
                          mstore(0x2c, shl(96, from)) // Store the `from` argument.
                          mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
                          success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
                          if iszero(and(eq(mload(0x00), 1), success)) {
                              success := lt(or(iszero(extcodesize(token)), returndatasize()), success)
                          }
                          mstore(0x60, 0) // Restore the zero slot to zero.
                          mstore(0x40, m) // Restore the free memory pointer.
                      }
                  }
                  /// @dev Sends all of ERC20 `token` from `from` to `to`.
                  /// Reverts upon failure.
                  ///
                  /// The `from` account must have their entire balance approved for the current contract to manage.
                  function safeTransferAllFrom(address token, address from, address to)
                      internal
                      returns (uint256 amount)
                  {
                      /// @solidity memory-safe-assembly
                      assembly {
                          let m := mload(0x40) // Cache the free memory pointer.
                          mstore(0x40, to) // Store the `to` argument.
                          mstore(0x2c, shl(96, from)) // Store the `from` argument.
                          mstore(0x0c, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
                          // Read the balance, reverting upon failure.
                          if iszero(
                              and( // The arguments of `and` are evaluated from right to left.
                                  gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                                  staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20)
                              )
                          ) {
                              mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
                              revert(0x1c, 0x04)
                          }
                          mstore(0x00, 0x23b872dd) // `transferFrom(address,address,uint256)`.
                          amount := mload(0x60) // The `amount` is already at 0x60. We'll need to return it.
                          // Perform the transfer, reverting upon failure.
                          let success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
                          if iszero(and(eq(mload(0x00), 1), success)) {
                              if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                                  mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
                                  revert(0x1c, 0x04)
                              }
                          }
                          mstore(0x60, 0) // Restore the zero slot to zero.
                          mstore(0x40, m) // Restore the free memory pointer.
                      }
                  }
                  /// @dev Sends `amount` of ERC20 `token` from the current contract to `to`.
                  /// Reverts upon failure.
                  function safeTransfer(address token, address to, uint256 amount) internal {
                      /// @solidity memory-safe-assembly
                      assembly {
                          mstore(0x14, to) // Store the `to` argument.
                          mstore(0x34, amount) // Store the `amount` argument.
                          mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
                          // Perform the transfer, reverting upon failure.
                          let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                          if iszero(and(eq(mload(0x00), 1), success)) {
                              if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                                  mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
                                  revert(0x1c, 0x04)
                              }
                          }
                          mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
                      }
                  }
                  /// @dev Sends all of ERC20 `token` from the current contract to `to`.
                  /// Reverts upon failure.
                  function safeTransferAll(address token, address to) internal returns (uint256 amount) {
                      /// @solidity memory-safe-assembly
                      assembly {
                          mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`.
                          mstore(0x20, address()) // Store the address of the current contract.
                          // Read the balance, reverting upon failure.
                          if iszero(
                              and( // The arguments of `and` are evaluated from right to left.
                                  gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                                  staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20)
                              )
                          ) {
                              mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
                              revert(0x1c, 0x04)
                          }
                          mstore(0x14, to) // Store the `to` argument.
                          amount := mload(0x34) // The `amount` is already at 0x34. We'll need to return it.
                          mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
                          // Perform the transfer, reverting upon failure.
                          let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                          if iszero(and(eq(mload(0x00), 1), success)) {
                              if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                                  mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
                                  revert(0x1c, 0x04)
                              }
                          }
                          mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
                      }
                  }
                  /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
                  /// Reverts upon failure.
                  function safeApprove(address token, address to, uint256 amount) internal {
                      /// @solidity memory-safe-assembly
                      assembly {
                          mstore(0x14, to) // Store the `to` argument.
                          mstore(0x34, amount) // Store the `amount` argument.
                          mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
                          let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                          if iszero(and(eq(mload(0x00), 1), success)) {
                              if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                                  mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
                                  revert(0x1c, 0x04)
                              }
                          }
                          mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
                      }
                  }
                  /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
                  /// If the initial attempt to approve fails, attempts to reset the approved amount to zero,
                  /// then retries the approval again (some tokens, e.g. USDT, requires this).
                  /// Reverts upon failure.
                  function safeApproveWithRetry(address token, address to, uint256 amount) internal {
                      /// @solidity memory-safe-assembly
                      assembly {
                          mstore(0x14, to) // Store the `to` argument.
                          mstore(0x34, amount) // Store the `amount` argument.
                          mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
                          // Perform the approval, retrying upon failure.
                          let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                          if iszero(and(eq(mload(0x00), 1), success)) {
                              if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                                  mstore(0x34, 0) // Store 0 for the `amount`.
                                  mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
                                  pop(call(gas(), token, 0, 0x10, 0x44, codesize(), 0x00)) // Reset the approval.
                                  mstore(0x34, amount) // Store back the original `amount`.
                                  // Retry the approval, reverting upon failure.
                                  success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                                  if iszero(and(eq(mload(0x00), 1), success)) {
                                      // Check the `extcodesize` again just in case the token selfdestructs lol.
                                      if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                                          mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
                                          revert(0x1c, 0x04)
                                      }
                                  }
                              }
                          }
                          mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
                      }
                  }
                  /// @dev Returns the amount of ERC20 `token` owned by `account`.
                  /// Returns zero if the `token` does not exist.
                  function balanceOf(address token, address account) internal view returns (uint256 amount) {
                      /// @solidity memory-safe-assembly
                      assembly {
                          mstore(0x14, account) // Store the `account` argument.
                          mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
                          amount :=
                              mul( // The arguments of `mul` are evaluated from right to left.
                                  mload(0x20),
                                  and( // The arguments of `and` are evaluated from right to left.
                                      gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                                      staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)
                                  )
                              )
                      }
                  }
                  /// @dev Performs a `token.balanceOf(account)` check.
                  /// `implemented` denotes whether the `token` does not implement `balanceOf`.
                  /// `amount` is zero if the `token` does not implement `balanceOf`.
                  function checkBalanceOf(address token, address account)
                      internal
                      view
                      returns (bool implemented, uint256 amount)
                  {
                      /// @solidity memory-safe-assembly
                      assembly {
                          mstore(0x14, account) // Store the `account` argument.
                          mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
                          implemented :=
                              and( // The arguments of `and` are evaluated from right to left.
                                  gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                                  staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)
                              )
                          amount := mul(mload(0x20), implemented)
                      }
                  }
                  /// @dev Returns the total supply of the `token`.
                  /// Reverts if the token does not exist or does not implement `totalSupply()`.
                  function totalSupply(address token) internal view returns (uint256 result) {
                      /// @solidity memory-safe-assembly
                      assembly {
                          mstore(0x00, 0x18160ddd) // `totalSupply()`.
                          if iszero(
                              and(gt(returndatasize(), 0x1f), staticcall(gas(), token, 0x1c, 0x04, 0x00, 0x20))
                          ) {
                              mstore(0x00, 0x54cd9435) // `TotalSupplyQueryFailed()`.
                              revert(0x1c, 0x04)
                          }
                          result := mload(0x00)
                      }
                  }
                  /// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
                  /// If the initial attempt fails, try to use Permit2 to transfer the token.
                  /// Reverts upon failure.
                  ///
                  /// The `from` account must have at least `amount` approved for the current contract to manage.
                  function safeTransferFrom2(address token, address from, address to, uint256 amount) internal {
                      if (!trySafeTransferFrom(token, from, to, amount)) {
                          permit2TransferFrom(token, from, to, amount);
                      }
                  }
                  /// @dev Sends `amount` of ERC20 `token` from `from` to `to` via Permit2.
                  /// Reverts upon failure.
                  function permit2TransferFrom(address token, address from, address to, uint256 amount)
                      internal
                  {
                      /// @solidity memory-safe-assembly
                      assembly {
                          let m := mload(0x40)
                          mstore(add(m, 0x74), shr(96, shl(96, token)))
                          mstore(add(m, 0x54), amount)
                          mstore(add(m, 0x34), to)
                          mstore(add(m, 0x20), shl(96, from))
                          // `transferFrom(address,address,uint160,address)`.
                          mstore(m, 0x36c78516000000000000000000000000)
                          let p := PERMIT2
                          let exists := eq(chainid(), 1)
                          if iszero(exists) { exists := iszero(iszero(extcodesize(p))) }
                          if iszero(
                              and(
                                  call(gas(), p, 0, add(m, 0x10), 0x84, codesize(), 0x00),
                                  lt(iszero(extcodesize(token)), exists) // Token has code and Permit2 exists.
                              )
                          ) {
                              mstore(0x00, 0x7939f4248757f0fd) // `TransferFromFailed()` or `Permit2AmountOverflow()`.
                              revert(add(0x18, shl(2, iszero(iszero(shr(160, amount))))), 0x04)
                          }
                      }
                  }
                  /// @dev Permit a user to spend a given amount of
                  /// another user's tokens via native EIP-2612 permit if possible, falling
                  /// back to Permit2 if native permit fails or is not implemented on the token.
                  function permit2(
                      address token,
                      address owner,
                      address spender,
                      uint256 amount,
                      uint256 deadline,
                      uint8 v,
                      bytes32 r,
                      bytes32 s
                  ) internal {
                      bool success;
                      /// @solidity memory-safe-assembly
                      assembly {
                          for {} shl(96, xor(token, WETH9)) {} {
                              mstore(0x00, 0x3644e515) // `DOMAIN_SEPARATOR()`.
                              if iszero(
                                  and( // The arguments of `and` are evaluated from right to left.
                                      lt(iszero(mload(0x00)), eq(returndatasize(), 0x20)), // Returns 1 non-zero word.
                                      // Gas stipend to limit gas burn for tokens that don't refund gas when
                                      // an non-existing function is called. 5K should be enough for a SLOAD.
                                      staticcall(5000, token, 0x1c, 0x04, 0x00, 0x20)
                                  )
                              ) { break }
                              // After here, we can be sure that token is a contract.
                              let m := mload(0x40)
                              mstore(add(m, 0x34), spender)
                              mstore(add(m, 0x20), shl(96, owner))
                              mstore(add(m, 0x74), deadline)
                              if eq(mload(0x00), DAI_DOMAIN_SEPARATOR) {
                                  mstore(0x14, owner)
                                  mstore(0x00, 0x7ecebe00000000000000000000000000) // `nonces(address)`.
                                  mstore(
                                      add(m, 0x94),
                                      lt(iszero(amount), staticcall(gas(), token, 0x10, 0x24, add(m, 0x54), 0x20))
                                  )
                                  mstore(m, 0x8fcbaf0c000000000000000000000000) // `IDAIPermit.permit`.
                                  // `nonces` is already at `add(m, 0x54)`.
                                  // `amount != 0` is already stored at `add(m, 0x94)`.
                                  mstore(add(m, 0xb4), and(0xff, v))
                                  mstore(add(m, 0xd4), r)
                                  mstore(add(m, 0xf4), s)
                                  success := call(gas(), token, 0, add(m, 0x10), 0x104, codesize(), 0x00)
                                  break
                              }
                              mstore(m, 0xd505accf000000000000000000000000) // `IERC20Permit.permit`.
                              mstore(add(m, 0x54), amount)
                              mstore(add(m, 0x94), and(0xff, v))
                              mstore(add(m, 0xb4), r)
                              mstore(add(m, 0xd4), s)
                              success := call(gas(), token, 0, add(m, 0x10), 0xe4, codesize(), 0x00)
                              break
                          }
                      }
                      if (!success) simplePermit2(token, owner, spender, amount, deadline, v, r, s);
                  }
                  /// @dev Simple permit on the Permit2 contract.
                  function simplePermit2(
                      address token,
                      address owner,
                      address spender,
                      uint256 amount,
                      uint256 deadline,
                      uint8 v,
                      bytes32 r,
                      bytes32 s
                  ) internal {
                      /// @solidity memory-safe-assembly
                      assembly {
                          let m := mload(0x40)
                          mstore(m, 0x927da105) // `allowance(address,address,address)`.
                          {
                              let addressMask := shr(96, not(0))
                              mstore(add(m, 0x20), and(addressMask, owner))
                              mstore(add(m, 0x40), and(addressMask, token))
                              mstore(add(m, 0x60), and(addressMask, spender))
                              mstore(add(m, 0xc0), and(addressMask, spender))
                          }
                          let p := mul(PERMIT2, iszero(shr(160, amount)))
                          if iszero(
                              and( // The arguments of `and` are evaluated from right to left.
                                  gt(returndatasize(), 0x5f), // Returns 3 words: `amount`, `expiration`, `nonce`.
                                  staticcall(gas(), p, add(m, 0x1c), 0x64, add(m, 0x60), 0x60)
                              )
                          ) {
                              mstore(0x00, 0x6b836e6b8757f0fd) // `Permit2Failed()` or `Permit2AmountOverflow()`.
                              revert(add(0x18, shl(2, iszero(p))), 0x04)
                          }
                          mstore(m, 0x2b67b570) // `Permit2.permit` (PermitSingle variant).
                          // `owner` is already `add(m, 0x20)`.
                          // `token` is already at `add(m, 0x40)`.
                          mstore(add(m, 0x60), amount)
                          mstore(add(m, 0x80), 0xffffffffffff) // `expiration = type(uint48).max`.
                          // `nonce` is already at `add(m, 0xa0)`.
                          // `spender` is already at `add(m, 0xc0)`.
                          mstore(add(m, 0xe0), deadline)
                          mstore(add(m, 0x100), 0x100) // `signature` offset.
                          mstore(add(m, 0x120), 0x41) // `signature` length.
                          mstore(add(m, 0x140), r)
                          mstore(add(m, 0x160), s)
                          mstore(add(m, 0x180), shl(248, v))
                          if iszero( // Revert if token does not have code, or if the call fails.
                          mul(extcodesize(token), call(gas(), p, 0, add(m, 0x1c), 0x184, codesize(), 0x00))) {
                              mstore(0x00, 0x6b836e6b) // `Permit2Failed()`.
                              revert(0x1c, 0x04)
                          }
                      }
                  }
                  /// @dev Approves `spender` to spend `amount` of `token` for `address(this)`.
                  function permit2Approve(address token, address spender, uint160 amount, uint48 expiration)
                      internal
                  {
                      /// @solidity memory-safe-assembly
                      assembly {
                          let addressMask := shr(96, not(0))
                          let m := mload(0x40)
                          mstore(m, 0x87517c45) // `approve(address,address,uint160,uint48)`.
                          mstore(add(m, 0x20), and(addressMask, token))
                          mstore(add(m, 0x40), and(addressMask, spender))
                          mstore(add(m, 0x60), and(addressMask, amount))
                          mstore(add(m, 0x80), and(0xffffffffffff, expiration))
                          if iszero(call(gas(), PERMIT2, 0, add(m, 0x1c), 0xa0, codesize(), 0x00)) {
                              mstore(0x00, 0x324f14ae) // `Permit2ApproveFailed()`.
                              revert(0x1c, 0x04)
                          }
                      }
                  }
                  /// @dev Revokes an approval for `token` and `spender` for `address(this)`.
                  function permit2Lockdown(address token, address spender) internal {
                      /// @solidity memory-safe-assembly
                      assembly {
                          let m := mload(0x40)
                          mstore(m, 0xcc53287f) // `Permit2.lockdown`.
                          mstore(add(m, 0x20), 0x20) // Offset of the `approvals`.
                          mstore(add(m, 0x40), 1) // `approvals.length`.
                          mstore(add(m, 0x60), shr(96, shl(96, token)))
                          mstore(add(m, 0x80), shr(96, shl(96, spender)))
                          if iszero(call(gas(), PERMIT2, 0, add(m, 0x1c), 0xa0, codesize(), 0x00)) {
                              mstore(0x00, 0x96b3de23) // `Permit2LockdownFailed()`.
                              revert(0x1c, 0x04)
                          }
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
              import {PositionInfo} from "../libraries/PositionInfoLibrary.sol";
              import {INotifier} from "./INotifier.sol";
              import {IImmutableState} from "./IImmutableState.sol";
              import {IERC721Permit_v4} from "./IERC721Permit_v4.sol";
              import {IEIP712_v4} from "./IEIP712_v4.sol";
              import {IMulticall_v4} from "./IMulticall_v4.sol";
              import {IPoolInitializer_v4} from "./IPoolInitializer_v4.sol";
              import {IUnorderedNonce} from "./IUnorderedNonce.sol";
              import {IPermit2Forwarder} from "./IPermit2Forwarder.sol";
              /// @title IPositionManager
              /// @notice Interface for the PositionManager contract
              interface IPositionManager is
                  INotifier,
                  IImmutableState,
                  IERC721Permit_v4,
                  IEIP712_v4,
                  IMulticall_v4,
                  IPoolInitializer_v4,
                  IUnorderedNonce,
                  IPermit2Forwarder
              {
                  /// @notice Thrown when the caller is not approved to modify a position
                  error NotApproved(address caller);
                  /// @notice Thrown when the block.timestamp exceeds the user-provided deadline
                  error DeadlinePassed(uint256 deadline);
                  /// @notice Thrown when calling transfer, subscribe, or unsubscribe when the PoolManager is unlocked.
                  /// @dev This is to prevent hooks from being able to trigger notifications at the same time the position is being modified.
                  error PoolManagerMustBeLocked();
                  /// @notice Unlocks Uniswap v4 PoolManager and batches actions for modifying liquidity
                  /// @dev This is the standard entrypoint for the PositionManager
                  /// @param unlockData is an encoding of actions, and parameters for those actions
                  /// @param deadline is the deadline for the batched actions to be executed
                  function modifyLiquidities(bytes calldata unlockData, uint256 deadline) external payable;
                  /// @notice Batches actions for modifying liquidity without unlocking v4 PoolManager
                  /// @dev This must be called by a contract that has already unlocked the v4 PoolManager
                  /// @param actions the actions to perform
                  /// @param params the parameters to provide for the actions
                  function modifyLiquiditiesWithoutUnlock(bytes calldata actions, bytes[] calldata params) external payable;
                  /// @notice Used to get the ID that will be used for the next minted liquidity position
                  /// @return uint256 The next token ID
                  function nextTokenId() external view returns (uint256);
                  /// @notice Returns the liquidity of a position
                  /// @param tokenId the ERC721 tokenId
                  /// @return liquidity the position's liquidity, as a liquidityAmount
                  /// @dev this value can be processed as an amount0 and amount1 by using the LiquidityAmounts library
                  function getPositionLiquidity(uint256 tokenId) external view returns (uint128 liquidity);
                  /// @notice Returns the pool key and position info of a position
                  /// @param tokenId the ERC721 tokenId
                  /// @return poolKey the pool key of the position
                  /// @return PositionInfo a uint256 packed value holding information about the position including the range (tickLower, tickUpper)
                  function getPoolAndPositionInfo(uint256 tokenId) external view returns (PoolKey memory, PositionInfo);
                  /// @notice Returns the position info of a position
                  /// @param tokenId the ERC721 tokenId
                  /// @return a uint256 packed value holding information about the position including the range (tickLower, tickUpper)
                  function positionInfo(uint256 tokenId) external view returns (PositionInfo);
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import {IEIP712} from "./IEIP712.sol";
              /// @title AllowanceTransfer
              /// @notice Handles ERC20 token permissions through signature based allowance setting and ERC20 token transfers by checking allowed amounts
              /// @dev Requires user's token approval on the Permit2 contract
              interface IAllowanceTransfer is IEIP712 {
                  /// @notice Thrown when an allowance on a token has expired.
                  /// @param deadline The timestamp at which the allowed amount is no longer valid
                  error AllowanceExpired(uint256 deadline);
                  /// @notice Thrown when an allowance on a token has been depleted.
                  /// @param amount The maximum amount allowed
                  error InsufficientAllowance(uint256 amount);
                  /// @notice Thrown when too many nonces are invalidated.
                  error ExcessiveInvalidation();
                  /// @notice Emits an event when the owner successfully invalidates an ordered nonce.
                  event NonceInvalidation(
                      address indexed owner, address indexed token, address indexed spender, uint48 newNonce, uint48 oldNonce
                  );
                  /// @notice Emits an event when the owner successfully sets permissions on a token for the spender.
                  event Approval(
                      address indexed owner, address indexed token, address indexed spender, uint160 amount, uint48 expiration
                  );
                  /// @notice Emits an event when the owner successfully sets permissions using a permit signature on a token for the spender.
                  event Permit(
                      address indexed owner,
                      address indexed token,
                      address indexed spender,
                      uint160 amount,
                      uint48 expiration,
                      uint48 nonce
                  );
                  /// @notice Emits an event when the owner sets the allowance back to 0 with the lockdown function.
                  event Lockdown(address indexed owner, address token, address spender);
                  /// @notice The permit data for a token
                  struct PermitDetails {
                      // ERC20 token address
                      address token;
                      // the maximum amount allowed to spend
                      uint160 amount;
                      // timestamp at which a spender's token allowances become invalid
                      uint48 expiration;
                      // an incrementing value indexed per owner,token,and spender for each signature
                      uint48 nonce;
                  }
                  /// @notice The permit message signed for a single token allowance
                  struct PermitSingle {
                      // the permit data for a single token alownce
                      PermitDetails details;
                      // address permissioned on the allowed tokens
                      address spender;
                      // deadline on the permit signature
                      uint256 sigDeadline;
                  }
                  /// @notice The permit message signed for multiple token allowances
                  struct PermitBatch {
                      // the permit data for multiple token allowances
                      PermitDetails[] details;
                      // address permissioned on the allowed tokens
                      address spender;
                      // deadline on the permit signature
                      uint256 sigDeadline;
                  }
                  /// @notice The saved permissions
                  /// @dev This info is saved per owner, per token, per spender and all signed over in the permit message
                  /// @dev Setting amount to type(uint160).max sets an unlimited approval
                  struct PackedAllowance {
                      // amount allowed
                      uint160 amount;
                      // permission expiry
                      uint48 expiration;
                      // an incrementing value indexed per owner,token,and spender for each signature
                      uint48 nonce;
                  }
                  /// @notice A token spender pair.
                  struct TokenSpenderPair {
                      // the token the spender is approved
                      address token;
                      // the spender address
                      address spender;
                  }
                  /// @notice Details for a token transfer.
                  struct AllowanceTransferDetails {
                      // the owner of the token
                      address from;
                      // the recipient of the token
                      address to;
                      // the amount of the token
                      uint160 amount;
                      // the token to be transferred
                      address token;
                  }
                  /// @notice A mapping from owner address to token address to spender address to PackedAllowance struct, which contains details and conditions of the approval.
                  /// @notice The mapping is indexed in the above order see: allowance[ownerAddress][tokenAddress][spenderAddress]
                  /// @dev The packed slot holds the allowed amount, expiration at which the allowed amount is no longer valid, and current nonce thats updated on any signature based approvals.
                  function allowance(address user, address token, address spender)
                      external
                      view
                      returns (uint160 amount, uint48 expiration, uint48 nonce);
                  /// @notice Approves the spender to use up to amount of the specified token up until the expiration
                  /// @param token The token to approve
                  /// @param spender The spender address to approve
                  /// @param amount The approved amount of the token
                  /// @param expiration The timestamp at which the approval is no longer valid
                  /// @dev The packed allowance also holds a nonce, which will stay unchanged in approve
                  /// @dev Setting amount to type(uint160).max sets an unlimited approval
                  function approve(address token, address spender, uint160 amount, uint48 expiration) external;
                  /// @notice Permit a spender to a given amount of the owners token via the owner's EIP-712 signature
                  /// @dev May fail if the owner's nonce was invalidated in-flight by invalidateNonce
                  /// @param owner The owner of the tokens being approved
                  /// @param permitSingle Data signed over by the owner specifying the terms of approval
                  /// @param signature The owner's signature over the permit data
                  function permit(address owner, PermitSingle memory permitSingle, bytes calldata signature) external;
                  /// @notice Permit a spender to the signed amounts of the owners tokens via the owner's EIP-712 signature
                  /// @dev May fail if the owner's nonce was invalidated in-flight by invalidateNonce
                  /// @param owner The owner of the tokens being approved
                  /// @param permitBatch Data signed over by the owner specifying the terms of approval
                  /// @param signature The owner's signature over the permit data
                  function permit(address owner, PermitBatch memory permitBatch, bytes calldata signature) external;
                  /// @notice Transfer approved tokens from one address to another
                  /// @param from The address to transfer from
                  /// @param to The address of the recipient
                  /// @param amount The amount of the token to transfer
                  /// @param token The token address to transfer
                  /// @dev Requires the from address to have approved at least the desired amount
                  /// of tokens to msg.sender.
                  function transferFrom(address from, address to, uint160 amount, address token) external;
                  /// @notice Transfer approved tokens in a batch
                  /// @param transferDetails Array of owners, recipients, amounts, and tokens for the transfers
                  /// @dev Requires the from addresses to have approved at least the desired amount
                  /// of tokens to msg.sender.
                  function transferFrom(AllowanceTransferDetails[] calldata transferDetails) external;
                  /// @notice Enables performing a "lockdown" of the sender's Permit2 identity
                  /// by batch revoking approvals
                  /// @param approvals Array of approvals to revoke.
                  function lockdown(TokenSpenderPair[] calldata approvals) external;
                  /// @notice Invalidate nonces for a given (token, spender) pair
                  /// @param token The token to invalidate nonces for
                  /// @param spender The spender to invalidate nonces for
                  /// @param newNonce The new nonce to set. Invalidates all nonces less than it.
                  /// @dev Can't invalidate more than 2**16 nonces per transaction.
                  function invalidateNonces(address token, address spender, uint48 newNonce) external;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import {PoolKey} from "../types/PoolKey.sol";
              import {BalanceDelta} from "../types/BalanceDelta.sol";
              import {ModifyLiquidityParams, SwapParams} from "../types/PoolOperation.sol";
              import {BeforeSwapDelta} from "../types/BeforeSwapDelta.sol";
              /// @notice V4 decides whether to invoke specific hooks by inspecting the least significant bits
              /// of the address that the hooks contract is deployed to.
              /// For example, a hooks contract deployed to address: 0x0000000000000000000000000000000000002400
              /// has the lowest bits '10 0100 0000 0000' which would cause the 'before initialize' and 'after add liquidity' hooks to be used.
              /// See the Hooks library for the full spec.
              /// @dev Should only be callable by the v4 PoolManager.
              interface IHooks {
                  /// @notice The hook called before the state of a pool is initialized
                  /// @param sender The initial msg.sender for the initialize call
                  /// @param key The key for the pool being initialized
                  /// @param sqrtPriceX96 The sqrt(price) of the pool as a Q64.96
                  /// @return bytes4 The function selector for the hook
                  function beforeInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96) external returns (bytes4);
                  /// @notice The hook called after the state of a pool is initialized
                  /// @param sender The initial msg.sender for the initialize call
                  /// @param key The key for the pool being initialized
                  /// @param sqrtPriceX96 The sqrt(price) of the pool as a Q64.96
                  /// @param tick The current tick after the state of a pool is initialized
                  /// @return bytes4 The function selector for the hook
                  function afterInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96, int24 tick)
                      external
                      returns (bytes4);
                  /// @notice The hook called before liquidity is added
                  /// @param sender The initial msg.sender for the add liquidity call
                  /// @param key The key for the pool
                  /// @param params The parameters for adding liquidity
                  /// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be passed on to the hook
                  /// @return bytes4 The function selector for the hook
                  function beforeAddLiquidity(
                      address sender,
                      PoolKey calldata key,
                      ModifyLiquidityParams calldata params,
                      bytes calldata hookData
                  ) external returns (bytes4);
                  /// @notice The hook called after liquidity is added
                  /// @param sender The initial msg.sender for the add liquidity call
                  /// @param key The key for the pool
                  /// @param params The parameters for adding liquidity
                  /// @param delta The caller's balance delta after adding liquidity; the sum of principal delta, fees accrued, and hook delta
                  /// @param feesAccrued The fees accrued since the last time fees were collected from this position
                  /// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be passed on to the hook
                  /// @return bytes4 The function selector for the hook
                  /// @return BalanceDelta The hook's delta in token0 and token1. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
                  function afterAddLiquidity(
                      address sender,
                      PoolKey calldata key,
                      ModifyLiquidityParams calldata params,
                      BalanceDelta delta,
                      BalanceDelta feesAccrued,
                      bytes calldata hookData
                  ) external returns (bytes4, BalanceDelta);
                  /// @notice The hook called before liquidity is removed
                  /// @param sender The initial msg.sender for the remove liquidity call
                  /// @param key The key for the pool
                  /// @param params The parameters for removing liquidity
                  /// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be be passed on to the hook
                  /// @return bytes4 The function selector for the hook
                  function beforeRemoveLiquidity(
                      address sender,
                      PoolKey calldata key,
                      ModifyLiquidityParams calldata params,
                      bytes calldata hookData
                  ) external returns (bytes4);
                  /// @notice The hook called after liquidity is removed
                  /// @param sender The initial msg.sender for the remove liquidity call
                  /// @param key The key for the pool
                  /// @param params The parameters for removing liquidity
                  /// @param delta The caller's balance delta after removing liquidity; the sum of principal delta, fees accrued, and hook delta
                  /// @param feesAccrued The fees accrued since the last time fees were collected from this position
                  /// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be be passed on to the hook
                  /// @return bytes4 The function selector for the hook
                  /// @return BalanceDelta The hook's delta in token0 and token1. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
                  function afterRemoveLiquidity(
                      address sender,
                      PoolKey calldata key,
                      ModifyLiquidityParams calldata params,
                      BalanceDelta delta,
                      BalanceDelta feesAccrued,
                      bytes calldata hookData
                  ) external returns (bytes4, BalanceDelta);
                  /// @notice The hook called before a swap
                  /// @param sender The initial msg.sender for the swap call
                  /// @param key The key for the pool
                  /// @param params The parameters for the swap
                  /// @param hookData Arbitrary data handed into the PoolManager by the swapper to be be passed on to the hook
                  /// @return bytes4 The function selector for the hook
                  /// @return BeforeSwapDelta The hook's delta in specified and unspecified currencies. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
                  /// @return uint24 Optionally override the lp fee, only used if three conditions are met: 1. the Pool has a dynamic fee, 2. the value's 2nd highest bit is set (23rd bit, 0x400000), and 3. the value is less than or equal to the maximum fee (1 million)
                  function beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata hookData)
                      external
                      returns (bytes4, BeforeSwapDelta, uint24);
                  /// @notice The hook called after a swap
                  /// @param sender The initial msg.sender for the swap call
                  /// @param key The key for the pool
                  /// @param params The parameters for the swap
                  /// @param delta The amount owed to the caller (positive) or owed to the pool (negative)
                  /// @param hookData Arbitrary data handed into the PoolManager by the swapper to be be passed on to the hook
                  /// @return bytes4 The function selector for the hook
                  /// @return int128 The hook's delta in unspecified currency. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
                  function afterSwap(
                      address sender,
                      PoolKey calldata key,
                      SwapParams calldata params,
                      BalanceDelta delta,
                      bytes calldata hookData
                  ) external returns (bytes4, int128);
                  /// @notice The hook called before donate
                  /// @param sender The initial msg.sender for the donate call
                  /// @param key The key for the pool
                  /// @param amount0 The amount of token0 being donated
                  /// @param amount1 The amount of token1 being donated
                  /// @param hookData Arbitrary data handed into the PoolManager by the donor to be be passed on to the hook
                  /// @return bytes4 The function selector for the hook
                  function beforeDonate(
                      address sender,
                      PoolKey calldata key,
                      uint256 amount0,
                      uint256 amount1,
                      bytes calldata hookData
                  ) external returns (bytes4);
                  /// @notice The hook called after donate
                  /// @param sender The initial msg.sender for the donate call
                  /// @param key The key for the pool
                  /// @param amount0 The amount of token0 being donated
                  /// @param amount1 The amount of token1 being donated
                  /// @param hookData Arbitrary data handed into the PoolManager by the donor to be be passed on to the hook
                  /// @return bytes4 The function selector for the hook
                  function afterDonate(
                      address sender,
                      PoolKey calldata key,
                      uint256 amount0,
                      uint256 amount1,
                      bytes calldata hookData
                  ) external returns (bytes4);
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import {IERC20Minimal} from "../interfaces/external/IERC20Minimal.sol";
              import {CustomRevert} from "../libraries/CustomRevert.sol";
              type Currency is address;
              using {greaterThan as >, lessThan as <, greaterThanOrEqualTo as >=, equals as ==} for Currency global;
              using CurrencyLibrary for Currency global;
              function equals(Currency currency, Currency other) pure returns (bool) {
                  return Currency.unwrap(currency) == Currency.unwrap(other);
              }
              function greaterThan(Currency currency, Currency other) pure returns (bool) {
                  return Currency.unwrap(currency) > Currency.unwrap(other);
              }
              function lessThan(Currency currency, Currency other) pure returns (bool) {
                  return Currency.unwrap(currency) < Currency.unwrap(other);
              }
              function greaterThanOrEqualTo(Currency currency, Currency other) pure returns (bool) {
                  return Currency.unwrap(currency) >= Currency.unwrap(other);
              }
              /// @title CurrencyLibrary
              /// @dev This library allows for transferring and holding native tokens and ERC20 tokens
              library CurrencyLibrary {
                  /// @notice Additional context for ERC-7751 wrapped error when a native transfer fails
                  error NativeTransferFailed();
                  /// @notice Additional context for ERC-7751 wrapped error when an ERC20 transfer fails
                  error ERC20TransferFailed();
                  /// @notice A constant to represent the native currency
                  Currency public constant ADDRESS_ZERO = Currency.wrap(address(0));
                  function transfer(Currency currency, address to, uint256 amount) internal {
                      // altered from https://github.com/transmissions11/solmate/blob/44a9963d4c78111f77caa0e65d677b8b46d6f2e6/src/utils/SafeTransferLib.sol
                      // modified custom error selectors
                      bool success;
                      if (currency.isAddressZero()) {
                          assembly ("memory-safe") {
                              // Transfer the ETH and revert if it fails.
                              success := call(gas(), to, amount, 0, 0, 0, 0)
                          }
                          // revert with NativeTransferFailed, containing the bubbled up error as an argument
                          if (!success) {
                              CustomRevert.bubbleUpAndRevertWith(to, bytes4(0), NativeTransferFailed.selector);
                          }
                      } else {
                          assembly ("memory-safe") {
                              // Get a pointer to some free memory.
                              let fmp := mload(0x40)
                              // Write the abi-encoded calldata into memory, beginning with the function selector.
                              mstore(fmp, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
                              mstore(add(fmp, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
                              mstore(add(fmp, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
                              success :=
                                  and(
                                      // Set success to whether the call reverted, if not we check it either
                                      // returned exactly 1 (can't just be non-zero data), or had no return data.
                                      or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                                      // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                                      // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                                      // Counterintuitively, this call must be positioned second to the or() call in the
                                      // surrounding and() call or else returndatasize() will be zero during the computation.
                                      call(gas(), currency, 0, fmp, 68, 0, 32)
                                  )
                              // Now clean the memory we used
                              mstore(fmp, 0) // 4 byte `selector` and 28 bytes of `to` were stored here
                              mstore(add(fmp, 0x20), 0) // 4 bytes of `to` and 28 bytes of `amount` were stored here
                              mstore(add(fmp, 0x40), 0) // 4 bytes of `amount` were stored here
                          }
                          // revert with ERC20TransferFailed, containing the bubbled up error as an argument
                          if (!success) {
                              CustomRevert.bubbleUpAndRevertWith(
                                  Currency.unwrap(currency), IERC20Minimal.transfer.selector, ERC20TransferFailed.selector
                              );
                          }
                      }
                  }
                  function balanceOfSelf(Currency currency) internal view returns (uint256) {
                      if (currency.isAddressZero()) {
                          return address(this).balance;
                      } else {
                          return IERC20Minimal(Currency.unwrap(currency)).balanceOf(address(this));
                      }
                  }
                  function balanceOf(Currency currency, address owner) internal view returns (uint256) {
                      if (currency.isAddressZero()) {
                          return owner.balance;
                      } else {
                          return IERC20Minimal(Currency.unwrap(currency)).balanceOf(owner);
                      }
                  }
                  function isAddressZero(Currency currency) internal pure returns (bool) {
                      return Currency.unwrap(currency) == Currency.unwrap(ADDRESS_ZERO);
                  }
                  function toId(Currency currency) internal pure returns (uint256) {
                      return uint160(Currency.unwrap(currency));
                  }
                  // If the upper 12 bytes are non-zero, they will be zero-ed out
                  // Therefore, fromId() and toId() are not inverses of each other
                  function fromId(uint256 id) internal pure returns (Currency) {
                      return Currency.wrap(address(uint160(id)));
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import {Currency} from "./Currency.sol";
              import {IHooks} from "../interfaces/IHooks.sol";
              import {PoolIdLibrary} from "./PoolId.sol";
              using PoolIdLibrary for PoolKey global;
              /// @notice Returns the key for identifying a pool
              struct PoolKey {
                  /// @notice The lower currency of the pool, sorted numerically
                  Currency currency0;
                  /// @notice The higher currency of the pool, sorted numerically
                  Currency currency1;
                  /// @notice The pool LP fee, capped at 1_000_000. If the highest bit is 1, the pool has a dynamic fee and must be exactly equal to 0x800000
                  uint24 fee;
                  /// @notice Ticks that involve positions must be a multiple of tick spacing
                  int24 tickSpacing;
                  /// @notice The hooks of the pool
                  IHooks hooks;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              /// @notice Library to define different pool actions.
              /// @dev These are suggested common commands, however additional commands should be defined as required
              /// Some of these actions are not supported in the Router contracts or Position Manager contracts, but are left as they may be helpful commands for other peripheral contracts.
              library Actions {
                  // pool actions
                  // liquidity actions
                  uint256 internal constant INCREASE_LIQUIDITY = 0x00;
                  uint256 internal constant DECREASE_LIQUIDITY = 0x01;
                  uint256 internal constant MINT_POSITION = 0x02;
                  uint256 internal constant BURN_POSITION = 0x03;
                  uint256 internal constant INCREASE_LIQUIDITY_FROM_DELTAS = 0x04;
                  uint256 internal constant MINT_POSITION_FROM_DELTAS = 0x05;
                  // swapping
                  uint256 internal constant SWAP_EXACT_IN_SINGLE = 0x06;
                  uint256 internal constant SWAP_EXACT_IN = 0x07;
                  uint256 internal constant SWAP_EXACT_OUT_SINGLE = 0x08;
                  uint256 internal constant SWAP_EXACT_OUT = 0x09;
                  // donate
                  // note this is not supported in the position manager or router
                  uint256 internal constant DONATE = 0x0a;
                  // closing deltas on the pool manager
                  // settling
                  uint256 internal constant SETTLE = 0x0b;
                  uint256 internal constant SETTLE_ALL = 0x0c;
                  uint256 internal constant SETTLE_PAIR = 0x0d;
                  // taking
                  uint256 internal constant TAKE = 0x0e;
                  uint256 internal constant TAKE_ALL = 0x0f;
                  uint256 internal constant TAKE_PORTION = 0x10;
                  uint256 internal constant TAKE_PAIR = 0x11;
                  uint256 internal constant CLOSE_CURRENCY = 0x12;
                  uint256 internal constant CLEAR_OR_TAKE = 0x13;
                  uint256 internal constant SWEEP = 0x14;
                  uint256 internal constant WRAP = 0x15;
                  uint256 internal constant UNWRAP = 0x16;
                  // minting/burning 6909s to close deltas
                  // note this is not supported in the position manager or router
                  uint256 internal constant MINT_6909 = 0x17;
                  uint256 internal constant BURN_6909 = 0x18;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import {BitMath} from "./BitMath.sol";
              import {CustomRevert} from "./CustomRevert.sol";
              /// @title Math library for computing sqrt prices from ticks and vice versa
              /// @notice Computes sqrt price for ticks of size 1.0001, i.e. sqrt(1.0001^tick) as fixed point Q64.96 numbers. Supports
              /// prices between 2**-128 and 2**128
              library TickMath {
                  using CustomRevert for bytes4;
                  /// @notice Thrown when the tick passed to #getSqrtPriceAtTick is not between MIN_TICK and MAX_TICK
                  error InvalidTick(int24 tick);
                  /// @notice Thrown when the price passed to #getTickAtSqrtPrice does not correspond to a price between MIN_TICK and MAX_TICK
                  error InvalidSqrtPrice(uint160 sqrtPriceX96);
                  /// @dev The minimum tick that may be passed to #getSqrtPriceAtTick computed from log base 1.0001 of 2**-128
                  /// @dev If ever MIN_TICK and MAX_TICK are not centered around 0, the absTick logic in getSqrtPriceAtTick cannot be used
                  int24 internal constant MIN_TICK = -887272;
                  /// @dev The maximum tick that may be passed to #getSqrtPriceAtTick computed from log base 1.0001 of 2**128
                  /// @dev If ever MIN_TICK and MAX_TICK are not centered around 0, the absTick logic in getSqrtPriceAtTick cannot be used
                  int24 internal constant MAX_TICK = 887272;
                  /// @dev The minimum tick spacing value drawn from the range of type int16 that is greater than 0, i.e. min from the range [1, 32767]
                  int24 internal constant MIN_TICK_SPACING = 1;
                  /// @dev The maximum tick spacing value drawn from the range of type int16, i.e. max from the range [1, 32767]
                  int24 internal constant MAX_TICK_SPACING = type(int16).max;
                  /// @dev The minimum value that can be returned from #getSqrtPriceAtTick. Equivalent to getSqrtPriceAtTick(MIN_TICK)
                  uint160 internal constant MIN_SQRT_PRICE = 4295128739;
                  /// @dev The maximum value that can be returned from #getSqrtPriceAtTick. Equivalent to getSqrtPriceAtTick(MAX_TICK)
                  uint160 internal constant MAX_SQRT_PRICE = 1461446703485210103287273052203988822378723970342;
                  /// @dev A threshold used for optimized bounds check, equals `MAX_SQRT_PRICE - MIN_SQRT_PRICE - 1`
                  uint160 internal constant MAX_SQRT_PRICE_MINUS_MIN_SQRT_PRICE_MINUS_ONE =
                      1461446703485210103287273052203988822378723970342 - 4295128739 - 1;
                  /// @notice Given a tickSpacing, compute the maximum usable tick
                  function maxUsableTick(int24 tickSpacing) internal pure returns (int24) {
                      unchecked {
                          return (MAX_TICK / tickSpacing) * tickSpacing;
                      }
                  }
                  /// @notice Given a tickSpacing, compute the minimum usable tick
                  function minUsableTick(int24 tickSpacing) internal pure returns (int24) {
                      unchecked {
                          return (MIN_TICK / tickSpacing) * tickSpacing;
                      }
                  }
                  /// @notice Calculates sqrt(1.0001^tick) * 2^96
                  /// @dev Throws if |tick| > max tick
                  /// @param tick The input tick for the above formula
                  /// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the price of the two assets (currency1/currency0)
                  /// at the given tick
                  function getSqrtPriceAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) {
                      unchecked {
                          uint256 absTick;
                          assembly ("memory-safe") {
                              tick := signextend(2, tick)
                              // mask = 0 if tick >= 0 else -1 (all 1s)
                              let mask := sar(255, tick)
                              // if tick >= 0, |tick| = tick = 0 ^ tick
                              // if tick < 0, |tick| = ~~|tick| = ~(-|tick| - 1) = ~(tick - 1) = (-1) ^ (tick - 1)
                              // either way, |tick| = mask ^ (tick + mask)
                              absTick := xor(mask, add(mask, tick))
                          }
                          if (absTick > uint256(int256(MAX_TICK))) InvalidTick.selector.revertWith(tick);
                          // The tick is decomposed into bits, and for each bit with index i that is set, the product of 1/sqrt(1.0001^(2^i))
                          // is calculated (using Q128.128). The constants used for this calculation are rounded to the nearest integer
                          // Equivalent to:
                          //     price = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000;
                          //     or price = int(2**128 / sqrt(1.0001)) if (absTick & 0x1) else 1 << 128
                          uint256 price;
                          assembly ("memory-safe") {
                              price := xor(shl(128, 1), mul(xor(shl(128, 1), 0xfffcb933bd6fad37aa2d162d1a594001), and(absTick, 0x1)))
                          }
                          if (absTick & 0x2 != 0) price = (price * 0xfff97272373d413259a46990580e213a) >> 128;
                          if (absTick & 0x4 != 0) price = (price * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
                          if (absTick & 0x8 != 0) price = (price * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
                          if (absTick & 0x10 != 0) price = (price * 0xffcb9843d60f6159c9db58835c926644) >> 128;
                          if (absTick & 0x20 != 0) price = (price * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
                          if (absTick & 0x40 != 0) price = (price * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
                          if (absTick & 0x80 != 0) price = (price * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
                          if (absTick & 0x100 != 0) price = (price * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
                          if (absTick & 0x200 != 0) price = (price * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
                          if (absTick & 0x400 != 0) price = (price * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
                          if (absTick & 0x800 != 0) price = (price * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
                          if (absTick & 0x1000 != 0) price = (price * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
                          if (absTick & 0x2000 != 0) price = (price * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
                          if (absTick & 0x4000 != 0) price = (price * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
                          if (absTick & 0x8000 != 0) price = (price * 0x31be135f97d08fd981231505542fcfa6) >> 128;
                          if (absTick & 0x10000 != 0) price = (price * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
                          if (absTick & 0x20000 != 0) price = (price * 0x5d6af8dedb81196699c329225ee604) >> 128;
                          if (absTick & 0x40000 != 0) price = (price * 0x2216e584f5fa1ea926041bedfe98) >> 128;
                          if (absTick & 0x80000 != 0) price = (price * 0x48a170391f7dc42444e8fa2) >> 128;
                          assembly ("memory-safe") {
                              // if (tick > 0) price = type(uint256).max / price;
                              if sgt(tick, 0) { price := div(not(0), price) }
                              // this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96.
                              // we then downcast because we know the result always fits within 160 bits due to our tick input constraint
                              // we round up in the division so getTickAtSqrtPrice of the output price is always consistent
                              // `sub(shl(32, 1), 1)` is `type(uint32).max`
                              // `price + type(uint32).max` will not overflow because `price` fits in 192 bits
                              sqrtPriceX96 := shr(32, add(price, sub(shl(32, 1), 1)))
                          }
                      }
                  }
                  /// @notice Calculates the greatest tick value such that getSqrtPriceAtTick(tick) <= sqrtPriceX96
                  /// @dev Throws in case sqrtPriceX96 < MIN_SQRT_PRICE, as MIN_SQRT_PRICE is the lowest value getSqrtPriceAtTick may
                  /// ever return.
                  /// @param sqrtPriceX96 The sqrt price for which to compute the tick as a Q64.96
                  /// @return tick The greatest tick for which the getSqrtPriceAtTick(tick) is less than or equal to the input sqrtPriceX96
                  function getTickAtSqrtPrice(uint160 sqrtPriceX96) internal pure returns (int24 tick) {
                      unchecked {
                          // Equivalent: if (sqrtPriceX96 < MIN_SQRT_PRICE || sqrtPriceX96 >= MAX_SQRT_PRICE) revert InvalidSqrtPrice();
                          // second inequality must be >= because the price can never reach the price at the max tick
                          // if sqrtPriceX96 < MIN_SQRT_PRICE, the `sub` underflows and `gt` is true
                          // if sqrtPriceX96 >= MAX_SQRT_PRICE, sqrtPriceX96 - MIN_SQRT_PRICE > MAX_SQRT_PRICE - MIN_SQRT_PRICE - 1
                          if ((sqrtPriceX96 - MIN_SQRT_PRICE) > MAX_SQRT_PRICE_MINUS_MIN_SQRT_PRICE_MINUS_ONE) {
                              InvalidSqrtPrice.selector.revertWith(sqrtPriceX96);
                          }
                          uint256 price = uint256(sqrtPriceX96) << 32;
                          uint256 r = price;
                          uint256 msb = BitMath.mostSignificantBit(r);
                          if (msb >= 128) r = price >> (msb - 127);
                          else r = price << (127 - msb);
                          int256 log_2 = (int256(msb) - 128) << 64;
                          assembly ("memory-safe") {
                              r := shr(127, mul(r, r))
                              let f := shr(128, r)
                              log_2 := or(log_2, shl(63, f))
                              r := shr(f, r)
                          }
                          assembly ("memory-safe") {
                              r := shr(127, mul(r, r))
                              let f := shr(128, r)
                              log_2 := or(log_2, shl(62, f))
                              r := shr(f, r)
                          }
                          assembly ("memory-safe") {
                              r := shr(127, mul(r, r))
                              let f := shr(128, r)
                              log_2 := or(log_2, shl(61, f))
                              r := shr(f, r)
                          }
                          assembly ("memory-safe") {
                              r := shr(127, mul(r, r))
                              let f := shr(128, r)
                              log_2 := or(log_2, shl(60, f))
                              r := shr(f, r)
                          }
                          assembly ("memory-safe") {
                              r := shr(127, mul(r, r))
                              let f := shr(128, r)
                              log_2 := or(log_2, shl(59, f))
                              r := shr(f, r)
                          }
                          assembly ("memory-safe") {
                              r := shr(127, mul(r, r))
                              let f := shr(128, r)
                              log_2 := or(log_2, shl(58, f))
                              r := shr(f, r)
                          }
                          assembly ("memory-safe") {
                              r := shr(127, mul(r, r))
                              let f := shr(128, r)
                              log_2 := or(log_2, shl(57, f))
                              r := shr(f, r)
                          }
                          assembly ("memory-safe") {
                              r := shr(127, mul(r, r))
                              let f := shr(128, r)
                              log_2 := or(log_2, shl(56, f))
                              r := shr(f, r)
                          }
                          assembly ("memory-safe") {
                              r := shr(127, mul(r, r))
                              let f := shr(128, r)
                              log_2 := or(log_2, shl(55, f))
                              r := shr(f, r)
                          }
                          assembly ("memory-safe") {
                              r := shr(127, mul(r, r))
                              let f := shr(128, r)
                              log_2 := or(log_2, shl(54, f))
                              r := shr(f, r)
                          }
                          assembly ("memory-safe") {
                              r := shr(127, mul(r, r))
                              let f := shr(128, r)
                              log_2 := or(log_2, shl(53, f))
                              r := shr(f, r)
                          }
                          assembly ("memory-safe") {
                              r := shr(127, mul(r, r))
                              let f := shr(128, r)
                              log_2 := or(log_2, shl(52, f))
                              r := shr(f, r)
                          }
                          assembly ("memory-safe") {
                              r := shr(127, mul(r, r))
                              let f := shr(128, r)
                              log_2 := or(log_2, shl(51, f))
                              r := shr(f, r)
                          }
                          assembly ("memory-safe") {
                              r := shr(127, mul(r, r))
                              let f := shr(128, r)
                              log_2 := or(log_2, shl(50, f))
                          }
                          int256 log_sqrt10001 = log_2 * 255738958999603826347141; // Q22.128 number
                          // Magic number represents the ceiling of the maximum value of the error when approximating log_sqrt10001(x)
                          int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128);
                          // Magic number represents the minimum value of the error when approximating log_sqrt10001(x), when
                          // sqrtPrice is from the range (2^-64, 2^64). This is safe as MIN_SQRT_PRICE is more than 2^-64. If MIN_SQRT_PRICE
                          // is changed, this may need to be changed too
                          int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128);
                          tick = tickLow == tickHi ? tickLow : getSqrtPriceAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow;
                      }
                  }
              }
              // SPDX-License-Identifier: UNLICENSED
              pragma solidity ^0.8.26;
              import {PathKey} from "../libraries/PathKey.sol";
              import {PoolKey} from "@v4/src/types/PoolKey.sol";
              import {Currency} from "@v4/src/types/Currency.sol";
              import {BalanceDelta} from "@v4/src/types/BalanceDelta.sol";
              import {ISignatureTransfer} from "@permit2/interfaces/ISignatureTransfer.sol";
              /// @title Uniswap V4 Swap Router
              /// @notice A simple, stateless router for execution of swaps against Uniswap v4 Pools
              /// @dev ABI inspired by UniswapV2Router02
              interface IUniswapV4Router04 {
                  /// ================ MULTI POOL SWAPS ================= ///
                  /// @notice Exact Input Swap; swap the specified amount of input tokens for as many output tokens as possible, along the path
                  /// @param amountIn the amount of input tokens to swap
                  /// @param amountOutMin the minimum amount of output tokens that must be received for the transaction not to revert. reverts on equals to
                  /// @param startCurrency the currency to start the swap from
                  /// @param path the path of v4 Pools to swap through
                  /// @param receiver the address to send the output tokens to
                  /// @param deadline block.timestamp must be before this value, otherwise the transaction will revert
                  /// @return Delta the balance changes from the swap
                  function swapExactTokensForTokens(
                      uint256 amountIn,
                      uint256 amountOutMin,
                      Currency startCurrency,
                      PathKey[] calldata path,
                      address receiver,
                      uint256 deadline
                  ) external payable returns (BalanceDelta);
                  /// @notice Exact Output Swap; swap as few input tokens as possible for the specified amount of output tokens, along the path
                  /// @param amountOut the amount of output tokens to receive
                  /// @param amountInMax the maximum amount of input tokens that can be spent for the transaction not to revert. reverts on equal to
                  /// @param startCurrency the currency to start the swap from
                  /// @param path the path of v4 Pools to swap through
                  /// @param receiver the address to send the output tokens to
                  /// @param deadline block.timestamp must be before this value, otherwise the transaction will revert
                  /// @return Delta the balance changes from the swap
                  function swapTokensForExactTokens(
                      uint256 amountOut,
                      uint256 amountInMax,
                      Currency startCurrency,
                      PathKey[] calldata path,
                      address receiver,
                      uint256 deadline
                  ) external payable returns (BalanceDelta);
                  /// @notice General-purpose swap interface for Uniswap v4 that handles all types of swaps
                  /// @param amountSpecified the amount of tokens to be swapped, negative for exact input swaps and positive for exact output swaps
                  /// @param amountLimit the minimum amount of output tokens for exact input swaps, the maximum amount of input tokens for exact output swaps
                  /// @param startCurrency the currency to start the swap from
                  /// @param path the path of v4 Pools to swap through
                  /// @param receiver the address to send the output tokens to
                  /// @param deadline block.timestamp must be before this value, otherwise the transaction will revert
                  /// @return Delta the balance changes from the swap
                  function swap(
                      int256 amountSpecified,
                      uint256 amountLimit,
                      Currency startCurrency,
                      PathKey[] calldata path,
                      address receiver,
                      uint256 deadline
                  ) external payable returns (BalanceDelta);
                  /// ================ SINGLE POOL SWAPS ================ ///
                  /// @notice Single pool, exact input swap - swap the specified amount of input tokens for as many output tokens as possible, on a single pool
                  /// @param amountIn the amount of input tokens to swap
                  /// @param amountOutMin the minimum amount of output tokens that must be received for the transaction not to revert
                  /// @param zeroForOne the direction of the swap, true if currency0 is being swapped for currency1
                  /// @param poolKey the pool to swap through
                  /// @param hookData the data to be passed to the hook
                  /// @param receiver the address to send the output tokens to
                  /// @param deadline block.timestamp must be before this value, otherwise the transaction will revert
                  /// @return Delta the balance changes from the swap
                  function swapExactTokensForTokens(
                      uint256 amountIn,
                      uint256 amountOutMin,
                      bool zeroForOne,
                      PoolKey calldata poolKey,
                      bytes calldata hookData,
                      address receiver,
                      uint256 deadline
                  ) external payable returns (BalanceDelta);
                  /// @notice Singe pool, exact output swap; swap as few input tokens as possible for the specified amount of output tokens, on a single pool
                  /// @param amountOut the amount of output tokens to receive
                  /// @param amountInMax the maximum amount of input tokens that can be spent for the transaction not to revert
                  /// @param zeroForOne the direction of the swap, true if currency0 is being swapped for currency1
                  /// @param poolKey the pool to swap through
                  /// @param hookData the data to be passed to the hook
                  /// @param receiver the address to send the output tokens to
                  /// @param deadline block.timestamp must be before this value, otherwise the transaction will revert
                  /// @return Delta the balance changes from the swap
                  function swapTokensForExactTokens(
                      uint256 amountOut,
                      uint256 amountInMax,
                      bool zeroForOne,
                      PoolKey calldata poolKey,
                      bytes calldata hookData,
                      address receiver,
                      uint256 deadline
                  ) external payable returns (BalanceDelta);
                  /// @notice General-purpose single-pool swap interface
                  /// @param amountSpecified the amount of tokens to be swapped, negative for exact input swaps and positive for exact output swaps
                  /// @param amountLimit the minimum amount of output tokens for exact input swaps, the maximum amount of input tokens for exact output swaps
                  /// @param zeroForOne the direction of the swap, true if currency0 is being swapped for currency1
                  /// @param poolKey the pool to swap through
                  /// @param hookData the data to be passed to the hook
                  /// @param receiver the address to send the output tokens to
                  /// @param deadline block.timestamp must be before this value, otherwise the transaction will revert
                  /// @return Delta the balance changes from the swap
                  function swap(
                      int256 amountSpecified,
                      uint256 amountLimit,
                      bool zeroForOne,
                      PoolKey calldata poolKey,
                      bytes calldata hookData,
                      address receiver,
                      uint256 deadline
                  ) external payable returns (BalanceDelta);
                  /// ================ OPTIMIZED ================ ///
                  /// @notice Generic multi-pool swap function that accepts pre-encoded calldata
                  /// @dev Minor optimization to reduce the number of onchain abi.encode calls
                  /// @param data Pre-encoded swap data in one of the following formats:
                  ///     1. For single-pool swaps: abi.encode(
                  ///         BaseData baseData,             // struct containing swap parameters
                  ///         bool zeroForOne,               // direction of swap
                  ///         PoolKey poolKey,               // key of the pool to swap through
                  ///         bytes hookData                 // data to pass to hooks
                  ///     )
                  ///     2. For multi-pool swaps: abi.encode(
                  ///         BaseData baseData,             // struct containing swap parameters
                  ///         Currency startCurrency,        // initial currency in the swap
                  ///         PathKey[] path                 // array of path keys defining the route
                  ///     )
                  ///
                  ///     PERMIT2 EXTENSION:
                  ///     1. For single pool swaps: abi.encode(
                  ///         BaseData baseData,             // struct containing swap parameters
                  ///         bool zeroForOne,               // direction of swap
                  ///         PoolKey poolKey,               // key of the pool to swap through
                  ///         bytes hookData,                // data to pass to hooks
                  ///         PermitPayload permitPayload    // permit2 signature payload
                  ///     )
                  ///     2. For multi-pool swaps: abi.encode(
                  ///         BaseData baseData,             // struct containing swap parameters
                  ///         Currency startCurrency,        // initial currency in the swap
                  ///         PathKey[] path,                // array of path keys defining the route
                  ///         PermitPayload permitPayload    // permit2 signature payload
                  ///     )
                  ///     Where BaseData.flags contains permit2 flag, and PermitPayload contains:
                  ///         - permit: ISignatureTransfer.PermitTransferFrom
                  ///         - signature: bytes
                  ///
                  /// @param deadline block.timestamp must be before this value, otherwise the transaction will revert
                  /// @return Delta the balance changes from the swap
                  function swap(bytes calldata data, uint256 deadline) external payable returns (BalanceDelta);
                  /// @notice Provides calldata compression fallback
                  fallback() external payable;
                  /// @notice Provides ETH receipts locked to Pool Manager
                  receive() external payable;
                  /// ================ GETTERS ================ ///
                  /// @notice Public view function to be used instead of msg.sender, as the contract performs self-reentrancy and at
                  /// times msg.sender == address(this). Instead msgSender() returns the initiator of the lock
                  function msgSender() external view returns (address);
              }
              // SPDX-License-Identifier: MIT
              pragma solidity >=0.7.5;
              import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
              interface IUniversalRouter {
                  /// @notice Thrown when a required command has failed
                  error ExecutionFailed(uint256 commandIndex, bytes message);
                  /// @notice Thrown when attempting to send ETH directly to the contract
                  error ETHNotAccepted();
                  /// @notice Thrown when executing commands with an expired deadline
                  error TransactionDeadlinePassed();
                  /// @notice Thrown when attempting to execute commands and an incorrect number of inputs are provided
                  error LengthMismatch();
                  // @notice Thrown when an address that isn't WETH tries to send ETH to the router without calldata
                  error InvalidEthSender();
                  /// @notice Executes encoded commands along with provided inputs. Reverts if deadline has expired.
                  /// @param commands A set of concatenated commands, each 1 byte in length
                  /// @param inputs An array of byte strings containing abi encoded inputs for each command
                  /// @param deadline The deadline by which the transaction must be executed
                  function execute(bytes calldata commands, bytes[] calldata inputs, uint256 deadline) external payable;
              }
              struct ExactInputSingleParams {
                  PoolKey poolKey;
                  bool zeroForOne;
                  uint128 amountIn;
                  uint128 amountOutMinimum;
                  bytes hookData;
              }
              interface IPunkStrategy {
                  function loadingLiquidity() external view returns (bool);
                  function owner() external view returns (address);
                  function addFees() external payable;
              }
              interface IPunkStrategyHook {
                  function feeCooldown() external;
                  function punksAreAccumulating() external;
              }
              interface IFeeSplit { 
                  function processDeposit() external payable;
              }
              struct Offer {
                  bool isForSale;
                  uint punkIndex;
                  address seller;
                  uint minValue;
                  address onlySellTo;
              }
              interface IPunks {
                  function buyPunk(uint punkIndex) external payable;
                  function offerPunkForSale(uint punkIndex, uint minSalePriceInWei) external;
                  function punksOfferedForSale(uint punkId) external view returns (bool isForSale, uint punkIndex, address seller, uint minValue, address onlySellTo);
                  function balanceOf(address owner) external view returns (uint256);
                  function punkIndexToAddress(uint punkIndex) external view returns (address);
              }
              interface IERC20 {
                  function totalSupply() external view returns (uint256);
                  function balanceOf(address account) external view returns (uint256);
                  function transfer(address to, uint256 amount) external returns (bool);
                  function allowance(address owner, address spender) external view returns (uint256);
                  function approve(address spender, uint256 amount) external returns (bool);
                  function transferFrom(address from, address to, uint256 amount) external returns (bool);
              }
              interface IValidRouter {
                  function msgSender() external view returns (address);
              }// SPDX-License-Identifier: MIT
              pragma solidity ^0.8.24;
              import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
              import {PoolId} from "@uniswap/v4-core/src/types/PoolId.sol";
              /**
               * @dev PositionInfo is a packed version of solidity structure.
               * Using the packaged version saves gas and memory by not storing the structure fields in memory slots.
               *
               * Layout:
               * 200 bits poolId | 24 bits tickUpper | 24 bits tickLower | 8 bits hasSubscriber
               *
               * Fields in the direction from the least significant bit:
               *
               * A flag to know if the tokenId is subscribed to an address
               * uint8 hasSubscriber;
               *
               * The tickUpper of the position
               * int24 tickUpper;
               *
               * The tickLower of the position
               * int24 tickLower;
               *
               * The truncated poolId. Truncates a bytes32 value so the most signifcant (highest) 200 bits are used.
               * bytes25 poolId;
               *
               * Note: If more bits are needed, hasSubscriber can be a single bit.
               *
               */
              type PositionInfo is uint256;
              using PositionInfoLibrary for PositionInfo global;
              library PositionInfoLibrary {
                  PositionInfo internal constant EMPTY_POSITION_INFO = PositionInfo.wrap(0);
                  uint256 internal constant MASK_UPPER_200_BITS = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000;
                  uint256 internal constant MASK_8_BITS = 0xFF;
                  uint24 internal constant MASK_24_BITS = 0xFFFFFF;
                  uint256 internal constant SET_UNSUBSCRIBE = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00;
                  uint256 internal constant SET_SUBSCRIBE = 0x01;
                  uint8 internal constant TICK_LOWER_OFFSET = 8;
                  uint8 internal constant TICK_UPPER_OFFSET = 32;
                  /// @dev This poolId is NOT compatible with the poolId used in UniswapV4 core. It is truncated to 25 bytes, and just used to lookup PoolKey in the poolKeys mapping.
                  function poolId(PositionInfo info) internal pure returns (bytes25 _poolId) {
                      assembly ("memory-safe") {
                          _poolId := and(MASK_UPPER_200_BITS, info)
                      }
                  }
                  function tickLower(PositionInfo info) internal pure returns (int24 _tickLower) {
                      assembly ("memory-safe") {
                          _tickLower := signextend(2, shr(TICK_LOWER_OFFSET, info))
                      }
                  }
                  function tickUpper(PositionInfo info) internal pure returns (int24 _tickUpper) {
                      assembly ("memory-safe") {
                          _tickUpper := signextend(2, shr(TICK_UPPER_OFFSET, info))
                      }
                  }
                  function hasSubscriber(PositionInfo info) internal pure returns (bool _hasSubscriber) {
                      assembly ("memory-safe") {
                          _hasSubscriber := and(MASK_8_BITS, info)
                      }
                  }
                  /// @dev this does not actually set any storage
                  function setSubscribe(PositionInfo info) internal pure returns (PositionInfo _info) {
                      assembly ("memory-safe") {
                          _info := or(info, SET_SUBSCRIBE)
                      }
                  }
                  /// @dev this does not actually set any storage
                  function setUnsubscribe(PositionInfo info) internal pure returns (PositionInfo _info) {
                      assembly ("memory-safe") {
                          _info := and(info, SET_UNSUBSCRIBE)
                      }
                  }
                  /// @notice Creates the default PositionInfo struct
                  /// @dev Called when minting a new position
                  /// @param _poolKey the pool key of the position
                  /// @param _tickLower the lower tick of the position
                  /// @param _tickUpper the upper tick of the position
                  /// @return info packed position info, with the truncated poolId and the hasSubscriber flag set to false
                  function initialize(PoolKey memory _poolKey, int24 _tickLower, int24 _tickUpper)
                      internal
                      pure
                      returns (PositionInfo info)
                  {
                      bytes25 _poolId = bytes25(PoolId.unwrap(_poolKey.toId()));
                      assembly {
                          info :=
                              or(
                                  or(and(MASK_UPPER_200_BITS, _poolId), shl(TICK_UPPER_OFFSET, and(MASK_24_BITS, _tickUpper))),
                                  shl(TICK_LOWER_OFFSET, and(MASK_24_BITS, _tickLower))
                              )
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import {ISubscriber} from "./ISubscriber.sol";
              /// @title INotifier
              /// @notice Interface for the Notifier contract
              interface INotifier {
                  /// @notice Thrown when unsubscribing without a subscriber
                  error NotSubscribed();
                  /// @notice Thrown when a subscriber does not have code
                  error NoCodeSubscriber();
                  /// @notice Thrown when a user specifies a gas limit too low to avoid valid unsubscribe notifications
                  error GasLimitTooLow();
                  /// @notice Wraps the revert message of the subscriber contract on a reverting subscription
                  error SubscriptionReverted(address subscriber, bytes reason);
                  /// @notice Wraps the revert message of the subscriber contract on a reverting modify liquidity notification
                  error ModifyLiquidityNotificationReverted(address subscriber, bytes reason);
                  /// @notice Wraps the revert message of the subscriber contract on a reverting burn notification
                  error BurnNotificationReverted(address subscriber, bytes reason);
                  /// @notice Thrown when a tokenId already has a subscriber
                  error AlreadySubscribed(uint256 tokenId, address subscriber);
                  /// @notice Emitted on a successful call to subscribe
                  event Subscription(uint256 indexed tokenId, address indexed subscriber);
                  /// @notice Emitted on a successful call to unsubscribe
                  event Unsubscription(uint256 indexed tokenId, address indexed subscriber);
                  /// @notice Returns the subscriber for a respective position
                  /// @param tokenId the ERC721 tokenId
                  /// @return subscriber the subscriber contract
                  function subscriber(uint256 tokenId) external view returns (ISubscriber subscriber);
                  /// @notice Enables the subscriber to receive notifications for a respective position
                  /// @param tokenId the ERC721 tokenId
                  /// @param newSubscriber the address of the subscriber contract
                  /// @param data caller-provided data that's forwarded to the subscriber contract
                  /// @dev Calling subscribe when a position is already subscribed will revert
                  /// @dev payable so it can be multicalled with NATIVE related actions
                  /// @dev will revert if pool manager is locked
                  function subscribe(uint256 tokenId, address newSubscriber, bytes calldata data) external payable;
                  /// @notice Removes the subscriber from receiving notifications for a respective position
                  /// @param tokenId the ERC721 tokenId
                  /// @dev Callers must specify a high gas limit (remaining gas should be higher than unsubscriberGasLimit) such that the subscriber can be notified
                  /// @dev payable so it can be multicalled with NATIVE related actions
                  /// @dev Must always allow a user to unsubscribe. In the case of a malicious subscriber, a user can always unsubscribe safely, ensuring liquidity is always modifiable.
                  /// @dev will revert if pool manager is locked
                  function unsubscribe(uint256 tokenId) external payable;
                  /// @notice Returns and determines the maximum allowable gas-used for notifying unsubscribe
                  /// @return uint256 the maximum gas limit when notifying a subscriber's `notifyUnsubscribe` function
                  function unsubscribeGasLimit() external view returns (uint256);
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
              /// @title IImmutableState
              /// @notice Interface for the ImmutableState contract
              interface IImmutableState {
                  /// @notice The Uniswap v4 PoolManager contract
                  function poolManager() external view returns (IPoolManager);
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              /// @title IERC721Permit_v4
              /// @notice Interface for the ERC721Permit_v4 contract
              interface IERC721Permit_v4 {
                  error SignatureDeadlineExpired();
                  error NoSelfPermit();
                  error Unauthorized();
                  /// @notice Approve of a specific token ID for spending by spender via signature
                  /// @param spender The account that is being approved
                  /// @param tokenId The ID of the token that is being approved for spending
                  /// @param deadline The deadline timestamp by which the call must be mined for the approve to work
                  /// @param nonce a unique value, for an owner, to prevent replay attacks; an unordered nonce where the top 248 bits correspond to a word and the bottom 8 bits calculate the bit position of the word
                  /// @param signature Concatenated data from a valid secp256k1 signature from the holder, i.e. abi.encodePacked(r, s, v)
                  /// @dev payable so it can be multicalled with NATIVE related actions
                  function permit(address spender, uint256 tokenId, uint256 deadline, uint256 nonce, bytes calldata signature)
                      external
                      payable;
                  /// @notice Set an operator with full permission to an owner's tokens via signature
                  /// @param owner The address that is setting the operator
                  /// @param operator The address that will be set as an operator for the owner
                  /// @param approved The permission to set on the operator
                  /// @param deadline The deadline timestamp by which the call must be mined for the approve to work
                  /// @param nonce a unique value, for an owner, to prevent replay attacks; an unordered nonce where the top 248 bits correspond to a word and the bottom 8 bits calculate the bit position of the word
                  /// @param signature Concatenated data from a valid secp256k1 signature from the holder, i.e. abi.encodePacked(r, s, v)
                  /// @dev payable so it can be multicalled with NATIVE related actions
                  function permitForAll(
                      address owner,
                      address operator,
                      bool approved,
                      uint256 deadline,
                      uint256 nonce,
                      bytes calldata signature
                  ) external payable;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              /// @title IEIP712_v4
              /// @notice Interface for the EIP712 contract
              interface IEIP712_v4 {
                  /// @notice Returns the domain separator for the current chain.
                  /// @return bytes32 The domain separator
                  function DOMAIN_SEPARATOR() external view returns (bytes32);
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              /// @title IMulticall_v4
              /// @notice Interface for the Multicall_v4 contract
              interface IMulticall_v4 {
                  /// @notice Call multiple functions in the current contract and return the data from all of them if they all succeed
                  /// @dev The `msg.value` is passed onto all subcalls, even if a previous subcall has consumed the ether.
                  /// Subcalls can instead use `address(this).value` to see the available ETH, and consume it using {value: x}.
                  /// @param data The encoded function data for each of the calls to make to this contract
                  /// @return results The results from each of the calls passed in via data
                  function multicall(bytes[] calldata data) external payable returns (bytes[] memory results);
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
              /// @title IPoolInitializer_v4
              /// @notice Interface for the PoolInitializer_v4 contract
              interface IPoolInitializer_v4 {
                  /// @notice Initialize a Uniswap v4 Pool
                  /// @dev If the pool is already initialized, this function will not revert and just return type(int24).max
                  /// @param key The PoolKey of the pool to initialize
                  /// @param sqrtPriceX96 The initial starting price of the pool, expressed as a sqrtPriceX96
                  /// @return The current tick of the pool, or type(int24).max if the pool creation failed, or the pool already existed
                  function initializePool(PoolKey calldata key, uint160 sqrtPriceX96) external payable returns (int24);
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              /// @title IUnorderedNonce
              /// @notice Interface for the UnorderedNonce contract
              interface IUnorderedNonce {
                  error NonceAlreadyUsed();
                  /// @notice mapping of nonces consumed by each address, where a nonce is a single bit on the 256-bit bitmap
                  /// @dev word is at most type(uint248).max
                  function nonces(address owner, uint256 word) external view returns (uint256);
                  /// @notice Revoke a nonce by spending it, preventing it from being used again
                  /// @dev Used in cases where a valid nonce has not been broadcasted onchain, and the owner wants to revoke the validity of the nonce
                  /// @dev payable so it can be multicalled with native-token related actions
                  function revokeNonce(uint256 nonce) external payable;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol";
              /// @title IPermit2Forwarder
              /// @notice Interface for the Permit2Forwarder contract
              interface IPermit2Forwarder {
                  /// @notice allows forwarding a single permit to permit2
                  /// @dev this function is payable to allow multicall with NATIVE based actions
                  /// @param owner the owner of the tokens
                  /// @param permitSingle the permit data
                  /// @param signature the signature of the permit; abi.encodePacked(r, s, v)
                  /// @return err the error returned by a reverting permit call, empty if successful
                  function permit(address owner, IAllowanceTransfer.PermitSingle calldata permitSingle, bytes calldata signature)
                      external
                      payable
                      returns (bytes memory err);
                  /// @notice allows forwarding batch permits to permit2
                  /// @dev this function is payable to allow multicall with NATIVE based actions
                  /// @param owner the owner of the tokens
                  /// @param _permitBatch a batch of approvals
                  /// @param signature the signature of the permit; abi.encodePacked(r, s, v)
                  /// @return err the error returned by a reverting permit call, empty if successful
                  function permitBatch(address owner, IAllowanceTransfer.PermitBatch calldata _permitBatch, bytes calldata signature)
                      external
                      payable
                      returns (bytes memory err);
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              interface IEIP712 {
                  function DOMAIN_SEPARATOR() external view returns (bytes32);
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import {SafeCast} from "../libraries/SafeCast.sol";
              /// @dev Two `int128` values packed into a single `int256` where the upper 128 bits represent the amount0
              /// and the lower 128 bits represent the amount1.
              type BalanceDelta is int256;
              using {add as +, sub as -, eq as ==, neq as !=} for BalanceDelta global;
              using BalanceDeltaLibrary for BalanceDelta global;
              using SafeCast for int256;
              function toBalanceDelta(int128 _amount0, int128 _amount1) pure returns (BalanceDelta balanceDelta) {
                  assembly ("memory-safe") {
                      balanceDelta := or(shl(128, _amount0), and(sub(shl(128, 1), 1), _amount1))
                  }
              }
              function add(BalanceDelta a, BalanceDelta b) pure returns (BalanceDelta) {
                  int256 res0;
                  int256 res1;
                  assembly ("memory-safe") {
                      let a0 := sar(128, a)
                      let a1 := signextend(15, a)
                      let b0 := sar(128, b)
                      let b1 := signextend(15, b)
                      res0 := add(a0, b0)
                      res1 := add(a1, b1)
                  }
                  return toBalanceDelta(res0.toInt128(), res1.toInt128());
              }
              function sub(BalanceDelta a, BalanceDelta b) pure returns (BalanceDelta) {
                  int256 res0;
                  int256 res1;
                  assembly ("memory-safe") {
                      let a0 := sar(128, a)
                      let a1 := signextend(15, a)
                      let b0 := sar(128, b)
                      let b1 := signextend(15, b)
                      res0 := sub(a0, b0)
                      res1 := sub(a1, b1)
                  }
                  return toBalanceDelta(res0.toInt128(), res1.toInt128());
              }
              function eq(BalanceDelta a, BalanceDelta b) pure returns (bool) {
                  return BalanceDelta.unwrap(a) == BalanceDelta.unwrap(b);
              }
              function neq(BalanceDelta a, BalanceDelta b) pure returns (bool) {
                  return BalanceDelta.unwrap(a) != BalanceDelta.unwrap(b);
              }
              /// @notice Library for getting the amount0 and amount1 deltas from the BalanceDelta type
              library BalanceDeltaLibrary {
                  /// @notice A BalanceDelta of 0
                  BalanceDelta public constant ZERO_DELTA = BalanceDelta.wrap(0);
                  function amount0(BalanceDelta balanceDelta) internal pure returns (int128 _amount0) {
                      assembly ("memory-safe") {
                          _amount0 := sar(128, balanceDelta)
                      }
                  }
                  function amount1(BalanceDelta balanceDelta) internal pure returns (int128 _amount1) {
                      assembly ("memory-safe") {
                          _amount1 := signextend(15, balanceDelta)
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.24;
              import {PoolKey} from "../types/PoolKey.sol";
              import {BalanceDelta} from "../types/BalanceDelta.sol";
              /// @notice Parameter struct for `ModifyLiquidity` pool operations
              struct ModifyLiquidityParams {
                  // the lower and upper tick of the position
                  int24 tickLower;
                  int24 tickUpper;
                  // how to modify the liquidity
                  int256 liquidityDelta;
                  // a value to set if you want unique liquidity positions at the same range
                  bytes32 salt;
              }
              /// @notice Parameter struct for `Swap` pool operations
              struct SwapParams {
                  /// Whether to swap token0 for token1 or vice versa
                  bool zeroForOne;
                  /// The desired input amount if negative (exactIn), or the desired output amount if positive (exactOut)
                  int256 amountSpecified;
                  /// The sqrt price at which, if reached, the swap will stop executing
                  uint160 sqrtPriceLimitX96;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              // Return type of the beforeSwap hook.
              // Upper 128 bits is the delta in specified tokens. Lower 128 bits is delta in unspecified tokens (to match the afterSwap hook)
              type BeforeSwapDelta is int256;
              // Creates a BeforeSwapDelta from specified and unspecified
              function toBeforeSwapDelta(int128 deltaSpecified, int128 deltaUnspecified)
                  pure
                  returns (BeforeSwapDelta beforeSwapDelta)
              {
                  assembly ("memory-safe") {
                      beforeSwapDelta := or(shl(128, deltaSpecified), and(sub(shl(128, 1), 1), deltaUnspecified))
                  }
              }
              /// @notice Library for getting the specified and unspecified deltas from the BeforeSwapDelta type
              library BeforeSwapDeltaLibrary {
                  /// @notice A BeforeSwapDelta of 0
                  BeforeSwapDelta public constant ZERO_DELTA = BeforeSwapDelta.wrap(0);
                  /// extracts int128 from the upper 128 bits of the BeforeSwapDelta
                  /// returned by beforeSwap
                  function getSpecifiedDelta(BeforeSwapDelta delta) internal pure returns (int128 deltaSpecified) {
                      assembly ("memory-safe") {
                          deltaSpecified := sar(128, delta)
                      }
                  }
                  /// extracts int128 from the lower 128 bits of the BeforeSwapDelta
                  /// returned by beforeSwap and afterSwap
                  function getUnspecifiedDelta(BeforeSwapDelta delta) internal pure returns (int128 deltaUnspecified) {
                      assembly ("memory-safe") {
                          deltaUnspecified := signextend(15, delta)
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              /// @title Minimal ERC20 interface for Uniswap
              /// @notice Contains a subset of the full ERC20 interface that is used in Uniswap V3
              interface IERC20Minimal {
                  /// @notice Returns an account's balance in the token
                  /// @param account The account for which to look up the number of tokens it has, i.e. its balance
                  /// @return The number of tokens held by the account
                  function balanceOf(address account) external view returns (uint256);
                  /// @notice Transfers the amount of token from the `msg.sender` to the recipient
                  /// @param recipient The account that will receive the amount transferred
                  /// @param amount The number of tokens to send from the sender to the recipient
                  /// @return Returns true for a successful transfer, false for an unsuccessful transfer
                  function transfer(address recipient, uint256 amount) external returns (bool);
                  /// @notice Returns the current allowance given to a spender by an owner
                  /// @param owner The account of the token owner
                  /// @param spender The account of the token spender
                  /// @return The current allowance granted by `owner` to `spender`
                  function allowance(address owner, address spender) external view returns (uint256);
                  /// @notice Sets the allowance of a spender from the `msg.sender` to the value `amount`
                  /// @param spender The account which will be allowed to spend a given amount of the owners tokens
                  /// @param amount The amount of tokens allowed to be used by `spender`
                  /// @return Returns true for a successful approval, false for unsuccessful
                  function approve(address spender, uint256 amount) external returns (bool);
                  /// @notice Transfers `amount` tokens from `sender` to `recipient` up to the allowance given to the `msg.sender`
                  /// @param sender The account from which the transfer will be initiated
                  /// @param recipient The recipient of the transfer
                  /// @param amount The amount of the transfer
                  /// @return Returns true for a successful transfer, false for unsuccessful
                  function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
                  /// @notice Event emitted when tokens are transferred from one address to another, either via `#transfer` or `#transferFrom`.
                  /// @param from The account from which the tokens were sent, i.e. the balance decreased
                  /// @param to The account to which the tokens were sent, i.e. the balance increased
                  /// @param value The amount of tokens that were transferred
                  event Transfer(address indexed from, address indexed to, uint256 value);
                  /// @notice Event emitted when the approval amount for the spender of a given owner's tokens changes.
                  /// @param owner The account that approved spending of its tokens
                  /// @param spender The account for which the spending allowance was modified
                  /// @param value The new allowance from the owner to the spender
                  event Approval(address indexed owner, address indexed spender, uint256 value);
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              /// @title Library for reverting with custom errors efficiently
              /// @notice Contains functions for reverting with custom errors with different argument types efficiently
              /// @dev To use this library, declare `using CustomRevert for bytes4;` and replace `revert CustomError()` with
              /// `CustomError.selector.revertWith()`
              /// @dev The functions may tamper with the free memory pointer but it is fine since the call context is exited immediately
              library CustomRevert {
                  /// @dev ERC-7751 error for wrapping bubbled up reverts
                  error WrappedError(address target, bytes4 selector, bytes reason, bytes details);
                  /// @dev Reverts with the selector of a custom error in the scratch space
                  function revertWith(bytes4 selector) internal pure {
                      assembly ("memory-safe") {
                          mstore(0, selector)
                          revert(0, 0x04)
                      }
                  }
                  /// @dev Reverts with a custom error with an address argument in the scratch space
                  function revertWith(bytes4 selector, address addr) internal pure {
                      assembly ("memory-safe") {
                          mstore(0, selector)
                          mstore(0x04, and(addr, 0xffffffffffffffffffffffffffffffffffffffff))
                          revert(0, 0x24)
                      }
                  }
                  /// @dev Reverts with a custom error with an int24 argument in the scratch space
                  function revertWith(bytes4 selector, int24 value) internal pure {
                      assembly ("memory-safe") {
                          mstore(0, selector)
                          mstore(0x04, signextend(2, value))
                          revert(0, 0x24)
                      }
                  }
                  /// @dev Reverts with a custom error with a uint160 argument in the scratch space
                  function revertWith(bytes4 selector, uint160 value) internal pure {
                      assembly ("memory-safe") {
                          mstore(0, selector)
                          mstore(0x04, and(value, 0xffffffffffffffffffffffffffffffffffffffff))
                          revert(0, 0x24)
                      }
                  }
                  /// @dev Reverts with a custom error with two int24 arguments
                  function revertWith(bytes4 selector, int24 value1, int24 value2) internal pure {
                      assembly ("memory-safe") {
                          let fmp := mload(0x40)
                          mstore(fmp, selector)
                          mstore(add(fmp, 0x04), signextend(2, value1))
                          mstore(add(fmp, 0x24), signextend(2, value2))
                          revert(fmp, 0x44)
                      }
                  }
                  /// @dev Reverts with a custom error with two uint160 arguments
                  function revertWith(bytes4 selector, uint160 value1, uint160 value2) internal pure {
                      assembly ("memory-safe") {
                          let fmp := mload(0x40)
                          mstore(fmp, selector)
                          mstore(add(fmp, 0x04), and(value1, 0xffffffffffffffffffffffffffffffffffffffff))
                          mstore(add(fmp, 0x24), and(value2, 0xffffffffffffffffffffffffffffffffffffffff))
                          revert(fmp, 0x44)
                      }
                  }
                  /// @dev Reverts with a custom error with two address arguments
                  function revertWith(bytes4 selector, address value1, address value2) internal pure {
                      assembly ("memory-safe") {
                          let fmp := mload(0x40)
                          mstore(fmp, selector)
                          mstore(add(fmp, 0x04), and(value1, 0xffffffffffffffffffffffffffffffffffffffff))
                          mstore(add(fmp, 0x24), and(value2, 0xffffffffffffffffffffffffffffffffffffffff))
                          revert(fmp, 0x44)
                      }
                  }
                  /// @notice bubble up the revert message returned by a call and revert with a wrapped ERC-7751 error
                  /// @dev this method can be vulnerable to revert data bombs
                  function bubbleUpAndRevertWith(
                      address revertingContract,
                      bytes4 revertingFunctionSelector,
                      bytes4 additionalContext
                  ) internal pure {
                      bytes4 wrappedErrorSelector = WrappedError.selector;
                      assembly ("memory-safe") {
                          // Ensure the size of the revert data is a multiple of 32 bytes
                          let encodedDataSize := mul(div(add(returndatasize(), 31), 32), 32)
                          let fmp := mload(0x40)
                          // Encode wrapped error selector, address, function selector, offset, additional context, size, revert reason
                          mstore(fmp, wrappedErrorSelector)
                          mstore(add(fmp, 0x04), and(revertingContract, 0xffffffffffffffffffffffffffffffffffffffff))
                          mstore(
                              add(fmp, 0x24),
                              and(revertingFunctionSelector, 0xffffffff00000000000000000000000000000000000000000000000000000000)
                          )
                          // offset revert reason
                          mstore(add(fmp, 0x44), 0x80)
                          // offset additional context
                          mstore(add(fmp, 0x64), add(0xa0, encodedDataSize))
                          // size revert reason
                          mstore(add(fmp, 0x84), returndatasize())
                          // revert reason
                          returndatacopy(add(fmp, 0xa4), 0, returndatasize())
                          // size additional context
                          mstore(add(fmp, add(0xa4, encodedDataSize)), 0x04)
                          // additional context
                          mstore(
                              add(fmp, add(0xc4, encodedDataSize)),
                              and(additionalContext, 0xffffffff00000000000000000000000000000000000000000000000000000000)
                          )
                          revert(fmp, add(0xe4, encodedDataSize))
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import {PoolKey} from "./PoolKey.sol";
              type PoolId is bytes32;
              /// @notice Library for computing the ID of a pool
              library PoolIdLibrary {
                  /// @notice Returns value equal to keccak256(abi.encode(poolKey))
                  function toId(PoolKey memory poolKey) internal pure returns (PoolId poolId) {
                      assembly ("memory-safe") {
                          // 0xa0 represents the total size of the poolKey struct (5 slots of 32 bytes)
                          poolId := keccak256(poolKey, 0xa0)
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              /// @title BitMath
              /// @dev This library provides functionality for computing bit properties of an unsigned integer
              /// @author Solady (https://github.com/Vectorized/solady/blob/8200a70e8dc2a77ecb074fc2e99a2a0d36547522/src/utils/LibBit.sol)
              library BitMath {
                  /// @notice Returns the index of the most significant bit of the number,
                  ///     where the least significant bit is at index 0 and the most significant bit is at index 255
                  /// @param x the value for which to compute the most significant bit, must be greater than 0
                  /// @return r the index of the most significant bit
                  function mostSignificantBit(uint256 x) internal pure returns (uint8 r) {
                      require(x > 0);
                      assembly ("memory-safe") {
                          r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
                          r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
                          r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
                          r := or(r, shl(4, lt(0xffff, shr(r, x))))
                          r := or(r, shl(3, lt(0xff, shr(r, x))))
                          // forgefmt: disable-next-item
                          r := or(r, byte(and(0x1f, shr(shr(r, x), 0x8421084210842108cc6318c6db6d54be)),
                              0x0706060506020500060203020504000106050205030304010505030400000000))
                      }
                  }
                  /// @notice Returns the index of the least significant bit of the number,
                  ///     where the least significant bit is at index 0 and the most significant bit is at index 255
                  /// @param x the value for which to compute the least significant bit, must be greater than 0
                  /// @return r the index of the least significant bit
                  function leastSignificantBit(uint256 x) internal pure returns (uint8 r) {
                      require(x > 0);
                      assembly ("memory-safe") {
                          // Isolate the least significant bit.
                          x := and(x, sub(0, x))
                          // For the upper 3 bits of the result, use a De Bruijn-like lookup.
                          // Credit to adhusson: https://blog.adhusson.com/cheap-find-first-set-evm/
                          // forgefmt: disable-next-item
                          r := shl(5, shr(252, shl(shl(2, shr(250, mul(x,
                              0xb6db6db6ddddddddd34d34d349249249210842108c6318c639ce739cffffffff))),
                              0x8040405543005266443200005020610674053026020000107506200176117077)))
                          // For the lower 5 bits of the result, use a De Bruijn lookup.
                          // forgefmt: disable-next-item
                          r := or(r, byte(and(div(0xd76453e0, shr(r, x)), 0x1f),
                              0x001f0d1e100c1d070f090b19131c1706010e11080a1a141802121b1503160405))
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.26;
              import {PoolKey} from "@v4/src/types/PoolKey.sol";
              import {IHooks} from "@v4/src/interfaces/IHooks.sol";
              import {Currency, CurrencyLibrary} from "@v4/src/types/Currency.sol";
              struct PathKey {
                  Currency intermediateCurrency;
                  uint24 fee;
                  int24 tickSpacing;
                  IHooks hooks;
                  bytes hookData;
              }
              using PathKeyLibrary for PathKey global;
              /// @title PathKey Library
              /// @notice Memory-oriented version of v4-periphery/src/libraries/PathKeyLibrary.sol
              /// @dev Handles PathKey operations in memory rather than calldata for router operations
              library PathKeyLibrary {
                  /// @notice Get the pool and swap direction for a given PathKey
                  /// @param params the given PathKey
                  /// @param currencyIn the input currency
                  /// @return poolKey the pool key of the swap
                  /// @return zeroForOne the direction of the swap, true if currency0 is being swapped for currency1
                  function getPoolAndSwapDirection(PathKey memory params, Currency currencyIn)
                      internal
                      pure
                      returns (PoolKey memory poolKey, bool zeroForOne)
                  {
                      Currency currencyOut = params.intermediateCurrency;
                      (Currency currency0, Currency currency1) =
                          currencyIn < currencyOut ? (currencyIn, currencyOut) : (currencyOut, currencyIn);
                      zeroForOne = currencyIn == currency0;
                      poolKey = PoolKey(currency0, currency1, params.fee, params.tickSpacing, params.hooks);
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import {IEIP712} from "./IEIP712.sol";
              /// @title SignatureTransfer
              /// @notice Handles ERC20 token transfers through signature based actions
              /// @dev Requires user's token approval on the Permit2 contract
              interface ISignatureTransfer is IEIP712 {
                  /// @notice Thrown when the requested amount for a transfer is larger than the permissioned amount
                  /// @param maxAmount The maximum amount a spender can request to transfer
                  error InvalidAmount(uint256 maxAmount);
                  /// @notice Thrown when the number of tokens permissioned to a spender does not match the number of tokens being transferred
                  /// @dev If the spender does not need to transfer the number of tokens permitted, the spender can request amount 0 to be transferred
                  error LengthMismatch();
                  /// @notice Emits an event when the owner successfully invalidates an unordered nonce.
                  event UnorderedNonceInvalidation(address indexed owner, uint256 word, uint256 mask);
                  /// @notice The token and amount details for a transfer signed in the permit transfer signature
                  struct TokenPermissions {
                      // ERC20 token address
                      address token;
                      // the maximum amount that can be spent
                      uint256 amount;
                  }
                  /// @notice The signed permit message for a single token transfer
                  struct PermitTransferFrom {
                      TokenPermissions permitted;
                      // a unique value for every token owner's signature to prevent signature replays
                      uint256 nonce;
                      // deadline on the permit signature
                      uint256 deadline;
                  }
                  /// @notice Specifies the recipient address and amount for batched transfers.
                  /// @dev Recipients and amounts correspond to the index of the signed token permissions array.
                  /// @dev Reverts if the requested amount is greater than the permitted signed amount.
                  struct SignatureTransferDetails {
                      // recipient address
                      address to;
                      // spender requested amount
                      uint256 requestedAmount;
                  }
                  /// @notice Used to reconstruct the signed permit message for multiple token transfers
                  /// @dev Do not need to pass in spender address as it is required that it is msg.sender
                  /// @dev Note that a user still signs over a spender address
                  struct PermitBatchTransferFrom {
                      // the tokens and corresponding amounts permitted for a transfer
                      TokenPermissions[] permitted;
                      // a unique value for every token owner's signature to prevent signature replays
                      uint256 nonce;
                      // deadline on the permit signature
                      uint256 deadline;
                  }
                  /// @notice A map from token owner address and a caller specified word index to a bitmap. Used to set bits in the bitmap to prevent against signature replay protection
                  /// @dev Uses unordered nonces so that permit messages do not need to be spent in a certain order
                  /// @dev The mapping is indexed first by the token owner, then by an index specified in the nonce
                  /// @dev It returns a uint256 bitmap
                  /// @dev The index, or wordPosition is capped at type(uint248).max
                  function nonceBitmap(address, uint256) external view returns (uint256);
                  /// @notice Transfers a token using a signed permit message
                  /// @dev Reverts if the requested amount is greater than the permitted signed amount
                  /// @param permit The permit data signed over by the owner
                  /// @param owner The owner of the tokens to transfer
                  /// @param transferDetails The spender's requested transfer details for the permitted token
                  /// @param signature The signature to verify
                  function permitTransferFrom(
                      PermitTransferFrom memory permit,
                      SignatureTransferDetails calldata transferDetails,
                      address owner,
                      bytes calldata signature
                  ) external;
                  /// @notice Transfers a token using a signed permit message
                  /// @notice Includes extra data provided by the caller to verify signature over
                  /// @dev The witness type string must follow EIP712 ordering of nested structs and must include the TokenPermissions type definition
                  /// @dev Reverts if the requested amount is greater than the permitted signed amount
                  /// @param permit The permit data signed over by the owner
                  /// @param owner The owner of the tokens to transfer
                  /// @param transferDetails The spender's requested transfer details for the permitted token
                  /// @param witness Extra data to include when checking the user signature
                  /// @param witnessTypeString The EIP-712 type definition for remaining string stub of the typehash
                  /// @param signature The signature to verify
                  function permitWitnessTransferFrom(
                      PermitTransferFrom memory permit,
                      SignatureTransferDetails calldata transferDetails,
                      address owner,
                      bytes32 witness,
                      string calldata witnessTypeString,
                      bytes calldata signature
                  ) external;
                  /// @notice Transfers multiple tokens using a signed permit message
                  /// @param permit The permit data signed over by the owner
                  /// @param owner The owner of the tokens to transfer
                  /// @param transferDetails Specifies the recipient and requested amount for the token transfer
                  /// @param signature The signature to verify
                  function permitTransferFrom(
                      PermitBatchTransferFrom memory permit,
                      SignatureTransferDetails[] calldata transferDetails,
                      address owner,
                      bytes calldata signature
                  ) external;
                  /// @notice Transfers multiple tokens using a signed permit message
                  /// @dev The witness type string must follow EIP712 ordering of nested structs and must include the TokenPermissions type definition
                  /// @notice Includes extra data provided by the caller to verify signature over
                  /// @param permit The permit data signed over by the owner
                  /// @param owner The owner of the tokens to transfer
                  /// @param transferDetails Specifies the recipient and requested amount for the token transfer
                  /// @param witness Extra data to include when checking the user signature
                  /// @param witnessTypeString The EIP-712 type definition for remaining string stub of the typehash
                  /// @param signature The signature to verify
                  function permitWitnessTransferFrom(
                      PermitBatchTransferFrom memory permit,
                      SignatureTransferDetails[] calldata transferDetails,
                      address owner,
                      bytes32 witness,
                      string calldata witnessTypeString,
                      bytes calldata signature
                  ) external;
                  /// @notice Invalidates the bits specified in mask for the bitmap at the word position
                  /// @dev The wordPos is maxed at type(uint248).max
                  /// @param wordPos A number to index the nonceBitmap at
                  /// @param mask A bitmap masked against msg.sender's current bitmap at the word position
                  function invalidateUnorderedNonces(uint256 wordPos, uint256 mask) external;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
              import {PositionInfo} from "../libraries/PositionInfoLibrary.sol";
              /// @title ISubscriber
              /// @notice Interface that a Subscriber contract should implement to receive updates from the v4 position manager
              interface ISubscriber {
                  /// @notice Called when a position subscribes to this subscriber contract
                  /// @param tokenId the token ID of the position
                  /// @param data additional data passed in by the caller
                  function notifySubscribe(uint256 tokenId, bytes memory data) external;
                  /// @notice Called when a position unsubscribes from the subscriber
                  /// @dev This call's gas is capped at `unsubscribeGasLimit` (set at deployment)
                  /// @dev Because of EIP-150, solidity may only allocate 63/64 of gasleft()
                  /// @param tokenId the token ID of the position
                  function notifyUnsubscribe(uint256 tokenId) external;
                  /// @notice Called when a position is burned
                  /// @param tokenId the token ID of the position
                  /// @param owner the current owner of the tokenId
                  /// @param info information about the position
                  /// @param liquidity the amount of liquidity decreased in the position, may be 0
                  /// @param feesAccrued the fees accrued by the position if liquidity was decreased
                  function notifyBurn(uint256 tokenId, address owner, PositionInfo info, uint256 liquidity, BalanceDelta feesAccrued)
                      external;
                  /// @notice Called when a position modifies its liquidity or collects fees
                  /// @param tokenId the token ID of the position
                  /// @param liquidityChange the change in liquidity on the underlying position
                  /// @param feesAccrued the fees to be collected from the position as a result of the modifyLiquidity call
                  /// @dev Note that feesAccrued can be artificially inflated by a malicious user
                  /// Pools with a single liquidity position can inflate feeGrowthGlobal (and consequently feesAccrued) by donating to themselves;
                  /// atomically donating and collecting fees within the same unlockCallback may further inflate feeGrowthGlobal/feesAccrued
                  function notifyModifyLiquidity(uint256 tokenId, int256 liquidityChange, BalanceDelta feesAccrued) external;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.24;
              import {Currency} from "../types/Currency.sol";
              import {PoolKey} from "../types/PoolKey.sol";
              import {IHooks} from "./IHooks.sol";
              import {IERC6909Claims} from "./external/IERC6909Claims.sol";
              import {IProtocolFees} from "./IProtocolFees.sol";
              import {BalanceDelta} from "../types/BalanceDelta.sol";
              import {PoolId} from "../types/PoolId.sol";
              import {IExtsload} from "./IExtsload.sol";
              import {IExttload} from "./IExttload.sol";
              import {ModifyLiquidityParams, SwapParams} from "../types/PoolOperation.sol";
              /// @notice Interface for the PoolManager
              interface IPoolManager is IProtocolFees, IERC6909Claims, IExtsload, IExttload {
                  /// @notice Thrown when a currency is not netted out after the contract is unlocked
                  error CurrencyNotSettled();
                  /// @notice Thrown when trying to interact with a non-initialized pool
                  error PoolNotInitialized();
                  /// @notice Thrown when unlock is called, but the contract is already unlocked
                  error AlreadyUnlocked();
                  /// @notice Thrown when a function is called that requires the contract to be unlocked, but it is not
                  error ManagerLocked();
                  /// @notice Pools are limited to type(int16).max tickSpacing in #initialize, to prevent overflow
                  error TickSpacingTooLarge(int24 tickSpacing);
                  /// @notice Pools must have a positive non-zero tickSpacing passed to #initialize
                  error TickSpacingTooSmall(int24 tickSpacing);
                  /// @notice PoolKey must have currencies where address(currency0) < address(currency1)
                  error CurrenciesOutOfOrderOrEqual(address currency0, address currency1);
                  /// @notice Thrown when a call to updateDynamicLPFee is made by an address that is not the hook,
                  /// or on a pool that does not have a dynamic swap fee.
                  error UnauthorizedDynamicLPFeeUpdate();
                  /// @notice Thrown when trying to swap amount of 0
                  error SwapAmountCannotBeZero();
                  ///@notice Thrown when native currency is passed to a non native settlement
                  error NonzeroNativeValue();
                  /// @notice Thrown when `clear` is called with an amount that is not exactly equal to the open currency delta.
                  error MustClearExactPositiveDelta();
                  /// @notice Emitted when a new pool is initialized
                  /// @param id The abi encoded hash of the pool key struct for the new pool
                  /// @param currency0 The first currency of the pool by address sort order
                  /// @param currency1 The second currency of the pool by address sort order
                  /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip
                  /// @param tickSpacing The minimum number of ticks between initialized ticks
                  /// @param hooks The hooks contract address for the pool, or address(0) if none
                  /// @param sqrtPriceX96 The price of the pool on initialization
                  /// @param tick The initial tick of the pool corresponding to the initialized price
                  event Initialize(
                      PoolId indexed id,
                      Currency indexed currency0,
                      Currency indexed currency1,
                      uint24 fee,
                      int24 tickSpacing,
                      IHooks hooks,
                      uint160 sqrtPriceX96,
                      int24 tick
                  );
                  /// @notice Emitted when a liquidity position is modified
                  /// @param id The abi encoded hash of the pool key struct for the pool that was modified
                  /// @param sender The address that modified the pool
                  /// @param tickLower The lower tick of the position
                  /// @param tickUpper The upper tick of the position
                  /// @param liquidityDelta The amount of liquidity that was added or removed
                  /// @param salt The extra data to make positions unique
                  event ModifyLiquidity(
                      PoolId indexed id, address indexed sender, int24 tickLower, int24 tickUpper, int256 liquidityDelta, bytes32 salt
                  );
                  /// @notice Emitted for swaps between currency0 and currency1
                  /// @param id The abi encoded hash of the pool key struct for the pool that was modified
                  /// @param sender The address that initiated the swap call, and that received the callback
                  /// @param amount0 The delta of the currency0 balance of the pool
                  /// @param amount1 The delta of the currency1 balance of the pool
                  /// @param sqrtPriceX96 The sqrt(price) of the pool after the swap, as a Q64.96
                  /// @param liquidity The liquidity of the pool after the swap
                  /// @param tick The log base 1.0001 of the price of the pool after the swap
                  /// @param fee The swap fee in hundredths of a bip
                  event Swap(
                      PoolId indexed id,
                      address indexed sender,
                      int128 amount0,
                      int128 amount1,
                      uint160 sqrtPriceX96,
                      uint128 liquidity,
                      int24 tick,
                      uint24 fee
                  );
                  /// @notice Emitted for donations
                  /// @param id The abi encoded hash of the pool key struct for the pool that was donated to
                  /// @param sender The address that initiated the donate call
                  /// @param amount0 The amount donated in currency0
                  /// @param amount1 The amount donated in currency1
                  event Donate(PoolId indexed id, address indexed sender, uint256 amount0, uint256 amount1);
                  /// @notice All interactions on the contract that account deltas require unlocking. A caller that calls `unlock` must implement
                  /// `IUnlockCallback(msg.sender).unlockCallback(data)`, where they interact with the remaining functions on this contract.
                  /// @dev The only functions callable without an unlocking are `initialize` and `updateDynamicLPFee`
                  /// @param data Any data to pass to the callback, via `IUnlockCallback(msg.sender).unlockCallback(data)`
                  /// @return The data returned by the call to `IUnlockCallback(msg.sender).unlockCallback(data)`
                  function unlock(bytes calldata data) external returns (bytes memory);
                  /// @notice Initialize the state for a given pool ID
                  /// @dev A swap fee totaling MAX_SWAP_FEE (100%) makes exact output swaps impossible since the input is entirely consumed by the fee
                  /// @param key The pool key for the pool to initialize
                  /// @param sqrtPriceX96 The initial square root price
                  /// @return tick The initial tick of the pool
                  function initialize(PoolKey memory key, uint160 sqrtPriceX96) external returns (int24 tick);
                  /// @notice Modify the liquidity for the given pool
                  /// @dev Poke by calling with a zero liquidityDelta
                  /// @param key The pool to modify liquidity in
                  /// @param params The parameters for modifying the liquidity
                  /// @param hookData The data to pass through to the add/removeLiquidity hooks
                  /// @return callerDelta The balance delta of the caller of modifyLiquidity. This is the total of both principal, fee deltas, and hook deltas if applicable
                  /// @return feesAccrued The balance delta of the fees generated in the liquidity range. Returned for informational purposes
                  /// @dev Note that feesAccrued can be artificially inflated by a malicious actor and integrators should be careful using the value
                  /// For pools with a single liquidity position, actors can donate to themselves to inflate feeGrowthGlobal (and consequently feesAccrued)
                  /// atomically donating and collecting fees in the same unlockCallback may make the inflated value more extreme
                  function modifyLiquidity(PoolKey memory key, ModifyLiquidityParams memory params, bytes calldata hookData)
                      external
                      returns (BalanceDelta callerDelta, BalanceDelta feesAccrued);
                  /// @notice Swap against the given pool
                  /// @param key The pool to swap in
                  /// @param params The parameters for swapping
                  /// @param hookData The data to pass through to the swap hooks
                  /// @return swapDelta The balance delta of the address swapping
                  /// @dev Swapping on low liquidity pools may cause unexpected swap amounts when liquidity available is less than amountSpecified.
                  /// Additionally note that if interacting with hooks that have the BEFORE_SWAP_RETURNS_DELTA_FLAG or AFTER_SWAP_RETURNS_DELTA_FLAG
                  /// the hook may alter the swap input/output. Integrators should perform checks on the returned swapDelta.
                  function swap(PoolKey memory key, SwapParams memory params, bytes calldata hookData)
                      external
                      returns (BalanceDelta swapDelta);
                  /// @notice Donate the given currency amounts to the in-range liquidity providers of a pool
                  /// @dev Calls to donate can be frontrun adding just-in-time liquidity, with the aim of receiving a portion donated funds.
                  /// Donors should keep this in mind when designing donation mechanisms.
                  /// @dev This function donates to in-range LPs at slot0.tick. In certain edge-cases of the swap algorithm, the `sqrtPrice` of
                  /// a pool can be at the lower boundary of tick `n`, but the `slot0.tick` of the pool is already `n - 1`. In this case a call to
                  /// `donate` would donate to tick `n - 1` (slot0.tick) not tick `n` (getTickAtSqrtPrice(slot0.sqrtPriceX96)).
                  /// Read the comments in `Pool.swap()` for more information about this.
                  /// @param key The key of the pool to donate to
                  /// @param amount0 The amount of currency0 to donate
                  /// @param amount1 The amount of currency1 to donate
                  /// @param hookData The data to pass through to the donate hooks
                  /// @return BalanceDelta The delta of the caller after the donate
                  function donate(PoolKey memory key, uint256 amount0, uint256 amount1, bytes calldata hookData)
                      external
                      returns (BalanceDelta);
                  /// @notice Writes the current ERC20 balance of the specified currency to transient storage
                  /// This is used to checkpoint balances for the manager and derive deltas for the caller.
                  /// @dev This MUST be called before any ERC20 tokens are sent into the contract, but can be skipped
                  /// for native tokens because the amount to settle is determined by the sent value.
                  /// However, if an ERC20 token has been synced and not settled, and the caller instead wants to settle
                  /// native funds, this function can be called with the native currency to then be able to settle the native currency
                  function sync(Currency currency) external;
                  /// @notice Called by the user to net out some value owed to the user
                  /// @dev Will revert if the requested amount is not available, consider using `mint` instead
                  /// @dev Can also be used as a mechanism for free flash loans
                  /// @param currency The currency to withdraw from the pool manager
                  /// @param to The address to withdraw to
                  /// @param amount The amount of currency to withdraw
                  function take(Currency currency, address to, uint256 amount) external;
                  /// @notice Called by the user to pay what is owed
                  /// @return paid The amount of currency settled
                  function settle() external payable returns (uint256 paid);
                  /// @notice Called by the user to pay on behalf of another address
                  /// @param recipient The address to credit for the payment
                  /// @return paid The amount of currency settled
                  function settleFor(address recipient) external payable returns (uint256 paid);
                  /// @notice WARNING - Any currency that is cleared, will be non-retrievable, and locked in the contract permanently.
                  /// A call to clear will zero out a positive balance WITHOUT a corresponding transfer.
                  /// @dev This could be used to clear a balance that is considered dust.
                  /// Additionally, the amount must be the exact positive balance. This is to enforce that the caller is aware of the amount being cleared.
                  function clear(Currency currency, uint256 amount) external;
                  /// @notice Called by the user to move value into ERC6909 balance
                  /// @param to The address to mint the tokens to
                  /// @param id The currency address to mint to ERC6909s, as a uint256
                  /// @param amount The amount of currency to mint
                  /// @dev The id is converted to a uint160 to correspond to a currency address
                  /// If the upper 12 bytes are not 0, they will be 0-ed out
                  function mint(address to, uint256 id, uint256 amount) external;
                  /// @notice Called by the user to move value from ERC6909 balance
                  /// @param from The address to burn the tokens from
                  /// @param id The currency address to burn from ERC6909s, as a uint256
                  /// @param amount The amount of currency to burn
                  /// @dev The id is converted to a uint160 to correspond to a currency address
                  /// If the upper 12 bytes are not 0, they will be 0-ed out
                  function burn(address from, uint256 id, uint256 amount) external;
                  /// @notice Updates the pools lp fees for the a pool that has enabled dynamic lp fees.
                  /// @dev A swap fee totaling MAX_SWAP_FEE (100%) makes exact output swaps impossible since the input is entirely consumed by the fee
                  /// @param key The key of the pool to update dynamic LP fees for
                  /// @param newDynamicLPFee The new dynamic pool LP fee
                  function updateDynamicLPFee(PoolKey memory key, uint24 newDynamicLPFee) external;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import {CustomRevert} from "./CustomRevert.sol";
              /// @title Safe casting methods
              /// @notice Contains methods for safely casting between types
              library SafeCast {
                  using CustomRevert for bytes4;
                  error SafeCastOverflow();
                  /// @notice Cast a uint256 to a uint160, revert on overflow
                  /// @param x The uint256 to be downcasted
                  /// @return y The downcasted integer, now type uint160
                  function toUint160(uint256 x) internal pure returns (uint160 y) {
                      y = uint160(x);
                      if (y != x) SafeCastOverflow.selector.revertWith();
                  }
                  /// @notice Cast a uint256 to a uint128, revert on overflow
                  /// @param x The uint256 to be downcasted
                  /// @return y The downcasted integer, now type uint128
                  function toUint128(uint256 x) internal pure returns (uint128 y) {
                      y = uint128(x);
                      if (x != y) SafeCastOverflow.selector.revertWith();
                  }
                  /// @notice Cast a int128 to a uint128, revert on overflow or underflow
                  /// @param x The int128 to be casted
                  /// @return y The casted integer, now type uint128
                  function toUint128(int128 x) internal pure returns (uint128 y) {
                      if (x < 0) SafeCastOverflow.selector.revertWith();
                      y = uint128(x);
                  }
                  /// @notice Cast a int256 to a int128, revert on overflow or underflow
                  /// @param x The int256 to be downcasted
                  /// @return y The downcasted integer, now type int128
                  function toInt128(int256 x) internal pure returns (int128 y) {
                      y = int128(x);
                      if (y != x) SafeCastOverflow.selector.revertWith();
                  }
                  /// @notice Cast a uint256 to a int256, revert on overflow
                  /// @param x The uint256 to be casted
                  /// @return y The casted integer, now type int256
                  function toInt256(uint256 x) internal pure returns (int256 y) {
                      y = int256(x);
                      if (y < 0) SafeCastOverflow.selector.revertWith();
                  }
                  /// @notice Cast a uint256 to a int128, revert on overflow
                  /// @param x The uint256 to be downcasted
                  /// @return The downcasted integer, now type int128
                  function toInt128(uint256 x) internal pure returns (int128) {
                      if (x >= 1 << 127) SafeCastOverflow.selector.revertWith();
                      return int128(int256(x));
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              interface IEIP712 {
                  function DOMAIN_SEPARATOR() external view returns (bytes32);
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              /// @notice Interface for claims over a contract balance, wrapped as a ERC6909
              interface IERC6909Claims {
                  /*//////////////////////////////////////////////////////////////
                                               EVENTS
                  //////////////////////////////////////////////////////////////*/
                  event OperatorSet(address indexed owner, address indexed operator, bool approved);
                  event Approval(address indexed owner, address indexed spender, uint256 indexed id, uint256 amount);
                  event Transfer(address caller, address indexed from, address indexed to, uint256 indexed id, uint256 amount);
                  /*//////////////////////////////////////////////////////////////
                                               FUNCTIONS
                  //////////////////////////////////////////////////////////////*/
                  /// @notice Owner balance of an id.
                  /// @param owner The address of the owner.
                  /// @param id The id of the token.
                  /// @return amount The balance of the token.
                  function balanceOf(address owner, uint256 id) external view returns (uint256 amount);
                  /// @notice Spender allowance of an id.
                  /// @param owner The address of the owner.
                  /// @param spender The address of the spender.
                  /// @param id The id of the token.
                  /// @return amount The allowance of the token.
                  function allowance(address owner, address spender, uint256 id) external view returns (uint256 amount);
                  /// @notice Checks if a spender is approved by an owner as an operator
                  /// @param owner The address of the owner.
                  /// @param spender The address of the spender.
                  /// @return approved The approval status.
                  function isOperator(address owner, address spender) external view returns (bool approved);
                  /// @notice Transfers an amount of an id from the caller to a receiver.
                  /// @param receiver The address of the receiver.
                  /// @param id The id of the token.
                  /// @param amount The amount of the token.
                  /// @return bool True, always, unless the function reverts
                  function transfer(address receiver, uint256 id, uint256 amount) external returns (bool);
                  /// @notice Transfers an amount of an id from a sender to a receiver.
                  /// @param sender The address of the sender.
                  /// @param receiver The address of the receiver.
                  /// @param id The id of the token.
                  /// @param amount The amount of the token.
                  /// @return bool True, always, unless the function reverts
                  function transferFrom(address sender, address receiver, uint256 id, uint256 amount) external returns (bool);
                  /// @notice Approves an amount of an id to a spender.
                  /// @param spender The address of the spender.
                  /// @param id The id of the token.
                  /// @param amount The amount of the token.
                  /// @return bool True, always
                  function approve(address spender, uint256 id, uint256 amount) external returns (bool);
                  /// @notice Sets or removes an operator for the caller.
                  /// @param operator The address of the operator.
                  /// @param approved The approval status.
                  /// @return bool True, always
                  function setOperator(address operator, bool approved) external returns (bool);
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import {Currency} from "../types/Currency.sol";
              import {PoolId} from "../types/PoolId.sol";
              import {PoolKey} from "../types/PoolKey.sol";
              /// @notice Interface for all protocol-fee related functions in the pool manager
              interface IProtocolFees {
                  /// @notice Thrown when protocol fee is set too high
                  error ProtocolFeeTooLarge(uint24 fee);
                  /// @notice Thrown when collectProtocolFees or setProtocolFee is not called by the controller.
                  error InvalidCaller();
                  /// @notice Thrown when collectProtocolFees is attempted on a token that is synced.
                  error ProtocolFeeCurrencySynced();
                  /// @notice Emitted when the protocol fee controller address is updated in setProtocolFeeController.
                  event ProtocolFeeControllerUpdated(address indexed protocolFeeController);
                  /// @notice Emitted when the protocol fee is updated for a pool.
                  event ProtocolFeeUpdated(PoolId indexed id, uint24 protocolFee);
                  /// @notice Given a currency address, returns the protocol fees accrued in that currency
                  /// @param currency The currency to check
                  /// @return amount The amount of protocol fees accrued in the currency
                  function protocolFeesAccrued(Currency currency) external view returns (uint256 amount);
                  /// @notice Sets the protocol fee for the given pool
                  /// @param key The key of the pool to set a protocol fee for
                  /// @param newProtocolFee The fee to set
                  function setProtocolFee(PoolKey memory key, uint24 newProtocolFee) external;
                  /// @notice Sets the protocol fee controller
                  /// @param controller The new protocol fee controller
                  function setProtocolFeeController(address controller) external;
                  /// @notice Collects the protocol fees for a given recipient and currency, returning the amount collected
                  /// @dev This will revert if the contract is unlocked
                  /// @param recipient The address to receive the protocol fees
                  /// @param currency The currency to withdraw
                  /// @param amount The amount of currency to withdraw
                  /// @return amountCollected The amount of currency successfully withdrawn
                  function collectProtocolFees(address recipient, Currency currency, uint256 amount)
                      external
                      returns (uint256 amountCollected);
                  /// @notice Returns the current protocol fee controller address
                  /// @return address The current protocol fee controller address
                  function protocolFeeController() external view returns (address);
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              /// @notice Interface for functions to access any storage slot in a contract
              interface IExtsload {
                  /// @notice Called by external contracts to access granular pool state
                  /// @param slot Key of slot to sload
                  /// @return value The value of the slot as bytes32
                  function extsload(bytes32 slot) external view returns (bytes32 value);
                  /// @notice Called by external contracts to access granular pool state
                  /// @param startSlot Key of slot to start sloading from
                  /// @param nSlots Number of slots to load into return value
                  /// @return values List of loaded values.
                  function extsload(bytes32 startSlot, uint256 nSlots) external view returns (bytes32[] memory values);
                  /// @notice Called by external contracts to access sparse pool state
                  /// @param slots List of slots to SLOAD from.
                  /// @return values List of loaded values.
                  function extsload(bytes32[] calldata slots) external view returns (bytes32[] memory values);
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.24;
              /// @notice Interface for functions to access any transient storage slot in a contract
              interface IExttload {
                  /// @notice Called by external contracts to access transient storage of the contract
                  /// @param slot Key of slot to tload
                  /// @return value The value of the slot as bytes32
                  function exttload(bytes32 slot) external view returns (bytes32 value);
                  /// @notice Called by external contracts to access sparse transient pool state
                  /// @param slots List of slots to tload
                  /// @return values List of loaded values
                  function exttload(bytes32[] calldata slots) external view returns (bytes32[] memory values);
              }