Contract

Contract✓ Verified (full match)via Sourcify
Native balance
Transactions
0 sent · 15,069 received
Token transfers
6,312
First seen
· block 10,388,203
Last seen
· block 10,832,739

Counts and history cover indexed blocks 10,388,153 onward — earlier activity isn't indexed yet.

Contract name
NoxaRouter
Compiler
v0.8.20+commit.a1b79de6
Language
solidity
Optimizer
enabled · 200 runs
EVM version
compiler default
Verified
2026-07-15 15:13:34 UTC · via Sourcify · full match

Source code1 file

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

/**
 * @title NoxaRouter
 * @notice Прокси для торговли на Uniswap V3 (Robinhood L2).
 *         Автоматически берёт роялти при buy/sell.
 *
 * Uniswap V3 Router : 0xCaf681a66D020601342297493863E78C959E5cb2
 * WETH              : 0x0bd7d308f8e1639fab988df18a8011f41eacad73
 * Pool fee          : 10000 (1%)
 */

interface IERC20 {
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
    function transfer(address to, uint256 amount) external returns (bool);
    function approve(address spender, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
}

interface IWETH {
    function deposit() external payable;
    function withdraw(uint256 wad) external;
    function transfer(address to, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
}

interface IUniswapRouter {
    struct ExactInputSingleParams {
        address tokenIn;
        address tokenOut;
        uint24  fee;
        address recipient;
        uint256 amountIn;
        uint256 amountOutMinimum;
        uint160 sqrtPriceLimitX96;
    }

    function exactInputSingle(ExactInputSingleParams calldata params)
        external payable returns (uint256 amountOut);

    struct ExactOutputSingleParams {
        address tokenIn;
        address tokenOut;
        uint24  fee;
        address recipient;
        uint256 amountOut;
        uint256 amountInMaximum;
        uint160 sqrtPriceLimitX96;
    }

    function exactOutputSingle(ExactOutputSingleParams calldata params)
        external payable returns (uint256 amountIn);

    function multicall(uint256 deadline, bytes[] calldata data)
        external payable returns (bytes[] memory);

    function wrapETH(uint256 value) external payable;

