RobinPadFactory

0x1461e52f235b614bd9cfb909383778521ad7d8bf

Verification
Verified
v0.8.28+commit.7893614a
Type
Contract
16,936 bytes
ABI entries
65
20 read · 9 write
License
none

Contract information

Address
0x1461e52f235b614bd9cfb909383778521ad7d8bf
Chain
Robinhood Chain (4663)
Compiler
v0.8.28+commit.7893614a
Optimization
Enabled
Creation tx
0xdc4203589d…44cdf414eb

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (20)

FEE_TIER()uint24
createTokenEnabled()bool
deployedTokens(uint256)address, string, string, address, address, address, uint256, string, uint256, uint256, uint256, uint256, uint256
feeRecipientForToken(address)address
getDeploysPaginated(uint256, uint256)tuple[]
getTokenMarketCap(address)uint256
initialBuyAmount()uint256
owner()address
positionManager()address
robinPadDeployer()address
swapRouter()address
tickSpacing()int24
tokenCount()uint256
tokenFeesGenerated(address)uint256
tokenInfoByAddress(address)address, string, string, address, address, address, uint256, string, uint256, uint256, uint256, uint256, uint256
tokenToNFTId(address)uint256
uniV3ParameterSetCount()uint256
uniV3Parameters(uint256)uint160, int24, int24, uint256, uint256
uniswapV3Factory()address
weth()address

Events (8)

