SingleSidedLauncher

0xa3a71925be892c609ac4be4efe918dc9c35fc5e8

Verification
Verified
v0.8.28+commit.7893614a
Type
Contract
19,098 bytes
ABI entries
40
23 read · 6 write
License
none

Contract information

Address
0xa3a71925be892c609ac4be4efe918dc9c35fc5e8
Chain
Robinhood Chain (4663)
Compiler
v0.8.28+commit.7893614a
Optimization
Enabled
Creation tx
0x075975a89c…75f7b29d9b

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (23)

LP_FEE()uint24
MAX_TREASURY_BPS()uint256
OPEN_SQRT_PRICE()uint160
TICK_LOWER()int24
TICK_UPPER()int24
TOTAL_SUPPLY()uint256
distributorOf(address)address
dividendsOf(address, address)uint256
launchFeeWei()uint256
launches(address)address, uint256, uint8, address, address, bool
owner()address
poolOf(address)address
positionManager()address
priceUsd(address, uint256)uint256
rwaTreasuryBps()uint256
swapRouter()address
tokenNonce()uint256
treasury()address
treasuryBps()uint256
usdg()address
usdgWethFee()uint24
v3Factory()address
weth()address

Events (7)

CreatorFeePaidFeesHarvestedLaunchFeeSetLaunchedToDEXTokenBoughtTokenCreatedTreasuryBpsSet

ABI

