// SPDX-License-Identifier: MIT pragma solidity 0.8.30; import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import {IPonsFactory, IPonsEscrow, IWinFeeRouter} from "./WinPonsCollectors.sol"; import {IPonsLaunch, IPonsCurveSweep, IPonsPoolSweep} from "./WinPonsLauncher.sol"; interface ITaxedCurvePolicy { function feeBps() external view returns(uint256); function protocolFeeShareBps() external view returns(uint16); } interface ITaxedHookPolicy { function currentFeePolicy() external view returns(address,uint16,uint16,uint16,uint16); } /** Per-coin Pons deployer and fee recipient. Only the fixed vault can receive fees. */ contract WinPonsTaxedLaunchAccount is ReentrancyGuard { uint16 public constant CREATOR_TAX_BPS=200; address public immutable registry; address public immutable creator; IPonsLaunch public immutable factory; IPonsEscrow public immutable escrow; IWinFeeRouter public immutable vault; address public token; address public curve; uint256 public totalForwarded; error Unauthorized();error InvalidLaunch();error ReceiptMismatch(); event FeesForwarded(address indexed token,uint256 amount); event PoolSweepDeferred(address indexed token,bytes32 indexed poolId); constructor(address creator_,address factory_,address escrow_,address vault_) { registry=msg.sender;creator=creator_;factory=IPonsLaunch(factory_); escrow=IPonsEscrow(escrow_);vault=IWinFeeRouter(vault_); } receive() external payable {if(msg.sender!=address(escrow))revert Unauthorized();} function launch(IPonsLaunch.TokenParams calldata input,uint256 config) external payable returns(address,address) { if(msg.sender!=registry||token!=address(0))revert Unauthorized(); if(config!=0||input.creatorTaxBps!=CREATOR_TAX_BPS||input.buybackEnabled||input.expectedEconomics==bytes32(0))revert InvalidLaunch(); (,,,uint16 hookFee,)=ITaxedHookPolicy(factory.memeHook()).currentFeePolicy(); if(hookFee!=100)revert InvalidLaunch(); IPonsLaunch.TokenParams memory params=input; params.creatorFeeRecipient=address(this); (token,curve)=factory.launchToken{value:msg.value}(params,config,address(0)); IPonsFactory.Launch memory record=factory.getLaunchedToken(token); if(token.code.length==0||curve.code.length==0||!record.exists||record.token!=token|| record.curve!=curve||record.deployer!=address(this)||record.creatorFeeRecipient!=address(this)|| record.pairToken!=address(0)||record.creatorTaxBps!=CREATOR_TAX_BPS||record.buybackEnabled||record.poolFee!=0)revert InvalidLaunch(); // Base fee 1%, PONS share 30% of that base, plus 2% entirely to WIN. // Every collected creator fee reaches the existing 80/20 jackpot ledger. if(ITaxedCurvePolicy(curve).feeBps()!=100||ITaxedCurvePolicy(curve).protocolFeeShareBps()!=3000)revert InvalidLaunch(); return(token,curve); } // Pons permits the account to sweep quote-only pool fees. Memecoin conversion // still needs its operator; failure leaves those fees in Pons for later retry. function collect() external nonReentrant returns(uint256 amount) { if(token==address(0))revert InvalidLaunch(); IPonsFactory.Launch memory record=factory.getLaunchedToken(token); if(record.phase==0)IPonsCurveSweep(curve).sweepFees(0); else if(record.phase==2){ address hook=factory.memeHook(); bytes32 poolId=keccak256(abi.encode(address(0),token,record.poolFee,record.tickSpacing,hook)); // Never execute conversions with a zero minimum, even if Pons were // to designate this account as its trusted operator in the future. if(IPonsPoolSweep(hook).feeSweepOperator()==address(this)){emit PoolSweepDeferred(token,poolId);} else{try IPonsPoolSweep(hook).sweepPoolFees(poolId,0,0){}catch{emit PoolSweepDeferred(token,poolId);}} } if(escrow.balanceOf(address(this))==0)return 0; uint256 beforeBalance=address(this).balance; amount=escrow.claim(); if(amount==0||address(this).balance-beforeBalance!=amount)revert ReceiptMismatch(); totalForwarded+=amount;vault.depositCreatorFees{value:amount}(token); emit FeesForwarded(token,amount); } } /** Unreviewed launch candidate. Defaults closed; cannot bypass Pons economics. */ contract WinPonsTaxedLauncher is ReentrancyGuard { uint16 public constant CREATOR_TAX_BPS=200; address public immutable owner; IPonsLaunch public immutable factory; address public immutable feeEscrow; address public immutable vault; bytes32 public immutable factoryHash; bool public paused=true; mapping(address=>address) public collectorFor; mapping(address=>address) public creatorFor; mapping(address=>uint256) public createdAt; address[] private tokens; error Unauthorized();error InvalidDependency();error LaunchesPaused(); event TokenRegistered(address indexed token,address indexed collector,address indexed creator,address curve); event PauseChanged(bool paused); constructor(address owner_,address factory_,address vault_,bytes32 expectedHash) { if(owner_==address(0)||factory_.code.length==0||vault_.code.length==0||factory_.codehash!=expectedHash)revert InvalidDependency(); owner=owner_;factory=IPonsLaunch(factory_);factoryHash=expectedHash;vault=vault_; feeEscrow=IPonsLaunch(factory_).feeEscrow(); if(feeEscrow.code.length==0)revert InvalidDependency(); } function setPaused(bool value) external {if(msg.sender!=owner)revert Unauthorized();paused=value;emit PauseChanged(value);} function launch(IPonsLaunch.TokenParams calldata params,uint256 config) external payable nonReentrant returns(address token,address account) { if(paused)revert LaunchesPaused(); if(address(factory).codehash!=factoryHash||factory.feeEscrow()!=feeEscrow)revert InvalidDependency(); bytes32 salt=keccak256(abi.encode(msg.sender,params.salt)); WinPonsTaxedLaunchAccount created=new WinPonsTaxedLaunchAccount{salt:salt}(msg.sender,address(factory),feeEscrow,vault); address curve;(token,curve)=created.launch{value:msg.value}(params,config);account=address(created); collectorFor[token]=account;creatorFor[token]=msg.sender;createdAt[token]=block.timestamp;tokens.push(token); emit TokenRegistered(token,account,msg.sender,curve); } function predictAccount(address creator,bytes32 salt) external view returns(address) { bytes32 initHash=keccak256(abi.encodePacked(type(WinPonsTaxedLaunchAccount).creationCode,abi.encode(creator,address(factory),feeEscrow,vault))); return address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff),address(this),keccak256(abi.encode(creator,salt)),initHash))))); } function totalTokens() external view returns(uint256){return tokens.length;} function tokenAt(uint256 index) external view returns(address){return tokens[index];} }