Clanker

0xd3f2cc1731b7fd17f28798835c2e02f0a1839a94

Verification
Verified
v0.8.28+commit.7893614a
Type
Contract
12,070 bytes
ABI entries
55
11 read · 11 write
License
none

Contract information

Address
0xd3f2cc1731b7fd17f28798835c2e02f0a1839a94
Chain
Robinhood Chain (4663)
Compiler
v0.8.28+commit.7893614a
Optimization
Enabled
Creation tx
0x28b5e185a9…433ae9892f

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (11)

BPS()uint256
MAX_EXTENSIONS()uint256
MAX_EXTENSION_BPS()uint16
TOKEN_SUPPLY()uint256
admins(address)bool
deploymentInfoForToken(address)address, address, address
deprecated()bool
enabledLockers(address, address)bool
owner()address
teamFeeRecipient()address
tokenDeploymentInfo(address)tuple

Events (11)

ClaimTeamFeesExtensionTriggeredOwnershipTransferredSetAdminSetDeprecatedSetExtensionSetHookSetLockerSetMevModuleSetTeamFeeRecipientTokenCreated

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner_",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "Deprecated",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ExtensionMsgValueMismatch",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ExtensionNotEnabled",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "HookNotEnabled",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidExtension",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidHook",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidLocker",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidMevModule",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "LockerNotEnabled",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "MaxExtensionBpsExceeded",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "MaxExtensionsExceeded",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "MevModuleNotEnabled",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotFound",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "OnlyNonOriginatingChains",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "OnlyOriginatingChain",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      }
    ],
    "name": "OwnableInvalidOwner",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "OwnableUnauthorizedAccount",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ReentrancyGuardReentrantCall",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "SafeERC20FailedOperation",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TeamFeeRecipientNotSet",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "Unauthorized",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "ClaimTeamFees",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "extension",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "extensionSupply",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "msgValue",
        "type": "uint256"
      }
    ],
    "name": "ExtensionTriggered",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferred",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "admin",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "enabled",
        "type": "bool"
      }
    ],
    "name": "SetAdmin",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "bool",
        "name": "deprecated",
        "type": "bool"
      }
    ],
    "name": "SetDeprecated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "extension",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "enabled",
        "type": "bool"
      }
    ],
    "name": "SetExtension",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "hook",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "enabled",
        "type": "bool"
      }
    ],
    "name": "SetHook",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "locker",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "hook",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "enabled",
        "type": "bool"
      }
    ],
    "name": "SetLocker",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "mevModule",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "enabled",
        "type": "bool"
      }
    ],
    "name": "SetMevModule",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "oldTeamFeeRecipient",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "newTeamFeeRecipient",
        "type": "address"
      }
    ],
    "name": "SetTeamFeeRecipient",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "msgSender",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "tokenAddress",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "tokenAdmin",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "tokenImage",
        "type": "string"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "tokenName",
        "type": "string"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "tokenSymbol",
        "type": "string"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "tokenMetadata",
        "type": "string"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "tokenContext",
        "type": "string"
      },
      {
        "indexed": false,
        "internalType": "int24",
        "name": "startingTick",
        "type": "int24"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "poolHook",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "PoolId",
        "name": "poolId",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "pairedToken",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "locker",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "mevModule",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "extensionsSupply",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "address[]",
        "name": "extensions",
        "type": "address[]"
      }
    ],
    "name": "TokenCreated",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "BPS",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_EXTENSIONS",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_EXTENSION_BPS",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "TOKEN_SUPPLY",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "admins",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "claimTeamFees",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "components": [
          {
            "components": [
              {
                "internalType": "address",
                "name": "tokenAdmin",
                "type": "address"
              },
              {
                "internalType": "string",
                "name": "name",
                "type": "string"
              },
              {
                "internalType": "string",
                "name": "symbol",
                "type": "string"
              },
              {
                "internalType": "bytes32",
                "name": "salt",
                "type": "bytes32"
              },
              {
                "internalType": "string",
                "name": "image",
                "type": "string"
              },
              {
                "internalType": "string",
                "name": "metadata",
                "type": "string"
              },
              {
                "internalType": "string",
                "name": "context",
                "type": "string"
              },
              {
                "internalType": "uint256",
                "name": "originatingChainId",
                "type": "uint256"
              }
            ],
            "internalType": "struct IClanker.TokenConfig",
            "name": "tokenConfig",
            "type": "tuple"
          },
          {
            "components": [
              {
                "internalType": "address",
                "name": "hook",
                "type": "address"
              },
              {
                "internalType": "address",
                "name": "pairedToken",
                "type": "address"
              },
              {
                "internalType": "int24",
                "name": "tickIfToken0IsClanker",
                "type": "int24"
              },
              {
                "internalType": "int24",
                "name": "tickSpacing",
                "type": "int24"
              },
              {
                "internalType": "bytes",
                "name": "poolData",
                "type": "bytes"
              }
            ],
            "internalType": "struct IClanker.PoolConfig",
            "name": "poolConfig",
            "type": "tuple"
          },
          {
            "components": [
              {
                "internalType": "address",
                "name": "locker",
                "type": "address"
              },
              {
                "internalType": "address[]",
                "name": "rewardAdmins",
                "type": "address[]"
              },
              {
                "internalType": "address[]",
                "name": "rewardRecipients",
                "type": "address[]"
              },
              {
                "internalType": "uint16[]",
                "name": "rewardBps",
                "type": "uint16[]"
              },
              {
                "internalType": "int24[]",
                "name": "tickLower",
                "type": "int24[]"
              },
              {
                "internalType": "int24[]",
                "name": "tickUpper",
                "type": "int24[]"
              },
              {
                "internalType": "uint16[]",
                "name": "positionBps",
                "type": "uint16[]"
              },
              {
                "internalType": "bytes",
                "name": "lockerData",
                "type": "bytes"
              }
            ],
            "internalType": "struct IClanker.LockerConfig",
            "name": "lockerConfig",
            "type": "tuple"
          },
          {
            "components": [
              {
                "internalType": "address",
                "name": "mevModule",
                "type": "address"
              },
              {
                "internalType": "bytes",
                "name": "mevModuleData",
                "type": "bytes"
              }
            ],
            "internalType": "struct IClanker.MevModuleConfig",
            "name": "mevModuleConfig",
            "type": "tuple"
          },
          {
            "components": [
              {
                "internalType": "address",
                "name": "extension",
                "type": "address"
              },
              {
                "internalType": "uint256",
                "name": "msgValue",
                "type": "uint256"
              },
              {
                "internalType": "uint16",
                "name": "extensionBps",
                "type": "uint16"
              },
              {
                "internalType": "bytes",
                "name": "extensionData",
                "type": "bytes"
              }
            ],
            "internalType": "struct IClanker.ExtensionConfig[]",
            "name": "extensionConfigs",
            "type": "tuple[]"
          }
        ],
        "internalType": "struct IClanker.DeploymentConfig",
        "name": "deploymentConfig",
        "type": "tuple"
      }
    ],
    "name": "deployToken",
    "outputs": [
      {
        "internalType": "address",
        "name": "tokenAddress",
        "type": "address"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "deploymentInfoForToken",
    "outputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "hook",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "locker",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "deprecated",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "locker",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "hook",
        "type": "address"
      }
    ],
    "name": "enabledLockers",
    "outputs": [
      {
        "internalType": "bool",
        "name": "enabled",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "admin",
        "type": "address"
      },
      {
        "internalType": "bool",
        "name": "enabled",
        "type": "bool"
      }
    ],
    "name": "setAdmin",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bool",
        "name": "deprecated_",
        "type": "bool"
      }
    ],
    "name": "setDeprecated",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "extension",
        "type": "address"
      },
      {
        "internalType": "bool",
        "name": "enabled",
        "type": "bool"
      }
    ],
    "name": "setExtension",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "hook",
        "type": "address"
      },
      {
        "internalType": "bool",
        "name": "enabled",
        "type": "bool"
      }
    ],
    "name": "setHook",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "locker",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "hook",
        "type": "address"
      },
      {
        "internalType": "bool",
        "name": "enabled",
        "type": "bool"
      }
    ],
    "name": "setLocker",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "mevModule",
        "type": "address"
      },
      {
        "internalType": "bool",
        "name": "enabled",
        "type": "bool"
      }
    ],
    "name": "setMevModule",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "teamFeeRecipient_",
        "type": "address"
      }
    ],
    "name": "setTeamFeeRecipient",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "teamFeeRecipient",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "tokenDeploymentInfo",
    "outputs": [
      {
        "components": [
          {
            "internalType": "address",
            "name": "token",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "hook",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "locker",
            "type": "address"
          },
          {
            "internalType": "address[]",
            "name": "extensions",
            "type": "address[]"
          }
        ],
        "internalType": "struct IClanker.DeploymentInfo",
        "name": "",
        "type": "tuple"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  }
]