CreateTokenSwitchedERC20TokenCreatedInitialBuyAmountUpdatedOwnershipTransferredTokenFeeRecipientSetUniv3ParameterSetCreatedUniv3ParameterSetDeletedUniv3ParameterSetUpdated

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "uniswapV3Factory_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "positionManager_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "weth_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "swapRouter_",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "CannotDeleteDefaultUniv3ParameterSet",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "DeployDisabled",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InsufficientInitialBuy",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidFeeRecipient",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidInitialBuyAmount",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidPositionManager",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidSqrtPrice",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidStoredTickLower",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidTickRange",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidTickSpacing",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidTokenAddressOrder",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidTokenPairOrder",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidUniv3ParameterSetId",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidWeth",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NoTokensDeployed",
    "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": "PageOutOfRange",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ReentrancyGuardReentrantCall",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "SafeERC20FailedOperation",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TickLowerOutOfRange",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TickLowerSpacingInvalid",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TickUpperOutOfRange",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TickUpperSpacingInvalid",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "Token1MustBeWeth",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TokenAllocationExceedsSupply",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "bool",
        "name": "enabled",
        "type": "bool"
      }
    ],
    "name": "CreateTokenSwitched",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "tokenAddress",
        "type": "address"
      }
    ],
    "name": "ERC20TokenCreated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "oldAmount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "newAmount",
        "type": "uint256"
      }
    ],
    "name": "InitialBuyAmountUpdated",
    "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": "tokenAddress",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "feeRecipient",
        "type": "address"
      }
    ],
    "name": "TokenFeeRecipientSet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "parameterSetId",
        "type": "uint256"
      }
    ],
    "name": "Univ3ParameterSetCreated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "parameterSetId",
        "type": "uint256"
      }
    ],
    "name": "Univ3ParameterSetDeleted",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "parameterSetId",
        "type": "uint256"
      }
    ],
    "name": "Univ3ParameterSetUpdated",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "FEE_TIER",
    "outputs": [
      {
        "internalType": "uint24",
        "name": "",
        "type": "uint24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      }
    ],
    "name": "collectFees",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "amount0",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "amount1",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "metadata",
        "type": "string"
      },
      {
        "internalType": "address",
        "name": "feeRecipient",
        "type": "address"
      },
      {
        "internalType": "bytes32",
        "name": "salt",
        "type": "bytes32"
      },
      {
        "internalType": "uint256",
        "name": "split",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "uniV3ParameterSetId",
        "type": "uint256"
      }
    ],
    "name": "createToken",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "tokensReceived",
        "type": "uint256"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "createTokenEnabled",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint160",
        "name": "sqrtPriceX96",
        "type": "uint160"
      },
      {
        "internalType": "int24",
        "name": "tickLower",
        "type": "int24"
      },
      {
        "internalType": "int24",
        "name": "tickUpper",
        "type": "int24"
      },
      {
        "internalType": "uint256",
        "name": "amount0Desired",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "amount1Desired",
        "type": "uint256"
      }
    ],
    "name": "createUniv3ParameterSet",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "parameterSetId",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "parameterSetId",
        "type": "uint256"
      }
    ],
    "name": "deleteUniv3ParameterSet",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "deployedTokens",
    "outputs": [
      {
        "internalType": "address",
        "name": "tokenAddress",
        "type": "address"
      },
      {
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      },
      {
        "internalType": "address",
        "name": "deployer",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "feeRecipient",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "time",
        "type": "uint256"
      },
      {
        "internalType": "string",
        "name": "metadata",
        "type": "string"
      },
      {
        "internalType": "uint256",
        "name": "marketCapInETH",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "split",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "nftId",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "tokenBalance",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "totalFeesGenerated",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "feeRecipientForToken",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "page",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "order",
        "type": "uint256"
      }
    ],
    "name": "getDeploysPaginated",
    "outputs": [
      {
        "components": [
          {
            "internalType": "address",
            "name": "tokenAddress",
            "type": "address"
          },
          {
            "internalType": "string",
            "name": "name",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "symbol",
            "type": "string"
          },
          {
            "internalType": "address",
            "name": "deployer",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "creator",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "feeRecipient",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "time",
            "type": "uint256"
          },
          {
            "internalType": "string",
            "name": "metadata",
            "type": "string"
          },
          {
            "internalType": "uint256",
            "name": "marketCapInETH",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "split",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "nftId",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "tokenBalance",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "totalFeesGenerated",
            "type": "uint256"
          }
        ],
        "internalType": "struct TokenInfo[]",
        "name": "",
        "type": "tuple[]"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "tokenAddress",
        "type": "address"
      }
    ],
    "name": "getTokenMarketCap",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "marketCap",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "initialBuyAmount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "positionManager",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "robinPadDeployer",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "setInitialBuyAmount",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "swapRouter",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "switchCreateToken",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "tickSpacing",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "tokenCount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "tokenFeesGenerated",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "tokenInfoByAddress",
    "outputs": [
      {
        "internalType": "address",
        "name": "tokenAddress",
        "type": "address"
      },
      {
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      },
      {
        "internalType": "address",
        "name": "deployer",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "feeRecipient",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "time",
        "type": "uint256"
      },
      {
        "internalType": "string",
        "name": "metadata",
        "type": "string"
      },
      {
        "internalType": "uint256",
        "name": "marketCapInETH",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "split",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "nftId",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "tokenBalance",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "totalFeesGenerated",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "tokenToNFTId",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "uniV3ParameterSetCount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "uniV3Parameters",
    "outputs": [
      {
        "internalType": "uint160",
        "name": "sqrtPriceX96",
        "type": "uint160"
      },
      {
        "internalType": "int24",
        "name": "tickLower",
        "type": "int24"
      },
      {
        "internalType": "int24",
        "name": "tickUpper",
        "type": "int24"
      },
      {
        "internalType": "uint256",
        "name": "amount0Desired",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "amount1Desired",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "uniswapV3Factory",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "parameterSetId",
        "type": "uint256"
      },
      {
        "internalType": "uint160",
        "name": "sqrtPriceX96",
        "type": "uint160"
      },
      {
        "internalType": "int24",
        "name": "tickLower",
        "type": "int24"
      },
      {
        "internalType": "int24",
        "name": "tickUpper",
        "type": "int24"
      },
      {
        "internalType": "uint256",
        "name": "amount0Desired",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "amount1Desired",
        "type": "uint256"
      }
    ],
    "name": "updateUniv3ParameterSet",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "weth",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "stateMutability": "payable",
    "type": "receive"
  }
]

