EquifoldV4StockLaunchModuleFactoryV3

0x934b878273a0becae9bdbad279d83856d36f762a

Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
16,577 bytes
ABI entries
37
16 read · 7 write
License
none

Contract information

Address
0x934b878273a0becae9bdbad279d83856d36f762a
Chain
Robinhood Chain (4663)
Compiler
v0.8.24+commit.e11b9ed9
Optimization
Enabled
Creation tx
0xc09a89c8c2…e2f8a32365

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
router()address
splitRegistry()address
standardTokenFactory()address
stockOf(address)address
stockRegistry()address
treasury()address

Events (6)

OwnershipTransferStartedOwnershipTransferredRouterConfiguredStandardTokenFactoryConfiguredV4StockModuleCreatedV4StockPoolCreated

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "initialOwner",
        "type": "address"
      },
      {
        "internalType": "contract IPoolManager",
        "name": "poolManager_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "stockRegistry_",
        "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"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "stock",
        "type": "address"
      }
    ],
    "name": "V4StockModuleCreated",
    "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": "stock",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "module",
        "type": "address"
      }
    ],
    "name": "V4StockPoolCreated",
    "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": "address",
        "name": "stock",
        "type": "address"
      },
      {
        "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": [],
    "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": "token",
        "type": "address"
      }
    ],
    "name": "stockOf",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "stockRegistry",
    "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"
  }
]

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 { EquifoldV4StockLaunchModuleV3 } from "../dex/EquifoldV4StockLaunchModuleV3.sol";
import { CreatorSplitRegistryV2 } from "./CreatorSplitRegistryV2.sol";

/// @title Equifold stock-paired v4 launch module factory (gen4 v3, hookless)
/// @author Equifold
/// @notice The creation boundary for {EquifoldV4StockLaunchModuleV3}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 {V4StockPoolCreated} with the real token + pool.
/// @dev A byte-faithful clone of {EquifoldV4StockLaunchModuleFactoryV2} with the gen4 deltas: it holds
///      an immutable {CreatorSplitRegistryV2} and calls `registerProject` at module creation (so the
///      module's creator fee leg can be redirected without a redeploy), and it constructs the gen4 v3
///      module. The event name/layout {V4StockPoolCreated} is UNCHANGED from v2 so the indexer reuses
///      its decoder; the factory ADDRESS distinguishes gen4. Launch pools carry NO hook
///      (`hooks == address(0)`) — externally routable from block one; anti-snipe discipline lives
///      entirely on the launch token ({EquifoldRestrictedTokenV4}).
contract EquifoldV4StockLaunchModuleFactoryV3 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 stock-paired v4 economics (binding launch parameters).
    uint24 public constant POOL_FEE = 10_000;
    int24 public constant TICK_SPACING = 200;

    IPoolManager public immutable poolManager;
    address public immutable stockRegistry;
    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 module => address stock) private _stockByModule;
    mapping(address token => PoolKey key) private _poolKeyOf;
    mapping(address token => address stock) private _stockOf;

    event StandardTokenFactoryConfigured(address indexed standardTokenFactory);
    event RouterConfigured(address indexed router);
    event V4StockModuleCreated(
        uint256 indexed projectNumber,
        address indexed module,
        address indexed creator,
        address stock
    );
    /// @notice Indexer-discovery event: emitted once a module's v4 pool actually exists.
    event V4StockPoolCreated(
        uint256 indexed projectNumber,
        address indexed token,
        bytes32 indexed poolId,
        address stock,
        address module
    );

    constructor(
        address initialOwner,
        IPoolManager poolManager_,
        address stockRegistry_,
        CreatorSplitRegistryV2 splitRegistry_,
        address payable treasury_
    ) Ownable(initialOwner) {
        if (
            initialOwner == address(0) || address(poolManager_) == address(0)
                || stockRegistry_ == address(0) || address(splitRegistry_) == address(0)
                || treasury_ == address(0)
        ) revert InvalidAddress();
        if (
            address(poolManager_).code.length == 0 || stockRegistry_.code.length == 0
                || address(splitRegistry_).code.length == 0
        ) revert InvalidAddress();
        poolManager = poolManager_;
        stockRegistry = stockRegistry_;
        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 `stock`, 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,
        address stock,
        int24 launchTick
    ) external returns (address module) {
        if (msg.sender != standardTokenFactory) revert NotStandardTokenFactory();
        if (creator == address(0) || stock == address(0)) revert InvalidAddress();
        if (projectNumber == 0 || moduleByProject[projectNumber] != address(0)) {
            revert InvalidAddress();
        }

        EquifoldV4StockLaunchModuleV3 created = new EquifoldV4StockLaunchModuleV3(
            EquifoldV4StockLaunchModuleV3.Initialization({
                moduleFactory: address(this),
                standardTokenFactory: standardTokenFactory,
                creator: creator,
                treasury: treasury,
                poolManager: poolManager,
                stockRegistry: stockRegistry,
                stock: stock,
                splitRegistry: splitRegistry,
                projectNumber: projectNumber,
                poolFee: POOL_FEE,
                tickSpacing: TICK_SPACING,
                launchTick: launchTick
            })
        );
        module = address(created);
        moduleByProject[projectNumber] = module;
        projectByModule[module] = projectNumber;
        isModule[module] = true;
        _stockByModule[module] = stock;
        splitRegistry.registerProject(projectNumber, creator);
        emit V4StockModuleCreated(projectNumber, module, creator, stock);
    }

    /// @notice Called once by a module this factory created, right after its pool is initialized, to
    ///         record the token->pool/stock 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();
        address stock = _stockByModule[msg.sender];
        _poolKeyOf[token] = key;
        _stockOf[token] = stock;
        emit V4StockPoolCreated(projectNumber, token, PoolId.unwrap(key.toId()), stock, 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 stock a launched token's pool is quoted in (zero until its pool exists).
    function stockOf(
        address token
    ) external view returns (address) {
        return _stockOf[token];
    }
}
Chain explorer696msChain node95ms