ETH Price: $1,930.00 (-4.20%)

Transaction Decoder

Block:
23712309 at Nov-02-2025 02:43:11 PM +UTC
Transaction Fee:
0.000118994618106875 ETH $0.23
Gas Used:
141,175 Gas / 0.842887325 Gwei

Emitted Events:

92 0x13239c268beddd88ad0cb02050d3ff6a9d00de6d.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x000000000000000000000000ccbd08746816de921ca91611d277a90ab083bc7a, 0x00000000000000000000000005b50fea12da0635f83696cb403439b04f0e0c55, 00000000000000000000000000000000000000000000003df64fbc205f7c0000 )

Account State Difference:

  Address   Before After State Difference Code
0x05B50fea...04F0e0C55
0.018464046392644921 Eth
Nonce: 0
0.018295051774538046 Eth
Nonce: 1
0.000168994618106875
0x13239C26...a9d00de6D
3.262112393169445917 Eth3.262182980669445917 Eth0.0000705875
0x2c6f1e10...A464633b9 26.68382 Eth26.68387 Eth0.00005
0xCCBd0874...AB083bc7A

Execution Trace

ETH 0.00005 0xccbd08746816de921ca91611d277a90ab083bc7a.69659658( )
  • ETH 0.00005 0x85a530f3c0be16e895cfc935350480afb7215b4b.69659658( )
    • Null: 0x000...001.a9dc6783( )
    • BOS: BOS Token.a9059cbb( )
      • 0xba4f5ddf715b95abf861604a83a046be7b16df49.a9059cbb( )
      • 0xed09c23677d02f4f486e55f0f081cfc114eea53b.cb5cd391( )
      • 0xed09c23677d02f4f486e55f0f081cfc114eea53b.16605a0d( )
        • TTFeeCollector.getFeeToken( unlockerAddress=0xCCBd08746816DE921Ca91611D277A90AB083bc7A ) => ( tokenAddress=0x0000000000000000000000000000000000000000 )
        • ETH 0.00005 TTFeeCollector.CALL( )
          // SPDX-License-Identifier: AGPL v3
          pragma solidity ^0.8.20;
          import { ITTFeeCollector } from "../interfaces/ITTFeeCollector.sol";
          import { Ownable } from "solady/auth/Ownable.sol";
          import { IERC20 } from "@openzeppelin-contracts/interfaces/IERC20.sol";
          import { SafeERC20 } from "@openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
          import { Initializable } from "solady/utils/Initializable.sol";
          import { ITTCreate3Initializable } from "../interfaces/ITTCreate3Initializable.sol";
          contract TTFeeCollector is ITTCreate3Initializable, ITTFeeCollector, Ownable, Initializable {
              using SafeERC20 for IERC20;
              uint256 public constant BIPS_PRECISION = 10 ** 4;
              uint256 public constant MAX_FEE_BIPS = 10 ** 3;
              uint256 public defaultFee; // Default fee is nothing is set
              address public defaultFeeToken;
              mapping(address => address) public customFeeTokens;
              mapping(address => uint256) internal _customFeesBips;
              mapping(address => uint256) internal _customFeesFixed;
              function initialize(address owner) external initializer {
                  _initializeOwner(owner);
              }
              receive() external payable { }
              function withdrawFee(IERC20 token, uint256 amount) external onlyOwner {
                  if (address(token) == address(0)) {
                      (bool success, bytes memory data) = owner().call{ value: amount }("");
                      // solhint-disable-next-line gas-custom-errors
                      require(success, string(data));
                  } else {
                      token.safeTransfer(owner(), amount);
                  }
              }
              function setDefaultFee(uint256 fee) external onlyOwner {
                  defaultFee = fee;
                  emit DefaultFeeSet(fee);
              }
              /// @dev Setting bips to MAX_FEE_BIPS means 0 fees, so technically the MAX_FEE_BIPS is MAX_FEE_BIPS - 1
              function setCustomFeeBips(address unlockerAddress, uint256 bips) external onlyOwner {
                  if (bips > MAX_FEE_BIPS) revert FeesTooHigh();
                  _customFeesBips[unlockerAddress] = bips;
                  emit CustomFeeSetBips(unlockerAddress, bips);
              }
              function setCustomFeeFixed(address unlockerAddress, uint256 fixedFee) external onlyOwner {
                  /// Not capping a MAX_FEE for fixed fee since it cannot apply equally to all token values
                  _customFeesFixed[unlockerAddress] = fixedFee;
                  emit CustomFeeSetFixed(unlockerAddress, fixedFee);
              }
              function setDefaultFeeToken(address tokenAddress) external onlyOwner {
                  defaultFeeToken = tokenAddress;
              }
              function setCustomFeeToken(address unlockerAddress, address tokenAddress) external onlyOwner {
                  customFeeTokens[unlockerAddress] = tokenAddress;
              }
              function getFee(
                  address unlockerAddress,
                  uint256 tokenTransferred
              )
                  external
                  view
                  override
                  returns (uint256 tokensCollected)
              {
                  if (tokenTransferred == 0) return 0;
                  uint256 feeFixed = _customFeesFixed[unlockerAddress];
                  // If there is a fixed fee, return that fee immediately without checking bips fee
                  if (feeFixed > 0) {
                      return feeFixed;
                  }
                  uint256 feeBips = _customFeesBips[unlockerAddress];
                  // If there is no fixed custom fee or custom fee bips, then return default fee
                  if (feeBips == 0) {
                      return defaultFee;
                  } else if (feeBips == MAX_FEE_BIPS) {
                      feeBips = 0;
                  }
                  tokensCollected = (tokenTransferred * feeBips) / BIPS_PRECISION;
              }
              function getFeeToken(address unlockerAddress) external view override returns (address tokenAddress) {
                  tokenAddress = customFeeTokens[unlockerAddress];
                  if (tokenAddress == address(0)) {
                      tokenAddress = defaultFeeToken;
                  }
              }
              function version() external pure returns (string memory) {
                  return "2.7.1-TTFeeCollector";
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.20;
          /**
           * @title ITTFeeCollector
           * @author Jack Xu @ Sign
           * @dev This contract handles TokenTable service fee calculation.
           */
          interface ITTFeeCollector {
              event DefaultFeeSetBips(uint256 bips);
              event DefaultFeeSet(uint256 fee);
              event CustomFeeSetBips(address unlockerAddress, uint256 bips);
              event CustomFeeSetFixed(address unlockerAddress, uint256 fixedFee);
              /**
               * @dev 0xc9034e18
               */
              error FeesTooHigh();
              /**
               * @notice Returns the amount of fees to collect. A fixed fee will always override a dynamic fee.
               * @param unlockerAddress The address of the Unlocker (or any contract that uses the fee collector). Used to fetch
               * pricing.
               * @param tokenTransferred The number of tokens transferred.
               * @return tokensCollected The number of tokens to collect as fees.
               */
              function getFee(
                  address unlockerAddress,
                  uint256 tokenTransferred
              )
                  external
                  view
                  returns (uint256 tokensCollected);
              /**
               * @notice Returns the fee token address.
               * @param unlockerAddress The address of the Unlocker (or any contract that uses the fee collector). Used to fetch
               * pricing.
               */
              function getFeeToken(address unlockerAddress) external view returns (address tokenAddress);
          }
          // 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
          // OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)
          pragma solidity ^0.8.20;
          import {IERC20} from "../token/ERC20/IERC20.sol";
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)
          pragma solidity ^0.8.20;
          import {IERC20} from "../IERC20.sol";
          import {IERC1363} from "../../../interfaces/IERC1363.sol";
          /**
           * @title SafeERC20
           * @dev Wrappers around ERC-20 operations that throw on failure (when the token
           * contract returns false). Tokens that return no value (and instead revert or
           * throw on failure) are also supported, non-reverting calls are assumed to be
           * successful.
           * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
           * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
           */
          library SafeERC20 {
              /**
               * @dev An operation with an ERC-20 token failed.
               */
              error SafeERC20FailedOperation(address token);
              /**
               * @dev Indicates a failed `decreaseAllowance` request.
               */
              error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
              /**
               * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
               * non-reverting calls are assumed to be successful.
               */
              function safeTransfer(IERC20 token, address to, uint256 value) internal {
                  _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
              }
              /**
               * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
               * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
               */
              function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
                  _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
              }
              /**
               * @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
               */
              function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
                  return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
              }
              /**
               * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
               */
              function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
                  return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));
              }
              /**
               * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
               * non-reverting calls are assumed to be successful.
               *
               * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
               * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
               * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
               * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
               */
              function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                  uint256 oldAllowance = token.allowance(address(this), spender);
                  forceApprove(token, spender, oldAllowance + value);
              }
              /**
               * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
               * value, non-reverting calls are assumed to be successful.
               *
               * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
               * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
               * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
               * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
               */
              function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
                  unchecked {
                      uint256 currentAllowance = token.allowance(address(this), spender);
                      if (currentAllowance < requestedDecrease) {
                          revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
                      }
                      forceApprove(token, spender, currentAllowance - requestedDecrease);
                  }
              }
              /**
               * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
               * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
               * to be set to zero before setting it to a non-zero value, such as USDT.
               *
               * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
               * only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
               * set here.
               */
              function forceApprove(IERC20 token, address spender, uint256 value) internal {
                  bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
                  if (!_callOptionalReturnBool(token, approvalCall)) {
                      _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
                      _callOptionalReturn(token, approvalCall);
                  }
              }
              /**
               * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
               * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
               * targeting contracts.
               *
               * Reverts if the returned value is other than `true`.
               */
              function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
                  if (to.code.length == 0) {
                      safeTransfer(token, to, value);
                  } else if (!token.transferAndCall(to, value, data)) {
                      revert SafeERC20FailedOperation(address(token));
                  }
              }
              /**
               * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
               * has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
               * targeting contracts.
               *
               * Reverts if the returned value is other than `true`.
               */
              function transferFromAndCallRelaxed(
                  IERC1363 token,
                  address from,
                  address to,
                  uint256 value,
                  bytes memory data
              ) internal {
                  if (to.code.length == 0) {
                      safeTransferFrom(token, from, to, value);
                  } else if (!token.transferFromAndCall(from, to, value, data)) {
                      revert SafeERC20FailedOperation(address(token));
                  }
              }
              /**
               * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
               * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
               * targeting contracts.
               *
               * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
               * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
               * once without retrying, and relies on the returned value to be true.
               *
               * Reverts if the returned value is other than `true`.
               */
              function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
                  if (to.code.length == 0) {
                      forceApprove(token, to, value);
                  } else if (!token.approveAndCall(to, value, data)) {
                      revert SafeERC20FailedOperation(address(token));
                  }
              }
              /**
               * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
               * on the return value: the return value is optional (but if data is returned, it must not be false).
               * @param token The token targeted by the call.
               * @param data The call data (encoded using abi.encode or one of its variants).
               *
               * This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
               */
              function _callOptionalReturn(IERC20 token, bytes memory data) private {
                  uint256 returnSize;
                  uint256 returnValue;
                  assembly ("memory-safe") {
                      let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
                      // bubble errors
                      if iszero(success) {
                          let ptr := mload(0x40)
                          returndatacopy(ptr, 0, returndatasize())
                          revert(ptr, returndatasize())
                      }
                      returnSize := returndatasize()
                      returnValue := mload(0)
                  }
                  if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
                      revert SafeERC20FailedOperation(address(token));
                  }
              }
              /**
               * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
               * on the return value: the return value is optional (but if data is returned, it must not be false).
               * @param token The token targeted by the call.
               * @param data The call data (encoded using abi.encode or one of its variants).
               *
               * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
               */
              function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
                  bool success;
                  uint256 returnSize;
                  uint256 returnValue;
                  assembly ("memory-safe") {
                      success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
                      returnSize := returndatasize()
                      returnValue := mload(0)
                  }
                  return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.4;
          /// @notice Initializable mixin for the upgradeable contracts.
          /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/Initializable.sol)
          /// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/proxy/utils/Initializable.sol)
          abstract contract Initializable {
              /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
              /*                       CUSTOM ERRORS                        */
              /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
              /// @dev The contract is already initialized.
              error InvalidInitialization();
              /// @dev The contract is not initializing.
              error NotInitializing();
              /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
              /*                           EVENTS                           */
              /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
              /// @dev Triggered when the contract has been initialized.
              event Initialized(uint64 version);
              /// @dev `keccak256(bytes("Initialized(uint64)"))`.
              bytes32 private constant _INITIALIZED_EVENT_SIGNATURE =
                  0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2;
              /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
              /*                          STORAGE                           */
              /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
              /// @dev The default initializable slot is given by:
              /// `bytes32(~uint256(uint32(bytes4(keccak256("_INITIALIZABLE_SLOT")))))`.
              ///
              /// Bits Layout:
              /// - [0]     `initializing`
              /// - [1..64] `initializedVersion`
              bytes32 private constant _INITIALIZABLE_SLOT =
                  0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffbf601132;
              /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
              /*                        CONSTRUCTOR                         */
              /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
              constructor() {
                  // Construction time check to ensure that `_initializableSlot()` is not
                  // overridden to zero. Will be optimized away if there is no revert.
                  require(_initializableSlot() != bytes32(0));
              }
              /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
              /*                         OPERATIONS                         */
              /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
              /// @dev Override to return a non-zero custom storage slot if required.
              function _initializableSlot() internal pure virtual returns (bytes32) {
                  return _INITIALIZABLE_SLOT;
              }
              /// @dev Guards an initializer function so that it can be invoked at most once.
              ///
              /// You can guard a function with `onlyInitializing` such that it can be called
              /// through a function guarded with `initializer`.
              ///
              /// This is similar to `reinitializer(1)`, except that in the context of a constructor,
              /// an `initializer` guarded function can be invoked multiple times.
              /// This can be useful during testing and is not expected to be used in production.
              ///
              /// Emits an {Initialized} event.
              modifier initializer() virtual {
                  bytes32 s = _initializableSlot();
                  /// @solidity memory-safe-assembly
                  assembly {
                      let i := sload(s)
                      // Set `initializing` to 1, `initializedVersion` to 1.
                      sstore(s, 3)
                      // If `!(initializing == 0 && initializedVersion == 0)`.
                      if i {
                          // If `!(address(this).code.length == 0 && initializedVersion == 1)`.
                          if iszero(lt(extcodesize(address()), eq(shr(1, i), 1))) {
                              mstore(0x00, 0xf92ee8a9) // `InvalidInitialization()`.
                              revert(0x1c, 0x04)
                          }
                          s := shl(shl(255, i), s) // Skip initializing if `initializing == 1`.
                      }
                  }
                  _;
                  /// @solidity memory-safe-assembly
                  assembly {
                      if s {
                          // Set `initializing` to 0, `initializedVersion` to 1.
                          sstore(s, 2)
                          // Emit the {Initialized} event.
                          mstore(0x20, 1)
                          log1(0x20, 0x20, _INITIALIZED_EVENT_SIGNATURE)
                      }
                  }
              }
              /// @dev Guards a reinitializer function so that it can be invoked at most once.
              ///
              /// You can guard a function with `onlyInitializing` such that it can be called
              /// through a function guarded with `reinitializer`.
              ///
              /// Emits an {Initialized} event.
              modifier reinitializer(uint64 version) virtual {
                  bytes32 s = _initializableSlot();
                  /// @solidity memory-safe-assembly
                  assembly {
                      // Clean upper bits, and shift left by 1 to make space for the initializing bit.
                      version := shl(1, and(version, 0xffffffffffffffff))
                      let i := sload(s)
                      // If `initializing == 1 || initializedVersion >= version`.
                      if iszero(lt(and(i, 1), lt(i, version))) {
                          mstore(0x00, 0xf92ee8a9) // `InvalidInitialization()`.
                          revert(0x1c, 0x04)
                      }
                      // Set `initializing` to 1, `initializedVersion` to `version`.
                      sstore(s, or(1, version))
                  }
                  _;
                  /// @solidity memory-safe-assembly
                  assembly {
                      // Set `initializing` to 0, `initializedVersion` to `version`.
                      sstore(s, version)
                      // Emit the {Initialized} event.
                      mstore(0x20, shr(1, version))
                      log1(0x20, 0x20, _INITIALIZED_EVENT_SIGNATURE)
                  }
              }
              /// @dev Guards a function such that it can only be called in the scope
              /// of a function guarded with `initializer` or `reinitializer`.
              modifier onlyInitializing() virtual {
                  _checkInitializing();
                  _;
              }
              /// @dev Reverts if the contract is not initializing.
              function _checkInitializing() internal view virtual {
                  bytes32 s = _initializableSlot();
                  /// @solidity memory-safe-assembly
                  assembly {
                      if iszero(and(1, sload(s))) {
                          mstore(0x00, 0xd7e6bcf8) // `NotInitializing()`.
                          revert(0x1c, 0x04)
                      }
                  }
              }
              /// @dev Locks any future initializations by setting the initialized version to `2**64 - 1`.
              ///
              /// Calling this in the constructor will prevent the contract from being initialized
              /// or reinitialized. It is recommended to use this to lock implementation contracts
              /// that are designed to be called through proxies.
              ///
              /// Emits an {Initialized} event the first time it is successfully called.
              function _disableInitializers() internal virtual {
                  bytes32 s = _initializableSlot();
                  /// @solidity memory-safe-assembly
                  assembly {
                      let i := sload(s)
                      if and(i, 1) {
                          mstore(0x00, 0xf92ee8a9) // `InvalidInitialization()`.
                          revert(0x1c, 0x04)
                      }
                      let uint64max := 0xffffffffffffffff
                      if iszero(eq(shr(1, i), uint64max)) {
                          // Set `initializing` to 0, `initializedVersion` to `2**64 - 1`.
                          sstore(s, shl(1, uint64max))
                          // Emit the {Initialized} event.
                          mstore(0x20, uint64max)
                          log1(0x20, 0x20, _INITIALIZED_EVENT_SIGNATURE)
                      }
                  }
              }
              /// @dev Returns the highest version that has been initialized.
              function _getInitializedVersion() internal view virtual returns (uint64 version) {
                  bytes32 s = _initializableSlot();
                  /// @solidity memory-safe-assembly
                  assembly {
                      version := shr(1, sload(s))
                  }
              }
              /// @dev Returns whether the contract is currently initializing.
              function _isInitializing() internal view virtual returns (bool result) {
                  bytes32 s = _initializableSlot();
                  /// @solidity memory-safe-assembly
                  assembly {
                      result := and(1, sload(s))
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.20;
          /**
           * @title ITTCreate3Initializable
           * @author Jack Xu @ Sign
           * @dev This interface enforces an initializer for contracts that are directly deployed by Create3.
           */
          interface ITTCreate3Initializable {
              function initialize(address owner) external;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)
          pragma solidity ^0.8.20;
          /**
           * @dev Interface of the ERC-20 standard as defined in the ERC.
           */
          interface IERC20 {
              /**
               * @dev Emitted when `value` tokens are moved from one account (`from`) to
               * another (`to`).
               *
               * Note that `value` may be zero.
               */
              event Transfer(address indexed from, address indexed to, uint256 value);
              /**
               * @dev Emitted when the allowance of a `spender` for an `owner` is set by
               * a call to {approve}. `value` is the new allowance.
               */
              event Approval(address indexed owner, address indexed spender, uint256 value);
              /**
               * @dev Returns the value of tokens in existence.
               */
              function totalSupply() external view returns (uint256);
              /**
               * @dev Returns the value of tokens owned by `account`.
               */
              function balanceOf(address account) external view returns (uint256);
              /**
               * @dev Moves a `value` amount of tokens from the caller's account to `to`.
               *
               * Returns a boolean value indicating whether the operation succeeded.
               *
               * Emits a {Transfer} event.
               */
              function transfer(address to, uint256 value) external returns (bool);
              /**
               * @dev Returns the remaining number of tokens that `spender` will be
               * allowed to spend on behalf of `owner` through {transferFrom}. This is
               * zero by default.
               *
               * This value changes when {approve} or {transferFrom} are called.
               */
              function allowance(address owner, address spender) external view returns (uint256);
              /**
               * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
               * caller's tokens.
               *
               * Returns a boolean value indicating whether the operation succeeded.
               *
               * IMPORTANT: Beware that changing an allowance with this method brings the risk
               * that someone may use both the old and the new allowance by unfortunate
               * transaction ordering. One possible solution to mitigate this race
               * condition is to first reduce the spender's allowance to 0 and set the
               * desired value afterwards:
               * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
               *
               * Emits an {Approval} event.
               */
              function approve(address spender, uint256 value) external returns (bool);
              /**
               * @dev Moves a `value` amount of tokens from `from` to `to` using the
               * allowance mechanism. `value` is then deducted from the caller's
               * allowance.
               *
               * Returns a boolean value indicating whether the operation succeeded.
               *
               * Emits a {Transfer} event.
               */
              function transferFrom(address from, address to, uint256 value) external returns (bool);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1363.sol)
          pragma solidity ^0.8.20;
          import {IERC20} from "./IERC20.sol";
          import {IERC165} from "./IERC165.sol";
          /**
           * @title IERC1363
           * @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
           *
           * Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
           * after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
           */
          interface IERC1363 is IERC20, IERC165 {
              /*
               * Note: the ERC-165 identifier for this interface is 0xb0202a11.
               * 0xb0202a11 ===
               *   bytes4(keccak256('transferAndCall(address,uint256)')) ^
               *   bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
               *   bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
               *   bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
               *   bytes4(keccak256('approveAndCall(address,uint256)')) ^
               *   bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
               */
              /**
               * @dev Moves a `value` amount of tokens from the caller's account to `to`
               * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
               * @param to The address which you want to transfer to.
               * @param value The amount of tokens to be transferred.
               * @return A boolean value indicating whether the operation succeeded unless throwing.
               */
              function transferAndCall(address to, uint256 value) external returns (bool);
              /**
               * @dev Moves a `value` amount of tokens from the caller's account to `to`
               * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
               * @param to The address which you want to transfer to.
               * @param value The amount of tokens to be transferred.
               * @param data Additional data with no specified format, sent in call to `to`.
               * @return A boolean value indicating whether the operation succeeded unless throwing.
               */
              function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);
              /**
               * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
               * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
               * @param from The address which you want to send tokens from.
               * @param to The address which you want to transfer to.
               * @param value The amount of tokens to be transferred.
               * @return A boolean value indicating whether the operation succeeded unless throwing.
               */
              function transferFromAndCall(address from, address to, uint256 value) external returns (bool);
              /**
               * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
               * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
               * @param from The address which you want to send tokens from.
               * @param to The address which you want to transfer to.
               * @param value The amount of tokens to be transferred.
               * @param data Additional data with no specified format, sent in call to `to`.
               * @return A boolean value indicating whether the operation succeeded unless throwing.
               */
              function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);
              /**
               * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
               * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
               * @param spender The address which will spend the funds.
               * @param value The amount of tokens to be spent.
               * @return A boolean value indicating whether the operation succeeded unless throwing.
               */
              function approveAndCall(address spender, uint256 value) external returns (bool);
              /**
               * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
               * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
               * @param spender The address which will spend the funds.
               * @param value The amount of tokens to be spent.
               * @param data Additional data with no specified format, sent in call to `spender`.
               * @return A boolean value indicating whether the operation succeeded unless throwing.
               */
              function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol)
          pragma solidity ^0.8.20;
          import {IERC165} from "../utils/introspection/IERC165.sol";
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)
          pragma solidity ^0.8.20;
          /**
           * @dev Interface of the ERC-165 standard, as defined in the
           * https://eips.ethereum.org/EIPS/eip-165[ERC].
           *
           * Implementers can declare support of contract interfaces, which can then be
           * queried by others ({ERC165Checker}).
           *
           * For an implementation, see {ERC165}.
           */
          interface IERC165 {
              /**
               * @dev Returns true if this contract implements the interface defined by
               * `interfaceId`. See the corresponding
               * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
               * to learn more about how these ids are created.
               *
               * This function call must use less than 30 000 gas.
               */
              function supportsInterface(bytes4 interfaceId) external view returns (bool);
          }