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
- Creator
- 0xe662Ce5FeA…4A00CffC3C
- Creation tx
- 0xddb80e4745…f972f8832b
Token
- Name
- Oil
- Symbol
- USO
- Decimals
- 18
- Holders
- 12
- Market
- View token page →
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