Source code

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.9;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "./Events.sol";
import "./Interfaces.sol";
import "./RobinPadToken.sol";

contract RobinPadFactory is IFactoryEvents, Ownable, ReentrancyGuard {
    using SafeERC20 for IERC20;

    error InvalidPositionManager();
    error InvalidWeth();
    error DeployDisabled();
    error InvalidUniv3ParameterSetId();
    error InsufficientInitialBuy();
    error InvalidTokenAddressOrder();
    error NoTokensDeployed();
    error PageOutOfRange();
    error CannotDeleteDefaultUniv3ParameterSet();
    error InvalidTokenPairOrder();
    error InvalidStoredTickLower();
    error TickLowerOutOfRange();
    error TickUpperOutOfRange();
    error InvalidTickRange();
    error TickLowerSpacingInvalid();
    error TickUpperSpacingInvalid();
    error InvalidTickSpacing();
    error InvalidSqrtPrice();
    error Token1MustBeWeth();
    error TokenAllocationExceedsSupply();
    error InvalidInitialBuyAmount();
    error InvalidFeeRecipient();

    mapping(uint256 => TokenInfo) public deployedTokens;
    mapping(address => TokenInfo) public tokenInfoByAddress;
    mapping(address => address) public feeRecipientForToken;
    mapping(address => uint256) public tokenFeesGenerated;
    mapping(address => uint256) public tokenToNFTId;
    mapping(uint256 => Univ3ParameterSet) public uniV3Parameters;

    uint256 public tokenCount = 0;
    uint256 public uniV3ParameterSetCount = 0;
    bool public createTokenEnabled = false;

    address public immutable uniswapV3Factory;
    address public immutable positionManager;
    address public immutable weth;
    address public immutable swapRouter;
    address public immutable robinPadDeployer;
    int24 public immutable tickSpacing;

    uint24 public constant FEE_TIER = 10000;
    int24 private constant MIN_TICK = -887272;
    int24 private constant MAX_TICK = 887272;
    uint256 private constant ITEMS_PER_PAGE = 50;
    uint256 private constant TOKEN_TOTAL_SUPPLY = 1000000000 * 10 ** 18;
    uint256 private constant BPS_DENOMINATOR = 10000;
    uint256 private constant ROBINPAD_FEE_SHARE_BPS = 3000;

    uint256 public initialBuyAmount = 0.00001 ether;

    constructor(
        address uniswapV3Factory_,
        address positionManager_,
        address weth_,
        address swapRouter_
    ) Ownable(msg.sender) {
        if (uniswapV3Factory_ == address(0)) revert InvalidPositionManager();
        if (positionManager_ == address(0)) revert InvalidPositionManager();
        if (weth_ == address(0)) revert InvalidWeth();
        if (swapRouter_ == address(0)) revert InvalidPositionManager();
        if (INonfungiblePositionManager(positionManager_).factory() != uniswapV3Factory_) revert InvalidPositionManager();
        if (INonfungiblePositionManager(positionManager_).WETH9() != weth_) revert InvalidWeth();

        int24 feeTickSpacing = IUniswapV3Factory(uniswapV3Factory_).feeAmountTickSpacing(FEE_TIER);
        if (feeTickSpacing <= 0) revert InvalidTickSpacing();

        uniswapV3Factory = uniswapV3Factory_;
        positionManager = positionManager_;
        weth = weth_;
        swapRouter = swapRouter_;
        robinPadDeployer = msg.sender;
        tickSpacing = feeTickSpacing;

        _validateUniv3ParameterSetTicks(200400, -87400);
        uniV3Parameters[0] = Univ3ParameterSet({
            sqrtPriceX96: 3527495286400061975389249,
            tickLower: 200400,
            tickUpper: -87400,
            amount0Desired: TOKEN_TOTAL_SUPPLY,
            amount1Desired: 0
        });
        uniV3ParameterSetCount = 1;
    }

    receive() external payable {}

    function createToken(
        string memory name,
        string memory symbol,
        string memory metadata,
        address feeRecipient,
        bytes32 salt,
        uint256 split,
        uint256 uniV3ParameterSetId
    ) public payable nonReentrant returns (uint256 tokensReceived) {
        if (!createTokenEnabled) revert DeployDisabled();
        if (uniV3ParameterSetId >= uniV3ParameterSetCount) revert InvalidUniv3ParameterSetId();
        if (msg.value < initialBuyAmount) revert InsufficientInitialBuy();
        if (feeRecipient == address(0)) revert InvalidFeeRecipient();

        uint256 configuredTokenLiquidity = uniV3Parameters[uniV3ParameterSetId].amount0Desired;
        _validateTokenLiquidityAmount(configuredTokenLiquidity);

        bytes32 validSalt = _findUniswapSalt(name, symbol, metadata, salt);
        address tokenAddress = _deployToken(validSalt, name, symbol, metadata);
        if (tokenAddress >= weth) revert InvalidTokenAddressOrder();

        _provideLiquidity(tokenAddress, weth, uniV3ParameterSetId);
        _registerToken(tokenAddress, name, symbol, metadata, split, feeRecipient);

        tokensReceived = _executeInitialBuy(tokenAddress);
        tokensReceived += _handleExtraBuy(tokenAddress);

        tokenCount++;
    }

    function switchCreateToken() external onlyOwner {
        createTokenEnabled = !createTokenEnabled;
        emit CreateTokenSwitched(createTokenEnabled);
    }

    function setInitialBuyAmount(uint256 amount) external onlyOwner {
        if (amount == 0) revert InvalidInitialBuyAmount();
        emit InitialBuyAmountUpdated(initialBuyAmount, amount);
        initialBuyAmount = amount;
    }

    function createUniv3ParameterSet(
        uint160 sqrtPriceX96,
        int24 tickLower,
        int24 tickUpper,
        uint256 amount0Desired,
        uint256 amount1Desired
    ) external onlyOwner returns (uint256 parameterSetId) {
        if (sqrtPriceX96 == 0) revert InvalidSqrtPrice();
        _validateTokenLiquidityAmount(amount0Desired);
        _validateUniv3ParameterSetTicks(tickLower, tickUpper);

        parameterSetId = uniV3ParameterSetCount;
        uniV3Parameters[parameterSetId] = Univ3ParameterSet({
            sqrtPriceX96: sqrtPriceX96,
            tickLower: tickLower,
            tickUpper: tickUpper,
            amount0Desired: amount0Desired,
            amount1Desired: amount1Desired
        });
        uniV3ParameterSetCount++;
        emit Univ3ParameterSetCreated(parameterSetId);
    }

    function updateUniv3ParameterSet(
        uint256 parameterSetId,
        uint160 sqrtPriceX96,
        int24 tickLower,
        int24 tickUpper,
        uint256 amount0Desired,
        uint256 amount1Desired
    ) external onlyOwner {
        if (parameterSetId >= uniV3ParameterSetCount) revert InvalidUniv3ParameterSetId();
        if (sqrtPriceX96 == 0) revert InvalidSqrtPrice();
        _validateTokenLiquidityAmount(amount0Desired);
        _validateUniv3ParameterSetTicks(tickLower, tickUpper);

        uniV3Parameters[parameterSetId] = Univ3ParameterSet({
            sqrtPriceX96: sqrtPriceX96,
            tickLower: tickLower,
            tickUpper: tickUpper,
            amount0Desired: amount0Desired,
            amount1Desired: amount1Desired
        });
        emit Univ3ParameterSetUpdated(parameterSetId);
    }

    function deleteUniv3ParameterSet(uint256 parameterSetId) external onlyOwner {
        if (parameterSetId >= uniV3ParameterSetCount) revert InvalidUniv3ParameterSetId();
        if (parameterSetId == 0) revert CannotDeleteDefaultUniv3ParameterSet();

        delete uniV3Parameters[parameterSetId];
        emit Univ3ParameterSetDeleted(parameterSetId);
    }

    function getDeploysPaginated(uint256 page, uint256 order) public view returns (TokenInfo[] memory) {
        if (tokenCount == 0) revert NoTokensDeployed();

        uint256 totalPages = (tokenCount + ITEMS_PER_PAGE - 1) / ITEMS_PER_PAGE;
        if (page >= totalPages) revert PageOutOfRange();

        uint256 start;
        uint256 end;
        uint256 j = 0;

        if (order == 0) {
            start = tokenCount > (page + 1) * ITEMS_PER_PAGE ? tokenCount - (page + 1) * ITEMS_PER_PAGE : 0;
            end = tokenCount - page * ITEMS_PER_PAGE;
            if (end > tokenCount) end = tokenCount;
        } else {
            start = page * ITEMS_PER_PAGE;
            end = start + ITEMS_PER_PAGE;
            if (end > tokenCount) end = tokenCount;
        }

        TokenInfo[] memory tokens = new TokenInfo[](end - start);
        for (uint256 i = start; i < end; i++) {
            uint256 index = order == 0 ? end - 1 - (i - start) : i;
            TokenInfo memory info = deployedTokens[index];

            tokens[j++] = TokenInfo({
                tokenAddress: info.tokenAddress,
                name: info.name,
                symbol: info.symbol,
                deployer: info.deployer,
                creator: info.creator,
                feeRecipient: info.feeRecipient,
                time: info.time,
                metadata: info.metadata,
                marketCapInETH: getTokenMarketCap(info.tokenAddress),
                split: info.split,
                nftId: info.nftId,
                tokenBalance: 0,
                totalFeesGenerated: tokenFeesGenerated[info.tokenAddress]
            });
        }

        return tokens;
    }

    function collectFees(uint256 tokenId) external nonReentrant returns (uint256 amount0, uint256 amount1) {
        (, , address token0Raw, address token1Raw, , , , , , , , ) =
            INonfungiblePositionManager(positionManager).positions(tokenId);

        address token0 = token0Raw;
        address token1 = token1Raw;
        if (token0Raw == weth && token1Raw != weth) {
            token0 = token1Raw;
            token1 = token0Raw;
        }
        if (token1 != weth) revert Token1MustBeWeth();

        uint256 beforeToken0 = IERC20(token0).balanceOf(address(this));
        uint256 beforeToken1 = IERC20(token1).balanceOf(address(this));

        INonfungiblePositionManager(positionManager).collect(
            INonfungiblePositionManager.CollectParams({
                tokenId: tokenId,
                recipient: address(this),
                amount0Max: type(uint128).max,
                amount1Max: type(uint128).max
            })
        );

        amount0 = IERC20(token0).balanceOf(address(this)) - beforeToken0;
        amount1 = IERC20(token1).balanceOf(address(this)) - beforeToken1;

        address launchFeeRecipient = feeRecipientForToken[token0];
        if (launchFeeRecipient == address(0)) revert InvalidFeeRecipient();

        _transferFeeSplit(token0, amount0, launchFeeRecipient);
        _transferFeeSplit(token1, amount1, launchFeeRecipient);
        tokenFeesGenerated[token0] += amount1;
    }

    function getTokenMarketCap(address tokenAddress) public view returns (uint256 marketCap) {
        address pool = IUniswapV3Factory(uniswapV3Factory).getPool(tokenAddress, weth, FEE_TIER);
        if (pool == address(0)) return 0;

        uint256 totalSupply = IERC20(tokenAddress).totalSupply();
        (uint160 sqrtPriceX96, , , , , , ) = IUniswapV3Pool(pool).slot0();
        if (sqrtPriceX96 == 0 || totalSupply == 0) return 0;

        bool tokenIsToken0 = tokenAddress < weth;
        uint256 scaledSupply = totalSupply / 1e12;

        if (tokenIsToken0) {
            uint256 scaledPrice = (uint256(sqrtPriceX96) * sqrtPriceX96) >> 96;
            marketCap = (scaledSupply * scaledPrice) >> 96;
        } else {
            uint256 denominator = (uint256(sqrtPriceX96) * sqrtPriceX96) >> 96;
            if (denominator > 0) {
                marketCap = (scaledSupply << 96) / denominator;
            }
        }
    }

    function _deployToken(
        bytes32 salt,
        string memory name,
        string memory symbol,
        string memory metadata
    ) internal returns (address tokenAddress) {
        tokenAddress = address(new RobinPadToken{salt: salt}(name, symbol, metadata, positionManager, weth));
        emit ERC20TokenCreated(tokenAddress);
    }

    function _registerToken(
        address tokenAddress,
        string memory name,
        string memory symbol,
        string memory metadata,
        uint256 split,
        address feeRecipient
    ) internal {
        TokenInfo memory newTokenInfo = TokenInfo({
            tokenAddress: tokenAddress,
            name: name,
            symbol: symbol,
            deployer: msg.sender,
            creator: msg.sender,
            feeRecipient: feeRecipient,
            time: block.timestamp,
            metadata: metadata,
            marketCapInETH: 0,
            split: split,
            nftId: tokenToNFTId[tokenAddress],
            tokenBalance: 0,
            totalFeesGenerated: 0
        });

        deployedTokens[tokenCount] = newTokenInfo;
        tokenInfoByAddress[tokenAddress] = newTokenInfo;
        feeRecipientForToken[tokenAddress] = feeRecipient;
        emit TokenFeeRecipientSet(tokenAddress, feeRecipient);
    }

    function _executeInitialBuy(address tokenAddress) internal returns (uint256 tokensReceived) {
        IWETH(weth).deposit{value: initialBuyAmount}();
        IERC20(weth).approve(swapRouter, initialBuyAmount);

        tokensReceived = ISwapRouter02(swapRouter).exactInputSingle(
            ISwapRouter02.ExactInputSingleParams({
                tokenIn: weth,
                tokenOut: tokenAddress,
                fee: FEE_TIER,
                recipient: msg.sender,
                amountIn: initialBuyAmount,
                amountOutMinimum: 0,
                sqrtPriceLimitX96: 0
            })
        );
    }

    function _handleExtraBuy(address tokenAddress) internal returns (uint256 tokensReceived) {
        uint256 extraEth = msg.value - initialBuyAmount;
        if (extraEth == 0) return 0;

        IWETH(weth).deposit{value: extraEth}();
        IERC20(weth).approve(swapRouter, extraEth);

        tokensReceived = ISwapRouter02(swapRouter).exactInputSingle(
            ISwapRouter02.ExactInputSingleParams({
                tokenIn: weth,
                tokenOut: tokenAddress,
                fee: FEE_TIER,
                recipient: msg.sender,
                amountIn: extraEth,
                amountOutMinimum: 0,
                sqrtPriceLimitX96: 0
            })
        );
    }

    function _provideLiquidity(address tokenA, address tokenB, uint256 parameterSetId) internal {
        Univ3ParameterSet memory parameterSet = uniV3Parameters[parameterSetId];
        _validateUniv3ParameterSetTicks(parameterSet.tickLower, parameterSet.tickUpper);

        if (tokenA >= tokenB) revert InvalidTokenPairOrder();

        IERC20(tokenA).approve(positionManager, type(uint256).max);
        IERC20(tokenB).approve(positionManager, type(uint256).max);

        INonfungiblePositionManager manager = INonfungiblePositionManager(positionManager);
        manager.createAndInitializePoolIfNecessary(tokenA, tokenB, FEE_TIER, parameterSet.sqrtPriceX96);

        (uint256 tokenId, , , ) = manager.mint(
            INonfungiblePositionManager.MintParams({
                token0: tokenA,
                token1: tokenB,
                fee: FEE_TIER,
                tickLower: -parameterSet.tickLower,
                tickUpper: parameterSet.tickUpper,
                amount0Desired: parameterSet.amount0Desired,
                amount1Desired: parameterSet.amount1Desired,
                amount0Min: 0,
                amount1Min: 0,
                recipient: address(this),
                deadline: block.timestamp
            })
        );

        tokenToNFTId[tokenA] = tokenId;
    }

    function _transferFeeSplit(address token, uint256 amount, address launchFeeRecipient) internal {
        if (amount == 0) return;

        uint256 robinPadAmount = (amount * ROBINPAD_FEE_SHARE_BPS) / BPS_DENOMINATOR;
        uint256 launchAmount = amount - robinPadAmount;

        if (launchAmount > 0) {
            IERC20(token).safeTransfer(launchFeeRecipient, launchAmount);
        }
        if (robinPadAmount > 0) {
            IERC20(token).safeTransfer(robinPadDeployer, robinPadAmount);
        }
    }

    function _getTokenBytecode(
        string memory name,
        string memory symbol,
        string memory metadata
    ) internal view returns (bytes memory bytecode) {
        bytecode = abi.encodePacked(
            type(RobinPadToken).creationCode,
            abi.encode(name, symbol, metadata, positionManager, weth)
        );
    }

    function _predictTokenAddress(
        string memory name,
        string memory symbol,
        string memory metadata,
        bytes32 salt
    ) internal view returns (address) {
        bytes memory bytecode = _getTokenBytecode(name, symbol, metadata);
        bytes32 hash = keccak256(
            abi.encodePacked(
                bytes1(0xff),
                address(this),
                salt,
                keccak256(bytecode)
            )
        );
        return address(uint160(uint256(hash)));
    }

    function _findUniswapSalt(
        string memory name,
        string memory symbol,
        string memory metadata,
        bytes32 baseSalt
    ) internal view returns (bytes32) {
        bytes32 salt = baseSalt;
        uint256 nonce = 0;

        while (nonce < 1000) {
            address predictedAddress = _predictTokenAddress(name, symbol, metadata, salt);
            if (predictedAddress < weth) return salt;
            salt = keccak256(abi.encodePacked(baseSalt, nonce));
            nonce++;
        }

        revert("Could not find v3-compatible salt");
    }

    function _validateUniv3ParameterSetTicks(int24 storedTickLower, int24 storedTickUpper) internal view {
        if (storedTickLower <= 0) revert InvalidStoredTickLower();

        int24 actualTickLower = -storedTickLower;
        int24 minUsableTick = (MIN_TICK / tickSpacing) * tickSpacing;
        int24 maxUsableTick = (MAX_TICK / tickSpacing) * tickSpacing;
        if (actualTickLower < minUsableTick) revert TickLowerOutOfRange();
        if (storedTickUpper < minUsableTick || storedTickUpper > maxUsableTick) revert TickUpperOutOfRange();
        if (actualTickLower >= storedTickUpper) revert InvalidTickRange();
        if (actualTickLower % tickSpacing != 0) revert TickLowerSpacingInvalid();
        if (storedTickUpper % tickSpacing != 0) revert TickUpperSpacingInvalid();
    }

    function _validateTokenLiquidityAmount(uint256 amount0Desired) internal pure {
        if (amount0Desired > TOKEN_TOTAL_SUPPLY) revert TokenAllocationExceedsSupply();
    }
}
Chain explorer5338msChain node116ms