StoxesFunFactory
0x3a5a312b66a3f3aabb647e75e8e81ccc08b7bb58
Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
5,722 bytes
ABI entries
36
19 read · 4 write
License
none
Contract information
- Address
- 0x3a5a312b66a3f3aabb647e75e8e81ccc08b7bb58
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.26+commit.8a97fa7a
- Optimization
- Enabled
- Creator
- 0x9Cf1e0bff5…CcA354c20B
- Creation tx
- 0x7703556e26…883208df53
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (19)
MAX_STOCKS() → uint256
MAX_TAX_BPS() → uint16
MIN_TAX_BPS() → uint16
VANITY_SUFFIX() → uint16
WETH() → address
allTokens(uint256) → address
allTokensLength() → uint256
computeTokenAddress(address, bytes32) → address
creatorOf(address) → address
earnings() → address
keeper() → address
owner() → address
portal() → address
stockRegistry(address) → bool
tokenImplementation() → address
tokenInitCodeHash() → bytes32
trackerImplementation() → address
trackerOf(address) → address
uniswapV2Factory() → address
Events (4)
KeeperChangedOwnershipTransferredStockRegistrySetTokenLaunched
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "tokenImplementation_",
"type": "address"
},
{
"internalType": "address",
"name": "trackerImplementation_",
"type": "address"
},
{
"internalType": "address",
"name": "portal_",
"type": "address"
},
{
"internalType": "address",
"name": "earnings_",
"type": "address"
},
{
"internalType": "address",
"name": "uniswapV2Factory_",
"type": "address"
},
{
"internalType": "address",
"name": "weth_",
"type": "address"
},
{
"internalType": "address",
"name": "keeper_",
"type": "address"
},
{
"internalType": "address",
"name": "admin_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "CloneCreationFailed",
"type": "error"
},
{
"inputs": [],
"name": "InvalidParams",
"type": "error"
},
{
"inputs": [],
"name": "NotOwner",
"type": "error"
},
{
"inputs": [],
"name": "Reentrancy",
"type": "error"
},
{
"inputs": [],
"name": "StockNotRegistered",
"type": "error"
},
{
"inputs": [],
"name": "TaxOutOfBounds",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "VanitySuffixMismatch",
"type": "error"
},
{
"inputs": [],
"name": "ZeroAddress",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "oldKeeper",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newKeeper",
"type": "address"
}
],
"name": "KeeperChanged",
"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": "stock",
"type": "address"
},
{
"indexed": false,
"internalType": "bool",
"name": "allowed",
"type": "bool"
}
],
"name": "StockRegistrySet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"indexed": false,
"internalType": "address[]",
"name": "stocks",
"type": "address[]"
},
{
"indexed": false,
"internalType": "address",
"name": "tracker",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "pair",
"type": "address"
},
{
"indexed": false,
"internalType": "uint16",
"name": "buyTaxBps",
"type": "uint16"
},
{
"indexed": false,
"internalType": "uint16",
"name": "sellTaxBps",
"type": "uint16"
}
],
"name": "TokenLaunched",
"type": "event"
},
{
"inputs": [],
"name": "MAX_STOCKS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_TAX_BPS",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MIN_TAX_BPS",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "VANITY_SUFFIX",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "WETH",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "allTokens",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "allTokensLength",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "deployer",
"type": "address"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
}
],
"name": "computeTokenAddress",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "creatorOf",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "earnings",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "keeper",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "string",
"name": "metaURI",
"type": "string"
},
{
"internalType": "uint16",
"name": "buyTaxBps",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "sellTaxBps",
"type": "uint16"
},
{
"internalType": "address[]",
"name": "stocks",
"type": "address[]"
},
{
"internalType": "address",
"name": "creatorWallet",
"type": "address"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
}
],
"internalType": "struct StoxesFunFactory.LaunchParams",
"name": "params",
"type": "tuple"
}
],
"name": "launch",
"outputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "tracker",
"type": "address"
},
{
"internalType": "address",
"name": "pair",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "portal",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newKeeper",
"type": "address"
}
],
"name": "setKeeper",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "stock",
"type": "address"
},
{
"internalType": "bool",
"name": "allowed",
"type": "bool"
}
],
"name": "setStockRegistered",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "stockRegistry",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "tokenImplementation",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "tokenInitCodeHash",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "trackerImplementation",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "trackerOf",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "uniswapV2Factory",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {OwnableLite, ReentrancyGuardLite} from "./base/Common.sol";
import {Clones} from "./vendor/Clones.sol";
import {
IStoxesFunTaxToken,
IStoxesFunDividendTracker,
IStoxesFunEarnings
} from "./interfaces/IStoxesFun.sol";
import {IUniswapV2Factory} from "./interfaces/IUniswapV2Factory.sol";
interface IERC20Transfer {
function transfer(address to, uint256 value) external returns (bool);
}
interface IPortalRegister {
function registerToken(address token, address pair) external;
}
/// @title StoxesFunFactory — the stoxes.fun launch entry point on Robinhood Chain
/// @author stoxes.fun
/// @notice Self-serve launcher: anyone deploys a stoxes.fun tax token with a chosen
/// name, ticker, socials URI, buy/sell tax (1%–10% per side, immutable) and a
/// dividend stock chosen from the curated registry of official Robinhood
/// Chain stock tokens. Uses the same clone-factory pattern as flap.sh's
/// verified Portal (one audited implementation, EIP-1167 clones per launch).
///
/// Launch wiring (single transaction):
/// 1. clone + initialize the dividend tracker (stock + keeper + admin);
/// 2. pre-create the Uniswap V2 pair (token/WETH) so the token's pool
/// registry is complete from block one;
/// 3. clone + initialize the tax token (full 1B supply minted here);
/// 4. hand the supply + token ownership to the Portal and register the
/// curve;
/// 5. lock the creator's earnings wallet in the earnings splitter
/// (immutable — the 0.25pt/side creator cut always pays there).
contract StoxesFunFactory is OwnableLite, ReentrancyGuardLite {
/// @notice Platform tax bounds: 1%–10% per side, immutable platform-wide.
uint16 public constant MIN_TAX_BPS = 100;
uint16 public constant MAX_TAX_BPS = 1000;
/// @notice Every launched token address must end in this suffix ("SAFE") —
/// same vanity contract as the non-taxed V3 launch mode. The token
/// clone is CREATE2-deployed, so the address is mined off-chain
/// against the constant EIP-1167 init-code hash and ENFORCED here.
uint16 public constant VANITY_SUFFIX = 0x5afe;
/// @notice Hard bound on dividend stocks per launch (mirrors the tracker's bound).
uint256 public constant MAX_STOCKS = 10;
/// @notice The audited implementations cloned per launch.
address public immutable tokenImplementation;
address public immutable trackerImplementation;
/// @notice Core wiring (immutable at deploy).
address public immutable portal;
address public immutable earnings;
address public immutable uniswapV2Factory;
address public immutable WETH;
/// @notice The stoxes.fun keeper wallet baked into each launch.
address public keeper;
/// @notice Curated registry of official Robinhood Chain stock tokens usable as
/// dividend assets.
mapping(address => bool) public stockRegistry;
/// @notice All launched tokens, in launch order.
address[] public allTokens;
/// @notice token => tracker / creator lookups.
mapping(address => address) public trackerOf;
mapping(address => address) public creatorOf;
struct LaunchParams {
string name;
string symbol;
string metaURI; // stoxes.fun-hosted metadata JSON (socials, logo)
uint16 buyTaxBps; // 100..1000, immutable
uint16 sellTaxBps; // 100..1000, immutable
address[] stocks; // dividend stocks from the registry (1..MAX_STOCKS)
address creatorWallet; // immutable earnings wallet
bytes32 salt; // browser-mined so the token address ends in 0x5afe
}
event TokenLaunched(
address indexed token,
address indexed creator,
address[] stocks,
address tracker,
address pair,
uint16 buyTaxBps,
uint16 sellTaxBps
);
event StockRegistrySet(address indexed stock, bool allowed);
event KeeperChanged(address indexed oldKeeper, address indexed newKeeper);
error TaxOutOfBounds();
error StockNotRegistered();
error InvalidParams();
error VanitySuffixMismatch(address token);
constructor(
address tokenImplementation_,
address trackerImplementation_,
address portal_,
address earnings_,
address uniswapV2Factory_,
address weth_,
address keeper_,
address admin_
) {
if (
tokenImplementation_ == address(0) || trackerImplementation_ == address(0)
|| portal_ == address(0) || earnings_ == address(0) || uniswapV2Factory_ == address(0)
|| weth_ == address(0) || keeper_ == address(0)
) revert InvalidParams();
tokenImplementation = tokenImplementation_;
trackerImplementation = trackerImplementation_;
portal = portal_;
earnings = earnings_;
uniswapV2Factory = uniswapV2Factory_;
WETH = weth_;
keeper = keeper_;
_initOwner(admin_);
}
/// @notice Launches a new stoxes.fun tax token. Fully self-serve.
function launch(LaunchParams calldata params)
external
nonReentrant
returns (address token, address tracker, address pair)
{
if (
params.buyTaxBps < MIN_TAX_BPS || params.buyTaxBps > MAX_TAX_BPS
|| params.sellTaxBps < MIN_TAX_BPS || params.sellTaxBps > MAX_TAX_BPS
) revert TaxOutOfBounds();
uint256 stockCount = params.stocks.length;
if (stockCount == 0 || stockCount > MAX_STOCKS) revert InvalidParams();
for (uint256 i = 0; i < stockCount; i++) {
if (!stockRegistry[params.stocks[i]]) revert StockNotRegistered();
for (uint256 j = 0; j < i; j++) {
if (params.stocks[j] == params.stocks[i]) revert InvalidParams();
}
}
if (params.creatorWallet == address(0)) revert InvalidParams();
if (bytes(params.name).length == 0 || bytes(params.symbol).length == 0) {
revert InvalidParams();
}
// 1. Dividend tracker clone (admin = factory owner for keeper rotation only).
tracker = Clones.clone(trackerImplementation);
// 2. Token clone address is needed for the pair; clone first, init after.
// CREATE2 with the browser-mined salt, bound to the caller so a
// front-runner cannot steal a mined address. The vanity suffix is
// ENFORCED — a launch whose address does not end in 0x5afe reverts.
token = Clones.cloneDeterministic(
tokenImplementation, keccak256(abi.encode(msg.sender, params.salt))
);
if (uint16(uint160(token)) != VANITY_SUFFIX) revert VanitySuffixMismatch(token);
IStoxesFunDividendTracker(tracker).initialize(token, params.stocks, keeper, owner());
// 3. Pre-create the V2 pair so it is in the pool registry from block one.
// If a griefer pre-created the pair at the predicted CREATE2 address,
// reuse it instead of letting `createPair` revert and brick the launch.
pair = IUniswapV2Factory(uniswapV2Factory).getPair(token, WETH);
if (pair == address(0)) {
pair = IUniswapV2Factory(uniswapV2Factory).createPair(token, WETH);
}
address[] memory pools = new address[](1);
pools[0] = pair;
IStoxesFunTaxToken(token).initialize(
IStoxesFunTaxToken.InitParams({
name: params.name,
symbol: params.symbol,
metaURI: params.metaURI,
buyTaxBps: params.buyTaxBps,
sellTaxBps: params.sellTaxBps,
taxProcessor: keeper,
dividendTracker: tracker,
portal: portal,
pools: pools
})
);
// 4. The token has NO owner (owner() == 0 from birth — renounced for
// scanners). The Portal is its lifecycle authority via initialize,
// and receives the curve inventory here.
IERC20Transfer(token).transfer(portal, 1e9 ether);
IPortalRegister(portal).registerToken(token, pair);
// 5. Lock the creator's earnings wallet forever.
IStoxesFunEarnings(earnings).registerToken(token, params.creatorWallet);
allTokens.push(token);
trackerOf[token] = tracker;
creatorOf[token] = msg.sender;
emit TokenLaunched(
token, msg.sender, params.stocks, tracker, pair, params.buyTaxBps, params.sellTaxBps
);
}
// ------------------------------------------------------------------
// Admin
// ------------------------------------------------------------------
/// @notice Curates the official Robinhood stock-token registry.
function setStockRegistered(address stock, bool allowed) external onlyOwner {
if (stock == address(0)) revert InvalidParams();
stockRegistry[stock] = allowed;
emit StockRegistrySet(stock, allowed);
}
/// @notice Rotates the keeper used for FUTURE launches (existing tokens rotate
/// via the Portal / tracker admin paths).
function setKeeper(address newKeeper) external onlyOwner {
if (newKeeper == address(0)) revert InvalidParams();
emit KeeperChanged(keeper, newKeeper);
keeper = newKeeper;
}
// ------------------------------------------------------------------
// Views
// ------------------------------------------------------------------
function allTokensLength() external view returns (uint256) {
return allTokens.length;
}
/// @notice The CREATE2 clone address `salt` would produce for `deployer` —
/// the browser miner's ground truth (staticCall to sanity-check a
/// salt before sending the launch tx).
function computeTokenAddress(address deployer, bytes32 salt) external view returns (address) {
return Clones.predictDeterministicAddress(
tokenImplementation, keccak256(abi.encode(deployer, salt)), address(this)
);
}
/// @notice The constant EIP-1167 init-code hash the in-browser salt miner
/// needs (a pure function of the token implementation address).
function tokenInitCodeHash() external view returns (bytes32) {
return Clones.initCodeHash(tokenImplementation);
}
}
Chain explorer2114msChain node72ms