Source code

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {ClankerDeployer} from "./utils/ClankerDeployer.sol";
import {OwnerAdmins} from "./utils/OwnerAdmins.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import {IClanker} from "./interfaces/IClanker.sol";
import {IClankerExtension} from "./interfaces/IClankerExtension.sol";
import {IClankerHook} from "./interfaces/IClankerHook.sol";
import {IClankerLpLocker} from "./interfaces/IClankerLpLocker.sol";
import {IClankerMevModule} from "./interfaces/IClankerMevModule.sol";

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
/*
 .--..--..--..--..--..--..--..--..--..--..--..--..--..--..--..--..--..--..--..--..--..--..--..--. 
/ .. \.. \.. \.. \.. \.. \.. \.. \.. \.. \.. \.. \.. \.. \.. \.. \.. \.. \.. \.. \.. \.. \.. \.. \
\ \/\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ \/ /
 \/ /`--'`--'`--'`--'`--'`--'`--'`--'`--'`--'`--'`--'`--'`--'`--'`--'`--'`--'`--'`--'`--'`--'\/ / 
 / /\  ````````````````````````````````````````````````````````````````````````````````````  / /\ 
/ /\ \ ```````````````````````````````````````````````````````````````````````````````````` / /\ \
\ \/ / ```````::::::::``:::````````````:::`````::::````:::`:::````:::`::::::::::`:::::::::` \ \/ /
 \/ /  `````:+:````:+:`:+:``````````:+:`:+:```:+:+:```:+:`:+:```:+:``:+:````````:+:````:+:`  \/ / 
 / /\  ````+:+````````+:+`````````+:+```+:+``:+:+:+``+:+`+:+``+:+```+:+````````+:+````+:+``  / /\ 
/ /\ \ ```+#+````````+#+````````+#++:++#++:`+#+`+:+`+#+`+#++:++````+#++:++#```+#++:++#:```` / /\ \
\ \/ / ``+#+````````+#+````````+#+`````+#+`+#+``+#+#+#`+#+``+#+```+#+````````+#+````+#+```` \ \/ /
 \/ /  `#+#````#+#`#+#````````#+#`````#+#`#+#```#+#+#`#+#```#+#``#+#````````#+#````#+#`````  \/ / 
 / /\  `########``##########`###`````###`###````####`###````###`##########`###````###``````  / /\ 
/ /\ \ ```````````````````````````````````````````````````````````````````````````````````` / /\ \
\ \/ / ```````````````````````````````````````````````````````````````````````````````````` \ \/ /
 \/ /  ````````````````````````````````````````````````````````````````````````````````````  \/ / 
 / /\.--..--..--..--..--..--..--..--..--..--..--..--..--..--..--..--..--..--..--..--..--..--./ /\ 
/ /\ \.. \.. \.. \.. \.. \.. \.. \.. \.. \.. \.. \.. \.. \.. \.. \.. \.. \.. \.. \.. \.. \.. \/\ \
\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ `'\ `' /
 `--'`--'`--'`--'`--'`--'`--'`--'`--'`--'`--'`--'`--'`--'`--'`--'`--'`--'`--'`--'`--'`--'`--'`--' 
*/

