StockFactory

0x4714ca6ee4ffcf7c75614d203ff6736c4862e34c

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
16,578 bytes
ABI entries
56
29 read · 16 write
License
none

Contract information

Address
0x4714ca6ee4ffcf7c75614d203ff6736c4862e34c
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0x083151a8e9…111556db7a

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (29)

FACTORY()address
POSITION_MANAGER()address
SWAP_ROUTER()address
WBNB()address
_toHex(bytes)string
buybackPool()address
calculateSqrtPriceX96(uint256, uint256, uint256)uint160
conveyor()address
deployCoinEnabled()bool
deployedTokens(uint256)address, string, string, address, address, uint256, string, uint256
deployerTokens(address, uint256)address
devAddress()address
getAllTokensByDeployer(address)address[]
getTokenFeesClaimed(address)uint256
getTokenFeesGenerated(address)uint256
getTokenMarketCapInQuote(address)uint256
getTokenStats(address)uint256, uint256, address, address
indexer()address
platformController()address
quoteTokenOf(address)address
stockConfigCount()uint256
stockConfigs(uint256)address, uint160, int24, int24, uint256, uint256, bool
tokenCount()uint256
tokenCreators(address)address
tokenFeesClaimed(address)uint256
tokenFeesGenerated(address)uint256
tokenInfoByAddress(address)address, string, string, address, address, uint256, string, uint256
tokenToNFTId(address)uint256
tokenToPool(address)address

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "_indexer",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "_buybackPool",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "_devAddress",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "DeploymentDisabled",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidConfigId",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NoBNBToWithdraw",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NoPosition",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotController",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "PoolTokenMismatch",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "QuoteTokenNotSet",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ReentrancyGuardReentrantCall",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TokenMustBeToken0",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "FACTORY",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "POSITION_MANAGER",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "SWAP_ROUTER",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "WBNB",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes",
        "name": "buffer",
        "type": "bytes"
      }
    ],
    "name": "_toHex",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "_quoteToken",
        "type": "address"
      },
      {
        "internalType": "uint160",
        "name": "_sqrtPriceX96",
        "type": "uint160"
      },
      {
        "internalType": "int24",
        "name": "_tickLower",
        "type": "int24"
      },
      {
        "internalType": "int24",
        "name": "_tickUpper",
        "type": "int24"
      },
      {
        "internalType": "uint256",
        "name": "_quoteAmountDesired",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "_tokenAmountDesired",
        "type": "uint256"
      }
    ],
    "name": "addStockConfig",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "configId",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "_quoteToken",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "totalSupply",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "targetMcapUsd",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "quoteTokenUsdPrice",
        "type": "uint256"
      },
      {
        "internalType": "int24",
        "name": "_tickLower",
        "type": "int24"
      },
      {
        "internalType": "int24",
        "name": "_tickUpper",
        "type": "int24"
      },
      {
        "internalType": "uint256",
        "name": "_quoteAmountDesired",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "_tokenAmountDesired",
        "type": "uint256"
      }
    ],
    "name": "addStockConfigForTargetMcap",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "configId",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "buybackPool",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "totalSupply",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "targetMcapUsd",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "quoteTokenUsdPrice",
        "type": "uint256"
      }
    ],
    "name": "calculateSqrtPriceX96",
    "outputs": [
      {
        "internalType": "uint160",
        "name": "",
        "type": "uint160"
      }
    ],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "tokenAddress",
        "type": "address"
      }
    ],
    "name": "collectStockFeesAndBuyback",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "tokenFeesCollected",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "quoteFeesCollected",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "creatorQuotePaid",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "conveyor",
    "outputs": [
      {
        "internalType": "contract StockFactoryConveyor",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "deployCoinEnabled",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "_name",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "_symbol",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "_metadata",
        "type": "string"
      },
      {
        "internalType": "bytes32",
        "name": "saltSeed",
        "type": "bytes32"
      },
      {
        "internalType": "uint256",
        "name": "stockConfigId",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "initialBuyQuoteAmount",
        "type": "uint256"
      }
    ],
    "name": "deployStockCoin",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "tokensReceived",
        "type": "uint256"
      }
    ],
    "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": "quoteToken",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "time",
        "type": "uint256"
      },
      {
        "internalType": "string",
        "name": "metadata",
        "type": "string"
      },
      {
        "internalType": "uint256",
        "name": "nftId",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "deployerTokens",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "devAddress",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "_deployer",
        "type": "address"
      }
    ],
    "name": "getAllTokensByDeployer",
    "outputs": [
      {
        "internalType": "address[]",
        "name": "",
        "type": "address[]"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "tokenAddress",
        "type": "address"
      }
    ],
    "name": "getTokenFeesClaimed",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "tokenAddress",
        "type": "address"
      }
    ],
    "name": "getTokenFeesGenerated",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "tokenAddress",
        "type": "address"
      }
    ],
    "name": "getTokenMarketCapInQuote",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "marketCapInQuote",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "tokenAddress",
        "type": "address"
      }
    ],
    "name": "getTokenStats",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "marketCapInQuote",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "totalFeesGenerated",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "creatorAddress",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "quoteToken",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "indexer",
    "outputs": [
      {
        "internalType": "contract IIndexer",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "platformController",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "quoteTokenOf",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "rescueERC20",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "pool",
        "type": "address"
      }
    ],
    "name": "setBuybackPool",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "_conveyor",
        "type": "address"
      }
    ],
    "name": "setConveyor",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "_devAddress",
        "type": "address"
      }
    ],
    "name": "setDevAddress",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "_factory",
        "type": "address"
      }
    ],
    "name": "setFactory",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "_newIndexer",
        "type": "address"
      }
    ],
    "name": "setIndexer",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "configId",
        "type": "uint256"
      },
      {
        "internalType": "bool",
        "name": "enabled",
        "type": "bool"
      }
    ],
    "name": "setStockConfigEnabled",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "newCreator",
        "type": "address"
      }
    ],
    "name": "setTokenCreator",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "stockConfigCount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "stockConfigs",
    "outputs": [
      {
        "internalType": "address",
        "name": "quoteToken",
        "type": "address"
      },
      {
        "internalType": "uint160",
        "name": "sqrtPriceX96",
        "type": "uint160"
      },
      {
        "internalType": "int24",
        "name": "tickLower",
        "type": "int24"
      },
      {
        "internalType": "int24",
        "name": "tickUpper",
        "type": "int24"
      },
      {
        "internalType": "uint256",
        "name": "quoteAmountDesired",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "tokenAmountDesired",
        "type": "uint256"
      },
      {
        "internalType": "bool",
        "name": "enabled",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "toggleDeployCoin",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "tokenCount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "tokenCreators",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "tokenFeesClaimed",
    "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": "quoteToken",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "time",
        "type": "uint256"
      },
      {
        "internalType": "string",
        "name": "metadata",
        "type": "string"
      },
      {
        "internalType": "uint256",
        "name": "nftId",
        "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": "",
        "type": "address"
      }
    ],
    "name": "tokenToPool",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "configId",
        "type": "uint256"
      },
      {
        "internalType": "uint160",
        "name": "_sqrtPriceX96",
        "type": "uint160"
      },
      {
        "internalType": "int24",
        "name": "_tickLower",
        "type": "int24"
      },
      {
        "internalType": "int24",
        "name": "_tickUpper",
        "type": "int24"
      },
      {
        "internalType": "uint256",
        "name": "_quoteAmountDesired",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "_tokenAmountDesired",
        "type": "uint256"
      }
    ],
    "name": "updateStockConfig",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "configId",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "totalSupply",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "targetMcapUsd",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "quoteTokenUsdPrice",
        "type": "uint256"
      },
      {
        "internalType": "int24",
        "name": "_tickLower",
        "type": "int24"
      },
      {
        "internalType": "int24",
        "name": "_tickUpper",
        "type": "int24"
      },
      {
        "internalType": "uint256",
        "name": "_quoteAmountDesired",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "_tokenAmountDesired",
        "type": "uint256"
      }
    ],
    "name": "updateStockConfigForTargetMcap",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "withdrawFeesBNB",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "stateMutability": "payable",
    "type": "receive"
  }
]

