RedditFactory

0x8c5dffa69eccb132b256ef963db04a124bebe0d2

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
8,639 bytes
ABI entries
34
13 read · 7 write
License
none

Contract information

Address
0x8c5dffa69eccb132b256ef963db04a124bebe0d2
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0x718d319d76…9eacfc8460

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (13)

FEE()uint24
MAX_USABLE_TICK()int24
MIN_USABLE_TICK()int24
NPM()address
SUPPLY()uint256
TICK_SPACING()int24
WETH()address
launcher()address
onERC721Received(address, address, uint256, bytes)bytes4
owner()address
pendingOwner()address
renounceOwnership()
tokenByPost(bytes32)address

Events (4)

LaunchedLauncherChangedOwnershipTransferStartedOwnershipTransferred

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "_launcher",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "_npm",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "AlreadyLaunched",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "BadTick",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotLauncher",
    "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": "PoolPrecreated",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "RenounceDisabled",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "SafeERC20FailedOperation",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "pool",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "lpTokenId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "redditId",
        "type": "string"
      }
    ],
    "name": "Launched",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "newLauncher",
        "type": "address"
      }
    ],
    "name": "LauncherChanged",
    "type": "event"
  },
  {
    "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"
  },
  {
    "inputs": [],
    "name": "FEE",
    "outputs": [
      {
        "internalType": "uint24",
        "name": "",
        "type": "uint24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_USABLE_TICK",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MIN_USABLE_TICK",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "NPM",
    "outputs": [
      {
        "internalType": "contract INonfungiblePositionManager",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "SUPPLY",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "TICK_SPACING",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "WETH",
    "outputs": [
      {
        "internalType": "contract IWETH9",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "acceptOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "id",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      }
    ],
    "name": "collect",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "a0",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "a1",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      },
      {
        "internalType": "bytes32",
        "name": "salt",
        "type": "bytes32"
      },
      {
        "internalType": "int24",
        "name": "launchTick",
        "type": "int24"
      },
      {
        "internalType": "string",
        "name": "redditId",
        "type": "string"
      }
    ],
    "name": "launch",
    "outputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "pool",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "lpTokenId",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "launcher",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      },
      {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }
    ],
    "name": "onERC721Received",
    "outputs": [
      {
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      }
    ],
    "stateMutability": "pure",
    "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": "renounceOwnership",
    "outputs": [],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "a",
        "type": "address"
      }
    ],
    "name": "setLauncher",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      }
    ],
    "name": "sweepEth",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "t",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      }
    ],
    "name": "sweepToken",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "name": "tokenByPost",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "stateMutability": "payable",
    "type": "receive"
  }
]

Source code

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

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20 as IERC20OZ} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {TickMath} from "./lib/TickMath.sol";
import {RedditToken} from "./RedditToken.sol";
import {IERC20} from "./interfaces/IERC20.sol";
import {IWETH9} from "./interfaces/IWETH9.sol";
import {INonfungiblePositionManager} from "./interfaces/INonfungiblePositionManager.sol";
import {IUniswapV3Pool} from "./interfaces/IUniswapV3Pool.sol";

contract RedditFactory is Ownable2Step, IERC721Receiver {
    using SafeERC20 for IERC20OZ;

    INonfungiblePositionManager public immutable NPM;
    IWETH9 public constant WETH = IWETH9(0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73);
    uint24 public constant FEE = 10000;
    int24 public constant TICK_SPACING = 200;
    int24 public constant MIN_USABLE_TICK = -887200;
    int24 public constant MAX_USABLE_TICK = 887200;
    uint256 public constant SUPPLY = 1_000_000_000e18;

    address public launcher;
    mapping(bytes32 => address) public tokenByPost;

    event Launched(
        address indexed token, address indexed pool, uint256 lpTokenId, string name, string symbol, string redditId
    );
    event LauncherChanged(address indexed newLauncher);

    error NotLauncher();
    error BadTick();
    error AlreadyLaunched();
    error PoolPrecreated();
    error RenounceDisabled();

    modifier onlyLauncher() {
        if (msg.sender != launcher) revert NotLauncher();
        _;
    }

    constructor(address _launcher, address _npm) Ownable(msg.sender) {
        NPM = INonfungiblePositionManager(_npm);
        launcher = _launcher;
        emit LauncherChanged(_launcher);
    }

    function setLauncher(address a) external onlyOwner {
        launcher = a;
        emit LauncherChanged(a);
    }

    function launch(
        string calldata name,
        string calldata symbol,
        bytes32 salt,
        int24 launchTick,
        string calldata redditId
    ) external onlyLauncher returns (address token, address pool, uint256 lpTokenId) {
        if (launchTick % TICK_SPACING != 0 || launchTick <= MIN_USABLE_TICK || launchTick >= MAX_USABLE_TICK) {
            revert BadTick();
        }
        bytes32 key = keccak256(bytes(redditId));
        if (tokenByPost[key] != address(0)) revert AlreadyLaunched();

        token = address(new RedditToken{salt: keccak256(abi.encode(salt, redditId))}(name, symbol));
        require(token > address(WETH), "token must sort above WETH");

        (pool, lpTokenId) = _initPoolAndMint(token, launchTick);

        tokenByPost[key] = token;
        emit Launched(token, pool, lpTokenId, name, symbol, redditId);
    }

    function _initPoolAndMint(address token, int24 launchTick) internal returns (address pool, uint256 lpTokenId) {
        pool =
            NPM.createAndInitializePoolIfNecessary(address(WETH), token, FEE, TickMath.getSqrtRatioAtTick(launchTick));

        (, int24 curTick,,,,,) = IUniswapV3Pool(pool).slot0();
        if (curTick != launchTick) revert PoolPrecreated();

        IERC20(token).approve(address(NPM), SUPPLY);
        (lpTokenId,,,) = NPM.mint(
            INonfungiblePositionManager.MintParams({
                token0: address(WETH),
                token1: token,
                fee: FEE,
                tickLower: MIN_USABLE_TICK,
                tickUpper: launchTick,
                amount0Desired: 0,
                amount1Desired: SUPPLY,
                amount0Min: 0,
                amount1Min: 0,
                recipient: address(this),
                deadline: block.timestamp
            })
        );
    }

    function collect(uint256 id, address to) external onlyOwner returns (uint256 a0, uint256 a1) {
        (a0, a1) = NPM.collect(INonfungiblePositionManager.CollectParams(id, to, type(uint128).max, type(uint128).max));
    }

    function sweepToken(address t, address to) external onlyOwner {
        IERC20OZ(t).safeTransfer(to, IERC20OZ(t).balanceOf(address(this)));
    }

    function renounceOwnership() public view override onlyOwner {
        revert RenounceDisabled();
    }

    function sweepEth(address to) external onlyOwner {
        (bool ok,) = to.call{value: address(this).balance}("");
        require(ok, "ETH transfer failed");
    }

    function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4) {
        return IERC721Receiver.onERC721Received.selector;
    }

    receive() external payable {}
}
Chain explorer4763msChain node83ms