EquifoldV3LaunchModuleFactory

0x7a27d4c9c339c048ae6740ccb55095aa9611dbff

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

Contract information

Address
0x7a27d4c9c339c048ae6740ccb55095aa9611dbff
Chain
Robinhood Chain (4663)
Compiler
v0.8.24+commit.e11b9ed9
Optimization
Enabled
Creation tx
0x2dbfc58d16…d4b17da3b5

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (22)

FACTORY_VERSION()uint32
INITIAL_TICK()int24
MAX_TX_BPS()uint16
MAX_WALLET_BPS()uint16
POOL_FEE()uint24
RESTRICTION_BLOCKS()uint32
feeCollector()address
launchRequirements()address
maxTxBps()uint16
maxWalletBps()uint16
moduleByProject(uint256)address
owner()address
pendingOwner()address
poolFee()uint24
positionManager()address
projectByModule(address)uint256
restrictionBlocks()uint32
standardTokenFactory()address
swapRouter()address
treasury()address
v3Factory()address
weth()address

Events (6)

OwnershipTransferStartedOwnershipTransferredProjectReleasedStandardTokenFactoryConfiguredV3NativeModuleCreatedV3NativePoolCreated

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "initialOwner",
        "type": "address"
      },
      {
        "internalType": "contract ILaunchRequirements",
        "name": "launchRequirements_",
        "type": "address"
      },
      {
        "internalType": "contract LaunchpadFeeCollector",
        "name": "feeCollector_",
        "type": "address"
      },
      {
        "internalType": "contract IWrappedNative",
        "name": "weth_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "v3Factory_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "positionManager_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "swapRouter_",
        "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": "InvalidLaunchData",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotInitialized",
    "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": "RestrictionActive",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "UnknownProject",
    "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": "uint256",
        "name": "projectNumber",
        "type": "uint256"
      }
    ],
    "name": "ProjectReleased",
    "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": "bytes32",
        "name": "templateKey",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "uint32",
        "name": "templateVersion",
        "type": "uint32"
      }
    ],
    "name": "V3NativeModuleCreated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "projectNumber",
        "type": "uint256"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "pool",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "module",
        "type": "address"
      }
    ],
    "name": "V3NativePoolCreated",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "FACTORY_VERSION",
    "outputs": [
      {
        "internalType": "uint32",
        "name": "",
        "type": "uint32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "INITIAL_TICK",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_TX_BPS",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_WALLET_BPS",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "POOL_FEE",
    "outputs": [
      {
        "internalType": "uint24",
        "name": "",
        "type": "uint24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "RESTRICTION_BLOCKS",
    "outputs": [
      {
        "internalType": "uint32",
        "name": "",
        "type": "uint32"
      }
    ],
    "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": "bytes32",
        "name": "templateKey",
        "type": "bytes32"
      },
      {
        "internalType": "uint32",
        "name": "templateVersion",
        "type": "uint32"
      },
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      },
      {
        "internalType": "bytes",
        "name": "launchData",
        "type": "bytes"
      }
    ],
    "name": "createLaunchModule",
    "outputs": [
      {
        "internalType": "address",
        "name": "launchModule",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "feeCollector",
    "outputs": [
      {
        "internalType": "contract LaunchpadFeeCollector",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "launchRequirements",
    "outputs": [
      {
        "internalType": "contract ILaunchRequirements",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "maxTxBps",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "maxWalletBps",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "pure",
    "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"
      },
      {
        "internalType": "address",
        "name": "pool",
        "type": "address"
      }
    ],
    "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": [],
    "name": "poolFee",
    "outputs": [
      {
        "internalType": "uint24",
        "name": "",
        "type": "uint24"
      }
    ],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "positionManager",
    "outputs": [
      {
        "internalType": "address",
        "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": "uint256",
        "name": "projectNumber",
        "type": "uint256"
      }
    ],
    "name": "releaseProject",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "restrictionBlocks",
    "outputs": [
      {
        "internalType": "uint32",
        "name": "",
        "type": "uint32"
      }
    ],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "standardTokenFactory_",
        "type": "address"
      }
    ],
    "name": "setStandardTokenFactory",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "standardTokenFactory",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "swapRouter",
    "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": "v3Factory",
    "outputs": [
      {
        "internalType": "address",
        "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 { EquifoldV3LaunchModule } from "../dex/EquifoldV3LaunchModule.sol";
import { EquifoldRestrictedToken } from "./EquifoldRestrictedToken.sol";
import { LaunchpadFeeCollector } from "./LaunchpadFeeCollector.sol";
import { IFeeShareRegistry } from "../interfaces/IFeeShareRegistry.sol";
import { ILaunchRequirements } from "../interfaces/ILaunchRequirements.sol";
import { IStandardTokenLaunchModuleFactory } from "../interfaces/IStandardTokenLaunchModule.sol";
import { IWrappedNative } from "../interfaces/IWrappedNative.sol";

/// @title Equifold gen3 V3-native launch module factory
/// @author Equifold
/// @notice The creation boundary for {EquifoldV3LaunchModule}s, mirroring {BondingCurveFactory}:
///         only the StandardTokenFactory may create modules, each is registered with the launchpad
///         fee collector, and each starts with the default single-creator fee split. Hard-codes the
///         gen3 V3-native economics (pool fee, launch tick, per-wallet/per-tx buy caps, restriction
///         window) and exposes them as config getters consumed when deploying the launch token. This
///         factory is also the launch requirements' lifecycle manager: it releases a project's
///         reservation once the token's launch restriction window has elapsed.
contract EquifoldV3LaunchModuleFactory is IStandardTokenLaunchModuleFactory, Ownable2Step {
    error AlreadyConfigured();
    error InvalidAddress();
    error InvalidLaunchData();
    error NotInitialized();
    error NotModule();
    error NotStandardTokenFactory();
    error RestrictionActive();
    error UnknownProject();

    uint32 public constant FACTORY_VERSION = 1;

    /// @notice Hard-coded gen3 V3-native economics (binding launch parameters).
    uint24 public constant POOL_FEE = 10_000;
    int24 public constant INITIAL_TICK = -203_600;
    uint16 public constant MAX_WALLET_BPS = 300;
    uint16 public constant MAX_TX_BPS = 330;
    uint32 public constant RESTRICTION_BLOCKS = 3000;

    ILaunchRequirements public immutable launchRequirements;
    LaunchpadFeeCollector public immutable feeCollector;
    IWrappedNative public immutable weth;
    address public immutable v3Factory;
    address public immutable positionManager;
    address public immutable swapRouter;
    address payable public immutable treasury;

    address public standardTokenFactory;
    mapping(uint256 projectNumber => address module) public moduleByProject;
    mapping(address module => uint256 projectNumber) public projectByModule;

    event StandardTokenFactoryConfigured(address indexed standardTokenFactory);
    event V3NativeModuleCreated(
        uint256 indexed projectNumber,
        address indexed module,
        address indexed creator,
        bytes32 templateKey,
        uint32 templateVersion
    );
    /// @notice DEX-discovery event: emitted once a module's V3 pool actually exists.
    event V3NativePoolCreated(
        uint256 indexed projectNumber, address indexed token, address indexed pool, address module
    );
    event ProjectReleased(uint256 indexed projectNumber);

    constructor(
        address initialOwner,
        ILaunchRequirements launchRequirements_,
        LaunchpadFeeCollector feeCollector_,
        IWrappedNative weth_,
        address v3Factory_,
        address positionManager_,
        address swapRouter_,
        address payable treasury_
    ) Ownable(initialOwner) {
        if (
            initialOwner == address(0) || address(launchRequirements_) == address(0)
                || address(feeCollector_) == address(0) || address(weth_) == address(0)
                || v3Factory_ == address(0) || positionManager_ == address(0)
                || swapRouter_ == address(0) || treasury_ == address(0)
        ) revert InvalidAddress();
        if (
            address(launchRequirements_).code.length == 0 || address(feeCollector_).code.length == 0
                || address(weth_).code.length == 0 || v3Factory_.code.length == 0
                || positionManager_.code.length == 0 || swapRouter_.code.length == 0
        ) revert InvalidAddress();
        launchRequirements = launchRequirements_;
        feeCollector = feeCollector_;
        weth = weth_;
        v3Factory = v3Factory_;
        positionManager = positionManager_;
        swapRouter = swapRouter_;
        treasury = treasury_;
    }

    /// @notice One-time wiring of the StandardTokenFactory allowed to create modules.
    /// @dev Mirrors {BondingCurveFactory.setStandardTokenFactory}.
    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_);
    }

    /// @inheritdoc IStandardTokenLaunchModuleFactory
    /// @dev Mirrors {BondingCurveFactory.createLaunchModule}: StandardTokenFactory-only, empty
    ///      `launchData`, then deploy → record → register with the fee collector → default split.
    ///      `creatorFeeBps` is unused here — the V3 module's fee split is a fixed 70/30.
    function createLaunchModule(
        address creator,
        uint256 projectNumber,
        bytes32 templateKey,
        uint32 templateVersion,
        uint16, // creatorFeeBps — unused: the V3 module uses a fixed 70/30 split, not a configurable bps
        bytes calldata launchData
    ) external returns (address launchModule) {
        if (msg.sender != standardTokenFactory) revert NotStandardTokenFactory();
        if (launchData.length != 0) revert InvalidLaunchData();

        EquifoldV3LaunchModule module = new EquifoldV3LaunchModule(
            EquifoldV3LaunchModule.Initialization({
                moduleFactory: address(this),
                standardTokenFactory: standardTokenFactory,
                creator: creator,
                treasury: treasury,
                feeCollector: address(feeCollector),
                weth: weth,
                v3Factory: v3Factory,
                positionManager: positionManager,
                swapRouter: swapRouter,
                projectNumber: projectNumber,
                poolFee: POOL_FEE,
                initialTick: INITIAL_TICK
            })
        );
        launchModule = address(module);
        moduleByProject[projectNumber] = launchModule;
        projectByModule[launchModule] = projectNumber;
        feeCollector.registerCurve(projectNumber, launchModule, creator);
        feeCollector.initializeCreatorSplit(projectNumber, _defaultCreatorSplit(creator));
        emit V3NativeModuleCreated(
            projectNumber, launchModule, creator, templateKey, templateVersion
        );
    }

    /// @notice Called once by a module this factory created, right after its pool is initialized, to
    ///         emit the DEX-discovery event with the real token + pool addresses.
    /// @dev Restricted to modules this factory actually created.
    function notifyPoolInitialized(
        address token,
        address pool
    ) external {
        uint256 projectNumber = projectByModule[msg.sender];
        if (projectNumber == 0) revert NotModule();
        emit V3NativePoolCreated(projectNumber, token, pool, msg.sender);
    }

    /// @notice Releases a project's launch reservation once its token's restriction window has
    ///         elapsed. Permissionless — the elapsed window is the only gate.
    /// @dev This factory is the {ILaunchRequirements} lifecycle manager; it forwards the release.
    function releaseProject(
        uint256 projectNumber
    ) external {
        address module = moduleByProject[projectNumber];
        if (module == address(0)) revert UnknownProject();
        address token = EquifoldV3LaunchModule(payable(module)).token();
        if (token == address(0)) revert NotInitialized();
        if (block.number <= EquifoldRestrictedToken(token).restrictionEndBlock()) {
            revert RestrictionActive();
        }
        launchRequirements.releaseLaunch(projectNumber);
        emit ProjectReleased(projectNumber);
    }

    /// @notice The launch token's pool fee tier.
    function poolFee() external pure returns (uint24) {
        return POOL_FEE;
    }

    /// @notice Per-recipient balance cap (bps of supply) during the restriction window.
    function maxWalletBps() external pure returns (uint16) {
        return MAX_WALLET_BPS;
    }

    /// @notice Per-recipient cumulative buy cap (bps of supply) during the restriction window.
    function maxTxBps() external pure returns (uint16) {
        return MAX_TX_BPS;
    }

    /// @notice Length, in blocks, of the post-launch buy-restriction window.
    function restrictionBlocks() external pure returns (uint32) {
        return RESTRICTION_BLOCKS;
    }

    /// @dev Every launch starts with a single WALLET recipient (the creator) holding 100% of the
    ///      creator fee share. Copied from {BondingCurveFactory._defaultCreatorSplit}.
    function _defaultCreatorSplit(
        address creator
    ) private pure returns (IFeeShareRegistry.Recipient[] memory recipients) {
        recipients = new IFeeShareRegistry.Recipient[](1);
        recipients[0] = IFeeShareRegistry.Recipient({
            kind: IFeeShareRegistry.RecipientKind.WALLET,
            id: bytes32(uint256(uint160(creator))),
            bps: 10_000
        });
    }
}
Chain explorer1691msChain node88ms