Token

0x17b7b30c90a96e6254e1747c303d245751a8def1

Verification
Verified
v0.8.28+commit.7893614a
Type
Contract
4,819 bytes
ABI entries
54
19 read · 6 write
License
none

Contract information

Address
0x17b7b30c90a96e6254e1747c303d245751a8def1
Chain
Robinhood Chain (4663)
Compiler
v0.8.28+commit.7893614a
Optimization
Enabled
Creation tx
0x66b4bf39aa…84beb9b189

Token

Name
onlyme
Symbol
ONLYME
Decimals
18
Holders
9

Read contract (19)

DOMAIN_SEPARATOR()bytes32
ENS_REVERSE_REGISTRAR()address
MAX_ENS_NAME_LEN()uint256
MAX_METADATA_URI_LEN()uint256
TOTAL_SUPPLY()uint256
allowance(address, address)uint256
balanceOf(address)uint256
bondingCurve()address
contractURI()string
creator()address
decimals()uint8
eip712Domain()bytes1, string, string, uint256, address, bytes32, uint256[]
ensReverseSet()bool
factory()address
graduated()bool
name()string
nonces(address)uint256
symbol()string
totalSupply()uint256

Events (4)

ApprovalEIP712DomainChangedEnsReverseNameSetTransfer

ABI

[
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "name_",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "symbol_",
        "type": "string"
      },
      {
        "internalType": "address",
        "name": "factory_",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "AlreadyGraduated",
    "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": "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": [],
    "name": "EmptyName",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "EnsReverseAlreadySet",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "currentNonce",
        "type": "uint256"
      }
    ],
    "name": "InvalidAccountNonce",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidMetadataChar",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidMetadataLength",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidShortString",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "MissingCurveHandshake",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "OnlyBondingCurve",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "OnlyCreator",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "str",
        "type": "string"
      }
    ],
    "name": "StringTooLong",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TransfersLocked",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAddress",
    "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": [],
    "name": "EIP712DomainChanged",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "string",
        "name": "name",
        "type": "string"
      }
    ],
    "name": "EnsReverseNameSet",
    "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": "DOMAIN_SEPARATOR",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "ENS_REVERSE_REGISTRAR",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_ENS_NAME_LEN",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_METADATA_URI_LEN",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "TOTAL_SUPPLY",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "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": [],
    "name": "bondingCurve",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "contractURI",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "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": "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": "ensReverseSet",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "factory",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "finalizeGraduation",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "graduated",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "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": "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": "string",
        "name": "name",
        "type": "string"
      }
    ],
    "name": "setEnsReverseName",
    "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.28;

import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";

import { IBondingCurve } from "./interfaces/IBondingCurve.sol";
import { IFactory } from "./interfaces/IFactory.sol";
import { IToken } from "./interfaces/IToken.sol";

/// @dev Single function used on the canonical ENS Reverse Registrar
///      (`addr.reverse` namespace owner). `setName` claims `<msg.sender>.addr.reverse`,
///      attaches a default reverse resolver, and writes the name record in one call.
interface IReverseRegistrar {
    function setName(string calldata name) external returns (bytes32);
}

