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
- Creator
- 0x9008C9eE6e…13083680c6
- 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