GenesisTokenV3

0x60e906a5c15f7517845f00fa8f61ce22b40e14ed

Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
8,256 bytes
ABI entries
59
21 read · 7 write
License
none

Contract information

Address
0x60e906a5c15f7517845f00fa8f61ce22b40e14ed
Chain
Robinhood Chain (4663)
Compiler
v0.8.24+commit.e11b9ed9
Optimization
Enabled
Creation tx
0xb52a100ed0…828e726931

Token

Name
Crash Cat
Symbol
CRASHCAT
Decimals
18
Holders
52

Read contract (21)

BURN_ADDRESS()address
CLOCK_MODE()string
DOMAIN_SEPARATOR()bytes32
EXEMPT_INITIAL_HOLDER()address
MAX_WALLET_BPS()uint16
allowance(address, address)uint256
balanceOf(address)uint256
checkpoints(address, uint32)tuple
clock()uint48
decimals()uint8
delegates(address)address
eip712Domain()bytes1, string, string, uint256, address, bytes32, uint256[]
exemptPool()address
getPastTotalSupply(uint256)uint256
getPastVotes(address, uint256)uint256
getVotes(address)uint256
name()string
nonces(address)uint256
numCheckpoints(address)uint32
symbol()string
totalSupply()uint256

Events (5)

ApprovalDelegateChangedDelegateVotesChangedEIP712DomainChangedTransfer

ABI

[
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "name_",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "symbol_",
        "type": "string"
      },
      {
        "internalType": "uint256",
        "name": "totalSupply_",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      },
      {
        "internalType": "uint16",
        "name": "maxWalletBps_",
        "type": "uint16"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "CheckpointUnorderedInsertion",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ECDSAInvalidSignature",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "length",
        "type": "uint256"
      }
    ],
    "name": "ECDSAInvalidSignatureLength",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "s",
        "type": "bytes32"
      }
    ],
    "name": "ECDSAInvalidSignatureS",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "increasedSupply",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "cap",
        "type": "uint256"
      }
    ],
    "name": "ERC20ExceededSafeSupply",
    "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": [
      {
        "internalType": "uint256",
        "name": "deadline",
        "type": "uint256"
      }
    ],
    "name": "ERC2612ExpiredSignature",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "signer",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      }
    ],
    "name": "ERC2612InvalidSigner",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "timepoint",
        "type": "uint256"
      },
      {
        "internalType": "uint48",
        "name": "clock",
        "type": "uint48"
      }
    ],
    "name": "ERC5805FutureLookup",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ERC6372InconsistentClock",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "currentNonce",
        "type": "uint256"
      }
    ],
    "name": "InvalidAccountNonce",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidShortString",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint16",
        "name": "requested",
        "type": "uint16"
      }
    ],
    "name": "MaxWalletBpsTooHigh",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "balance",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "cap",
        "type": "uint256"
      }
    ],
    "name": "MaxWalletExceeded",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotFactory",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "PoolExemptionAlreadySet",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "bits",
        "type": "uint8"
      },
      {
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "SafeCastOverflowedUintDowncast",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "str",
        "type": "string"
      }
    ],
    "name": "StringTooLong",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "expiry",
        "type": "uint256"
      }
    ],
    "name": "VotesExpiredSignature",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroPoolAddress",
    "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": "delegator",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "fromDelegate",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "toDelegate",
        "type": "address"
      }
    ],
    "name": "DelegateChanged",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "delegate",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "previousVotes",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "newVotes",
        "type": "uint256"
      }
    ],
    "name": "DelegateVotesChanged",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [],
    "name": "EIP712DomainChanged",
    "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": "BURN_ADDRESS",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "CLOCK_MODE",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "DOMAIN_SEPARATOR",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "EXEMPT_INITIAL_HOLDER",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_WALLET_BPS",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "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": "address",
        "name": "account",
        "type": "address"
      },
      {
        "internalType": "uint32",
        "name": "pos",
        "type": "uint32"
      }
    ],
    "name": "checkpoints",
    "outputs": [
      {
        "components": [
          {
            "internalType": "uint48",
            "name": "_key",
            "type": "uint48"
          },
          {
            "internalType": "uint208",
            "name": "_value",
            "type": "uint208"
          }
        ],
        "internalType": "struct Checkpoints.Checkpoint208",
        "name": "",
        "type": "tuple"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "clock",
    "outputs": [
      {
        "internalType": "uint48",
        "name": "",
        "type": "uint48"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "decimals",
    "outputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "delegatee",
        "type": "address"
      }
    ],
    "name": "delegate",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "delegatee",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "nonce",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "expiry",
        "type": "uint256"
      },
      {
        "internalType": "uint8",
        "name": "v",
        "type": "uint8"
      },
      {
        "internalType": "bytes32",
        "name": "r",
        "type": "bytes32"
      },
      {
        "internalType": "bytes32",
        "name": "s",
        "type": "bytes32"
      }
    ],
    "name": "delegateBySig",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "delegates",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "eip712Domain",
    "outputs": [
      {
        "internalType": "bytes1",
        "name": "fields",
        "type": "bytes1"
      },
      {
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "version",
        "type": "string"
      },
      {
        "internalType": "uint256",
        "name": "chainId",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "verifyingContract",
        "type": "address"
      },
      {
        "internalType": "bytes32",
        "name": "salt",
        "type": "bytes32"
      },
      {
        "internalType": "uint256[]",
        "name": "extensions",
        "type": "uint256[]"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "exemptPool",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "timepoint",
        "type": "uint256"
      }
    ],
    "name": "getPastTotalSupply",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "timepoint",
        "type": "uint256"
      }
    ],
    "name": "getPastVotes",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "getVotes",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "name",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      }
    ],
    "name": "nonces",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "numCheckpoints",
    "outputs": [
      {
        "internalType": "uint32",
        "name": "",
        "type": "uint32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "spender",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "deadline",
        "type": "uint256"
      },
      {
        "internalType": "uint8",
        "name": "v",
        "type": "uint8"
      },
      {
        "internalType": "bytes32",
        "name": "r",
        "type": "bytes32"
      },
      {
        "internalType": "bytes32",
        "name": "s",
        "type": "bytes32"
      }
    ],
    "name": "permit",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "pool",
        "type": "address"
      }
    ],
    "name": "setPoolExemption",
    "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 {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import {ERC20Votes} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