/// @title  Token
/// @notice Fixed-supply ERC20 with EIP-2612 permit, ERC-7572 metadata URI, and a
///         pre-graduation transfer lock. The bonding-curve address comes from a factory
///         handshake — read at construction via `IFactory(msg.sender).pendingCurve()` —
///         keeping the token's CREATE2 init-code hash independent of the curve and so
///         vanity-mineable.
contract Token is ERC20, ERC20Permit, IToken {
    error ZeroAddress();
    error InvalidMetadataLength();
    error InvalidMetadataChar();
    error TransfersLocked();
    error OnlyBondingCurve();
    error OnlyCreator();
    error AlreadyGraduated();
    error MissingCurveHandshake();
    error EnsReverseAlreadySet();
    error EmptyName();

    uint256 public constant TOTAL_SUPPLY = 1_000_000_000 * 1e18;
    uint256 public constant MAX_METADATA_URI_LEN = 512;
    uint256 public constant MAX_ENS_NAME_LEN = 255;

    /// @notice Canonical mainnet ENS Reverse Registrar
    ///         (owner of `namehash("addr.reverse")` on the ENS Registry). Hard-coded
    ///         because the address is part of the protocol's external surface; deploying
    ///         on a different chain would require a different fork/redeploy of this
    ///         contract.
    address public constant ENS_REVERSE_REGISTRAR = 0xa58E81fe9b61B5c3fE2AFD33CF304c454AbFc7Cb;

    /// @notice Single-shot guard for {setEnsReverseName}.
    /// @inheritdoc IToken
    bool public override ensReverseSet;

    /// @notice Emitted exactly once when the token's reverse ENS name is claimed — by
    ///         the current creator directly or by the factory's authorized deployer
    ///         proxy during an atomic deploy.
    event EnsReverseNameSet(string name);

    /// @inheritdoc IToken
    address public immutable override bondingCurve;
    /// @inheritdoc IToken
    address public immutable override factory;
    /// @inheritdoc IToken
    bool public override graduated;

    /// @inheritdoc IToken
    /// @dev ERC-7572. Set once via the factory handshake; no setter exists.
    string public override contractURI;

    /// @param name_    Length ∈ [1, 32]; rejects ASCII control characters to defend block
    ///                 explorers that render the name verbatim.
    /// @param symbol_  Length ∈ [1, 10]; same charset rule.
    /// @param factory_ Must equal `msg.sender`. Provides the bonding-curve and metadata-URI
    ///                 values via storage handshake.
    constructor(string memory name_, string memory symbol_, address factory_) ERC20(name_, symbol_) ERC20Permit(name_) {
        if (factory_ == address(0)) revert ZeroAddress();
        if (factory_ != msg.sender) revert MissingCurveHandshake();
        _validateMetadata(bytes(name_), 32);
        _validateMetadata(bytes(symbol_), 10);

        address curve = IFactory(factory_).pendingCurve();
        if (curve == address(0)) revert MissingCurveHandshake();

        bondingCurve = curve;
        factory = factory_;

        // Empty URI is allowed (opt-out); length is bounded to limit deploy gas + storage.
        string memory uri = IFactory(factory_).pendingURI();
        if (bytes(uri).length > MAX_METADATA_URI_LEN) revert InvalidMetadataLength();
        contractURI = uri;

        _mint(curve, TOTAL_SUPPLY);
    }

    /// @inheritdoc IToken
    function creator() external view override returns (address) {
        return IBondingCurve(bondingCurve).CREATOR();
    }

    /// @inheritdoc IToken
    function finalizeGraduation() external override {
        if (msg.sender != bondingCurve) revert OnlyBondingCurve();
        if (graduated) revert AlreadyGraduated();
        graduated = true;
    }

    /// @dev Pre-graduation, only transfers involving the bonding curve are allowed.
    ///      Mints (`from == 0`) and burns (`to == 0`) always pass.
    function _update(address from, address to, uint256 value) internal override {
        if (!graduated && from != address(0) && to != address(0)) {
            if (from != bondingCurve && to != bondingCurve) revert TransfersLocked();
        }
        super._update(from, to, value);
    }

    function _validateMetadata(bytes memory s, uint256 maxLen) private pure {
        uint256 len = s.length;
        if (len == 0 || len > maxLen) revert InvalidMetadataLength();
        for (uint256 i = 0; i < len; ++i) {
            uint8 c = uint8(s[i]);
            if (c < 0x20 || c == 0x7F) revert InvalidMetadataChar();
        }
    }

    /// @inheritdoc IToken
    /// @dev Security model:
    ///        - Single-shot (`ensReverseSet` flag), so a rotated creator cannot churn
    ///          the displayed name. Once set, frozen forever.
    ///        - Two authorized principals: the *current* creator per
    ///          {IBondingCurve.CREATOR}, OR the factory's currently-registered
    ///          `deployerProxy` (`IFactory.deployerProxy()`). The proxy path enables
    ///          atomic forward-and-reverse registration inside a single deploy tx; the
    ///          creator path remains for direct claims outside the proxy flow.
    ///        - The reverse record is, by ENS protocol, `<this token's address>.addr.reverse`.
    ///          Setting it does NOT make `name` automatically resolve to this token —
    ///          ENS-aware wallets validate that the FORWARD `addr(node)` lookup of `name`
    ///          points back to this address before displaying it. So a creator (or proxy)
    ///          setting a misleading name (e.g. "vitalik.eth") cannot impersonate;
    ///          reverse-without-forward-validation is filtered out at the wallet layer.
    ///        - Bounded length so the call cannot grief downstream `name()` readers
    ///          with an unbounded string.
    function setEnsReverseName(string calldata name) external override {
        address proxy = IFactory(factory).deployerProxy();
        bool isCreator = msg.sender == IBondingCurve(bondingCurve).CREATOR();
        bool isProxy = proxy != address(0) && msg.sender == proxy;
        if (!isCreator && !isProxy) revert OnlyCreator();
        if (ensReverseSet) revert EnsReverseAlreadySet();
        uint256 len = bytes(name).length;
        if (len == 0) revert EmptyName();
        if (len > MAX_ENS_NAME_LEN) revert InvalidMetadataLength();

        ensReverseSet = true;
        IReverseRegistrar(ENS_REVERSE_REGISTRAR).setName(name);
        emit EnsReverseNameSet(name);
    }
}
Chain explorer3274msChain node82ms