SimpleERC721
A standard ERC-721 collection with base URI, max supply cap, and owner-controlled sequential minting.
SimpleERC721.sol
// SPDX-License-Identifier: MITpragma solidity ^0.8.20; interface IERC721Receiver { function onERC721Received(address, address, uint256, bytes calldata) external returns (bytes4);} contract SimpleERC721 { string public name; string public symbol; string private baseURI; address public owner; uint256 public immutable maxSupply; uint256 public nextTokenId; mapping(uint256 => address) public ownerOf; mapping(address => uint256) public balanceOf; mapping(uint256 => address) public getApproved; mapping(address => mapping(address => bool)) public isApprovedForAll; event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); event ApprovalForAll(address indexed owner, address indexed operator, bool approved); constructor(string memory name_, string memory symbol_, string memory baseURI_, uint256 maxSupply_) { name = name_; symbol = symbol_; baseURI = baseURI_; maxSupply = maxSupply_; owner = msg.sender; } function mint(address to) external returns (uint256) { require(msg.sender == owner, "NOT_OWNER"); require(nextTokenId < maxSupply, "SOLD_OUT"); uint256 id = nextTokenId++; ownerOf[id] = to; balanceOf[to] += 1; emit Transfer(address(0), to, id); return id; } function approve(address to, uint256 tokenId) external { address holder = ownerOf[tokenId]; require(msg.sender == holder || isApprovedForAll[holder][msg.sender], "NOT_AUTHORIZED"); getApproved[tokenId] = to; emit Approval(holder, to, tokenId); } function setApprovalForAll(address operator, bool approved) external { isApprovedForAll[msg.sender][operator] = approved; emit ApprovalForAll(msg.sender, operator, approved); } function transferFrom(address from, address to, uint256 tokenId) public { require(ownerOf[tokenId] == from, "WRONG_FROM"); require(to != address(0), "ZERO_ADDR"); require( msg.sender == from || isApprovedForAll[from][msg.sender] || getApproved[tokenId] == msg.sender, "NOT_AUTHORIZED" ); delete getApproved[tokenId]; balanceOf[from] -= 1; balanceOf[to] += 1; ownerOf[tokenId] = to; emit Transfer(from, to, tokenId); } function safeTransferFrom(address from, address to, uint256 tokenId) external { transferFrom(from, to, tokenId); if (to.code.length != 0) { require( IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, "") == IERC721Receiver.onERC721Received.selector, "UNSAFE_RECIPIENT" ); } } function tokenURI(uint256 tokenId) external view returns (string memory) { require(ownerOf[tokenId] != address(0), "NOT_MINTED"); return string(abi.encodePacked(baseURI, _toString(tokenId))); } function supportsInterface(bytes4 id) external pure returns (bool) { return id == 0x80ac58cd || id == 0x01ffc9a7; } function _toString(uint256 v) internal pure returns (string memory) { if (v == 0) return "0"; uint256 j = v; uint256 len; while (j != 0) { len++; j /= 10; } bytes memory b = new bytes(len); while (v != 0) { len--; b[len] = bytes1(uint8(48 + v % 10)); v /= 10; } return string(b); }}