StandardTokenFactoryV3Native

0xfe292f3414109a05c90c0f8cce03b3e8fa5eede1

Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
21,154 bytes
ABI entries
49
20 read · 8 write
License
none

Contract information

Address
0xfe292f3414109a05c90c0f8cce03b3e8fa5eede1
Chain
Robinhood Chain (4663)
Compiler
v0.8.24+commit.e11b9ed9
Optimization
Enabled
Creation tx
0xcd09eda0a4…b8a609f2b2

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (20)

DEFAULT_ADMIN_ROLE()bytes32
FACTORY_VERSION()uint32
PAUSER_ROLE()bytes32
getLaunch(uint256)tuple
getRoleAdmin(bytes32)bytes32
hasRole(bytes32, address)bool
hashLaunchRequest(address, uint256, tuple)bytes32
identityVerifier()address
launchFeeManager()address
launchModuleFactory()address
launchRequirements()address
metadataRegistry()address
moduleConfig()address
paused()bool
projectNumberByToken(address)uint256
projectRegistry()address
supportsInterface(bytes4)bool
templateRegistry()address
tokenAt(uint256)address
tokenCount()uint256

Events (8)

LaunchOpenedMetadataRegistryConfiguredPausedRoleAdminChangedRoleGrantedRoleRevokedTokenCreatedUnpaused

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "admin",
        "type": "address"
      },
      {
        "internalType": "contract ProjectRegistry",
        "name": "projectRegistry_",
        "type": "address"
      },
      {
        "internalType": "contract LaunchTemplateRegistry",
        "name": "templateRegistry_",
        "type": "address"
      },
      {
        "internalType": "contract IIdentityAttestationVerifier",
        "name": "identityVerifier_",
        "type": "address"
      },
      {
        "internalType": "contract ILaunchRequirements",
        "name": "launchRequirements_",
        "type": "address"
      },
      {
        "internalType": "contract ILaunchFeeManager",
        "name": "launchFeeManager_",
        "type": "address"
      },
      {
        "internalType": "contract EquifoldV3LaunchModuleFactory",
        "name": "launchModuleFactory_",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "AccessControlBadConfirmation",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "internalType": "bytes32",
        "name": "neededRole",
        "type": "bytes32"
      }
    ],
    "name": "AccessControlUnauthorizedAccount",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "EnforcedPause",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ExpectedPause",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "IncorrectLaunchFee",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidAddress",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidCreatorFee",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidIdentityAttestation",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidInitialBuy",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidLaunchModule",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ReentrancyGuardReentrantCall",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TemplateDisabled",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "projectNumber",
        "type": "uint256"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "launchModule",
        "type": "address"
      }
    ],
    "name": "LaunchOpened",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "metadataRegistry",
        "type": "address"
      }
    ],
    "name": "MetadataRegistryConfigured",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "Paused",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "previousAdminRole",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "newAdminRole",
        "type": "bytes32"
      }
    ],
    "name": "RoleAdminChanged",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "sender",
        "type": "address"
      }
    ],
    "name": "RoleGranted",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "sender",
        "type": "address"
      }
    ],
    "name": "RoleRevoked",
    "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": "creator",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "launchModule",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "bytes32",
        "name": "templateKey",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "uint32",
        "name": "templateVersion",
        "type": "uint32"
      },
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "creatorFeeBps",
        "type": "uint16"
      }
    ],
    "name": "TokenCreated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "Unpaused",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "DEFAULT_ADMIN_ROLE",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "FACTORY_VERSION",
    "outputs": [
      {
        "internalType": "uint32",
        "name": "",
        "type": "uint32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "PAUSER_ROLE",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "components": [
          {
            "internalType": "string",
            "name": "name",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "symbol",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "metadataURI",
            "type": "string"
          },
          {
            "internalType": "bytes32",
            "name": "templateKey",
            "type": "bytes32"
          },
          {
            "internalType": "uint32",
            "name": "templateVersion",
            "type": "uint32"
          },
          {
            "internalType": "uint16",
            "name": "creatorFeeBps",
            "type": "uint16"
          },
          {
            "internalType": "bytes",
            "name": "identityAttestation",
            "type": "bytes"
          },
          {
            "internalType": "bytes",
            "name": "launchData",
            "type": "bytes"
          }
        ],
        "internalType": "struct StandardTokenFactoryV3Native.LaunchRequest",
        "name": "request",
        "type": "tuple"
      }
    ],
    "name": "createToken",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "projectNumber",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "launchModule",
        "type": "address"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "components": [
          {
            "internalType": "string",
            "name": "name",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "symbol",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "metadataURI",
            "type": "string"
          },
          {
            "internalType": "bytes32",
            "name": "templateKey",
            "type": "bytes32"
          },
          {
            "internalType": "uint32",
            "name": "templateVersion",
            "type": "uint32"
          },
          {
            "internalType": "uint16",
            "name": "creatorFeeBps",
            "type": "uint16"
          },
          {
            "internalType": "bytes",
            "name": "identityAttestation",
            "type": "bytes"
          },
          {
            "internalType": "bytes",
            "name": "launchData",
            "type": "bytes"
          }
        ],
        "internalType": "struct StandardTokenFactoryV3Native.LaunchRequest",
        "name": "request",
        "type": "tuple"
      },
      {
        "internalType": "uint256",
        "name": "initialBuyAmount",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "minTokensOut",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "deadline",
        "type": "uint256"
      }
    ],
    "name": "createTokenAndBuy",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "projectNumber",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "launchModule",
        "type": "address"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "projectNumber",
        "type": "uint256"
      }
    ],
    "name": "getLaunch",
    "outputs": [
      {
        "components": [
          {
            "internalType": "address",
            "name": "creator",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "token",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "launchModule",
            "type": "address"
          },
          {
            "internalType": "bytes32",
            "name": "templateKey",
            "type": "bytes32"
          },
          {
            "internalType": "uint32",
            "name": "templateVersion",
            "type": "uint32"
          },
          {
            "internalType": "uint16",
            "name": "creatorFeeBps",
            "type": "uint16"
          },
          {
            "internalType": "bytes32",
            "name": "normalizedNameHash",
            "type": "bytes32"
          },
          {
            "internalType": "bytes32",
            "name": "normalizedSymbolHash",
            "type": "bytes32"
          }
        ],
        "internalType": "struct StandardTokenFactoryV3Native.TokenLaunchRecord",
        "name": "",
        "type": "tuple"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      }
    ],
    "name": "getRoleAdmin",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "grantRole",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "hasRole",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      },
      {
        "components": [
          {
            "internalType": "string",
            "name": "name",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "symbol",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "metadataURI",
            "type": "string"
          },
          {
            "internalType": "bytes32",
            "name": "templateKey",
            "type": "bytes32"
          },
          {
            "internalType": "uint32",
            "name": "templateVersion",
            "type": "uint32"
          },
          {
            "internalType": "uint16",
            "name": "creatorFeeBps",
            "type": "uint16"
          },
          {
            "internalType": "bytes",
            "name": "identityAttestation",
            "type": "bytes"
          },
          {
            "internalType": "bytes",
            "name": "launchData",
            "type": "bytes"
          }
        ],
        "internalType": "struct StandardTokenFactoryV3Native.LaunchRequest",
        "name": "request",
        "type": "tuple"
      }
    ],
    "name": "hashLaunchRequest",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "identityVerifier",
    "outputs": [
      {
        "internalType": "contract IIdentityAttestationVerifier",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "launchFeeManager",
    "outputs": [
      {
        "internalType": "contract ILaunchFeeManager",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "launchModuleFactory",
    "outputs": [
      {
        "internalType": "contract IStandardTokenLaunchModuleFactory",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "launchRequirements",
    "outputs": [
      {
        "internalType": "contract ILaunchRequirements",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "metadataRegistry",
    "outputs": [
      {
        "internalType": "contract IMetadataRegistry",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "moduleConfig",
    "outputs": [
      {
        "internalType": "contract EquifoldV3LaunchModuleFactory",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pause",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "paused",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "projectNumberByToken",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "projectNumber",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "projectRegistry",
    "outputs": [
      {
        "internalType": "contract ProjectRegistry",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "callerConfirmation",
        "type": "address"
      }
    ],
    "name": "renounceRole",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "revokeRole",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "contract IMetadataRegistry",
        "name": "metadataRegistry_",
        "type": "address"
      }
    ],
    "name": "setMetadataRegistry",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes4",
        "name": "interfaceId",
        "type": "bytes4"
      }
    ],
    "name": "supportsInterface",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "templateRegistry",
    "outputs": [
      {
        "internalType": "contract LaunchTemplateRegistry",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "index",
        "type": "uint256"
      }
    ],
    "name": "tokenAt",
    "outputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "tokenCount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "unpause",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  }
]

Source code

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol";
import { Pausable } from "@openzeppelin/contracts/utils/Pausable.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { IIdentityAttestationVerifier } from "../interfaces/IIdentityAttestationVerifier.sol";
import { ILaunchFeeManager } from "../interfaces/ILaunchFeeManager.sol";
import { ILaunchRequirements } from "../interfaces/ILaunchRequirements.sol";
import { IMetadataRegistry } from "../interfaces/IMetadataRegistry.sol";
import {
    IStandardTokenLaunchModule,
    IStandardTokenLaunchModuleFactory
} from "../interfaces/IStandardTokenLaunchModule.sol";
import { ProductType } from "../types/ProductType.sol";
import { EquifoldRestrictedToken } from "./EquifoldRestrictedToken.sol";
import { EquifoldToken } from "./EquifoldToken.sol";
import { EquifoldV3LaunchModuleFactory } from "./EquifoldV3LaunchModuleFactory.sol";
import { LaunchTemplateRegistry } from "./LaunchTemplateRegistry.sol";
import { ProjectRegistry } from "./ProjectRegistry.sol";

/// @title Equifold Standard Token Factory (gen3 V3-native)
/// @author Equifold
/// @notice gen3 V3-native variant — deploys restriction-windowed tokens whose launch module is a
///         Uniswap V3 single-sided position.
contract StandardTokenFactoryV3Native is AccessControl, Pausable, ReentrancyGuard {
    error IncorrectLaunchFee();
    error InvalidInitialBuy();
    error InvalidAddress();
    error InvalidCreatorFee();
    error InvalidIdentityAttestation();
    error InvalidLaunchModule();
    error TemplateDisabled();

    uint32 public constant FACTORY_VERSION = 1;
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");

    struct LaunchRequest {
        string name;
        string symbol;
        string metadataURI;
        bytes32 templateKey;
        uint32 templateVersion;
        uint16 creatorFeeBps;
        bytes identityAttestation;
        bytes launchData;
    }

    struct TokenLaunchRecord {
        address creator;
        address token;
        address launchModule;
        bytes32 templateKey;
        uint32 templateVersion;
        uint16 creatorFeeBps;
        bytes32 normalizedNameHash;
        bytes32 normalizedSymbolHash;
    }

    struct LaunchCreation {
        uint256 projectNumber;
        address token;
        address launchModule;
        bytes32 normalizedNameHash;
        bytes32 normalizedSymbolHash;
    }

    ProjectRegistry public immutable projectRegistry;
    LaunchTemplateRegistry public immutable templateRegistry;
    IIdentityAttestationVerifier public immutable identityVerifier;
    ILaunchRequirements public immutable launchRequirements;
    ILaunchFeeManager public immutable launchFeeManager;
    IStandardTokenLaunchModuleFactory public immutable launchModuleFactory;
    EquifoldV3LaunchModuleFactory public immutable moduleConfig;
    IMetadataRegistry public metadataRegistry;

    uint256 public tokenCount;
    mapping(uint256 index => address token) public tokenAt;
    mapping(uint256 projectNumber => TokenLaunchRecord launch) private _launches;
    mapping(address token => uint256 projectNumber) public projectNumberByToken;

    event TokenCreated(
        uint256 indexed projectNumber,
        address indexed token,
        address indexed creator,
        address launchModule,
        bytes32 templateKey,
        uint32 templateVersion,
        uint16 creatorFeeBps
    );
    event LaunchOpened(
        uint256 indexed projectNumber, address indexed token, address indexed launchModule
    );
    event MetadataRegistryConfigured(address indexed metadataRegistry);

    constructor(
        address admin,
        ProjectRegistry projectRegistry_,
        LaunchTemplateRegistry templateRegistry_,
        IIdentityAttestationVerifier identityVerifier_,
        ILaunchRequirements launchRequirements_,
        ILaunchFeeManager launchFeeManager_,
        EquifoldV3LaunchModuleFactory launchModuleFactory_
    ) {
        if (
            admin == address(0) || address(projectRegistry_) == address(0)
                || address(templateRegistry_) == address(0)
                || address(identityVerifier_) == address(0)
                || address(launchRequirements_) == address(0)
                || address(launchFeeManager_) == address(0)
                || address(launchModuleFactory_) == address(0)
        ) revert InvalidAddress();
        if (
            address(projectRegistry_).code.length == 0
                || address(templateRegistry_).code.length == 0
                || address(identityVerifier_).code.length == 0
                || address(launchRequirements_).code.length == 0
                || address(launchFeeManager_).code.length == 0
                || address(launchModuleFactory_).code.length == 0
        ) revert InvalidAddress();

        projectRegistry = projectRegistry_;
        templateRegistry = templateRegistry_;
        identityVerifier = identityVerifier_;
        launchRequirements = launchRequirements_;
        launchFeeManager = launchFeeManager_;
        launchModuleFactory = launchModuleFactory_;
        moduleConfig = launchModuleFactory_;

        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(PAUSER_ROLE, admin);
    }

    function setMetadataRegistry(
        IMetadataRegistry metadataRegistry_
    ) external onlyRole(DEFAULT_ADMIN_ROLE) {
        if (address(metadataRegistry) != address(0)) revert InvalidAddress();
        if (address(metadataRegistry_) == address(0) || address(metadataRegistry_).code.length == 0)
        {
            revert InvalidAddress();
        }
        metadataRegistry = metadataRegistry_;
        emit MetadataRegistryConfigured(address(metadataRegistry_));
    }

    /// @notice Creates a standard token through the configured identity, requirements, and launch modules.
    function createToken(
        LaunchRequest calldata request
    )
        external
        payable
        whenNotPaused
        nonReentrant
        returns (uint256 projectNumber, address token, address launchModule)
    {
        return _createToken(request, 0, 0, 0);
    }

    /// @notice Creates a token and executes a disclosed creator buy atomically.
    function createTokenAndBuy(
        LaunchRequest calldata request,
        uint256 initialBuyAmount,
        uint256 minTokensOut,
        uint256 deadline
    )
        external
        payable
        whenNotPaused
        nonReentrant
        returns (uint256 projectNumber, address token, address launchModule)
    {
        if (initialBuyAmount == 0 || minTokensOut == 0 || deadline < block.timestamp) {
            revert InvalidInitialBuy();
        }
        return _createToken(request, initialBuyAmount, minTokensOut, deadline);
    }

    function _createToken(
        LaunchRequest calldata request,
        uint256 initialBuyAmount,
        uint256 minTokensOut,
        uint256 deadline
    ) private returns (uint256 projectNumber, address token, address launchModule) {
        LaunchTemplateRegistry.StandardTokenTemplate memory template =
            templateRegistry.getTemplate(request.templateKey, request.templateVersion);
        if (!template.enabled) revert TemplateDisabled();
        // The template, not the creator, fixes the initial fee. A creator may
        // still reduce it later through the curve's one-way reduction path.
        if (request.creatorFeeBps != template.creatorFeeBps) revert InvalidCreatorFee();
        if (msg.value != template.launchFeeWei + initialBuyAmount) revert IncorrectLaunchFee();

        LaunchCreation memory creation;
        creation.projectNumber = projectRegistry.nextProjectNumber();
        (creation.normalizedNameHash, creation.normalizedSymbolHash, creation.launchModule) =
            _prepareLaunch(request, creation.projectNumber);
        creation.token =
            _deployToken(request, template, creation.projectNumber, creation.launchModule);
        IStandardTokenLaunchModule(creation.launchModule).initializeToken(creation.token);

        _finalizeLaunch(request, template, creation, initialBuyAmount, minTokensOut, deadline);
        return (creation.projectNumber, creation.token, creation.launchModule);
    }

    function _finalizeLaunch(
        LaunchRequest calldata request,
        LaunchTemplateRegistry.StandardTokenTemplate memory template,
        LaunchCreation memory creation,
        uint256 initialBuyAmount,
        uint256 minTokensOut,
        uint256 deadline
    ) private {
        _registerProject(
            creation.projectNumber,
            creation.token,
            request,
            template,
            creation.normalizedNameHash,
            creation.normalizedSymbolHash
        );
        if (address(metadataRegistry) != address(0)) {
            metadataRegistry.registerMetadata(
                creation.projectNumber,
                msg.sender,
                request.metadataURI,
                keccak256(bytes(request.metadataURI))
            );
        }
        launchFeeManager.collectLaunchFee{ value: template.launchFeeWei }(
            msg.sender, creation.projectNumber, request.templateKey, request.templateVersion
        );

        _recordLaunch(
            request,
            creation.projectNumber,
            creation.token,
            creation.launchModule,
            creation.normalizedNameHash,
            creation.normalizedSymbolHash
        );
        if (initialBuyAmount != 0) {
            IStandardTokenLaunchModule(creation.launchModule).initialBuy{ value: initialBuyAmount }(
                minTokensOut, msg.sender, deadline
            );
        }
    }

    function _prepareLaunch(
        LaunchRequest calldata request,
        uint256 projectNumber
    ) private returns (bytes32 normalizedNameHash, bytes32 normalizedSymbolHash, address module) {
        bytes32 launchParametersHash = hashLaunchRequest(msg.sender, projectNumber, request);
        (normalizedNameHash, normalizedSymbolHash) = identityVerifier.consumeAttestation(
            msg.sender,
            request.name,
            request.symbol,
            launchParametersHash,
            request.identityAttestation
        );
        if (normalizedNameHash == bytes32(0) || normalizedSymbolHash == bytes32(0)) {
            revert InvalidIdentityAttestation();
        }

        launchRequirements.reserveLaunch(
            msg.sender, projectNumber, request.templateKey, request.templateVersion
        );
        module = launchModuleFactory.createLaunchModule(
            msg.sender,
            projectNumber,
            request.templateKey,
            request.templateVersion,
            request.creatorFeeBps,
            request.launchData
        );
        if (module == address(0) || module.code.length == 0) revert InvalidLaunchModule();
    }

    function _recordLaunch(
        LaunchRequest calldata request,
        uint256 projectNumber,
        address token,
        address launchModule,
        bytes32 normalizedNameHash,
        bytes32 normalizedSymbolHash
    ) private {
        _launches[projectNumber] = TokenLaunchRecord({
            creator: msg.sender,
            token: token,
            launchModule: launchModule,
            templateKey: request.templateKey,
            templateVersion: request.templateVersion,
            creatorFeeBps: request.creatorFeeBps,
            normalizedNameHash: normalizedNameHash,
            normalizedSymbolHash: normalizedSymbolHash
        });
        projectNumberByToken[token] = projectNumber;
        tokenAt[tokenCount] = token;
        ++tokenCount;

        emit TokenCreated(
            projectNumber,
            token,
            msg.sender,
            launchModule,
            request.templateKey,
            request.templateVersion,
            request.creatorFeeBps
        );
        emit LaunchOpened(projectNumber, token, launchModule);
    }

    function _deployToken(
        LaunchRequest calldata request,
        LaunchTemplateRegistry.StandardTokenTemplate memory template,
        uint256 projectNumber,
        address launchModule
    ) private returns (address token) {
        token = address(
            new EquifoldRestrictedToken(
                EquifoldToken.Initialization({
                    name: request.name,
                    symbol: request.symbol,
                    decimals: template.tokenDecimals,
                    initialSupply: template.tokenSupply,
                    burnable: template.burnable,
                    creator: msg.sender,
                    projectNumber: projectNumber,
                    metadataURI: request.metadataURI,
                    launchContract: launchModule
                }),
                EquifoldRestrictedToken.RestrictionConfig({
                    dexFactory: moduleConfig.v3Factory(),
                    pairToken: address(moduleConfig.weth()),
                    poolFee: moduleConfig.poolFee(),
                    maxWalletBps: moduleConfig.maxWalletBps(),
                    maxTxBps: moduleConfig.maxTxBps(),
                    restrictionBlocks: moduleConfig.restrictionBlocks()
                })
            )
        );
    }

    function _registerProject(
        uint256 projectNumber,
        address token,
        LaunchRequest calldata request,
        LaunchTemplateRegistry.StandardTokenTemplate memory template,
        bytes32 normalizedNameHash,
        bytes32 normalizedSymbolHash
    ) private {
        projectRegistry.registerProject(
            projectNumber,
            ProjectRegistry.Registration({
                creator: msg.sender,
                productType: ProductType.STANDARD_TOKEN,
                primaryContract: token,
                templateKey: request.templateKey,
                templateVersion: request.templateVersion,
                metadataHash: keccak256(bytes(request.metadataURI)),
                normalizedNameHash: normalizedNameHash,
                normalizedSymbolHash: normalizedSymbolHash,
                isGenesis: template.isGenesis,
                isOfficial: template.isOfficial
            })
        );
    }

    /// @notice Hash bound into identity attestations to prevent cross-chain and
    /// cross-request replay.
    /// @dev The (factory-sequenced) project number is intentionally NOT bound
    /// into the hash. Binding `nextProjectNumber()` serialized all launches — two
    /// concurrent launches invalidated each other's attestation, enabling a
    /// front-run DoS — while adding no replay protection beyond the per-creator
    /// nonce already enforced by the verifier. The parameter is retained for ABI
    /// stability and remains ignored.
    function hashLaunchRequest(
        address creator,
        uint256,
        LaunchRequest calldata request
    ) public view returns (bytes32) {
        return keccak256(
            abi.encode(
                block.chainid,
                address(this),
                creator,
                keccak256(bytes(request.name)),
                keccak256(bytes(request.symbol)),
                keccak256(bytes(request.metadataURI)),
                request.templateKey,
                request.templateVersion,
                request.creatorFeeBps,
                keccak256(request.launchData)
            )
        );
    }

    function getLaunch(
        uint256 projectNumber
    ) external view returns (TokenLaunchRecord memory) {
        return _launches[projectNumber];
    }

    function pause() external onlyRole(PAUSER_ROLE) {
        _pause();
    }

    function unpause() external onlyRole(PAUSER_ROLE) {
        _unpause();
    }
}
Chain explorer3481msChain node75ms