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
- Creator
- 0x290A12d965…4bE69A27Cb
- 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