// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import "./Strings.sol";
import "./IERC165.sol";
import "./IERC721.sol";
import "./IERC721Metadata.sol";
import "./IERC721Enumerable.sol";
import "./IERC721TokenReceiver.sol";
contract ERC721 is IERC165, IERC721, IERC721Metadata, IERC721Enumerable {
using Strings for uint256;
// =============================================================
// IERC721
// =============================================================
mapping(uint => address) public ownerOf;
mapping(uint => address) public getApproved;
mapping(address => uint) public balanceOf;
mapping(address => mapping(address => bool)) public isApprovedForAll;
// ============================================================
// IERC721Metadata
// ============================================================
string public name;
string public symbol;
// ============================================================
// IERC721Enumerable
// ============================================================
uint256[] private _allTokens;
mapping(address => mapping(uint256 => uint256)) private _ownedTokens;
mapping(uint256 => uint256) private _ownedTokensIndex;
mapping(uint256 => uint256) private _allTokensIndex;
constructor(string memory _name, string memory _symbol) {
name = _name;
symbol = _symbol;
}
function _mint(address to, uint id) internal {
require(to != address(0), "mint to zero address");
require(ownerOf[id] == address(0), "already minted");
_beforeTokenTransfer(address(0), to, id);
balanceOf[to]++;
ownerOf[id] = to;
emit Transfer(address(0), to, id);
}
function _burn(uint id) internal {
address owner = ownerOf[id];
require(owner != address(0), "not minted");
_beforeTokenTransfer(owner, address(0), id);
balanceOf[owner] -= 1;
delete ownerOf[id];
delete getApproved[id];
emit Transfer(owner, address(0), id);
}
function _exists(uint256 tokenId) internal view returns (bool) {
return ownerOf[tokenId] != address(0);
}
function _baseURI() internal view virtual returns (string memory) {
return "";
}
// =============================================================
// IERC165
// =============================================================
function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165) returns (bool) {
return interfaceId == type(IERC165).interfaceId || interfaceId == type(IERC721).interfaceId || interfaceId == type(IERC721Metadata).interfaceId || interfaceId == type(IERC721Enumerable).interfaceId;
}
// =============================================================
// IERC721
// =============================================================
function safeTransferFrom(address from, address to, uint id) external {
transferFrom(from, to, id);
require(to.code.length == 0 || IERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, "") == IERC721TokenReceiver.onERC721Received.selector,
"unsafe recipient"
);
}
function safeTransferFrom(address from, address to, uint id, bytes calldata data) external {
transferFrom(from, to, id);
require(to.code.length == 0 || IERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, data) == IERC721TokenReceiver.onERC721Received.selector,
"unsafe recipient"
);
}
function transferFrom(address from, address to, uint id) public {
require(from == ownerOf[id], "from != owner");
require(to != address(0), "transfer to zero address");
require(_isApprovedOrOwner(from, msg.sender, id), "not authorized");
_beforeTokenTransfer(from, to, id);
balanceOf[from]--;
balanceOf[to]++;
ownerOf[id] = to;
delete getApproved[id];
emit Transfer(from, to, id);
}
function approve(address spender, uint id) external {
address owner = ownerOf[id];
require(msg.sender == owner || isApprovedForAll[owner][msg.sender], "not authorized");
getApproved[id] = spender;
emit Approval(owner, spender, id);
}
function setApprovalForAll(address operator, bool approved) external {
isApprovedForAll[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
function _isApprovedOrOwner(address owner, address spender, uint id) internal view returns (bool) {
return spender == owner || isApprovedForAll[owner][spender] || spender == getApproved[id];
}
// ============================================================
// IERC721Metadata
// ============================================================
function tokenURI(uint256 tokenId) public view returns (string memory) {
require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
string memory baseURI = _baseURI();
return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
}
// ============================================================
// IERC721Enumerable
// ============================================================
function tokenOfOwnerByIndex(address owner, uint256 index) public view returns (uint256) {
require(index < balanceOf[owner], "ERC721Enumerable: owner index out of bounds");
return _ownedTokens[owner][index];
}
function totalSupply() public view returns (uint256) {
return _allTokens.length;
}
function tokenByIndex(uint256 index) public view returns (uint256) {
require(index < totalSupply(), "ERC721Enumerable: global index out of bounds");
return _allTokens[index];
}
function _beforeTokenTransfer(address from,address to,uint256 tokenId) internal virtual {
if (from == address(0)) {
_addTokenToAllTokensEnumeration(tokenId);
} else if (from != to) {
_removeTokenFromOwnerEnumeration(from, tokenId);
}
if (to == address(0)) {
_removeTokenFromAllTokensEnumeration(tokenId);
} else if (to != from) {
_addTokenToOwnerEnumeration(to, tokenId);
}
}
function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
uint256 length = balanceOf[to];
_ownedTokens[to][length] = tokenId;
_ownedTokensIndex[tokenId] = length;
}
function _addTokenToAllTokensEnumeration(uint256 tokenId) private {
_allTokensIndex[tokenId] = _allTokens.length;
_allTokens.push(tokenId);
}
function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private {
uint256 lastTokenIndex = balanceOf[from] - 1;
uint256 tokenIndex = _ownedTokensIndex[tokenId];
if (tokenIndex != lastTokenIndex) {
uint256 lastTokenId = _ownedTokens[from][lastTokenIndex];
_ownedTokens[from][tokenIndex] = lastTokenId;
_ownedTokensIndex[lastTokenId] = tokenIndex;
}
delete _ownedTokensIndex[tokenId];
delete _ownedTokens[from][lastTokenIndex];
}
function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private {
uint256 lastTokenIndex = _allTokens.length - 1;
uint256 tokenIndex = _allTokensIndex[tokenId];
uint256 lastTokenId = _allTokens[lastTokenIndex];
_allTokens[tokenIndex] = lastTokenId;
_allTokensIndex[lastTokenId] = tokenIndex;
delete _allTokensIndex[tokenId];
_allTokens.pop();
}
}