EquifoldV4LaunchModuleFactory
0xb89e73b40fba26b095a2f61097fd3803bcd55fbe
Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
16,149 bytes
ABI entries
37
16 read · 7 write
License
none
Contract information
- Address
- 0xb89e73b40fba26b095a2f61097fd3803bcd55fbe
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.24+commit.e11b9ed9
- Optimization
- Enabled
- Creator
- 0xd03543d97B…F446Cd1418
- Creation tx
- 0xd09a4d6fd3…d9e1e4e355
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (16)
FACTORY_VERSION() → uint32
POOL_FEE() → uint24
TICK_SPACING() → int24
isModule(address) → bool
moduleByProject(uint256) → address
owner() → address
pendingOwner() → address
poolKeyOf(address) → tuple
poolManager() → address
projectByModule(address) → uint256
quoteOf(address) → address
router() → address
splitRegistry() → address
standardTokenFactory() → address
treasury() → address
weth() → address
Events (6)
OwnershipTransferStartedOwnershipTransferredRouterConfiguredStandardTokenFactoryConfiguredV4NativeModuleCreatedV4PoolCreated
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "initialOwner",
"type": "address"
},
{
"internalType": "contract IPoolManager",
"name": "poolManager_",
"type": "address"
},
{
"internalType": "contract IWrappedNative",
"name": "weth_",
"type": "address"
},
{
"internalType": "contract CreatorSplitRegistryV2",
"name": "splitRegistry_",
"type": "address"
},
{
"internalType": "address payable",
"name": "treasury_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "AlreadyConfigured",
"type": "error"
},
{
"inputs": [],
"name": "InvalidAddress",
"type": "error"
},
{
"inputs": [],
"name": "NotModule",
"type": "error"
},
{
"inputs": [],
"name": "NotStandardTokenFactory",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "OwnableInvalidOwner",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "OwnableUnauthorizedAccount",
"type": "error"
},
{
"inputs": [],
"name": "RouterAlreadySet",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferStarted",
"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": "router",
"type": "address"
}
],
"name": "RouterConfigured",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "standardTokenFactory",
"type": "address"
}
],
"name": "StandardTokenFactoryConfigured",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "projectNumber",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "module",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "creator",
"type": "address"
}
],
"name": "V4NativeModuleCreated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "projectNumber",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "bytes32",
"name": "poolId",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "address",
"name": "quote",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "module",
"type": "address"
}
],
"name": "V4PoolCreated",
"type": "event"
},
{
"inputs": [],
"name": "FACTORY_VERSION",
"outputs": [
{
"internalType": "uint32",
"name": "",
"type": "uint32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "POOL_FEE",
"outputs": [
{
"internalType": "uint24",
"name": "",
"type": "uint24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "TICK_SPACING",
"outputs": [
{
"internalType": "int24",
"name": "",
"type": "int24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "acceptOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"internalType": "uint256",
"name": "projectNumber",
"type": "uint256"
},
{
"internalType": "int24",
"name": "launchTick",
"type": "int24"
}
],
"name": "createModule",
"outputs": [
{
"internalType": "address",
"name": "module",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "module",
"type": "address"
}
],
"name": "isModule",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "projectNumber",
"type": "uint256"
}
],
"name": "moduleByProject",
"outputs": [
{
"internalType": "address",
"name": "module",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"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": "key",
"type": "tuple"
}
],
"name": "notifyPoolInitialized",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "pendingOwner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "poolKeyOf",
"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": [],
"name": "poolManager",
"outputs": [
{
"internalType": "contract IPoolManager",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "module",
"type": "address"
}
],
"name": "projectByModule",
"outputs": [
{
"internalType": "uint256",
"name": "projectNumber",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "quoteOf",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "router",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "router_",
"type": "address"
}
],
"name": "setRouter",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "standardTokenFactory_",
"type": "address"
}
],
"name": "setStandardTokenFactory",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "splitRegistry",
"outputs": [
{
"internalType": "contract CreatorSplitRegistryV2",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "standardTokenFactory",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "treasury",
"outputs": [
{
"internalType": "address payable",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "weth",
"outputs": [
{
"internalType": "contract IWrappedNative",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import { Ownable2Step, Ownable } from "@openzeppelin/contracts/access/Ownable2Step.sol";
import { IPoolManager } from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import { PoolKey } from "@uniswap/v4-core/src/types/PoolKey.sol";
import { PoolId, PoolIdLibrary } from "@uniswap/v4-core/src/types/PoolId.sol";
import { EquifoldV4LaunchModule } from "../dex/EquifoldV4LaunchModule.sol";
import { CreatorSplitRegistryV2 } from "./CreatorSplitRegistryV2.sol";
import { IWrappedNative } from "../interfaces/IWrappedNative.sol";
/// @title Equifold gen4 hookless WETH-quoted v4 launch module factory
/// @author Equifold
/// @notice The creation boundary for {EquifoldV4LaunchModule}s: only the standard-token factory may
/// create modules, each is recorded ({isModule}) and registers its project's creator once with
/// the shared {CreatorSplitRegistryV2} (this factory holds `REGISTRAR_ROLE`), and the shared
/// launch router is wired once by the owner (it deploys after the modules). Hard-codes the
/// vertical's v4 economics (pool fee, tick spacing). This factory is also the vertical's
/// indexer-discovery source: a module calls back once its pool actually exists and the factory
/// emits {V4PoolCreated} (identical field layout to the stock vertical's `V4StockPoolCreated`,
/// so the indexer reuses its decoder) with the real token + pool.
/// @dev Launch pools carry NO hook (`hooks == address(0)`) — externally routable from block one.
/// Anti-snipe discipline lives entirely on the launch token ({EquifoldRestrictedTokenV4}), not on
/// this factory or its modules, so there is deliberately no hook wiring here.
contract EquifoldV4LaunchModuleFactory is Ownable2Step {
using PoolIdLibrary for PoolKey;
error AlreadyConfigured();
error InvalidAddress();
error NotModule();
error NotStandardTokenFactory();
error RouterAlreadySet();
uint32 public constant FACTORY_VERSION = 1;
/// @notice Hard-coded gen4 v4 economics (binding launch parameters).
uint24 public constant POOL_FEE = 10_000;
int24 public constant TICK_SPACING = 200;
IPoolManager public immutable poolManager;
/// @notice The WETH every launch pool of this vertical is quoted in.
IWrappedNative public immutable weth;
CreatorSplitRegistryV2 public immutable splitRegistry;
address payable public immutable treasury;
address public standardTokenFactory;
/// @notice The shared launch router every module routes its initial buy through. Owner-set once.
address public router;
mapping(uint256 projectNumber => address module) public moduleByProject;
mapping(address module => uint256 projectNumber) public projectByModule;
mapping(address module => bool) public isModule;
mapping(address token => PoolKey key) private _poolKeyOf;
mapping(address token => address quote) private _quoteOf;
event StandardTokenFactoryConfigured(address indexed standardTokenFactory);
event RouterConfigured(address indexed router);
event V4NativeModuleCreated(
uint256 indexed projectNumber, address indexed module, address indexed creator
);
/// @notice Indexer-discovery event: emitted once a module's v4 pool actually exists. Field layout
/// is identical (types/positions/indexing) to the stock vertical's `V4StockPoolCreated` so
/// the indexer reuses its decoder; the fourth field carries the WETH quote address.
event V4PoolCreated(
uint256 indexed projectNumber,
address indexed token,
bytes32 indexed poolId,
address quote,
address module
);
constructor(
address initialOwner,
IPoolManager poolManager_,
IWrappedNative weth_,
CreatorSplitRegistryV2 splitRegistry_,
address payable treasury_
) Ownable(initialOwner) {
if (
initialOwner == address(0) || address(poolManager_) == address(0)
|| address(weth_) == address(0) || address(splitRegistry_) == address(0)
|| treasury_ == address(0)
) revert InvalidAddress();
if (
address(poolManager_).code.length == 0 || address(weth_).code.length == 0
|| address(splitRegistry_).code.length == 0
) revert InvalidAddress();
poolManager = poolManager_;
weth = weth_;
splitRegistry = splitRegistry_;
treasury = treasury_;
}
/// @notice One-time wiring of the standard-token factory allowed to create modules.
function setStandardTokenFactory(
address standardTokenFactory_
) external onlyOwner {
if (standardTokenFactory != address(0)) revert AlreadyConfigured();
if (standardTokenFactory_ == address(0) || standardTokenFactory_.code.length == 0) {
revert InvalidAddress();
}
standardTokenFactory = standardTokenFactory_;
emit StandardTokenFactoryConfigured(standardTokenFactory_);
}
/// @notice One-time wiring of the shared launch router (it deploys after this factory).
function setRouter(
address router_
) external onlyOwner {
if (router != address(0)) revert RouterAlreadySet();
if (router_ == address(0) || router_.code.length == 0) revert InvalidAddress();
router = router_;
emit RouterConfigured(router_);
}
/// @notice Creates a launch module for `projectNumber`, quoted in WETH, opening at `launchTick`,
/// and registers the project's creator with the shared split registry.
/// @dev Standard-token-factory-only.
function createModule(
address creator,
uint256 projectNumber,
int24 launchTick
) external returns (address module) {
if (msg.sender != standardTokenFactory) revert NotStandardTokenFactory();
if (creator == address(0)) revert InvalidAddress();
if (projectNumber == 0 || moduleByProject[projectNumber] != address(0)) {
revert InvalidAddress();
}
EquifoldV4LaunchModule created = new EquifoldV4LaunchModule(
EquifoldV4LaunchModule.Initialization({
moduleFactory: address(this),
standardTokenFactory: standardTokenFactory,
creator: creator,
treasuryWallet: treasury,
poolManager: poolManager,
splitRegistry: splitRegistry,
weth: weth,
projectNumber: projectNumber,
poolFee: POOL_FEE,
tickSpacing: TICK_SPACING,
launchTick: launchTick
})
);
module = address(created);
moduleByProject[projectNumber] = module;
projectByModule[module] = projectNumber;
isModule[module] = true;
splitRegistry.registerProject(projectNumber, creator);
emit V4NativeModuleCreated(projectNumber, module, creator);
}
/// @notice Called once by a module this factory created, right after its pool is initialized, to
/// record the token->pool/quote mapping and emit the indexer-discovery event.
/// @dev Restricted to modules this factory actually created.
function notifyPoolInitialized(
address token,
PoolKey calldata key
) external {
uint256 projectNumber = projectByModule[msg.sender];
if (projectNumber == 0) revert NotModule();
_poolKeyOf[token] = key;
_quoteOf[token] = address(weth);
emit V4PoolCreated(
projectNumber, token, PoolId.unwrap(key.toId()), address(weth), msg.sender
);
}
/// @notice The launch pool key for a launched token (zero-valued until its pool exists).
function poolKeyOf(
address token
) external view returns (PoolKey memory) {
return _poolKeyOf[token];
}
/// @notice The quote currency (WETH) a launched token's pool is quoted in (zero until it exists).
function quoteOf(
address token
) external view returns (address) {
return _quoteOf[token];
}
}
Chain explorer4580msChain node97ms