Robinhood MainnetChain ID: 4663
Contract detail

NeonNodes

NeonNodes is a verified Robinhood Chain smart contract at 0xb3ac2c2c…a645eac7 with 17 indexed transactions.

Address type
Smart contract
Transactions
17
First indexed block
#28455229
Latest indexed block
#28503744
Emitted events
54
Token tracker
testing mods
Verification
Verified
Compiler
v0.8.27+commit.40a35a09
Verification match
full
Creation transaction
0xc8e7cd42…46304ef4
Created at block
#28455229
Language
solidity
EVM version
cancun
License
none
Compiler optimization
Enabled · 200 runs
Related activity

Transactions

Verified contract

Contract source code

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

import "erc721a/contracts/ERC721A.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
import "@openzeppelin/contracts/token/common/ERC2981.sol";

interface ITransferValidator {
    function validateTransfer(address caller, address from, address to, uint256 tokenId) external;
}

contract NeonNodes is ERC721A, Ownable, ERC2981 {
    using ECDSA for bytes32;
    using MessageHashUtils for bytes32;

    // Testing deployment — 50 tokens, matches /prod/testing_42 image set.
    // Bump to 5555 for the real production deploy.
    uint256 public constant MAX_SUPPLY = 50;
    uint256 public constant MAX_PER_WALLET = 25;
    uint8   public constant MAX_BATCH = 5;
    uint256 public mintPrice = 0.00075 ether;

    address public signer;
    address public fundsReceiver;
    string  private _baseTokenURI;
    string  private _contractMetadataURI;
    bool    public mintActive;

    mapping(bytes32 => bool) public usedNonces;

    // ─── ICreatorToken (LimitBreak) transfer validator ────────────────────
    address private _transferValidator;
    event TransferValidatorUpdated(address oldValidator, address newValidator);

    error MintNotActive();
    error MaxSupplyReached();
    error WalletLimitReached();
    error InsufficientPayment();
    error InvalidSignature();
    error NonceAlreadyUsed();
    error TransferFailed();
    error InvalidQuantity();

    constructor(
        address _signer,
        address _royaltyReceiver,
        address _fundsReceiver,
        string memory baseURI_,
        string memory contractURI_
    ) ERC721A("testing mods", "testing mods") Ownable(msg.sender) {
        signer = _signer;
        fundsReceiver = _fundsReceiver;
        _baseTokenURI = baseURI_;
        _contractMetadataURI = contractURI_;
        _setDefaultRoyalty(_royaltyReceiver, 500); // 5% (500 basis points)
    }

    function mint(
        bytes32 nonce,
        bytes calldata signature
    ) external payable {
        if (!mintActive) revert MintNotActive();
        if (_totalMinted() + 1 > MAX_SUPPLY) revert MaxSupplyReached();
        if (_numberMinted(msg.sender) + 1 > MAX_PER_WALLET) revert WalletLimitReached();
        if (msg.value < mintPrice) revert InsufficientPayment();
        if (usedNonces[nonce]) revert NonceAlreadyUsed();

        bytes32 messageHash = keccak256(
            abi.encodePacked(msg.sender, nonce, address(this), block.chainid)
        );
        bytes32 ethSignedHash = messageHash.toEthSignedMessageHash();
        if (ethSignedHash.recover(signature) != signer) revert InvalidSignature();

        usedNonces[nonce] = true;
        _mint(msg.sender, 1);

        (bool success, ) = payable(fundsReceiver).call{value: msg.value}("");
        if (!success) revert TransferFailed();
    }

    function mintBatch(
        uint8 quantity,
        bytes32 nonce,
        bytes calldata signature
    ) external payable {
        if (!mintActive) revert MintNotActive();
        if (quantity == 0 || quantity > MAX_BATCH) revert InvalidQuantity();
        if (_totalMinted() + quantity > MAX_SUPPLY) revert MaxSupplyReached();
        if (_numberMinted(msg.sender) + quantity > MAX_PER_WALLET) revert WalletLimitReached();
        if (msg.value < mintPrice * quantity) revert InsufficientPayment();
        if (usedNonces[nonce]) revert NonceAlreadyUsed();

        bytes32 messageHash = keccak256(
            abi.encodePacked(msg.sender, nonce, quantity, address(this), block.chainid)
        );
        bytes32 ethSignedHash = messageHash.toEthSignedMessageHash();
        if (ethSignedHash.recover(signature) != signer) revert InvalidSignature();

        usedNonces[nonce] = true;
        _mint(msg.sender, quantity);

        (bool success, ) = payable(fundsReceiver).call{value: msg.value}("");
        if (!success) revert TransferFailed();
    }

    function totalMinted() external view returns (uint256) {
        return _totalMinted();
    }

    function numberMinted(address wallet) external view returns (uint256) {
        return _numberMinted(wallet);
    }

    function _startTokenId() internal pure override returns (uint256) {
        return 1;
    }

    function _baseURI() internal view override returns (string memory) {
        return _baseTokenURI;
    }

    function tokenURI(uint256 tokenId) public view override returns (string memory) {
        if (!_exists(tokenId)) revert URIQueryForNonexistentToken();
        string memory base = _baseURI();
        return bytes(base).length > 0
            ? string(abi.encodePacked(base, _toString(tokenId)))
            : "";
    }

    function contractURI() external view returns (string memory) {
        return _contractMetadataURI;
    }

    function setBaseURI(string calldata baseURI) external onlyOwner {
        _baseTokenURI = baseURI;
    }

    function setContractURI(string calldata uri) external onlyOwner {
        _contractMetadataURI = uri;
    }

    function setSigner(address _signer) external onlyOwner {
        signer = _signer;
    }

    function setMintPrice(uint256 _price) external onlyOwner {
        mintPrice = _price;
    }

    function setMintActive(bool _active) external onlyOwner {
        mintActive = _active;
    }

    function setFundsReceiver(address _fundsReceiver) external onlyOwner {
        fundsReceiver = _fundsReceiver;
    }

    function withdraw() external onlyOwner {
        (bool success, ) = payable(fundsReceiver).call{value: address(this).balance}("");
        if (!success) revert TransferFailed();
    }

    // ─── ICreatorToken (LimitBreak) — enforces royalties via marketplace whitelist ─
    function getTransferValidator() external view returns (address) {
        return _transferValidator;
    }

    function setTransferValidator(address validator) external onlyOwner {
        address old = _transferValidator;
        _transferValidator = validator;
        emit TransferValidatorUpdated(old, validator);
    }

    function getTransferValidationFunction() external pure returns (bytes4 functionSignature, bool isViewFunction) {
        // 0xcaee23ea = validateTransfer(address,address,address,uint256)
        return (0xcaee23ea, false);
    }

    function _beforeTokenTransfers(
        address from,
        address to,
        uint256 startTokenId,
        uint256 quantity
    ) internal virtual override {
        super._beforeTokenTransfers(from, to, startTokenId, quantity);
        address v = _transferValidator;
        // Skip on mint (from == 0) so bootstrapping and initial mints are never gated.
        if (v != address(0) && from != address(0)) {
            for (uint256 i = 0; i < quantity; i++) {
                ITransferValidator(v).validateTransfer(msg.sender, from, to, startTokenId + i);
            }
        }
    }

    function supportsInterface(
        bytes4 interfaceId
    ) public view override(ERC721A, ERC2981) returns (bool) {
        return interfaceId == 0xad0d7f6c // ICreatorToken
            || interfaceId == 0xe8a3d485 // contractURI()
            || ERC721A.supportsInterface(interfaceId)
            || ERC2981.supportsInterface(interfaceId);
    }
}