// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {DegenZV2TaxBondingCurve} from "./DegenZV2TaxBondingCurve.sol"; import {DegenZFunTaxToken} from "./DegenZFunTaxToken.sol"; import {DegenZV2TaxChildDeployer} from "./DegenZV2TaxChildDeployer.sol"; /// @title DegenZV2TaxLaunchpad — create Tax / Reward Fun coins with dynamic graduation → Uniswap V2. /// @dev Child CREATE bytecode lives in `DegenZV2TaxChildDeployer` to stay under EIP-170. contract DegenZV2TaxLaunchpad is Ownable { address public treasury; address public v2Router; address public weth; uint256 public createFee; uint256 public minGraduationMarketCapEth; uint256 public maxGraduationMarketCapEth; uint256 public treasuryGraduationFeeBps; // 500 = 5% address public bondingRouter; // optional Maestro-style router pointing at this factory via adapter address public feeRouter; // DegenZFeeRouter — excluded from tax address public immutable childDeployer; DegenZV2TaxBondingCurve[] public curves; mapping(address => address) public tokenCurve; mapping(address => uint8) public tokenKind; // 1 tax, 2 reward mapping(address => address) public tokenDistributor; event CoinCreated( address indexed creator, address indexed curve, address indexed token, string name, string symbol, string metadataUri, uint256 graduationMarketCapEth, uint8 tokenType, address marketingWallet, address rewardToken, address distributor, uint256 buyTaxBps, uint256 sellTaxBps ); event CreateFeeUpdated(uint256 fee); event TreasuryUpdated(address indexed treasury); event GraduationBoundsUpdated(uint256 minMcapEth, uint256 maxMcapEth); event FeeRouterUpdated(address indexed feeRouter); event BondingRouterUpdated(address indexed router); constructor( address treasury_, address v2Router_, address weth_, uint256 createFee_, uint256 minGraduationMarketCapEth_, uint256 maxGraduationMarketCapEth_, uint256 treasuryGraduationFeeBps_ ) Ownable(msg.sender) { require(treasury_ != address(0) && v2Router_ != address(0) && weth_ != address(0), "zero addr"); require(minGraduationMarketCapEth_ > 0 && maxGraduationMarketCapEth_ >= minGraduationMarketCapEth_, "grad bounds"); require(treasuryGraduationFeeBps_ <= 2000, "grad fee"); treasury = treasury_; v2Router = v2Router_; weth = weth_; createFee = createFee_; minGraduationMarketCapEth = minGraduationMarketCapEth_; maxGraduationMarketCapEth = maxGraduationMarketCapEth_; treasuryGraduationFeeBps = treasuryGraduationFeeBps_; childDeployer = address(new DegenZV2TaxChildDeployer(address(this))); } function createTaxCoin( string calldata name_, string calldata symbol_, string calldata metadataUri_, uint256 graduationMarketCapEth_, uint256 buyTaxBps_, uint256 sellTaxBps_, address marketingWallet_ ) external payable returns (address curveAddr, address tokenAddr) { return _create(name_, symbol_, metadataUri_, graduationMarketCapEth_, buyTaxBps_, sellTaxBps_, marketingWallet_, address(0), 1); } function createRewardCoin( string calldata name_, string calldata symbol_, string calldata metadataUri_, uint256 graduationMarketCapEth_, uint256 buyTaxBps_, uint256 sellTaxBps_, address rewardToken_ ) external payable returns (address curveAddr, address tokenAddr) { require(rewardToken_ != address(0), "reward=0"); return _create(name_, symbol_, metadataUri_, graduationMarketCapEth_, buyTaxBps_, sellTaxBps_, address(0), rewardToken_, 2); } function _create( string calldata name_, string calldata symbol_, string calldata metadataUri_, uint256 graduationMarketCapEth_, uint256 buyTaxBps_, uint256 sellTaxBps_, address marketingWallet_, address rewardToken_, uint8 tokenType_ ) internal returns (address curveAddr, address tokenAddr) { require(msg.value >= createFee, "fee too low"); require(bytes(name_).length > 0 && bytes(symbol_).length > 0, "name/symbol"); require( graduationMarketCapEth_ >= minGraduationMarketCapEth && graduationMarketCapEth_ <= maxGraduationMarketCapEth, "grad mcap" ); require(buyTaxBps_ <= 1000 && sellTaxBps_ <= 1000, "tax>10%"); (bool feeOk,) = treasury.call{value: createFee}(""); require(feeOk, "fee transfer failed"); uint256 devBuyEth = msg.value - createFee; address marketing = marketingWallet_; if (tokenType_ == 1) { if (marketing == address(0)) marketing = msg.sender; } // For reward coins, marketing is a temporary placeholder; replaced by distributor after deploy. if (tokenType_ == 2) { marketing = address(this); } (curveAddr, tokenAddr) = DegenZV2TaxChildDeployer(childDeployer).deployCurve( DegenZV2TaxChildDeployer.CurveParams({ name: name_, symbol: symbol_, metadataUri: metadataUri_, creator: msg.sender, v2Router: v2Router, weth: weth, treasury: treasury, marketingWallet: marketing, buyTaxBps: buyTaxBps_, sellTaxBps: sellTaxBps_, graduationMarketCapEth: graduationMarketCapEth_, treasuryGraduationFeeBps: treasuryGraduationFeeBps, tokenType: tokenType_ }) ); curves.push(DegenZV2TaxBondingCurve(payable(curveAddr))); tokenCurve[tokenAddr] = curveAddr; tokenKind[tokenAddr] = tokenType_; DegenZFunTaxToken t = DegenZFunTaxToken(tokenAddr); if (feeRouter != address(0)) { t.setExcludedFromTax(feeRouter, true); } if (bondingRouter != address(0)) { t.setExcludedFromTax(bondingRouter, true); } address distributor; if (tokenType_ == 2) { distributor = DegenZV2TaxChildDeployer(childDeployer).deployDistributor( tokenAddr, rewardToken_, v2Router, weth, msg.sender ); tokenDistributor[tokenAddr] = distributor; t.setMarketingWallet(distributor); t.setExcludedFromTax(distributor, true); t.setExcludedFromTax(address(this), true); } // Creator is already excluded as owner in token ctor (owner = factory). Also exclude creator EOA. t.setExcludedFromTax(msg.sender, true); if (devBuyEth > 0) { DegenZV2TaxBondingCurve(payable(curveAddr)).buy{value: devBuyEth}(0); uint256 tokens = IERC20(tokenAddr).balanceOf(address(this)); if (tokens > 0) { require(IERC20(tokenAddr).transfer(msg.sender, tokens), "dev buy transfer"); } } emit CoinCreated( msg.sender, curveAddr, tokenAddr, name_, symbol_, metadataUri_, graduationMarketCapEth_, tokenType_, tokenType_ == 1 ? marketing : distributor, rewardToken_, distributor, buyTaxBps_, sellTaxBps_ ); } /// @notice Creator may update marketing wallet while factory still owns the token. function setMarketingWallet(address token, address wallet) external { require(tokenCurve[token] != address(0), "unknown"); DegenZV2TaxBondingCurve curve = DegenZV2TaxBondingCurve(payable(tokenCurve[token])); require(msg.sender == curve.creator(), "not creator"); require(!curve.graduated(), "graduated"); DegenZFunTaxToken(token).setMarketingWallet(wallet); } /// @notice Creator may update buy/sell tax (max 10%) while factory still owns the token. function setTax(address token, uint256 buyBps, uint256 sellBps) external { require(tokenCurve[token] != address(0), "unknown"); DegenZV2TaxBondingCurve curve = DegenZV2TaxBondingCurve(payable(tokenCurve[token])); require(msg.sender == curve.creator(), "not creator"); require(!curve.graduated(), "graduated"); DegenZFunTaxToken(token).setTax(buyBps, sellBps); } /// @notice Called by curve at graduation — set V2 pair as AMM, lock taxes, renounce. function finishGraduation(address token, address pair) external { require(tokenCurve[token] == msg.sender, "only curve"); DegenZFunTaxToken t = DegenZFunTaxToken(token); t.setAutomatedMarketMakerPair(pair, true); t.lockTaxes(); Ownable(token).renounceOwnership(); } function renounceTokenAtGraduation(address token) external { require(tokenCurve[token] == msg.sender, "only curve"); // Kept for interface compatibility — prefer finishGraduation. Ownable(token).renounceOwnership(); } function curveCount() external view returns (uint256) { return curves.length; } function setCreateFee(uint256 fee) external onlyOwner { createFee = fee; emit CreateFeeUpdated(fee); } function setTreasury(address t) external onlyOwner { require(t != address(0), "treasury=0"); treasury = t; emit TreasuryUpdated(t); } function setGraduationBounds(uint256 minMcap, uint256 maxMcap) external onlyOwner { require(minMcap > 0 && maxMcap >= minMcap, "bounds"); minGraduationMarketCapEth = minMcap; maxGraduationMarketCapEth = maxMcap; emit GraduationBoundsUpdated(minMcap, maxMcap); } function setFeeRouter(address r) external onlyOwner { feeRouter = r; emit FeeRouterUpdated(r); } function setBondingRouter(address r) external onlyOwner { bondingRouter = r; emit BondingRouterUpdated(r); } }