[
  {
    "inputs": [
      {
        "internalType": "contract IWETH9",
        "name": "_weth",
        "type": "address"
      },
      {
        "internalType": "contract IERC20",
        "name": "_usdg",
        "type": "address"
      },
      {
        "internalType": "contract INonfungiblePositionManager",
        "name": "_pm",
        "type": "address"
      },
      {
        "internalType": "contract ISwapRouter02",
        "name": "_router",
        "type": "address"
      },
      {
        "internalType": "contract IV3Factory",
        "name": "_factory",
        "type": "address"
      },
      {
        "internalType": "uint24",
        "name": "_usdgWethFee",
        "type": "uint24"
      },
      {
        "internalType": "address",
        "name": "_treasury",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "ReentrancyGuardReentrantCall",
    "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": "recipient",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "tokenAmt",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "wethAmt",
        "type": "uint256"
      }
    ],
    "name": "CreatorFeePaid",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "tokenFee",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "wethFee",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "dest",
        "type": "address"
      }
    ],
    "name": "FeesHarvested",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "wei_",
        "type": "uint256"
      }
    ],
    "name": "LaunchFeeSet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "pool",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "eth",
        "type": "uint256"
      }
    ],
    "name": "LaunchedToDEX",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "ts",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "buyer",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "eth",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "fee",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "postPrice",
        "type": "uint256"
      }
    ],
    "name": "TokenBought",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "ts",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "nonce",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "meta",
        "type": "string"
      }
    ],
    "name": "TokenCreated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "bps",
        "type": "uint256"
      }
    ],
    "name": "TreasuryBpsSet",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "LP_FEE",
    "outputs": [
      {
        "internalType": "uint24",
        "name": "",
        "type": "uint24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_TREASURY_BPS",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "OPEN_SQRT_PRICE",
    "outputs": [
      {
        "internalType": "uint160",
        "name": "",
        "type": "uint160"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "TICK_LOWER",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "TICK_UPPER",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "TOTAL_SUPPLY",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "distributorOf",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "holder",
        "type": "address"
      }
    ],
    "name": "dividendsOf",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "harvestFees",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "tokenFee",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "wethFee",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "meta",
        "type": "string"
      },
      {
        "internalType": "address",
        "name": "feeWallet",
        "type": "address"
      }
    ],
    "name": "launch",
    "outputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "pool",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "tokensBought",
        "type": "uint256"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "launchFeeWei",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "meta",
        "type": "string"
      }
    ],
    "name": "launchRwa",
    "outputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "pool",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "tokensBought",
        "type": "uint256"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "launches",
    "outputs": [
      {
        "internalType": "address",
        "name": "pool",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      },
      {
        "internalType": "enum SingleSidedLauncher.Mode",
        "name": "mode",
        "type": "uint8"
      },
      {
        "internalType": "address",
        "name": "feeWallet",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "distributor",
        "type": "address"
      },
      {
        "internalType": "bool",
        "name": "exists",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "poolOf",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "positionManager",
    "outputs": [
      {
        "internalType": "contract INonfungiblePositionManager",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "ethUsdWad",
        "type": "uint256"
      }
    ],
    "name": "priceUsd",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "rwaTreasuryBps",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "wei_",
        "type": "uint256"
      }
    ],
    "name": "setLaunchFee",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "bps",
        "type": "uint256"
      }
    ],
    "name": "setRwaTreasuryBps",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "bps",
        "type": "uint256"
      }
    ],
    "name": "setTreasuryBps",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "swapRouter",
    "outputs": [
      {
        "internalType": "contract ISwapRouter02",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "tokenNonce",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "treasury",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "treasuryBps",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "usdg",
    "outputs": [
      {
        "internalType": "contract IERC20",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "usdgWethFee",
    "outputs": [
      {
        "internalType": "uint24",
        "name": "",
        "type": "uint24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "v3Factory",
    "outputs": [
      {
        "internalType": "contract IV3Factory",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "weth",
    "outputs": [
      {
        "internalType": "contract IWETH9",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "stateMutability": "payable",
    "type": "receive"
  }
]

Source code

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {MemeToken} from "./MemeToken.sol";
import {DividendMemeToken} from "./DividendMemeToken.sol";
import {DividendDistributor} from "./DividendDistributor.sol";
import {IWETH9} from "./interfaces/IWETH9.sol";
import {
    INonfungiblePositionManager,
    IUniswapV3PoolMinimal,
    ISwapRouter02
} from "./interfaces/IUniswapV3.sol";

interface IV3Factory {
    function getPool(address a, address b, uint24 fee) external view returns (address);
}

/// REAL — single-sided liquidity launchpad (noxa-style, no bonding curve).
///
/// Every launch, in ONE tx: mint a fixed 1B supply, create a real Uniswap v3
/// token/WETH pool at the 1% tier, deposit the ENTIRE supply as single-sided
/// token-only liquidity from the opening price upward, and LOCK the position NFT
/// here forever (never decreaseLiquidity → principal locked, only fees harvest).
/// An optional atomic dev-buy makes the creator's first buy provably first.
///
/// The single-sided v3 position IS the "bonding curve". Three modes differ ONLY
/// in where the harvested 1% fees go:
///   NORMAL → 100% treasury
///   BAGS   → creator wallet (minus a treasury cut)
///   RWA    → USDG dividends streamed pro-rata to holders (fees → USDG →
///            DividendDistributor); stock reserves pluggable later.
contract SingleSidedLauncher is ReentrancyGuard {
    using SafeERC20 for IERC20;

    address private constant DEAD = 0x000000000000000000000000000000000000dEaD;

    IWETH9 public immutable weth;
    IERC20 public immutable usdg;
    INonfungiblePositionManager public immutable positionManager;
    ISwapRouter02 public immutable swapRouter;
    IV3Factory public immutable v3Factory;
    uint24 public immutable usdgWethFee; // WETH->USDG swap tier for RWA dividends

    uint24 public constant LP_FEE = 10_000;        // 1% tier
    int24 public constant TICK_LOWER = -194_800;   // ~$6,024 open FDV @ ETH $1,736
    int24 public constant TICK_UPPER = 887_200;    // full range up (tickSpacing 200)
    uint160 public constant OPEN_SQRT_PRICE = 4667268838450426450913011; // sqrtRatioAtTick(-194800)
    uint256 public constant TOTAL_SUPPLY = 1_000_000_000e18;

    enum Mode { NORMAL, BAGS, RWA }

    struct Launch {
        address pool;
        uint256 tokenId;
        Mode mode;
        address feeWallet;   // BAGS creator
        address distributor; // RWA dividends
        bool exists;
    }

    mapping(address token => Launch) public launches;
    uint256 public tokenNonce;

    address public immutable treasury;
    address public immutable owner;
    uint256 public launchFeeWei = 0.0005 ether;
    uint256 public treasuryBps = 2000;             // treasury cut on BAGS harvest
    uint256 public rwaTreasuryBps = 5000;          // treasury cut on RWA harvest (default 50/50 holders/treasury)
    uint256 public constant MAX_TREASURY_BPS = 5000;

    event TokenCreated(
        uint256 ts, address creator, uint256 nonce, address token, string name, string symbol, string meta
    );
    event LaunchedToDEX(address token, address pool, uint256 amount, uint256 eth);
    event TokenBought(
        uint256 ts, address token, address buyer, uint256 amount, uint256 eth, uint256 fee, uint256 postPrice
    );
    event CreatorFeePaid(address indexed token, address indexed recipient, uint256 tokenAmt, uint256 wethAmt);
    event FeesHarvested(address indexed token, uint256 tokenFee, uint256 wethFee, address dest);
    event LaunchFeeSet(uint256 wei_);
    event TreasuryBpsSet(uint256 bps);

    constructor(
        IWETH9 _weth,
        IERC20 _usdg,
        INonfungiblePositionManager _pm,
        ISwapRouter02 _router,
        IV3Factory _factory,
        uint24 _usdgWethFee,
        address _treasury
    ) {
        require(_treasury != address(0), "zero treasury");
        weth = _weth;
        usdg = _usdg;
        positionManager = _pm;
        swapRouter = _router;
        v3Factory = _factory;
        usdgWethFee = _usdgWethFee;
        treasury = _treasury;
        owner = msg.sender;
    }

    // ── launch ────────────────────────────────────────────────────────────────

    /// NORMAL (feeWallet == 0) or BAGS (feeWallet = creator). Send >= launchFeeWei;
    /// extra ETH is an atomic dev-buy to msg.sender.
    function launch(string calldata name, string calldata symbol, string calldata meta, address feeWallet)
        external
        payable
        nonReentrant
        returns (address token, address pool, uint256 tokensBought)
    {
        require(msg.value >= launchFeeWei, "launch fee");
        Mode mode = feeWallet == address(0) ? Mode.NORMAL : Mode.BAGS;

        bytes memory init =
            abi.encodePacked(type(MemeToken).creationCode, abi.encode(name, symbol, TOTAL_SUPPLY, address(this)));
        bytes32 salt = _mineSalt(keccak256(init));
        token = address(new MemeToken{salt: salt}(name, symbol, TOTAL_SUPPLY, address(this)));

        uint256 tokenId;
        (pool, tokenId) = _openPool(token);
        launches[token] = Launch(pool, tokenId, mode, feeWallet, address(0), true);
        emit TokenCreated(block.timestamp, msg.sender, ++tokenNonce, token, name, symbol, meta);
        tokensBought = _finish(token, pool);
    }

    /// RWA dividend coin: holders earn USDG streamed from trading fees.
    function launchRwa(string calldata name, string calldata symbol, string calldata meta)
        external
        payable
        nonReentrant
        returns (address token, address pool, uint256 tokensBought)
    {
        require(msg.value >= launchFeeWei, "launch fee");

        bytes memory init = abi.encodePacked(
            type(DividendMemeToken).creationCode,
            abi.encode(name, symbol, TOTAL_SUPPLY, address(this), address(this))
        );
        bytes32 salt = _mineSalt(keccak256(init));
        token = address(new DividendMemeToken{salt: salt}(name, symbol, TOTAL_SUPPLY, address(this), address(this)));

        DividendDistributor dist = new DividendDistributor(usdg, token, address(this));
        DividendMemeToken(token).setDistributor(address(dist));

        uint256 tokenId;
        (pool, tokenId) = _openPool(token);

        // only real circulating holders earn — exclude the pool, the locked LP,
        // and the burn address from dividends.
        dist.setExcluded(pool, true);
        dist.setExcluded(address(this), true);
        dist.setExcluded(DEAD, true);

        launches[token] = Launch(pool, tokenId, Mode.RWA, address(0), address(dist), true);
        emit TokenCreated(block.timestamp, msg.sender, ++tokenNonce, token, name, symbol, meta);
        tokensBought = _finish(token, pool);
    }

    /// Create+initialize the pool and mint the full supply as single-sided
    /// token0 liquidity, locking the NFT here.
    function _openPool(address token) internal returns (address pool, uint256 tokenId) {
        require(token < address(weth), "ordering");
        pool = positionManager.createAndInitializePoolIfNecessary(token, address(weth), LP_FEE, OPEN_SQRT_PRICE);
        IERC20(token).forceApprove(address(positionManager), TOTAL_SUPPLY);
        (tokenId,,,) = positionManager.mint(
            INonfungiblePositionManager.MintParams({
                token0: token,
                token1: address(weth),
                fee: LP_FEE,
                tickLower: TICK_LOWER,
                tickUpper: TICK_UPPER,
                amount0Desired: TOTAL_SUPPLY,
                amount1Desired: 0,
                amount0Min: 0,
                amount1Min: 0,
                recipient: address(this),
                deadline: block.timestamp
            })
        );
    }

    /// launch fee to treasury; any remaining ETH is an atomic dev-buy.
    function _finish(address token, address pool) internal returns (uint256 bought) {
        if (launchFeeWei > 0) _sendEth(treasury, launchFeeWei);
        uint256 devEth = msg.value - launchFeeWei;
        if (devEth > 0) {
            weth.deposit{value: devEth}();
            IERC20(address(weth)).forceApprove(address(swapRouter), devEth);
            bought = swapRouter.exactInputSingle(
                ISwapRouter02.ExactInputSingleParams({
                    tokenIn: address(weth),
                    tokenOut: token,
                    fee: LP_FEE,
                    recipient: msg.sender,
                    amountIn: devEth,
                    amountOutMinimum: 0,
                    sqrtPriceLimitX96: 0
                })
            );
            emit TokenBought(block.timestamp, token, msg.sender, bought, devEth, 0, 0);
        }
        emit LaunchedToDEX(token, pool, TOTAL_SUPPLY, devEth);
    }

    /// Mine a CREATE2 salt so the token address sorts below WETH (token0==token).
    function _mineSalt(bytes32 initHash) internal view returns (bytes32) {
        for (uint256 s = 0; s < 512; s++) {
            address predicted = address(
                uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(s), initHash))))
            );
            if (predicted < address(weth)) return bytes32(s);
        }
        revert("no salt");
    }

    // ── harvest ─────────────────────────────────────────────────────────────

    /// Collect the locked position's 1% swap fees and route by mode. Permissionless.
    function harvestFees(address token) external nonReentrant returns (uint256 tokenFee, uint256 wethFee) {
        Launch memory L = launches[token];
        require(L.exists, "unknown token");
        (tokenFee, wethFee) = positionManager.collect(
            INonfungiblePositionManager.CollectParams({
                tokenId: L.tokenId,
                recipient: address(this),
                amount0Max: type(uint128).max,
                amount1Max: type(uint128).max
            })
        );

        if (L.mode == Mode.NORMAL) {
            if (tokenFee > 0) IERC20(token).safeTransfer(treasury, tokenFee);
            if (wethFee > 0) IERC20(address(weth)).safeTransfer(treasury, wethFee);
            emit FeesHarvested(token, tokenFee, wethFee, treasury);
        } else if (L.mode == Mode.BAGS) {
            _splitToTreasuryAnd(token, L.feeWallet, tokenFee, wethFee);
            emit CreatorFeePaid(token, L.feeWallet, tokenFee, wethFee);
            emit FeesHarvested(token, tokenFee, wethFee, L.feeWallet);
        } else {
            // RWA: 100% of the 1% pool fee becomes USDG dividends for holders —
            // the effective "1% buy + 1% sell tax to dividends". Buy-side fees
            // accrue in WETH, sell-side fees accrue in the token; convert BOTH to
            // USDG so every buy AND every sell fully feeds the dividend. No
            // treasury cut on RWA (the launchpad earns via the launch fee).
            uint256 wethForDiv = wethFee;
            if (tokenFee > 0) {
                IERC20(token).forceApprove(address(swapRouter), tokenFee);
                wethForDiv += swapRouter.exactInputSingle(
                    ISwapRouter02.ExactInputSingleParams({
                        tokenIn: token,
                        tokenOut: address(weth),
                        fee: LP_FEE,
                        recipient: address(this),
                        amountIn: tokenFee,
                        amountOutMinimum: 0,
                        sqrtPriceLimitX96: 0
                    })
                );
            }
            if (wethForDiv > 0 && rwaTreasuryBps > 0) {
                uint256 cut = wethForDiv * rwaTreasuryBps / 10_000;
                if (cut > 0) {
                    IERC20(address(weth)).safeTransfer(treasury, cut);
                    wethForDiv -= cut;
                }
            }
            if (wethForDiv > 0) {
                IERC20(address(weth)).forceApprove(address(swapRouter), wethForDiv);
                uint256 usdgOut = swapRouter.exactInputSingle(
                    ISwapRouter02.ExactInputSingleParams({
                        tokenIn: address(weth),
                        tokenOut: address(usdg),
                        fee: usdgWethFee,
                        recipient: address(this),
                        amountIn: wethForDiv,
                        amountOutMinimum: 0,
                        sqrtPriceLimitX96: 0
                    })
                );
                if (usdgOut > 0) {
                    usdg.safeTransfer(L.distributor, usdgOut);
                    DividendDistributor(L.distributor).distribute(usdgOut);
                }
            }
            emit FeesHarvested(token, tokenFee, wethFee, L.distributor);
        }
    }

    function _splitToTreasuryAnd(address token, address dest, uint256 tokenFee, uint256 wethFee) internal {
        require(dest != address(0), "no dest");
        if (tokenFee > 0) {
            uint256 t = tokenFee * treasuryBps / 10_000;
            if (t > 0) IERC20(token).safeTransfer(treasury, t);
            if (tokenFee - t > 0) IERC20(token).safeTransfer(dest, tokenFee - t);
        }
        if (wethFee > 0) {
            uint256 t = wethFee * treasuryBps / 10_000;
            if (t > 0) IERC20(address(weth)).safeTransfer(treasury, t);
            if (wethFee - t > 0) IERC20(address(weth)).safeTransfer(dest, wethFee - t);
        }
    }

    // ── views ─────────────────────────────────────────────────────────────────

    function poolOf(address token) external view returns (address) {
        return launches[token].pool;
    }

    function distributorOf(address token) external view returns (address) {
        return launches[token].distributor;
    }

    /// Live claimable USDG dividends for a holder of an RWA coin (0 for others).
    function dividendsOf(address token, address holder) external view returns (uint256) {
        address d = launches[token].distributor;
        return d == address(0) ? 0 : DividendDistributor(d).withdrawableDividendOf(holder);
    }

    function priceUsd(address token, uint256 ethUsdWad) external view returns (uint256) {
        address pool = launches[token].pool;
        if (pool == address(0)) return 0;
        (uint160 sp,,,,,,) = IUniswapV3PoolMinimal(pool).slot0();
        uint256 wethPerToken = uint256(sp) * sp * 1e18 >> 192;
        return wethPerToken * ethUsdWad / 1e18;
    }

    // ── admin ─────────────────────────────────────────────────────────────────

    function setLaunchFee(uint256 wei_) external {
        require(msg.sender == owner, "not owner");
        launchFeeWei = wei_;
        emit LaunchFeeSet(wei_);
    }

    function setTreasuryBps(uint256 bps) external {
        require(msg.sender == owner, "not owner");
        require(bps <= MAX_TREASURY_BPS, "cap 50%");
        treasuryBps = bps;
        emit TreasuryBpsSet(bps);
    }

    /// Treasury cut on RWA harvest, in bps. 0 (default) = 100% of the 1% fee to
    /// holder dividends; raise it to also route part of RWA volume to treasury.
    function setRwaTreasuryBps(uint256 bps) external {
        require(msg.sender == owner, "not owner");
        require(bps <= MAX_TREASURY_BPS, "cap 50%");
        rwaTreasuryBps = bps;
        emit TreasuryBpsSet(bps);
    }

    function _sendEth(address to, uint256 amt) internal {
        (bool ok,) = to.call{value: amt}("");
        require(ok, "eth send");
    }

    receive() external payable {
        require(msg.sender == address(weth), "direct eth");
    }
}
Chain explorer1884msChain node72ms