Gen4RestrictedTokenV4Pool

0x1c4670601bb9cf95a2407c9a8586e33fdc09f01d

Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
5,043 bytes
ABI entries
50
25 read · 6 write
License
none

Contract information

Address
0x1c4670601bb9cf95a2407c9a8586e33fdc09f01d
Chain
Robinhood Chain (4663)
Compiler
v0.8.24+commit.e11b9ed9
Optimization
Enabled
Creation tx
0xddb80e4745…f972f8832b

Token

Name
Oil
Symbol
USO
Decimals
18
Holders
12

Read contract (25)

MAX_DECIMALS()uint8
MIN_DECIMALS()uint8
TOKEN_VERSION()uint32
allowance(address, address)uint256
balanceOf(address)uint256
buyTaxRate()uint256
creator()address
decimals()uint8
factory()address
isBurnable()bool
launchBlock()uint256
launchTimestamp()uint48
maxBuysLimit()uint256
maxTxAmount()uint256
maxTxBps()uint16
maxWalletAmount()uint256
maxWalletBps()uint16
maxWalletLimit()uint256
metadataURI()string
name()string
poolManager()address
restrictionEndBlock()uint256
sellTaxRate()uint256
symbol()string
totalSupply()uint256

Events (2)

ApprovalTransfer

ABI

[
  {
    "inputs": [
      {
        "components": [
          {
            "internalType": "string",
            "name": "name",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "symbol",
            "type": "string"
          },
          {
            "internalType": "uint8",
            "name": "decimals",
            "type": "uint8"
          },
          {
            "internalType": "uint256",
            "name": "initialSupply",
            "type": "uint256"
          },
          {
            "internalType": "bool",
            "name": "burnable",
            "type": "bool"
          },
          {
            "internalType": "address",
            "name": "creator",
            "type": "address"
          },
          {
            "internalType": "string",
            "name": "metadataURI",
            "type": "string"
          }
        ],
        "internalType": "struct Gen4LaunchToken.Initialization",
        "name": "initial",
        "type": "tuple"
      },
      {
        "components": [
          {
            "internalType": "address",
            "name": "poolManager",
            "type": "address"
          },
          {
            "internalType": "uint16",
            "name": "maxWalletBps",
            "type": "uint16"
          },
          {
            "internalType": "uint16",
            "name": "maxTxBps",
            "type": "uint16"
          },
          {
            "internalType": "uint32",
            "name": "restrictionBlocks",
            "type": "uint32"
          }
        ],
        "internalType": "struct Gen4RestrictedTokenV4Pool.RestrictionConfigV4",
        "name": "restriction",
        "type": "tuple"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "BurningDisabled",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "spender",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "allowance",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "needed",
        "type": "uint256"
      }
    ],
    "name": "ERC20InsufficientAllowance",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "sender",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "balance",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "needed",
        "type": "uint256"
      }
    ],
    "name": "ERC20InsufficientBalance",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "approver",
        "type": "address"
      }
    ],
    "name": "ERC20InvalidApprover",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "receiver",
        "type": "address"
      }
    ],
    "name": "ERC20InvalidReceiver",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "sender",
        "type": "address"
      }
    ],
    "name": "ERC20InvalidSender",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "spender",
        "type": "address"
      }
    ],
    "name": "ERC20InvalidSpender",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidAddress",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidDecimals",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidMetadata",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidRestrictionConfig",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidSupply",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      }
    ],
    "name": "LaunchBlockBuyBlocked",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "cumulative",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "limit",
        "type": "uint256"
      }
    ],
    "name": "MaxBuysExceeded",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "resultingBalance",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "limit",
        "type": "uint256"
      }
    ],
    "name": "MaxWalletExceeded",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotLaunchFactory",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "owner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "spender",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "Approval",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "Transfer",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "MAX_DECIMALS",
    "outputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MIN_DECIMALS",
    "outputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "TOKEN_VERSION",
    "outputs": [
      {
        "internalType": "uint32",
        "name": "",
        "type": "uint32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "spender",
        "type": "address"
      }
    ],
    "name": "allowance",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "spender",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "approve",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "balanceOf",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "burn",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "burnFrom",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "buyTaxRate",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "creator",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "decimals",
    "outputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "factory",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "isBurnable",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "launchBlock",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "launchTimestamp",
    "outputs": [
      {
        "internalType": "uint48",
        "name": "",
        "type": "uint48"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "maxBuysLimit",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "maxTxAmount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "maxTxBps",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "maxWalletAmount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "maxWalletBps",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "maxWalletLimit",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "metadataURI",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "name",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "poolManager",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "restrictionEndBlock",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "sellTaxRate",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      }
    ],
    "name": "setInitialBuyRecipient",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "symbol",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "totalSupply",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "transfer",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "transferFrom",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  }
]