/// @notice Clanker Token Launcher
contract Clanker is OwnerAdmins, ReentrancyGuard, IClanker {
    string constant version = "4";

    uint256 public constant TOKEN_SUPPLY = 100_000_000_000e18; // 100b with 18 decimals
    uint256 public constant BPS = 10_000;
    uint256 public constant MAX_EXTENSIONS = 10;
    uint16 public constant MAX_EXTENSION_BPS = 9000;

    // if true, the factory will not allow supplied token deployments
    bool public deprecated;

    // receiver of the fees from the factory
    address public teamFeeRecipient;
    mapping(address token => DeploymentInfo deploymentInfo) public deploymentInfoForToken;

    // enabled factory modules
    mapping(address hook => bool enabled) enabledHooks;
    mapping(address locker => mapping(address hook => bool enabled)) public enabledLockers;
    mapping(address extension => bool enabled) enabledExtensions;
    mapping(address mevModule => bool enabled) enabledMevModules;

    constructor(address owner_) OwnerAdmins(owner_) {
        // only non-originating tokens deployments are enabled
        // before initialization
        deprecated = true;
    }

    function setDeprecated(bool deprecated_) external onlyOwner {
        deprecated = deprecated_;
        emit SetDeprecated(deprecated_);
    }

    function setTeamFeeRecipient(address teamFeeRecipient_) external onlyOwner {
        address oldTeamFeeRecipient = teamFeeRecipient;
        teamFeeRecipient = teamFeeRecipient_;
        emit SetTeamFeeRecipient(oldTeamFeeRecipient, teamFeeRecipient_);
    }

    function claimTeamFees(address token) external onlyOwnerOrAdmin {
        if (teamFeeRecipient == address(0)) revert TeamFeeRecipientNotSet();

        uint256 balance = IERC20(token).balanceOf(address(this));
        SafeERC20.safeTransfer(IERC20(token), teamFeeRecipient, balance);
        emit ClaimTeamFees(token, teamFeeRecipient, balance);
    }

    function tokenDeploymentInfo(address token) external view returns (DeploymentInfo memory) {
        return deploymentInfoForToken[token];
    }

    function setHook(address hook, bool enabled) external onlyOwnerOrAdmin {
        // check that the hook supports the IClankerHook interface
        if (!IClankerHook(hook).supportsInterface(type(IClankerHook).interfaceId)) {
            revert InvalidHook();
        }

        enabledHooks[hook] = enabled;

        emit SetHook(hook, enabled);
    }

    function setLocker(address locker, address hook, bool enabled) external onlyOwnerOrAdmin {
        // check that the locker supports the IClankerLpLocker interface
        if (!IClankerLpLocker(locker).supportsInterface(type(IClankerLpLocker).interfaceId)) {
            revert InvalidLocker();
        }

        enabledLockers[locker][hook] = enabled;

        emit SetLocker(locker, hook, enabled);
    }

    function setMevModule(address mevModule, bool enabled) external onlyOwnerOrAdmin {
        // check that the mev module supports the IClankerMevModule interface
        if (!IClankerMevModule(mevModule).supportsInterface(type(IClankerMevModule).interfaceId)) {
            revert InvalidMevModule();
        }

        enabledMevModules[mevModule] = enabled;

        emit SetMevModule(mevModule, enabled);
    }

    // enable a extension contract for use, note the extension may implement its own access control
    function setExtension(address extension, bool enabled) external onlyOwnerOrAdmin {
        // check that the extension contract supports the IClankerExtension interface
        if (!IClankerExtension(extension).supportsInterface(type(IClankerExtension).interfaceId)) {
            revert InvalidExtension();
        }

        enabledExtensions[extension] = enabled;

        emit SetExtension(extension, enabled);
    }

    // Deploy a token and pool with the option to vault the token and buy an initial amount
    function deployToken(DeploymentConfig memory deploymentConfig)
        public
        payable
        nonReentrant
        returns (address tokenAddress)
    {
        if (deprecated) revert Deprecated();
        if (block.chainid != deploymentConfig.tokenConfig.originatingChainId) {
            revert OnlyOriginatingChain();
        }

        // deploy the token
        tokenAddress = ClankerDeployer.deployToken(deploymentConfig.tokenConfig, TOKEN_SUPPLY);

        // figure out the supply split
        uint256 extensionsSupply = _prepareExtensions(deploymentConfig.extensionConfigs);
        uint256 poolSupply = TOKEN_SUPPLY - extensionsSupply;

        // configure the pool
        PoolKey memory poolKey = _initializePool({
            poolConfig: deploymentConfig.poolConfig,
            locker: deploymentConfig.lockerConfig.locker,
            mevModule: deploymentConfig.mevModuleConfig.mevModule,
            newToken: tokenAddress
        });

        // have locker mint liquidity and inform the hook of the position
        _initializeLiquidity(
            deploymentConfig.lockerConfig,
            deploymentConfig.poolConfig,
            poolKey,
            poolSupply,
            tokenAddress
        );

        // trigger the extensions
        _triggerExtensions(deploymentConfig, poolKey, tokenAddress);

        // initialize the mev module
        _initializeMevModule(deploymentConfig, poolKey);

        // add the deployment info to the deployment info for token
        address[] memory extensions = new address[](deploymentConfig.extensionConfigs.length);
        for (uint256 i = 0; i < deploymentConfig.extensionConfigs.length; i++) {
            extensions[i] = deploymentConfig.extensionConfigs[i].extension;
        }

        deploymentInfoForToken[tokenAddress] = DeploymentInfo({
            locker: deploymentConfig.lockerConfig.locker,
            token: tokenAddress,
            hook: deploymentConfig.poolConfig.hook,
            extensions: extensions
        });

        emit TokenCreated({
            msgSender: msg.sender,
            tokenAddress: tokenAddress,
            tokenAdmin: deploymentConfig.tokenConfig.tokenAdmin,
            tokenMetadata: deploymentConfig.tokenConfig.metadata,
            tokenImage: deploymentConfig.tokenConfig.image,
            tokenName: deploymentConfig.tokenConfig.name,
            tokenSymbol: deploymentConfig.tokenConfig.symbol,
            tokenContext: deploymentConfig.tokenConfig.context,
            poolHook: deploymentConfig.poolConfig.hook,
            poolId: poolKey.toId(),
            startingTick: deploymentConfig.poolConfig.tickIfToken0IsClanker,
            pairedToken: deploymentConfig.poolConfig.pairedToken,
            locker: deploymentConfig.lockerConfig.locker,
            mevModule: deploymentConfig.mevModuleConfig.mevModule,
            extensionsSupply: extensionsSupply,
            extensions: extensions
        });
    }

    function _initializeMevModule(DeploymentConfig memory deploymentConfig, PoolKey memory poolKey)
        internal
    {
        if (!enabledMevModules[deploymentConfig.mevModuleConfig.mevModule]) {
            revert MevModuleNotEnabled();
        }

        // initialize the mev module
        IClankerHook(deploymentConfig.poolConfig.hook).initializeMevModule(
            poolKey, deploymentConfig.mevModuleConfig.mevModuleData
        );
    }

    function _initializePool(
        PoolConfig memory poolConfig,
        address locker,
        address mevModule,
        address newToken
    ) internal returns (PoolKey memory poolKey) {
        // check that the pool hook is enabled
        if (!enabledHooks[poolConfig.hook]) {
            revert HookNotEnabled();
        }

        // call into the hook to initialize the pool
        poolKey = IClankerHook(poolConfig.hook).initializePool(
            newToken,
            poolConfig.pairedToken,
            poolConfig.tickIfToken0IsClanker,
            poolConfig.tickSpacing,
            locker,
            mevModule,
            poolConfig.poolData
        );
    }

    function _initializeLiquidity(
        LockerConfig memory lockerConfig,
        IClanker.PoolConfig memory poolConfig,
        PoolKey memory poolKey,
        uint256 poolSupply,
        address token
    ) internal {
        // check that the locker is enabled
        if (!enabledLockers[lockerConfig.locker][poolConfig.hook]) {
            revert LockerNotEnabled();
        }

        // approve the liquidity locker to take the pool's token supply
        IERC20(token).approve(address(lockerConfig.locker), poolSupply);

        // have the locker mint liquidity
        IClankerLpLocker(lockerConfig.locker).placeLiquidity(
            lockerConfig, poolConfig, poolKey, poolSupply, token
        );
    }

    function _prepareExtensions(ExtensionConfig[] memory extensions)
        internal
        view
        returns (uint256 extensionSupply)
    {
        if (extensions.length == 0) {
            return 0;
        }

        // check for max number of extensions
        if (extensions.length > MAX_EXTENSIONS) {
            revert MaxExtensionsExceeded();
        }

        // determine total supply percentage earmarked for extensions
        uint256 extensionSupplyPercentage = 0;
        for (uint256 i = 0; i < extensions.length; i++) {
            extensionSupplyPercentage += extensions[i].extensionBps;
        }

        // check that the extension supply percentage is less than the max extension bps
        if (extensionSupplyPercentage > MAX_EXTENSION_BPS) {
            revert MaxExtensionBpsExceeded();
        }

        // determine expected extension eth
        uint256 expectedExtensionEth = 0;
        for (uint256 i = 0; i < extensions.length; i++) {
            expectedExtensionEth += extensions[i].msgValue;
        }

        // ensure the extension expected eth is equal to the msg.value
        if (expectedExtensionEth != msg.value) {
            revert ExtensionMsgValueMismatch();
        }

        // check that the extensions are enabled
        for (uint256 i = 0; i < extensions.length; i++) {
            if (!enabledExtensions[extensions[i].extension]) {
                revert ExtensionNotEnabled();
            }
        }

        // figure out the extension supply
        extensionSupply = extensionSupplyPercentage * TOKEN_SUPPLY / BPS;
    }

    // send the tokens to the extension contract
    function _triggerExtensions(
        DeploymentConfig memory deploymentConfig,
        PoolKey memory poolKey,
        address token
    ) internal {
        // iterate over the extensions and trigger each one
        for (uint256 i = 0; i < deploymentConfig.extensionConfigs.length; i++) {
            // determine the supply for the extension
            uint256 extensionSupply =
                deploymentConfig.extensionConfigs[i].extensionBps * TOKEN_SUPPLY / BPS;

            // approve the extension contract to spend the token
            IERC20(token).approve(deploymentConfig.extensionConfigs[i].extension, extensionSupply);

            // trigger the extension
            IClankerExtension(deploymentConfig.extensionConfigs[i].extension).receiveTokens{
                value: deploymentConfig.extensionConfigs[i].msgValue
            }(deploymentConfig, poolKey, token, extensionSupply, i);

            emit ExtensionTriggered(
                deploymentConfig.extensionConfigs[i].extension,
                extensionSupply,
                deploymentConfig.extensionConfigs[i].msgValue
            );
        }
    }
}
Chain explorer2780msChain node92ms