Source code

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

import "./Indexer.sol";
import "./StockFactoryConveyor.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

error QuoteTokenNotSet();
error PoolTokenMismatch();

/// @notice Stock-paired launchpad: a sibling to Factory.sol, not a
/// replacement. Factory.sol always pairs a new token's pool against WBNB;
/// this contract pairs it against an admin-whitelisted tokenized stock
/// (TSLA, AAPL, SPY, ...) instead, so the pool - and creator fees - are
/// denominated in that stock rather than the native coin. Deployed
/// separately and authorized on the same Indexer; the live Factory is
/// untouched.
///
/// The creator supplies the quote token (e.g. TSLA) directly, via
/// transferFrom after approving this contract - not ETH. An earlier version
/// of this contract took ETH and swapped it into the quote token internally,
/// but that meant every launch depended on a liquid WBNB/quoteToken pool
/// existing at a specific fee tier. Confirmed on real chain state that this
/// doesn't hold: TSLA's liquid pool is at fee 3000, and AAPL currently has
/// NO liquid WBNB pool at all - a hardcoded/single fee tier can't work, and
/// even a per-config fee tier still fails for stocks with no market at all.
/// Taking the quote token directly sidesteps that entirely: no swap, no
/// slippage/sandwich risk, and it works for any stock the creator can get
/// their hands on, whether or not it has WBNB liquidity on this chain.
contract StockFactory is ReentrancyGuard {
    struct TokenInfo {
        address tokenAddress;
        string name;
        string symbol;
        address deployer;
        address quoteToken;
        uint256 time;
        string metadata;
        uint256 nftId;
    }

    /// @dev One config per supported stock. sqrtPriceX96 must be calibrated
    /// off-chain to that stock's current USD price whenever it moves
    /// meaningfully - same limitation the live Factory already has for its
    /// single ETH config, just now per-stock instead of a one-time constant.
    struct StockLiquidityConfig {
        address quoteToken;
        uint160 sqrtPriceX96;
        int24 tickLower;
        int24 tickUpper;
        // Deliberately NOT derived from the target mcap math - same as the
        // live Factory's own ETH config, this is a small dust amount that
        // only partially fills the position at the given tick range; the
        // sqrtPriceX96 (not this) is what actually sets the starting mcap.
        uint256 quoteAmountDesired;
        uint256 tokenAmountDesired; // new token amount seeded on the other side
        bool enabled;
    }

    IIndexer public indexer;

    mapping(uint256 => TokenInfo) public deployedTokens;
    mapping(address => TokenInfo) public tokenInfoByAddress;
    uint256 public tokenCount;

    address public platformController;
    bool public deployCoinEnabled;

    mapping(address => address[]) public deployerTokens;
    mapping(address => uint256) public tokenFeesGenerated; // in quote token units
    mapping(address => uint256) public tokenFeesClaimed; // in quote token units
    mapping(address => uint256) public tokenToNFTId;
    mapping(address => address) public tokenToPool;
    mapping(address => address) public tokenCreators;
    mapping(address => address) public quoteTokenOf; // launched token => the stock it's paired with

    mapping(uint256 => StockLiquidityConfig) public stockConfigs;
    uint256 public stockConfigCount;

    address public constant POSITION_MANAGER = PANCAKE_V3_POSITION_MANAGER;
    address public constant WBNB = WBNB_ADDRESS;
    address public constant SWAP_ROUTER = PANCAKE_V3_SWAP_ROUTER;
    address public FACTORY = PANCAKE_V3_FACTORY;

    uint24 private constant FEE_TIER = FEE_TIER_10000; // fee tier for the new token's own pool

    address public buybackPool;
    address public devAddress;

    StockFactoryConveyor public conveyor;

    constructor(address _indexer, address _buybackPool, address _devAddress) {
        platformController = tx.origin;
        indexer = IIndexer(_indexer);

        if (_buybackPool != address(0)) {
            buybackPool = _buybackPool;
        }
        if (_devAddress != address(0)) {
            devAddress = _devAddress;
        }
    }

    receive() external payable {}

    // ---------------------------------------------------------------------
    // Admin
    // ---------------------------------------------------------------------

    function setConveyor(address _conveyor) external {
        if (msg.sender != platformController) revert NotController();
        conveyor = StockFactoryConveyor(_conveyor);
        indexer.authorizeAddress(_conveyor);
        conveyor.setIndexer(address(indexer));
    }

    function setBuybackPool(address pool) external {
        if (msg.sender != platformController) revert NotController();
        require(pool != address(0), "Pool is zero");
        buybackPool = pool;
    }

    function setDevAddress(address _devAddress) external {
        if (msg.sender != platformController) revert NotController();
        require(_devAddress != address(0), "Dev address is zero");
        devAddress = _devAddress;
    }

    function setIndexer(address _newIndexer) external {
        if (msg.sender != platformController) revert NotController();
        indexer = IIndexer(_newIndexer);
        if (address(conveyor) != address(0)) {
            conveyor.setIndexer(_newIndexer);
        }
    }

    function setFactory(address _factory) external {
        if (msg.sender != platformController) revert NotController();
        FACTORY = _factory;
    }

    function toggleDeployCoin() external {
        if (msg.sender != platformController) revert NotController();
        deployCoinEnabled = !deployCoinEnabled;
    }

    function setTokenCreator(address token, address newCreator) external {
        if (msg.sender != platformController) revert NotController();
        tokenCreators[token] = newCreator;
    }

    /// @notice Whitelists a tokenized stock as a launch quote asset with its
    /// own calibrated starting price. Only configs added here can be used -
    /// creators cannot pair against an arbitrary address. Pass a
    /// pre-computed sqrtPriceX96 - see calculateSqrtPriceX96 if you have a
    /// target USD mcap instead, or addStockConfigForTargetMcap to compute
    /// and store in one call.
    function addStockConfig(
        address _quoteToken,
        uint160 _sqrtPriceX96,
        int24 _tickLower,
        int24 _tickUpper,
        uint256 _quoteAmountDesired,
        uint256 _tokenAmountDesired
    ) external returns (uint256 configId) {
        if (msg.sender != platformController) revert NotController();
        return
            _writeStockConfig(
                stockConfigCount,
                _quoteToken,
                _sqrtPriceX96,
                _tickLower,
                _tickUpper,
                _quoteAmountDesired,
                _tokenAmountDesired
            );
    }

    /// @notice Same as addStockConfig, but derives sqrtPriceX96 from a target
    /// USD market cap and the quote token's current USD price instead of you
    /// working out the raw value by hand. Every stock has a different,
    /// moving price - re-derive and call updateStockConfigForTargetMcap
    /// whenever it moves meaningfully. _quoteAmountDesired is NOT derived
    /// from this math - see the struct comment - pass a small dust amount,
    /// same spirit as the live Factory's own ETH config.
    ///
    /// totalSupply is fixed at 1,000,000 * 1e18 for every launched token
    /// today (see StockToken.sol), so pass that unless it ever changes.
    ///
    /// targetMcapUsd and quoteTokenUsdPrice must share the same fixed-point
    /// scale (e.g. both USD * 1e8, matching a typical price-feed convention)
    /// - only their ratio matters, so the scale itself is arbitrary as long
    /// as it's consistent between the two.
    function addStockConfigForTargetMcap(
        address _quoteToken,
        uint256 totalSupply,
        uint256 targetMcapUsd,
        uint256 quoteTokenUsdPrice,
        int24 _tickLower,
        int24 _tickUpper,
        uint256 _quoteAmountDesired,
        uint256 _tokenAmountDesired
    ) external returns (uint256 configId) {
        if (msg.sender != platformController) revert NotController();
        uint160 sqrtPriceX96 = calculateSqrtPriceX96(
            totalSupply,
            targetMcapUsd,
            quoteTokenUsdPrice
        );
        return
            _writeStockConfig(
                stockConfigCount,
                _quoteToken,
                sqrtPriceX96,
                _tickLower,
                _tickUpper,
                _quoteAmountDesired,
                _tokenAmountDesired
            );
    }

    function updateStockConfig(
        uint256 configId,
        uint160 _sqrtPriceX96,
        int24 _tickLower,
        int24 _tickUpper,
        uint256 _quoteAmountDesired,
        uint256 _tokenAmountDesired
    ) external {
        if (msg.sender != platformController) revert NotController();
        if (configId >= stockConfigCount) revert InvalidConfigId();

        StockLiquidityConfig storage c = stockConfigs[configId];
        c.sqrtPriceX96 = _sqrtPriceX96;
        c.tickLower = _tickLower;
        c.tickUpper = _tickUpper;
        c.quoteAmountDesired = _quoteAmountDesired;
        c.tokenAmountDesired = _tokenAmountDesired;
    }

    /// @notice Recalibrates an existing config from a target USD mcap - call
    /// this whenever the stock's real-world price has moved meaningfully
    /// since it was last set, instead of recomputing sqrtPriceX96 by hand.
    function updateStockConfigForTargetMcap(
        uint256 configId,
        uint256 totalSupply,
        uint256 targetMcapUsd,
        uint256 quoteTokenUsdPrice,
        int24 _tickLower,
        int24 _tickUpper,
        uint256 _quoteAmountDesired,
        uint256 _tokenAmountDesired
    ) external {
        if (msg.sender != platformController) revert NotController();
        if (configId >= stockConfigCount) revert InvalidConfigId();

        uint160 sqrtPriceX96 = calculateSqrtPriceX96(
            totalSupply,
            targetMcapUsd,
            quoteTokenUsdPrice
        );

        StockLiquidityConfig storage c = stockConfigs[configId];
        c.sqrtPriceX96 = sqrtPriceX96;
        c.tickLower = _tickLower;
        c.tickUpper = _tickUpper;
        c.quoteAmountDesired = _quoteAmountDesired;
        c.tokenAmountDesired = _tokenAmountDesired;
    }

    function _writeStockConfig(
        uint256 configId,
        address _quoteToken,
        uint160 _sqrtPriceX96,
        int24 _tickLower,
        int24 _tickUpper,
        uint256 _quoteAmountDesired,
        uint256 _tokenAmountDesired
    ) internal returns (uint256) {
        require(_quoteToken != address(0), "Quote token is zero");

        stockConfigs[configId] = StockLiquidityConfig({
            quoteToken: _quoteToken,
            sqrtPriceX96: _sqrtPriceX96,
            tickLower: _tickLower,
            tickUpper: _tickUpper,
            quoteAmountDesired: _quoteAmountDesired,
            tokenAmountDesired: _tokenAmountDesired,
            enabled: true
        });

        if (configId == stockConfigCount) {
            unchecked {
                ++stockConfigCount;
            }
        }

        return configId;
    }

    /// @notice The one canonical sqrtPriceX96 calculation for this contract.
    /// Given how many raw newToken units exist (totalSupply) and what
    /// starting market cap you want in USD, computes the sqrtPriceX96 a pool
    /// against `quoteTokenUsdPrice`-priced stock needs to be initialized at.
    /// Pure/free to call off-chain as often as needed - every stock has a
    /// different, moving price, so there is no single reusable constant the
    /// way the live Factory's single ETH config gets away with.
    ///
    /// Verified to reproduce the live Factory's own hardcoded ETH-paired
    /// sqrtPriceX96 exactly, given its implied ~$3,453 starting mcap and the
    /// ETH price noted in its constructor comment.
    function calculateSqrtPriceX96(
        uint256 totalSupply,
        uint256 targetMcapUsd,
        uint256 quoteTokenUsdPrice
    ) public pure returns (uint160) {
        require(quoteTokenUsdPrice > 0, "Quote price is zero");

        // mcap expressed in raw quote-token units (18 decimals)
        uint256 mcapInQuoteRaw = Math.mulDiv(
            targetMcapUsd,
            1e18,
            quoteTokenUsdPrice
        );
        require(mcapInQuoteRaw > 0, "Target mcap too small for this price");

        // quoteToken is always token0, newToken always token1 (see
        // findSaltTokenLTQuote), so Uniswap's price (token1/token0) here is
        // "newToken per quoteToken" - the inverse of price-per-token.
        uint256 priceX192 = Math.mulDiv(totalSupply, 1 << 192, mcapInQuoteRaw);
        uint256 sqrtPrice = Math.sqrt(priceX192);
        require(sqrtPrice <= type(uint160).max, "sqrtPriceX96 overflow");

        return uint160(sqrtPrice);
    }

    function setStockConfigEnabled(uint256 configId, bool enabled) external {
        if (msg.sender != platformController) revert NotController();
        if (configId >= stockConfigCount) revert InvalidConfigId();
        stockConfigs[configId].enabled = enabled;
    }

    /// @notice Rescues ERC20 dust (e.g. leftover WBNB/quote-token remainders
    /// from swap rounding). Unlike the live Factory, this contract routinely
    /// holds many different quote assets, so a generic sweep is safer than
    /// per-asset withdraw functions.
    function rescueERC20(address token, uint256 amount) external {
        if (msg.sender != platformController) revert NotController();
        IERC20(token).transfer(msg.sender, amount);
    }

    function withdrawFeesBNB() external {
        if (msg.sender != platformController) revert NotController();
        uint256 bnbBalance = address(this).balance;
        if (bnbBalance == 0) revert NoBNBToWithdraw();
        (bool success, ) = msg.sender.call{value: bnbBalance}("");
        require(success, "BNB transfer failed");
    }

    // ---------------------------------------------------------------------
    // Launch
    // ---------------------------------------------------------------------

    /// @param initialBuyQuoteAmount Optional - extra quote token (on top of
    /// config.quoteAmountDesired) to spend buying the new token right after
    /// launch. Both amounts are pulled together via transferFrom, so approve
    /// this contract for at least config.quoteAmountDesired +
    /// initialBuyQuoteAmount before calling.
    function deployStockCoin(
        string memory _name,
        string memory _symbol,
        string memory _metadata,
        bytes32 saltSeed,
        uint256 stockConfigId,
        uint256 initialBuyQuoteAmount
    ) public nonReentrant returns (uint256 tokensReceived) {
        if (!deployCoinEnabled) revert DeploymentDisabled();
        if (stockConfigId >= stockConfigCount) revert InvalidConfigId();
        require(address(conveyor) != address(0), "Conveyor not set");

        StockLiquidityConfig memory config = stockConfigs[stockConfigId];
        if (!config.enabled) revert DeploymentDisabled();

        uint256 totalQuoteNeeded = config.quoteAmountDesired + initialBuyQuoteAmount;
        IERC20(config.quoteToken).transferFrom(
            msg.sender,
            address(this),
            totalQuoteNeeded
        );

        (bytes32 chosenSalt, address predicted) = conveyor
            .findSaltTokenLTQuote(_name, _symbol, saltSeed, config.quoteToken);

        (address coinAddress, address predicted2) = conveyor.deployToken(
            _name,
            _symbol,
            chosenSalt,
            msg.sender,
            config.quoteToken
        );
        require(
            coinAddress == predicted && predicted2 == predicted,
            "Predicted mismatch"
        );

        tokenCreators[coinAddress] = msg.sender;
        quoteTokenOf[coinAddress] = config.quoteToken;

        _setupLiquidity(coinAddress, config);

        tokensReceived = _handleInitialBuy(
            coinAddress,
            config.quoteToken,
            initialBuyQuoteAmount
        );

        _storeTokenInfo(coinAddress, _name, _symbol, _metadata, config.quoteToken);

        return tokensReceived;
    }

    function _setupLiquidity(
        address coinAddress,
        StockLiquidityConfig memory config
    ) internal {
        address quoteToken = config.quoteToken;
        if (quoteToken >= coinAddress) revert TokenMustBeToken0();

        IERC20(quoteToken).approve(POSITION_MANAGER, type(uint256).max);
        IERC20(coinAddress).approve(POSITION_MANAGER, type(uint256).max);

        INonfungiblePositionManager manager = INonfungiblePositionManager(
            POSITION_MANAGER
        );

        address pool = manager.createAndInitializePoolIfNecessary(
            quoteToken,
            coinAddress,
            FEE_TIER,
            config.sqrtPriceX96
        );
        tokenToPool[coinAddress] = pool;

        (uint256 tokenId, , , ) = manager.mint(
            INonfungiblePositionManager.MintParams({
                token0: quoteToken,
                token1: coinAddress,
                fee: FEE_TIER,
                tickLower: config.tickLower,
                tickUpper: config.tickUpper,
                amount0Desired: config.quoteAmountDesired,
                amount1Desired: config.tokenAmountDesired,
                amount0Min: 0,
                amount1Min: 0,
                recipient: address(this),
                deadline: block.timestamp
            })
        );

        tokenToNFTId[coinAddress] = tokenId;
    }

    function _toHex(bytes memory buffer) public pure returns (string memory) {
        bytes memory converted = new bytes(buffer.length * 2);
        bytes memory _base = "0123456789abcdef";

        for (uint256 i = 0; i < buffer.length; i++) {
            converted[i * 2] = _base[uint8(buffer[i]) / _base.length];
            converted[i * 2 + 1] = _base[uint8(buffer[i]) % _base.length];
        }

        return string(abi.encodePacked("0x", converted));
    }

    /// @dev Single-hop quoteToken -> newToken swap, spending the extra
    /// quote-token amount the creator supplied on top of the liquidity dust.
    function _handleInitialBuy(
        address coinAddress,
        address quoteToken,
        uint256 buyAmountQuote
    ) internal returns (uint256) {
        if (buyAmountQuote == 0) return 0;

        address expected = IUniswapV3Factory(FACTORY).getPool(
            coinAddress,
            quoteToken,
            FEE_TIER
        );
        require(expected != address(0), "initial buy: pool not found for fee");
        require(
            expected == tokenToPool[coinAddress],
            "initial buy: router/factory pool mismatch"
        );
        require(expected.code.length > 0, "initial buy: pool has no code");
        require(
            IUniswapV3PoolState(expected).liquidity() > 0,
            "initial buy: pool has zero active liquidity"
        );

        if (
            IERC20(quoteToken).allowance(address(this), SWAP_ROUTER) <
            buyAmountQuote
        ) {
            IERC20(quoteToken).approve(SWAP_ROUTER, type(uint256).max);
        }

        ISwapRouter02.ExactInputSingleParams memory params = ISwapRouter02
            .ExactInputSingleParams({
                tokenIn: quoteToken,
                tokenOut: coinAddress,
                fee: FEE_TIER,
                recipient: address(this),
                amountIn: buyAmountQuote,
                amountOutMinimum: 0,
                sqrtPriceLimitX96: 0
            });

        uint256 amountOut;
        try ISwapRouter02(SWAP_ROUTER).exactInputSingle(params) returns (
            uint256 out
        ) {
            amountOut = out;
        } catch Error(string memory reason) {
            revert(string.concat("initial buy swap failed: ", reason));
        } catch (bytes memory data) {
            revert(
                string.concat("initial buy swap failed (raw): ", _toHex(data))
            );
        }

        IERC20(coinAddress).transfer(msg.sender, amountOut);
        indexer.emitTokenPurchased(
            msg.sender,
            coinAddress,
            buyAmountQuote,
            amountOut
        );

        return amountOut;
    }

    function _storeTokenInfo(
        address coinAddress,
        string memory name,
        string memory symbol,
        string memory metadata,
        address quoteToken
    ) internal {
        TokenInfo memory newTokenInfo = TokenInfo({
            tokenAddress: coinAddress,
            name: name,
            symbol: symbol,
            deployer: msg.sender,
            quoteToken: quoteToken,
            time: block.timestamp,
            metadata: metadata,
            nftId: tokenToNFTId[coinAddress]
        });

        deployedTokens[tokenCount] = newTokenInfo;
        tokenInfoByAddress[coinAddress] = newTokenInfo;
        deployerTokens[msg.sender].push(coinAddress);
        unchecked {
            ++tokenCount;
        }
    }

    function getAllTokensByDeployer(
        address _deployer
    ) public view returns (address[] memory) {
        return deployerTokens[_deployer];
    }

    // ---------------------------------------------------------------------
    // Fees - paid out in the quote token itself, not unwrapped to ETH. A
    // creator pairing against TSLA earns fees in TSLA, matching the pitch:
    // creator fees accrue in the stock, not gas-token dust.
    // ---------------------------------------------------------------------

    function collectStockFeesAndBuyback(
        address tokenAddress
    )
        external
        nonReentrant
        returns (
            uint256 tokenFeesCollected,
            uint256 quoteFeesCollected,
            uint256 creatorQuotePaid
        )
    {
        uint256 tokenId = tokenToNFTId[tokenAddress];
        if (tokenId == 0) revert NoPosition();

        address quoteToken = quoteTokenOf[tokenAddress];
        if (quoteToken == address(0)) revert QuoteTokenNotSet();

        address pool = tokenToPool[tokenAddress];
        require(pool != address(0), "No pool");

        address token0 = IUniswapV3PoolTokens(pool).token0();
        address token1 = IUniswapV3PoolTokens(pool).token1();
        if (token0 != quoteToken || token1 != tokenAddress) {
            revert PoolTokenMismatch();
        }

        uint256 beforeQuote = IERC20(quoteToken).balanceOf(address(this));
        uint256 beforeToken = IERC20(tokenAddress).balanceOf(address(this));

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

        quoteFeesCollected =
            IERC20(quoteToken).balanceOf(address(this)) -
            beforeQuote;
        tokenFeesCollected =
            IERC20(tokenAddress).balanceOf(address(this)) -
            beforeToken;

        if (quoteFeesCollected > 0) {
            tokenFeesGenerated[tokenAddress] += quoteFeesCollected;
            tokenFeesClaimed[tokenAddress] += quoteFeesCollected;
        }

        // Always burn/dead-send the launched token's own fee leg, same as
        // the live Factory does for its non-WBNB leg.
        if (tokenFeesCollected > 0) {
            _burnOrSendToDead(tokenAddress, tokenFeesCollected);
        }

        uint256 platformQuote = 0;
        if (quoteFeesCollected > 0) {
            address creator = tokenCreators[tokenAddress];
            if (creator != address(0)) {
                uint256 creatorAmount = (quoteFeesCollected * 20) / 100;
                platformQuote = quoteFeesCollected - creatorAmount;
                IERC20(quoteToken).transfer(creator, creatorAmount);
                creatorQuotePaid = creatorAmount;
            } else {
                platformQuote = quoteFeesCollected;
            }
        }

        if (devAddress != address(0) && platformQuote > 0) {
            uint256 devAmount = (platformQuote * 50) / 100;
            platformQuote -= devAmount;
            IERC20(quoteToken).transfer(devAddress, devAmount);
        }

        if (buybackPool != address(0) && platformQuote > 0) {
            IERC20(quoteToken).transfer(buybackPool, platformQuote);
        }

        indexer.emitFeesCollected(
            tokenId,
            tokenAddress,
            tokenFeesCollected,
            quoteFeesCollected
        );
    }

    function _burnOrSendToDead(address token, uint256 amount) internal {
        if (amount == 0) return;
        try ERC20Burnable(token).burn(amount) {} catch {
            IERC20(token).transfer(
                0x000000000000000000000000000000000000dEaD,
                amount
            );
        }
    }

    function getTokenFeesGenerated(
        address tokenAddress
    ) public view returns (uint256) {
        return tokenFeesGenerated[tokenAddress];
    }

    function getTokenFeesClaimed(
        address tokenAddress
    ) public view returns (uint256) {
        return tokenFeesClaimed[tokenAddress];
    }

    /// @notice Live market cap denominated in the token's own quote asset
    /// (e.g. "in TSLA"), not USD - USD conversion happens off-chain using
    /// the same stock price feed the /stocks page already reads.
    function getTokenMarketCapInQuote(
        address tokenAddress
    ) public view returns (uint256 marketCapInQuote) {
        address quoteToken = quoteTokenOf[tokenAddress];
        if (quoteToken == address(0)) return 0;

        address pool = tokenToPool[tokenAddress];
        if (pool == address(0)) return 0;

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

        // quoteToken is always token0 and tokenAddress always token1 by
        // construction (see findSaltTokenLTQuote), so price is quote-per-token.
        uint256 priceX192 = uint256(sqrtPriceX96) * uint256(sqrtPriceX96);
        marketCapInQuote = Math.mulDiv(totalSupply, priceX192, 1 << 192);
    }

    function getTokenStats(
        address tokenAddress
    )
        external
        view
        returns (
            uint256 marketCapInQuote,
            uint256 totalFeesGenerated,
            address creatorAddress,
            address quoteToken
        )
    {
        creatorAddress = tokenCreators[tokenAddress];
        quoteToken = quoteTokenOf[tokenAddress];
        marketCapInQuote = getTokenMarketCapInQuote(tokenAddress);
        totalFeesGenerated = tokenFeesGenerated[tokenAddress];
    }
}
Chain explorer5522msChain node89ms