import {Nonces} from "@openzeppelin/contracts/utils/Nonces.sol";

/// @title GenesisTokenV3
/// @notice The one consistent ERC20 template for every direct-Uniswap-V3 GenesisPad launch.
///
/// Named `GenesisTokenV3` (not `GenesisToken`) to avoid any collision with the legacy
/// bonding-curve token template at contracts/src/GenesisToken.sol, which remains
/// untouched and continues to serve existing live tokens - see docs/MIGRATION_PLAN.md.
/// This is an intentional, documented naming deviation from the spec's literal
/// "GenesisToken" name, per Requirement 3's own allowance: "Equivalent names are
/// acceptable when justified."
///
/// By design this contract has NO privileged role whatsoever after construction:
/// no owner, no Ownable, no AccessControl, no mint function, no pause, no blacklist,
/// no whitelist, no tax, no adjustable fee, no trading switch, no proxy/upgradeability/
/// delegatecall, and no external calls from `_update`. The entire fixed supply is minted
/// exactly once, to `recipient`, inside the constructor.
///
/// The one exception is an OPTIONAL max-wallet cap, added per explicit product request:
/// creators may choose one at deploy time. It is set once and permanently locked - see
/// `setPoolExemption` below for why the pool-exemption address specifically is set via a
/// dedicated function instead of the constructor, and why that doesn't weaken the
/// "no ongoing admin control" guarantee.
///
/// Deliberately excludes any on-chain metadata URI/social-link storage - per
/// Requirement 3's "Token metadata" guidance, that information belongs in
/// GenesisPad backend metadata (which is attached off-chain per launch),
/// not duplicated into every token's storage.
contract GenesisTokenV3 is ERC20, ERC20Permit, ERC20Votes {
    error MaxWalletBpsTooHigh(uint16 requested);
    error MaxWalletExceeded(address account, uint256 balance, uint256 cap);
    error NotFactory();
    error PoolExemptionAlreadySet();
    error ZeroPoolAddress();

    address public constant BURN_ADDRESS = 0x000000000000000000000000000000000000dEaD;

    /// @notice Max wallet cap, in basis points of total supply. 0 disables the check entirely.
    /// Immutable - set once at deployment, never adjustable by anyone afterward.
    uint16 public immutable MAX_WALLET_BPS;

    /// @notice The address the full supply was minted to (GenesisLaunchFactory in the standard
    /// flow), exempt from the cap - it transiently holds the whole supply before supplying it
    /// to the V3 position. Immutable, set in the constructor.
    address public immutable EXEMPT_INITIAL_HOLDER;

    /// @notice The Uniswap V3 pool address, exempt from the cap once set (a V3 pool contract
    /// holds its own reserves as a real ERC20 balance - for a single-sided launch that's
    /// effectively the entire supply, which must never be blocked by a wallet cap meant for
    /// individual holders).
    ///
    /// This is NOT a constructor parameter, deliberately: the pool's address depends on
    /// comparing this token's own address against the paired asset's address (Uniswap V3 sorts
    /// pool assets by address) - but this token's address would itself change if the pool
    /// address were baked into its constructor's init code, since CREATE2 addresses depend on
    /// the full constructor argument encoding. Including the pool address as a constructor arg
    /// would make the token's own address depend on the pool address, which depends on the
    /// token's address - an unresolvable circular dependency. Instead, `setPoolExemption` is
    /// called by the launch factory exactly once, atomically within the same launch
    /// transaction, immediately after the factory creates the pool (which only needs to know
    /// this token's already-fixed address, not the other way around) and before minting any
    /// liquidity into it. Restricted to `EXEMPT_INITIAL_HOLDER` (the factory) and settable only
    /// once - functionally equivalent to a constructor-time immutable, without the circularity.
    address public exemptPool;

    /// @param name_ Token name.
    /// @param symbol_ Token symbol.
    /// @param totalSupply_ Fixed total supply, minted entirely to `recipient` in this constructor.
    /// @param recipient Address receiving the entire minted supply. In the standard GenesisPad
    /// launch flow this is GenesisLaunchFactory, which immediately supplies it to the Uniswap V3
    /// position in the same transaction (see GenesisLaunchFactory.sol) - the factory is never
    /// meant to retain a meaningful balance afterward.
    /// @param maxWalletBps_ Optional max-wallet cap in basis points of total supply (0-10000).
    /// 0 disables the check entirely. Immutable once set - see contract-level documentation.
    constructor(
        string memory name_,
        string memory symbol_,
        uint256 totalSupply_,
        address recipient,
        uint16 maxWalletBps_
    ) ERC20(name_, symbol_) ERC20Permit(name_) {
        if (maxWalletBps_ > 10_000) revert MaxWalletBpsTooHigh(maxWalletBps_);
        MAX_WALLET_BPS = maxWalletBps_;
        EXEMPT_INITIAL_HOLDER = recipient;
        _mint(recipient, totalSupply_);
    }

    /// @notice Sets the pool-exemption address exactly once. Callable only by
    /// `EXEMPT_INITIAL_HOLDER` (the launch factory), which calls this atomically within the
    /// same launch transaction that deploys this token - there is no window in which any other
    /// party could call it first, and it can never be called again afterward by anyone,
    /// including the factory itself.
    function setPoolExemption(address pool) external {
        if (msg.sender != EXEMPT_INITIAL_HOLDER) revert NotFactory();
        if (exemptPool != address(0)) revert PoolExemptionAlreadySet();
        if (pool == address(0)) revert ZeroPoolAddress();
        exemptPool = pool;
    }

    function _isExemptFromMaxWallet(address account) internal view returns (bool) {
        return account == exemptPool || account == EXEMPT_INITIAL_HOLDER || account == BURN_ADDRESS;
    }

    // --- Required overrides: ERC20Votes and ERC20Permit both touch _update/nonces in OZ v5 ---

    function _update(address from, address to, uint256 value) internal override(ERC20, ERC20Votes) {
        super._update(from, to, value);

        if (MAX_WALLET_BPS > 0 && to != address(0) && !_isExemptFromMaxWallet(to)) {
            uint256 cap = (totalSupply() * MAX_WALLET_BPS) / 10_000;
            uint256 balance = balanceOf(to);
            if (balance > cap) revert MaxWalletExceeded(to, balance, cap);
        }
    }

    function nonces(address owner) public view override(ERC20Permit, Nonces) returns (uint256) {
        return super.nonces(owner);
    }
}
Chain explorer4859msChain node69ms