ZephyrFactory

0x120975d920179da71d00c2a3f9fa6f0ccf2a94cc

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
16,903 bytes
ABI entries
69
28 read · 12 write
License
none

Contract information

Address
0x120975d920179da71d00c2a3f9fa6f0ccf2a94cc
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0x1a9ae28341…dd0213d104

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (28)

STANDARD_SUPPLY()uint256
TICK_RANGE()int24
TICK_SPACING()int24
TYPE_MEME()uint8
TYPE_UTILITY()uint8
agents()address
allowedPairs(address)bool
antiSniperDeployer()address
baseRewardDeployer()address
deploymentFee()uint256
getAllowedFees(uint8)uint24[]
getPoolKey(address, uint24, address)tuple
getTokenByIndex(uint256)tuple
getTokenInfo(address)tuple
getTokenInfoByAgentId(uint256)tuple
getTokensPaginated(uint256, uint256)tuple[]
getValidTicks(address, uint8)uint24[]
isDeployed(address)bool
locker()address
owner()address
poolManager()address
positionManager()address
rewardDeployer()address
standardDeployer()address
tokenByAgentId(uint256)address
tokenIndex(address)uint256
totalDeployed()uint256
weth()address

Events (9)

AgentsSetDeployersSetDeploymentFeeSetETHWithdrawnFeesConfiguredLockerSetOwnershipTransferredPairConfiguredTokenDeployed

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "positionManager_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "poolManager_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "weth_",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "AgentsNotSet",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "DeployerNotSet",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "DevBuyAmbiguousInput",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "DevBuyRequiresWETHPair",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InsufficientDeploymentFee",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidDistributorPct",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint24",
        "name": "fee",
        "type": "uint24"
      },
      {
        "internalType": "uint8",
        "name": "tokenType",
        "type": "uint8"
      }
    ],
    "name": "InvalidFee",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint24",
        "name": "tick",
        "type": "uint24"
      },
      {
        "internalType": "address",
        "name": "paired",
        "type": "address"
      }
    ],
    "name": "InvalidStartingTick",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "tokenType",
        "type": "uint8"
      }
    ],
    "name": "InvalidTokenType",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "LockerNotSet",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "tokenType",
        "type": "uint8"
      }
    ],
    "name": "NoFeesConfigured",
    "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": [
      {
        "internalType": "address",
        "name": "paired",
        "type": "address"
      }
    ],
    "name": "PairNotAllowed",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "rewardToken",
        "type": "address"
      }
    ],
    "name": "RewardTokenNotWhitelisted",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "RewardTokenRequired",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "TokenNotFound",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAddress",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "agents",
        "type": "address"
      }
    ],
    "name": "AgentsSet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "standard",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "antiSniper",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "reward",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "baseReward",
        "type": "address"
      }
    ],
    "name": "DeployersSet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "fee",
        "type": "uint256"
      }
    ],
    "name": "DeploymentFeeSet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "ETHWithdrawn",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint8",
        "name": "tokenType",
        "type": "uint8"
      },
      {
        "indexed": false,
        "internalType": "uint24[]",
        "name": "fees",
        "type": "uint24[]"
      }
    ],
    "name": "FeesConfigured",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "locker",
        "type": "address"
      }
    ],
    "name": "LockerSet",
    "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": "paired",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "allowed",
        "type": "bool"
      },
      {
        "indexed": false,
        "internalType": "uint24[]",
        "name": "ticks",
        "type": "uint24[]"
      }
    ],
    "name": "PairConfigured",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "paired",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "positionId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "PoolId",
        "name": "poolId",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "uint8",
        "name": "tokenType",
        "type": "uint8"
      },
      {
        "indexed": false,
        "internalType": "uint24",
        "name": "fee",
        "type": "uint24"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "ipfsHash",
        "type": "string"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "distributor",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "agentTokenId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "deployedAt",
        "type": "uint256"
      }
    ],
    "name": "TokenDeployed",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "STANDARD_SUPPLY",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "TICK_RANGE",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "TICK_SPACING",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "TYPE_MEME",
    "outputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "TYPE_UTILITY",
    "outputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "agents",
    "outputs": [
      {
        "internalType": "contract IAgentsMinter",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "allowedPairs",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "antiSniperDeployer",
    "outputs": [
      {
        "internalType": "contract ITokenDeployer",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "baseRewardDeployer",
    "outputs": [
      {
        "internalType": "contract IRewardTokenDeployer",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "paired",
        "type": "address"
      },
      {
        "internalType": "bool",
        "name": "allowed",
        "type": "bool"
      },
      {
        "internalType": "uint8",
        "name": "tokenType",
        "type": "uint8"
      },
      {
        "internalType": "uint24[]",
        "name": "ticks",
        "type": "uint24[]"
      }
    ],
    "name": "configurePair",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "components": [
          {
            "internalType": "string",
            "name": "name",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "symbol",
            "type": "string"
          },
          {
            "internalType": "uint24",
            "name": "startingTick",
            "type": "uint24"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "address",
            "name": "pairedToken",
            "type": "address"
          },
          {
            "internalType": "uint8",
            "name": "tokenType",
            "type": "uint8"
          },
          {
            "internalType": "uint256",
            "name": "devBuyAmountIn",
            "type": "uint256"
          },
          {
            "internalType": "string",
            "name": "ipfsHash",
            "type": "string"
          },
          {
            "internalType": "bool",
            "name": "sniperProtected",
            "type": "bool"
          },
          {
            "internalType": "bool",
            "name": "hasRewards",
            "type": "bool"
          },
          {
            "internalType": "address",
            "name": "rewardToken",
            "type": "address"
          },
          {
            "internalType": "uint8",
            "name": "distributorFeePercentage",
            "type": "uint8"
          },
          {
            "internalType": "string",
            "name": "agentCid",
            "type": "string"
          },
          {
            "internalType": "bytes",
            "name": "agentSignature",
            "type": "bytes"
          },
          {
            "internalType": "address[]",
            "name": "feeRecipientWallets",
            "type": "address[]"
          },
          {
            "internalType": "uint16[]",
            "name": "feeRecipientBps",
            "type": "uint16[]"
          }
        ],
        "internalType": "struct ZephyrFactory.DeployParams",
        "name": "p",
        "type": "tuple"
      }
    ],
    "name": "deploy",
    "outputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "positionId",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "tokenAmount",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "agentTokenId",
        "type": "uint256"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "deploymentFee",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "tokenType",
        "type": "uint8"
      }
    ],
    "name": "getAllowedFees",
    "outputs": [
      {
        "internalType": "uint24[]",
        "name": "",
        "type": "uint24[]"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "uint24",
        "name": "fee",
        "type": "uint24"
      },
      {
        "internalType": "address",
        "name": "pairedToken",
        "type": "address"
      }
    ],
    "name": "getPoolKey",
    "outputs": [
      {
        "components": [
          {
            "internalType": "Currency",
            "name": "currency0",
            "type": "address"
          },
          {
            "internalType": "Currency",
            "name": "currency1",
            "type": "address"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "int24",
            "name": "tickSpacing",
            "type": "int24"
          },
          {
            "internalType": "contract IHooks",
            "name": "hooks",
            "type": "address"
          }
        ],
        "internalType": "struct PoolKey",
        "name": "",
        "type": "tuple"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "index",
        "type": "uint256"
      }
    ],
    "name": "getTokenByIndex",
    "outputs": [
      {
        "components": [
          {
            "internalType": "address",
            "name": "token",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "creator",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "deployedAt",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "positionId",
            "type": "uint256"
          },
          {
            "internalType": "address",
            "name": "paired",
            "type": "address"
          },
          {
            "internalType": "PoolId",
            "name": "poolId",
            "type": "bytes32"
          },
          {
            "internalType": "uint8",
            "name": "tokenType",
            "type": "uint8"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "string",
            "name": "name",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "symbol",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "ipfsHash",
            "type": "string"
          },
          {
            "internalType": "address",
            "name": "distributor",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "agentTokenId",
            "type": "uint256"
          }
        ],
        "internalType": "struct ZephyrFactory.TokenInfo",
        "name": "",
        "type": "tuple"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "getTokenInfo",
    "outputs": [
      {
        "components": [
          {
            "internalType": "address",
            "name": "token",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "creator",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "deployedAt",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "positionId",
            "type": "uint256"
          },
          {
            "internalType": "address",
            "name": "paired",
            "type": "address"
          },
          {
            "internalType": "PoolId",
            "name": "poolId",
            "type": "bytes32"
          },
          {
            "internalType": "uint8",
            "name": "tokenType",
            "type": "uint8"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "string",
            "name": "name",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "symbol",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "ipfsHash",
            "type": "string"
          },
          {
            "internalType": "address",
            "name": "distributor",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "agentTokenId",
            "type": "uint256"
          }
        ],
        "internalType": "struct ZephyrFactory.TokenInfo",
        "name": "",
        "type": "tuple"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "agentTokenId",
        "type": "uint256"
      }
    ],
    "name": "getTokenInfoByAgentId",
    "outputs": [
      {
        "components": [
          {
            "internalType": "address",
            "name": "token",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "creator",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "deployedAt",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "positionId",
            "type": "uint256"
          },
          {
            "internalType": "address",
            "name": "paired",
            "type": "address"
          },
          {
            "internalType": "PoolId",
            "name": "poolId",
            "type": "bytes32"
          },
          {
            "internalType": "uint8",
            "name": "tokenType",
            "type": "uint8"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "string",
            "name": "name",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "symbol",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "ipfsHash",
            "type": "string"
          },
          {
            "internalType": "address",
            "name": "distributor",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "agentTokenId",
            "type": "uint256"
          }
        ],
        "internalType": "struct ZephyrFactory.TokenInfo",
        "name": "",
        "type": "tuple"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "offset",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "limit",
        "type": "uint256"
      }
    ],
    "name": "getTokensPaginated",
    "outputs": [
      {
        "components": [
          {
            "internalType": "address",
            "name": "token",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "creator",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "deployedAt",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "positionId",
            "type": "uint256"
          },
          {
            "internalType": "address",
            "name": "paired",
            "type": "address"
          },
          {
            "internalType": "PoolId",
            "name": "poolId",
            "type": "bytes32"
          },
          {
            "internalType": "uint8",
            "name": "tokenType",
            "type": "uint8"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "string",
            "name": "name",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "symbol",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "ipfsHash",
            "type": "string"
          },
          {
            "internalType": "address",
            "name": "distributor",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "agentTokenId",
            "type": "uint256"
          }
        ],
        "internalType": "struct ZephyrFactory.TokenInfo[]",
        "name": "page",
        "type": "tuple[]"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "paired",
        "type": "address"
      },
      {
        "internalType": "uint8",
        "name": "tokenType",
        "type": "uint8"
      }
    ],
    "name": "getValidTicks",
    "outputs": [
      {
        "internalType": "uint24[]",
        "name": "",
        "type": "uint24[]"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "isDeployed",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "locker",
    "outputs": [
      {
        "internalType": "contract ZephyrLocker",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "poolManager",
    "outputs": [
      {
        "internalType": "contract IPoolManagerMinimal",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "positionManager",
    "outputs": [
      {
        "internalType": "contract IPositionManagerMinimal",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "tokenAddr",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "rescueERC20",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "rewardDeployer",
    "outputs": [
      {
        "internalType": "contract IRewardTokenDeployer",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "agents_",
        "type": "address"
      }
    ],
    "name": "setAgents",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "tokenType",
        "type": "uint8"
      },
      {
        "internalType": "uint24[]",
        "name": "fees",
        "type": "uint24[]"
      }
    ],
    "name": "setAllowedFees",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "standard_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "antiSniper_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "reward_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "baseReward_",
        "type": "address"
      }
    ],
    "name": "setDeployers",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "fee",
        "type": "uint256"
      }
    ],
    "name": "setDeploymentFee",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "locker_",
        "type": "address"
      }
    ],
    "name": "setLocker",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "standardDeployer",
    "outputs": [
      {
        "internalType": "contract ITokenDeployer",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "tokenByAgentId",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "tokenIndex",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "totalDeployed",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes",
        "name": "data",
        "type": "bytes"
      }
    ],
    "name": "unlockCallback",
    "outputs": [
      {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "weth",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "withdrawETH",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "stateMutability": "payable",
    "type": "receive"
  }
]

Source code

// SPDX-License-Identifier: MIT
//  Factory — deploys tokens, creates V4 pools, mints project Agent NFTs.
pragma solidity ^0.8.26;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";

import {PoolKey}             from "@uniswap/v4-core/src/types/PoolKey.sol";
import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol";
import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol";
import {IHooks}              from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {IUnlockCallback}     from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol";
import {BalanceDelta, BalanceDeltaLibrary} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
import {IPoolManagerMinimal} from "./interfaces/IPoolManagerMinimal.sol";
import {IPoolInitializer_v4} from "@uniswap/v4-periphery/src/interfaces/IPoolInitializer_v4.sol";
import {Actions}             from "@uniswap/v4-periphery/src/libraries/Actions.sol";
import {TickMath}            from "@uniswap/v4-core/src/libraries/TickMath.sol";
import {IPositionManagerMinimal} from "./interfaces/IPositionManagerMinimal.sol";

import {ITokenDeployer}        from "./interfaces/ITokenDeployer.sol";
import {IRewardTokenDeployer}  from "./interfaces/IRewardTokenDeployer.sol";
import {IRewardToken}          from "./interfaces/IRewardToken.sol";
import {ZephyrLocker}        from "./ZephyrLocker.sol";

interface IWETH {
    function deposit() external payable;
    function transfer(address to, uint256 value) external returns (bool);
}

interface IAgentsMinter {
    function mintFromFactory(
        address to,
        string calldata cid,
        bytes calldata signature
    ) external returns (uint256 tokenId);
}

/**
 * @title  ZephyrFactory
 * @notice Single-tx project launcher: deploys an ERC20, mints an Agent NFT for the
 *         project, creates a Uniswap V4 pool seeded with a one-sided LP, optionally
 *         locks the LP, and runs an optional dev buy.
 *
 *         Project fee routing model:
 *           • Every launch mints an Agent NFT to the creator.
 *           • Whoever holds that Agent NFT receives the project's share of LP fees
 *             at every collectFees() call (looked up via ownerOf at claim time).
 *           • Platform always takes its volume-based cut (computed by the locker).
 *           • Reward tokens additionally route a configurable cut to their
 *             DividendDistributor.
 *
 *         Fixed invariants:
 *           • Every token has 18 decimals and a supply of 1,000,000,000 tokens.
 *           • 100 % of the supply is placed into the LP position.
 *           • The LP is one-sided (token-only). No WETH is deposited at launch.
 */
contract ZephyrFactory is Ownable, IUnlockCallback {
    using PoolIdLibrary for PoolKey;

    // ─────────────────────────────────────────────────────────────
    //  Constants
    // ─────────────────────────────────────────────────────────────

    uint256 public constant STANDARD_SUPPLY = 1_000_000_000 * 10 ** 18;
    int24   public constant TICK_RANGE      = 110_400;
    int24   public constant TICK_SPACING    = 200;

    uint8   public constant TYPE_MEME       = 0;
    uint8   public constant TYPE_UTILITY    = 1;

    // ─────────────────────────────────────────────────────────────
    //  Types
    // ─────────────────────────────────────────────────────────────

    struct TokenInfo {
        address token;          // deployed ERC20 address
        address creator;        // wallet that called deploy()
        uint256 deployedAt;     // block.timestamp
        uint256 positionId;     // Uniswap V4 LP NFT token ID
        address paired;         // paired token address
        PoolId  poolId;         // Uniswap V4 pool ID
        uint8   tokenType;      // TYPE_MEME or TYPE_UTILITY
        uint24  fee;            // pool fee
        string  name;
        string  symbol;
        string  ipfsHash;       // IPFS metadata hash for the ERC20 (logo, description)
        address distributor;    // DividendDistributor (address(0) for non-reward tokens)
        uint256 agentTokenId;   // tokenId of the project Agent NFT
    }

    /**
     * @param name              Token name.
     * @param symbol            Token symbol.
     * @param startingTick      Launch price expressed as a POSITIVE tick value.
     * @param fee               Pool fee tier (e.g. 10000 = 1%).
     * @param pairedToken       Token to pair with (address(0) → default WETH).
     * @param tokenType         TYPE_MEME (0) or TYPE_UTILITY (1).
     * @param devBuyAmountIn    Optional dev buy in paired token; for WETH pairs send msg.value instead.
     * @param ipfsHash          IPFS hash of the ERC20 metadata.
     * @param sniperProtected   Deploy a sniper-protected token variant.
     * @param hasRewards        Deploy a reward token with embedded DividendDistributor.
     * @param rewardToken       Token paid out as rewards (must be whitelisted on the locker).
     * @param distributorFeePercentage  % of post-platform LP fees routed to distributor (only used when hasRewards == true).
     * @param agentCid          Plaintext IPFS CID of the project Agent NFT metadata.
     * @param agentSignature    EIP-712 voucher signed by the Agents contract's signer key.
     */
    struct DeployParams {
        string    name;
        string    symbol;
        uint24    startingTick;
        uint24    fee;
        address   pairedToken;
        uint8     tokenType;
        uint256   devBuyAmountIn;
        string    ipfsHash;
        bool      sniperProtected;

        // Reward token fields (ignored when hasRewards == false)
        bool      hasRewards;
        address   rewardToken;
        uint8     distributorFeePercentage;

        // Project Agent NFT
        string    agentCid;
        bytes     agentSignature;

        // Initial fee recipients (optional — empty arrays = all fees go to Agent NFT owner)
        address[] feeRecipientWallets;
        uint16[]  feeRecipientBps;
    }

    /// @dev Passed through poolManager.unlock() to execute the dev buy swap.
    struct DevBuyData {
        PoolKey  key;
        bool     zeroForOne;
        uint256  amountIn;
        Currency pairedCurrency;
        address  recipient;
    }

    // ─────────────────────────────────────────────────────────────
    //  State
    // ─────────────────────────────────────────────────────────────

    IPositionManagerMinimal public immutable positionManager;
    IPoolManagerMinimal     public immutable poolManager;
    address                 public immutable weth;

    ITokenDeployer        public standardDeployer;
    ITokenDeployer        public antiSniperDeployer;
    IRewardTokenDeployer  public rewardDeployer;
    IRewardTokenDeployer  public baseRewardDeployer;

    /// @notice Optional locker. When set, LP NFTs are minted to the locker
    ///         and the project agent is registered for fee routing.
    ZephyrLocker public locker;

    /// @notice Agents ERC-721 contract. Required — every launch mints an Agent NFT.
    IAgentsMinter public agents;

    TokenInfo[] private _tokens;

    /// @notice token address → 1-based index into _tokens (0 = unknown).
    mapping(address => uint256) public tokenIndex;

    /// @notice agentTokenId → token address (reverse lookup).
    mapping(uint256 => address) public tokenByAgentId;

    /// @notice Whitelisted paired tokens.
    mapping(address => bool) public allowedPairs;

    /// @notice Valid startingTick values per paired token per token type.
    mapping(address => mapping(uint8 => uint24[])) private _validTicks;

    /// @notice Allowed pool fee values per token type (uint24, e.g. 10000 = 1%).
    ///         Set during deployment and updatable by owner.
    mapping(uint8 => uint24[]) private _allowedFees;

    /// @notice ETH fee charged on every deploy. Goes to the factory; withdrawable by owner.
    uint256 public deploymentFee = 0.000001 ether;

    // ─────────────────────────────────────────────────────────────
    //  Events / Errors
    // ─────────────────────────────────────────────────────────────

    event TokenDeployed(
        address indexed token,
        address indexed creator,
        address indexed paired,
        uint256 positionId,
        PoolId  poolId,
        uint8   tokenType,
        uint24  fee,
        string  name,
        string  symbol,
        string  ipfsHash,
        address distributor,
        uint256 agentTokenId,
        uint256 deployedAt
    );
    event LockerSet          (address indexed locker);
    event AgentsSet          (address indexed agents);
    event PairConfigured     (address indexed paired, bool allowed, uint24[] ticks);
    event DeployersSet       (address standard, address antiSniper, address reward, address baseReward);
    event FeesConfigured     (uint8 indexed tokenType, uint24[] fees);
    event DeploymentFeeSet   (uint256 fee);
    event ETHWithdrawn       (address indexed to, uint256 amount);

    error TokenNotFound(address token);
    error DeployerNotSet();
    error AgentsNotSet();
    error LockerNotSet();
    error InsufficientDeploymentFee();
    error DevBuyRequiresWETHPair();
    error DevBuyAmbiguousInput();
    error PairNotAllowed(address paired);
    error InvalidStartingTick(uint24 tick, address paired);
    error InvalidTokenType(uint8 tokenType);
    error InvalidFee(uint24 fee, uint8 tokenType);
    error RewardTokenRequired();
    error RewardTokenNotWhitelisted(address rewardToken);
    error InvalidDistributorPct();
    error NoFeesConfigured(uint8 tokenType);
    error ZeroAddress();

    // ─────────────────────────────────────────────────────────────
    //  Constructor
    // ─────────────────────────────────────────────────────────────

    constructor(
        address positionManager_,
        address poolManager_,
        address weth_
    ) Ownable(msg.sender) {
        positionManager = IPositionManagerMinimal(positionManager_);
        poolManager     = IPoolManagerMinimal(poolManager_);
        weth            = weth_;
    }

    // ─────────────────────────────────────────────────────────────
    //  Admin
    // ─────────────────────────────────────────────────────────────

    function setLocker(address locker_) external onlyOwner {
        locker = ZephyrLocker(locker_);
        emit LockerSet(locker_);
    }

    function setAgents(address agents_) external onlyOwner {
        agents = IAgentsMinter(agents_);
        emit AgentsSet(agents_);
    }

    function setDeployers(
        address standard_,
        address antiSniper_,
        address reward_,
        address baseReward_
    ) external onlyOwner {
        standardDeployer   = ITokenDeployer(standard_);
        antiSniperDeployer = ITokenDeployer(antiSniper_);
        rewardDeployer     = IRewardTokenDeployer(reward_);
        baseRewardDeployer = IRewardTokenDeployer(baseReward_);
        emit DeployersSet(standard_, antiSniper_, reward_, baseReward_);
    }

    /**
     * @notice Add or update a whitelisted paired token with valid ticks for one token type.
     */
    function configurePair(
        address  paired,
        bool     allowed,
        uint8    tokenType,
        uint24[] calldata ticks
    ) external onlyOwner {
        allowedPairs[paired] = allowed;
        if (ticks.length > 0) _validTicks[paired][tokenType] = ticks;
        emit PairConfigured(paired, allowed, ticks);
    }

    /**
     * @notice Set the allowed pool fee values for a token type.
     *         e.g. setAllowedFees(0, [10000]) → meme tokens can only use 1%
     *              setAllowedFees(1, [10000, 20000, 30000]) → utility: 1/2/3%
     */
    function setAllowedFees(uint8 tokenType, uint24[] calldata fees) external onlyOwner {
        require(fees.length > 0, "Need at least one fee");
        _allowedFees[tokenType] = fees;
        emit FeesConfigured(tokenType, fees);
    }

    function getAllowedFees(uint8 tokenType) external view returns (uint24[] memory) {
        return _allowedFees[tokenType];
    }

    function _isAllowedFee(uint8 tokenType, uint24 fee) internal view returns (bool) {
        uint24[] storage fees = _allowedFees[tokenType];
        for (uint256 i; i < fees.length; i++) {
            if (fees[i] == fee) return true;
        }
        return false;
    }

    function getValidTicks(address paired, uint8 tokenType) external view returns (uint24[] memory) {
        return _validTicks[paired][tokenType];
    }

    function _isValidTick(address paired, uint8 tokenType, uint24 tick) internal view returns (bool) {
        uint24[] storage ticks = _validTicks[paired][tokenType];
        for (uint256 i; i < ticks.length; i++) {
            if (ticks[i] == tick) return true;
        }
        return false;
    }

    // ─────────────────────────────────────────────────────────────
    //  Core: deploy
    // ─────────────────────────────────────────────────────────────

    /**
     * @notice Deploy a project: ERC20 + V4 pool + one-sided LP + Agent NFT.
     *
     * @param p            Deploy parameters (includes Agent NFT voucher).
     * @return token       Address of the newly deployed ERC20.
     * @return positionId  V4 LP NFT token ID.
     * @return tokenAmount Tokens received by creator from the dev buy (0 if no dev buy).
     * @return agentTokenId  The minted Agent NFT tokenId.
     */
    function deploy(DeployParams calldata p)
        external
        payable
        returns (address token, uint256 positionId, uint256 tokenAmount, uint256 agentTokenId)
    {
        address creator = msg.sender;

        // ── 0. Validate params ────────────────────────────────────────────
        if (address(agents) == address(0)) revert AgentsNotSet();
        if (address(locker) == address(0)) revert LockerNotSet();

        address paired = p.pairedToken == address(0) ? weth : p.pairedToken;
        if (!allowedPairs[paired]) revert PairNotAllowed(paired);

        if (p.tokenType != TYPE_MEME && p.tokenType != TYPE_UTILITY)
            revert InvalidTokenType(p.tokenType);

        if (!_isValidTick(paired, p.tokenType, p.startingTick))
            revert InvalidStartingTick(p.startingTick, paired);

        if (_allowedFees[p.tokenType].length == 0)
            revert NoFeesConfigured(p.tokenType);
        if (!_isAllowedFee(p.tokenType, p.fee))
            revert InvalidFee(p.fee, p.tokenType);

        if (p.hasRewards && p.distributorFeePercentage > 100)
            revert InvalidDistributorPct();

        // ── 1. Mint the project Agent NFT to the creator ──────────────────
        agentTokenId = agents.mintFromFactory(creator, p.agentCid, p.agentSignature);

        // ── 2. Deploy token via the appropriate deployer ──────────────────
        address distributor = address(0);

        // =========================DEPLOY TOKEN=========================//

        if (p.hasRewards) {
            if (p.rewardToken == address(0)) revert RewardTokenRequired();
            if (!locker.isRewardTokenAllowed(p.rewardToken))
                revert RewardTokenNotWhitelisted(p.rewardToken);

            IRewardTokenDeployer rd = p.sniperProtected ? rewardDeployer : baseRewardDeployer;
            if (address(rd) == address(0)) revert DeployerNotSet();

            uint256 minDistribution = locker.rewardMinDistribution(p.rewardToken);

            token = rd.deploy(
                p.name,
                p.symbol,
                STANDARD_SUPPLY,
                creator,
                address(poolManager),
                p.rewardToken,
                minDistribution,
                p.sniperProtected
            );

            distributor = IRewardToken(token).dividendDistributor();
        } else {
            ITokenDeployer deployer = p.sniperProtected ? antiSniperDeployer : standardDeployer;
            if (address(deployer) == address(0)) revert DeployerNotSet();
            token = deployer.deploy(p.name, p.symbol, STANDARD_SUPPLY, address(poolManager), creator);
        }

        // =======================MINT POSITION===========================//

        // ── 3. Capture next NFT id (must be before multicall) ────────────
        positionId = positionManager.nextTokenId();

        // ── 4. Compute ticks from positive startingTick input ──────────────
        bool pairedIsC0 = paired < token;
        int24 sTick     = int24(uint24(p.startingTick));

        int24 tickLower;
        int24 tickUpper;
        int24 actualStartingTick;

        if (pairedIsC0) {
            tickUpper          = sTick;
            tickLower          = sTick - TICK_RANGE;
            actualStartingTick = sTick;
        } else {
            tickLower          = -sTick;
            tickUpper          = -sTick + TICK_RANGE;
            actualStartingTick = tickLower;
        }

        uint160 sqrtPriceX96 = TickMath.getSqrtPriceAtTick(actualStartingTick);

        // ── 5. Sort currencies (lower address = currency0) ─────────────────
        Currency currency0 = Currency.wrap(pairedIsC0 ? paired : token);
        Currency currency1 = Currency.wrap(pairedIsC0 ? token  : paired);

        PoolKey memory key = PoolKey({
            currency0:   currency0,
            currency1:   currency1,
            fee:         p.fee,
            tickSpacing: TICK_SPACING,
            hooks:       IHooks(address(0))
        });

        // ── 5b. Register in indexed map ────────────────────────────────────
        _tokens.push(TokenInfo({
            token:        token,
            creator:      creator,
            deployedAt:   block.timestamp,
            positionId:   positionId,
            paired:       paired,
            poolId:       key.toId(),
            tokenType:    p.tokenType,
            fee:          p.fee,
            name:         p.name,
            symbol:       p.symbol,
            ipfsHash:     p.ipfsHash,
            distributor:  distributor,
            agentTokenId: agentTokenId
        }));
        tokenIndex[token]          = _tokens.length;
        tokenByAgentId[agentTokenId] = token;

        // ── 6. Transfer tokens to PositionManager ──────────────────────────
        IERC20(token).transfer(address(positionManager), STANDARD_SUPPLY);

        // ── 7. Build V4 actions ────────────────────────────────────────────
        uint128 amount0Max = pairedIsC0 ? 0                        : uint128(STANDARD_SUPPLY);
        uint128 amount1Max = pairedIsC0 ? uint128(STANDARD_SUPPLY) : 0;

        bytes memory actions = abi.encodePacked(
            uint8(Actions.SETTLE),
            uint8(Actions.MINT_POSITION_FROM_DELTAS),
            uint8(Actions.CLOSE_CURRENCY)
        );

        bytes[] memory actParams = new bytes[](3);

        uint256 CONTRACT_BALANCE = 0x8000000000000000000000000000000000000000000000000000000000000000;
        Currency tokenCurrency = Currency.wrap(token);
        actParams[0] = abi.encode(tokenCurrency, CONTRACT_BALANCE, false);

        // Mint LP NFT to the factory first so we can safeTransferFrom → locker
        // after multicall (triggering onERC721Received).
        actParams[1] = abi.encode(
            key,
            tickLower,
            tickUpper,
            amount0Max,
            amount1Max,
            address(this),
            bytes("")
        );

        actParams[2] = abi.encode(tokenCurrency);

        // ── 8. Batch: initializePool + modifyLiquidities ──────────────────
        bytes[] memory calls = new bytes[](2);
        calls[0] = abi.encodeCall(
            IPoolInitializer_v4.initializePool,
            (key, sqrtPriceX96)
        );
        calls[1] = abi.encodeCall(
            IPositionManagerMinimal.modifyLiquidities,
            (abi.encode(actions, actParams), block.timestamp)
        );

        positionManager.multicall(calls);

        //============================== LOCK POSITION && REGISTER PROJECT INFO IN LOCKER================================

        // ── 9. Lock + register project agent ──────────────────────────────
        IERC721(address(positionManager)).safeTransferFrom(
            address(this),
            address(locker),
            positionId
        );

        uint8 distPct = p.hasRewards ? p.distributorFeePercentage : 0;
        locker.setProjectAgent(
            positionId,
            paired,
            address(agents),
            agentTokenId,
            distributor,
            distPct,
            p.hasRewards ? p.rewardToken : address(0)
        );

        if (p.feeRecipientWallets.length > 0) {
            locker.setFeeRecipientsAuthorized(agentTokenId, p.feeRecipientWallets, p.feeRecipientBps);
        }

        emit TokenDeployed(
            token,
            creator,
            paired,
            positionId,
            key.toId(),
            p.tokenType,
            p.fee,
            p.name,
            p.symbol,
            p.ipfsHash,
            distributor,
            agentTokenId,
            block.timestamp
        );

        // ── 10. Deployment fee + optional dev buy ────────────────────────
        if (msg.value < deploymentFee) revert InsufficientDeploymentFee();

        uint256 devBuyEth = msg.value - deploymentFee;

        if (devBuyEth > 0 || p.devBuyAmountIn > 0) {
            if (devBuyEth > 0 && p.devBuyAmountIn > 0) revert DevBuyAmbiguousInput();
            if (devBuyEth > 0 && paired != weth)       revert DevBuyRequiresWETHPair();

            uint256 amountIn;
            if (devBuyEth > 0) {
                IWETH(weth).deposit{value: devBuyEth}();
                amountIn = devBuyEth;
            } else {
                IERC20(paired).transferFrom(creator, address(this), p.devBuyAmountIn);
                amountIn = p.devBuyAmountIn;
            }

            DevBuyData memory devBuy = DevBuyData({
                key:            key,
                zeroForOne:     pairedIsC0,
                amountIn:       amountIn,
                pairedCurrency: Currency.wrap(paired),
                recipient:      creator
            });

            bytes memory unlockResult = poolManager.unlock(abi.encode(devBuy));
            tokenAmount = abi.decode(unlockResult, (uint256));
        }

        // ── 11. Activate sniper limits (anti-sniper tokens only) ─────────
        if (p.sniperProtected) {
            (bool ok,) = token.call(abi.encodeWithSignature("launch()"));
            require(ok, "launch() failed");
        }
    }

    // ─────────────────────────────────────────────────────────────
    //  Unlock callback — dev buy swap
    // ─────────────────────────────────────────────────────────────

    function unlockCallback(bytes calldata data) external returns (bytes memory) {
        require(msg.sender == address(poolManager), "only PM");

        DevBuyData memory d = abi.decode(data, (DevBuyData));

        BalanceDelta delta = poolManager.swap(
            d.key,
            IPoolManagerMinimal.SwapParams({
                zeroForOne:        d.zeroForOne,
                amountSpecified:   -int256(d.amountIn),
                sqrtPriceLimitX96: d.zeroForOne
                    ? TickMath.MIN_SQRT_PRICE + 1
                    : TickMath.MAX_SQRT_PRICE - 1
            }),
            bytes("")
        );

        poolManager.sync(d.pairedCurrency);
        IERC20(Currency.unwrap(d.pairedCurrency)).transfer(address(poolManager), d.amountIn);
        poolManager.settle();

        int128 tokenDelta = d.zeroForOne
            ? BalanceDeltaLibrary.amount1(delta)
            : BalanceDeltaLibrary.amount0(delta);

        uint256 tokenAmount;
        if (tokenDelta > 0) {
            tokenAmount = uint128(tokenDelta);
            Currency tokenCurrency = d.zeroForOne ? d.key.currency1 : d.key.currency0;
            poolManager.take(tokenCurrency, d.recipient, uint128(tokenDelta));
        }

        return abi.encode(tokenAmount);
    }

    // ─────────────────────────────────────────────────────────────
    //  Views — token registry
    // ─────────────────────────────────────────────────────────────

    function totalDeployed() external view returns (uint256) {
        return _tokens.length;
    }

    function getTokenByIndex(uint256 index) external view returns (TokenInfo memory) {
        return _tokens[index];
    }

    function getTokenInfo(address token) external view returns (TokenInfo memory) {
        uint256 idx = tokenIndex[token];
        if (idx == 0) revert TokenNotFound(token);
        return _tokens[idx - 1];
    }

    function getTokensPaginated(uint256 offset, uint256 limit)
        external
        view
        returns (TokenInfo[] memory page)
    {
        uint256 total = _tokens.length;
        if (offset >= total) return page;
        uint256 end = offset + limit;
        if (end > total) end = total;
        page = new TokenInfo[](end - offset);
        for (uint256 i = offset; i < end; i++) {
            page[i - offset] = _tokens[i];
        }
    }

    function getTokenInfoByAgentId(uint256 agentTokenId) external view returns (TokenInfo memory) {
        address token = tokenByAgentId[agentTokenId];
        if (token == address(0)) revert TokenNotFound(address(0));
        return _tokens[tokenIndex[token] - 1];
    }

    function isDeployed(address token) external view returns (bool) {
        return tokenIndex[token] != 0;
    }

    function getPoolKey(
        address token,
        uint24  fee,
        address pairedToken
    ) external view returns (PoolKey memory) {
        address paired    = pairedToken == address(0) ? weth : pairedToken;
        bool    pairedIsC0 = paired < token;
        return PoolKey({
            currency0:   Currency.wrap(pairedIsC0 ? paired : token),
            currency1:   Currency.wrap(pairedIsC0 ? token  : paired),
            fee:         fee,
            tickSpacing: TICK_SPACING,
            hooks:       IHooks(address(0))
        });
    }

    // ─────────────────────────────────────────────────────────────
    //  Deployment fee
    // ─────────────────────────────────────────────────────────────

    /// @notice Update the ETH deployment fee. Pass 0 to make deploys free.
    function setDeploymentFee(uint256 fee) external onlyOwner {
        deploymentFee = fee;
        emit DeploymentFeeSet(fee);
    }

    /// @notice Withdraw accumulated deployment fees to any address.
    function withdrawETH(address to, uint256 amount) external onlyOwner {
        if (to == address(0)) revert ZeroAddress();
        (bool ok, ) = to.call{value: amount}("");
        require(ok, "ETH transfer failed");
        emit ETHWithdrawn(to, amount);
    }

    // ─────────────────────────────────────────────────────────────
    //  Safety
    // ─────────────────────────────────────────────────────────────

    function rescueERC20(address tokenAddr, uint256 amount) external onlyOwner {
        IERC20(tokenAddr).transfer(owner(), amount);
    }

    receive() external payable {}
}
Chain explorer673msChain node112ms