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
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