Source code

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import { Gen4LaunchToken } from "./Gen4LaunchToken.sol";

/// @title Equifold gen4 restricted launch token (Uniswap v4 pools)
/// @author Equifold
/// @notice The launch token minted by the two GEN4 v4 verticals:
///         {StandardTokenFactoryV4Native} (WETH-v4) and {StandardTokenFactoryV4StockV3} (v4stock).
///         The already-deployed {EquifoldRestrictedTokenV4} is its byte-frozen predecessor, kept
///         verbatim so `StandardTokenFactoryV4StockV2`'s live source still reproduces its on-chain
///         bytecode; the only differences here are the slim {Gen4LaunchToken.Initialization} base
///         (no `projectNumber`, no `launchContract`), the factory-gated
///         {setInitialBuyRecipient}, and the scanner aliases.
/// @dev Pool BUYS are limited during a fixed launch window. In Uniswap v4 every pool's balances are
///      custodied by the singleton PoolManager, so a "pool buy" is any transfer with
///      `from == poolManager` — one check covers every v4 pool of this token, with no per-fee-tier
///      probing. Launch-block buys are blocked (except the factory's atomic creator dev buy), then
///      each recipient is capped at {maxWalletBps} balance and {maxTxBps} cumulative buys through
///      {restrictionEndBlock} INCLUSIVE. Sells and wallet-to-wallet transfers are never restricted;
///      after the window the token behaves as a plain {Gen4LaunchToken}. External routers can
///      therefore trade the pool from block `launchBlock + 1` — only cap-breaking buys revert.
///
///      Window arithmetic, stated exactly (the factories' `restrictionBlocks` is admin-settable and
///      defaults to 2): `restrictionEndBlock = launchBlock + restrictionBlocks`, and {_update}
///      enforces while `block.number <= restrictionEndBlock`. So with `restrictionBlocks == 2` the
///      launch block bans all pool buys but the dev buy, blocks `launchBlock + 1` and
///      `launchBlock + 2` are capped, and the window lifts at `launchBlock + 3`.
contract Gen4RestrictedTokenV4Pool is Gen4LaunchToken {
    error InvalidRestrictionConfig();
    error LaunchBlockBuyBlocked(address recipient);
    error MaxBuysExceeded(address recipient, uint256 cumulative, uint256 limit);
    error MaxWalletExceeded(address recipient, uint256 resultingBalance, uint256 limit);
    error NotLaunchFactory();

    struct RestrictionConfigV4 {
        address poolManager;
        uint16 maxWalletBps;
        uint16 maxTxBps;
        uint32 restrictionBlocks;
    }

    uint16 private constant BPS_DENOMINATOR = 10_000;

    address public immutable poolManager;
    uint16 public immutable maxWalletBps;
    uint16 public immutable maxTxBps;
    uint256 public immutable launchBlock;
    /// @notice Last block on which {_update} still enforces the caps — INCLUSIVE. See the window
    ///         arithmetic in this contract's header.
    uint256 public immutable restrictionEndBlock;

    address private _initialBuyRecipient;
    mapping(address recipient => uint256 bought) private _restrictedPoolBuys;

    constructor(
        Initialization memory initial,
        RestrictionConfigV4 memory restriction
    ) Gen4LaunchToken(initial) {
        if (
            restriction.poolManager == address(0) || restriction.poolManager.code.length == 0
                || restriction.maxWalletBps == 0 || restriction.maxWalletBps > BPS_DENOMINATOR
                || restriction.maxTxBps < restriction.maxWalletBps
                || restriction.maxTxBps > BPS_DENOMINATOR || restriction.restrictionBlocks == 0
        ) revert InvalidRestrictionConfig();
        poolManager = restriction.poolManager;
        maxWalletBps = restriction.maxWalletBps;
        maxTxBps = restriction.maxTxBps;
        launchBlock = block.number;
        restrictionEndBlock = block.number + restriction.restrictionBlocks;
    }

    /// @notice Maximum recipient balance during the restricted launch window.
    function maxWalletLimit() public view returns (uint256) {
        return (totalSupply() * maxWalletBps) / BPS_DENOMINATOR;
    }

    /// @notice Cumulative pool-buy limit per recipient during the restricted window.
    function maxBuysLimit() public view returns (uint256) {
        return (totalSupply() * maxTxBps) / BPS_DENOMINATOR;
    }

    /// @notice Scanner-compatible alias for {maxWalletLimit}, reported as `type(uint256).max` once
    ///         the launch window has elapsed.
    /// @dev Risk scanners and aggregator "safety" panels probe these conventional names. Returning
    ///      the cap unconditionally (as other launchpads on this chain do) makes them display a
    ///      PERMANENT "3% max wallet" long after the window is over; returning `type(uint256).max`
    ///      past {restrictionEndBlock} makes them report "no limits", which is the truth — after the
    ///      window {_update} enforces nothing at all. The boundary matches {_update} exactly:
    ///      {restrictionEndBlock} itself is still capped, `restrictionEndBlock + 1` is not.
    function maxWalletAmount() external view returns (uint256) {
        if (block.number > restrictionEndBlock) return type(uint256).max;
        return maxWalletLimit();
    }

    /// @notice Scanner-compatible alias for {maxBuysLimit} (the per-recipient cumulative pool-buy
    ///         cap), reported as `type(uint256).max` once the launch window has elapsed.
    function maxTxAmount() external view returns (uint256) {
        if (block.number > restrictionEndBlock) return type(uint256).max;
        return maxBuysLimit();
    }

    /// @notice Always 0 — this token has no transfer tax, in either direction, ever. Declared so
    ///         scanners probing the conventional name read a hard zero instead of "unknown".
    function buyTaxRate() external pure returns (uint256) {
        return 0;
    }

    /// @notice Always 0 — see {buyTaxRate}.
    function sellTaxRate() external pure returns (uint256) {
        return 0;
    }

    /// @notice Opens or closes the single-recipient exemption used for the launch module's atomic
    ///         creator dev buy. Only callable by the {Gen4LaunchToken.factory} that deployed this
    ///         token, which wraps it around its own `initialBuy` call in the launch transaction.
    /// @dev Gated on the DEPLOYER rather than on a `launchContract` constructor field precisely
    ///      because the module address had to leave the CREATE2 init code. The exemption only
    ///      bypasses the caps while `block.number == launchBlock`; if left set afterward it has no
    ///      further effect (see {_update}).
    function setInitialBuyRecipient(
        address recipient
    ) external {
        if (msg.sender != factory) revert NotLaunchFactory();
        _initialBuyRecipient = recipient;
    }

    /// @dev Restricts only pool→user transfers (buys) during the launch window. The
    ///      {_initialBuyRecipient} exemption is scoped to the launch block itself, so it can only
    ///      ever cover the factory's atomic creator dev buy — never the 3%/3.3% caps for the rest of
    ///      the restriction window, even if left set by mistake.
    function _update(
        address from,
        address to,
        uint256 value
    ) internal override {
        if (
            from != address(0) && to != address(0) && block.number <= restrictionEndBlock
                && from == poolManager
        ) {
            bool exemptBuy = block.number == launchBlock && _initialBuyRecipient != address(0)
                && to == _initialBuyRecipient;
            if (!exemptBuy) {
                if (block.number == launchBlock) revert LaunchBlockBuyBlocked(to);
                uint256 resultingBalance = balanceOf(to) + value;
                uint256 walletLimit = maxWalletLimit();
                if (resultingBalance > walletLimit) {
                    revert MaxWalletExceeded(to, resultingBalance, walletLimit);
                }
                uint256 cumulative = _restrictedPoolBuys[to] + value;
                uint256 buysLimit = maxBuysLimit();
                if (cumulative > buysLimit) {
                    revert MaxBuysExceeded(to, cumulative, buysLimit);
                }
                _restrictedPoolBuys[to] = cumulative;
            }
        }
        super._update(from, to, value);
    }
}
Chain explorer2441msChain node94ms