    function unwrapWETH9(uint256 amountMinimum, address recipient) external payable;
}

interface IUniswapV3Factory {
    function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address);
}

interface IUniswapV3PoolMinimal {
    function liquidity() external view returns (uint128);
}

contract NoxaRouter {

    uint256 public constant BPS_DENOM      = 10_000;
    uint256 public constant MAX_ROYALTY_BPS = 1_000;
    uint24  public constant POOL_FEE        = 10000;
    // Same Uniswap V3 factory the seller/sniper bots already read pools
    // from off-chain (FACTORY_ADDR in noxa_seller.py / noxa_sniper.py).
    address public constant FACTORY = 0x1f7d7550B1b028f7571E69A784071F0205FD2EfA;

    address public owner;
    address public pendingOwner;

    address public immutable UNI_ROUTER;
    address public immutable WETH;
    address public immutable ROYALTY_ADDR;

    uint256 public buyRoyaltyBps;
    uint256 public sellRoyaltyBps;

    event Buy(
        address indexed token, address indexed buyer,
        uint256 ethIn, uint256 royaltyEth, uint256 tokensOut
    );
    event BuyExact(
        address indexed token, address indexed buyer,
        uint256 ethSpent, uint256 royaltyEth, uint256 tokensOut, uint256 refundedEth
    );
    event Sell(
        address indexed token, address indexed seller,
        uint256 tokensIn, uint256 ethOut, uint256 royaltyEth
    );
    event RoyaltyUpdated(uint256 newBuyBps, uint256 newSellBps);
    event OwnershipTransferred(address indexed old, address indexed new_);

    error Unauthorized();
    error InvalidRoyalty();
    error TransferFailed();
    error ZeroAmount();
    error SwapFailed();
    error InsufficientOutput();
    error RebuyTooSoon();
    error TradingNotOpen();
    error InsufficientRoyaltyBuffer();

    // Anti-flood: minimum seconds between two successful buys of the SAME
    // token by the SAME address through this router. Checked FIRST, before
    // any royalty transfer / WETH wrap / Uniswap call — a flood bot sending
    // many sequential-nonce buys only needs ONE to land; every later tx
    // from the same sender for the same token should die here as cheaply
    // as possible instead of burning gas on the full swap path.
    uint256 public rebuyCooldownSecs = 3;
    mapping(address => mapping(address => uint256)) public lastBuyAt; // buyer => token => timestamp

    modifier onlyOwner() {
        if (msg.sender != owner) revert Unauthorized();
        _;
    }

    function setRebuyCooldown(uint256 secs_) external onlyOwner {
        rebuyCooldownSecs = secs_;
    }

    /// @dev "Trading open" here is NOT a fixed block offset — the actual
    /// launch platform creates the pool / adds liquidity off-chain some
    /// random ~2-30s after the token deploy tx, so any block-count-based
    /// guess (e.g. a token's own launchBlock() getter) tells us nothing
    /// real. The only thing that's actually true on-chain is: does the
    /// pool exist yet, and does it have liquidity. Two cheap staticcalls
    /// (factory + pool), still a small fraction of the gas the full
    /// wrap+approve+swap path burns only to revert deep inside the swap.
    function _tradingOpen(address tokenAddress) internal view returns (bool) {
        address pool = IUniswapV3Factory(FACTORY).getPool(tokenAddress, WETH, POOL_FEE);
        if (pool == address(0)) return false;
        try IUniswapV3PoolMinimal(pool).liquidity() returns (uint128 liq) {
            return liq > 0;
        } catch {
            return false;
        }
    }

    constructor(
        address uniRouter_,
        address weth_,
        address royaltyAddr_,
        uint256 buyBps_,
        uint256 sellBps_
    ) {
        require(uniRouter_  != address(0), "zero router");
        require(weth_       != address(0), "zero weth");
        require(royaltyAddr_ != address(0), "zero royalty");
        require(buyBps_  <= MAX_ROYALTY_BPS, "buy royalty too high");
        require(sellBps_ <= MAX_ROYALTY_BPS, "sell royalty too high");

        owner          = msg.sender;
        UNI_ROUTER     = uniRouter_;
        WETH           = weth_;
        ROYALTY_ADDR   = royaltyAddr_;
        buyRoyaltyBps  = buyBps_;
        sellRoyaltyBps = sellBps_;

        // Approve WETH to Uniswap Router for swaps
        IERC20(weth_).approve(uniRouter_, type(uint256).max);
    }

    /// @param tokenAddress    токен, который покупаем
    /// @param amountOutMinimum минимальное кол-во токенов, которое согласны получить
    ///        (защита от проскальзывания / sandwich-атак). Считайте off-chain
    ///        через quote + допустимый slippage, например quote * 99 / 100.
    function buy(address tokenAddress, uint256 amountOutMinimum)
        external
        payable
        returns (uint256 tokensOut)
    {
        if (msg.value == 0) revert ZeroAmount();

        if (rebuyCooldownSecs > 0) {
            uint256 last = lastBuyAt[msg.sender][tokenAddress];
            if (last != 0 && block.timestamp - last < rebuyCooldownSecs) revert RebuyTooSoon();
            lastBuyAt[msg.sender][tokenAddress] = block.timestamp;
        }
        if (!_tradingOpen(tokenAddress)) revert TradingNotOpen();

        uint256 royalty = (msg.value * buyRoyaltyBps) / BPS_DENOM;
        uint256 swapEth = msg.value - royalty;

        if (royalty > 0) {
            (bool royaltyOk,) = ROYALTY_ADDR.call{value: royalty}("");
            if (!royaltyOk) revert TransferFailed();
        }

        // Wrap ETH to WETH directly (sends to this contract)
        IWETH(WETH).deposit{value: swapEth}();

        // Ensure Uniswap router can pull our WETH
        (bool wethApproveOk,) = WETH.call(
            abi.encodeWithSignature("approve(address,uint256)", UNI_ROUTER, type(uint256).max)
        );
        if (!wethApproveOk) revert TransferFailed();

        // Swap WETH -> Token with recipient = msg.sender (not this contract).
        // This bypasses per-address pool-buy limits (maxTxBps / maxWalletBps)
        // in tokens like PonsLauncherToken, because the pool-to-recipient
        // transfer credits _restrictedPoolBuys[msg.sender] individually
        // instead of accumulating on the router address.
        bytes[] memory data = new bytes[](1);
        data[0] = abi.encodeWithSelector(IUniswapRouter.exactInputSingle.selector,
            IUniswapRouter.ExactInputSingleParams({
                tokenIn:           WETH,
                tokenOut:          tokenAddress,
                fee:               POOL_FEE,
                recipient:         msg.sender,
                amountIn:          swapEth,
                amountOutMinimum:  amountOutMinimum,
                sqrtPriceLimitX96: 0
            })
        );

        uint256 deadline = block.timestamp + 600;
        bytes[] memory result = IUniswapRouter(UNI_ROUTER).multicall(deadline, data);

        tokensOut = abi.decode(result[0], (uint256));
        if (tokensOut == 0) revert SwapFailed();
        if (tokensOut < amountOutMinimum) revert InsufficientOutput();

        emit Buy(tokenAddress, msg.sender, msg.value, royalty, tokensOut);
    }

    /// @notice Покупает РОВНО amountOut токенов. msg.value — это потолок
    ///         (сколько эфира вы готовы потратить), а не точная сумма: шлите
    ///         с запасом на проскальзывание (например, quote * 101 / 100).
    ///         Всё, что реально не ушло в своп, возвращается вам ЭФИРОМ,
    ///         royalty при этом берётся с фактически потраченной суммы, а не
    ///         со всего msg.value.
    ///         Нужно, когда площадка (Noxa fi, первый час торгов) ограничивает
    ///         РАЗМЕР одной покупки в токенах (например, 20_000_000) — так
    ///         получаем ровно лимит за одну транзакцию.
    /// @param tokenAddress токен, который покупаем
    /// @param amountOut    точное количество токенов, которое хотим получить
    function buyExact(address tokenAddress, uint256 amountOut)
        external
        payable
        returns (uint256 ethSpent)
    {
        if (msg.value == 0) revert ZeroAmount();
        if (amountOut == 0) revert ZeroAmount();

        if (rebuyCooldownSecs > 0) {
            uint256 last = lastBuyAt[msg.sender][tokenAddress];
            if (last != 0 && block.timestamp - last < rebuyCooldownSecs) revert RebuyTooSoon();
            lastBuyAt[msg.sender][tokenAddress] = block.timestamp;
        }
        if (!_tradingOpen(tokenAddress)) revert TradingNotOpen();

        // Wrap the FULL msg.value — royalty is carved out afterwards, from the
        // actual swap cost, so it doesn't overcharge on the slippage buffer.
        IWETH(WETH).deposit{value: msg.value}();

        (bool wethApproveOk,) = WETH.call(
            abi.encodeWithSignature("approve(address,uint256)", UNI_ROUTER, type(uint256).max)
        );
        if (!wethApproveOk) revert TransferFailed();

        // recipient = msg.sender to bypass per-address pool-buy limits
        // (see buy() comment for details)
        bytes[] memory data = new bytes[](1);
        data[0] = abi.encodeWithSelector(IUniswapRouter.exactOutputSingle.selector,
            IUniswapRouter.ExactOutputSingleParams({
                tokenIn:           WETH,
                tokenOut:          tokenAddress,
                fee:               POOL_FEE,
                recipient:         msg.sender,
                amountOut:         amountOut,
                amountInMaximum:   msg.value,
                sqrtPriceLimitX96: 0
            })
        );

        uint256 deadline = block.timestamp + 600;
        bytes[] memory result = IUniswapRouter(UNI_ROUTER).multicall(deadline, data);

        uint256 amountIn = abi.decode(result[0], (uint256));
        if (amountIn == 0) revert SwapFailed();

        uint256 royalty = (amountIn * buyRoyaltyBps) / BPS_DENOM;
        uint256 leftoverWeth = msg.value - amountIn;
        if (leftoverWeth < royalty) revert InsufficientRoyaltyBuffer();

        uint256 refund = leftoverWeth - royalty;
        ethSpent = amountIn + royalty;

        if (leftoverWeth > 0) {
            IWETH(WETH).withdraw(leftoverWeth);
        }

        if (royalty > 0) {
            (bool royaltyOk,) = ROYALTY_ADDR.call{value: royalty}("");
            if (!royaltyOk) revert TransferFailed();
        }

        if (refund > 0) {
            (bool refundOk,) = msg.sender.call{value: refund}("");
            if (!refundOk) revert TransferFailed();
        }

        emit BuyExact(tokenAddress, msg.sender, ethSpent, royalty, amountOut, refund);
    }

    /// @param tokenAddress     токен, который продаём
    /// @param tokenAmount      сколько токенов продаём
    /// @param amountOutMinimum минимальное кол-во ETH (в виде WETH), которое согласны
    ///        получить со свопа, ДО вычета royalty
    function sell(address tokenAddress, uint256 tokenAmount, uint256 amountOutMinimum)
        external
        returns (uint256 ethReceived)
    {
        if (tokenAmount == 0) revert ZeroAmount();

        bool pullOk = IERC20(tokenAddress).transferFrom(msg.sender, address(this), tokenAmount);
        if (!pullOk) revert TransferFailed();

        IERC20(tokenAddress).approve(UNI_ROUTER, tokenAmount);

        bytes[] memory data = new bytes[](1);
        data[0] = abi.encodeWithSelector(IUniswapRouter.exactInputSingle.selector,
            IUniswapRouter.ExactInputSingleParams({
                tokenIn:           tokenAddress,
                tokenOut:          WETH,
                fee:               POOL_FEE,
                recipient:         address(this),
                amountIn:          tokenAmount,
                amountOutMinimum:  amountOutMinimum,
                sqrtPriceLimitX96: 0
            })
        );

        uint256 deadline = block.timestamp + 600;
        IUniswapRouter(UNI_ROUTER).multicall(deadline, data);

        // Unwrap WETH via Uniswap Router: transfer WETH to router, then unwrapWETH9
        uint256 wethBal = IERC20(WETH).balanceOf(address(this));
        if (wethBal > 0) {
            IERC20(WETH).transfer(UNI_ROUTER, wethBal);
            bytes[] memory unwrapData = new bytes[](1);
            unwrapData[0] = abi.encodeWithSelector(IUniswapRouter.unwrapWETH9.selector, 0, address(this));
            IUniswapRouter(UNI_ROUTER).multicall(block.timestamp + 600, unwrapData);
        }

        uint256 ethBalance = address(this).balance;
        uint256 royalty = (ethBalance * sellRoyaltyBps) / BPS_DENOM;
        ethReceived = ethBalance - royalty;

        if (royalty > 0) {
            (bool royaltyOk,) = ROYALTY_ADDR.call{value: royalty}("");
            if (!royaltyOk) revert TransferFailed();
        }

        (bool sendOk,) = msg.sender.call{value: ethReceived}("");
        if (!sendOk) revert TransferFailed();

        emit Sell(tokenAddress, msg.sender, tokenAmount, ethReceived, royalty);
    }

    function setRoyalty(uint256 buyBps_, uint256 sellBps_) external onlyOwner {
        if (buyBps_ > MAX_ROYALTY_BPS || sellBps_ > MAX_ROYALTY_BPS)
            revert InvalidRoyalty();
        buyRoyaltyBps  = buyBps_;
        sellRoyaltyBps = sellBps_;
        emit RoyaltyUpdated(buyBps_, sellBps_);
    }

    function transferOwnership(address newOwner) external onlyOwner {
        pendingOwner = newOwner;
    }

    function acceptOwnership() external {
        require(msg.sender == pendingOwner, "not pending");
        emit OwnershipTransferred(owner, msg.sender);
        owner = msg.sender;
        pendingOwner = address(0);
    }

    function rescueETH(uint256 amount) external onlyOwner {
        (bool ok,) = owner.call{value: amount}("");
        require(ok, "rescue failed");
    }

    function rescueERC20(address token, uint256 amount) external onlyOwner {
        IERC20(token).transfer(owner, amount);
    }

    function rescueWETH() external onlyOwner {
        uint256 wethBal = IERC20(WETH).balanceOf(address(this));
        if (wethBal > 0) {
            IWETH(WETH).withdraw(wethBal);
            (bool ok,) = owner.call{value: wethBal}("");
            require(ok, "rescue failed");
        }
    }

    function buyTotalEth(uint256 ethAmount) external view returns (uint256) {
        return ethAmount + (ethAmount * buyRoyaltyBps) / BPS_DENOM;
    }

    function sellNetEth(uint256 grossEth) external view returns (uint256) {
        return grossEth - (grossEth * sellRoyaltyBps) / BPS_DENOM;
    }

    receive() external payable {}
}

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "uniRouter_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "weth_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "royaltyAddr_",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "buyBps_",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "sellBps_",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "InsufficientOutput",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InsufficientRoyaltyBuffer",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidRoyalty",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "RebuyTooSoon",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "SwapFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TradingNotOpen",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TransferFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "Unauthorized",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAmount",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "buyer",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "ethIn",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "royaltyEth",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "tokensOut",
        "type": "uint256"
      }
    ],
    "name": "Buy",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "buyer",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "ethSpent",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "royaltyEth",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "tokensOut",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "refundedEth",
        "type": "uint256"
      }
    ],
    "name": "BuyExact",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "old",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "new_",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferred",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "newBuyBps",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "newSellBps",
        "type": "uint256"
      }
    ],
    "name": "RoyaltyUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "seller",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "tokensIn",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "ethOut",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "royaltyEth",
        "type": "uint256"
      }
    ],
    "name": "Sell",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "BPS_DENOM",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "FACTORY",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_ROYALTY_BPS",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "POOL_FEE",
    "outputs": [
      {
        "internalType": "uint24",
        "name": "",
        "type": "uint24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "ROYALTY_ADDR",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "UNI_ROUTER",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "WETH",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "acceptOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "tokenAddress",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amountOutMinimum",
        "type": "uint256"
      }
    ],
    "name": "buy",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "tokensOut",
        "type": "uint256"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "tokenAddress",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amountOut",
        "type": "uint256"
      }
    ],
    "name": "buyExact",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "ethSpent",
        "type": "uint256"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "buyRoyaltyBps",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "ethAmount",
        "type": "uint256"
      }
    ],
    "name": "buyTotalEth",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "lastBuyAt",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pendingOwner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "rebuyCooldownSecs",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "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": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "rescueETH",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "rescueWETH",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "tokenAddress",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "tokenAmount",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "amountOutMinimum",
        "type": "uint256"
      }
    ],
    "name": "sell",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "ethReceived",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "grossEth",
        "type": "uint256"
      }
    ],
    "name": "sellNetEth",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "sellRoyaltyBps",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "secs_",
        "type": "uint256"
      }
    ],
    "name": "setRebuyCooldown",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "buyBps_",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "sellBps_",
        "type": "uint256"
      }
    ],
    "name": "setRoyalty",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "stateMutability": "payable",
    "type": "receive"
  }
]

Bytecode hash 0x40a28d1f85e1487efe07f6df368b2497fd0f888a37033b7354d197dca93ce436