// SPDX-License-Identifier: MIT pragma solidity 0.8.15; import { ERC721Enumerable } from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; import { IOptimismMintableERC721 } from "src/universal/IOptimismMintableERC721.sol"; import { ISemver } from "src/universal/ISemver.sol"; /// @title OptimismMintableERC721 /// @notice This contract is the remote representation for some token that lives on another network, /// typically an Optimism representation of an Ethereum-based token. Standard reference /// implementation that can be extended or modified according to your needs. contract OptimismMintableERC721 is ERC721Enumerable, IOptimismMintableERC721, ISemver { /// @inheritdoc IOptimismMintableERC721 uint256 public immutable REMOTE_CHAIN_ID; /// @inheritdoc IOptimismMintableERC721 address public immutable REMOTE_TOKEN; /// @inheritdoc IOptimismMintableERC721 address public immutable BRIDGE; /// @notice Base token URI for this token. string public baseTokenURI; /// @notice Modifier that prevents callers other than the bridge from calling the function. modifier onlyBridge() { require(msg.sender == BRIDGE, "OptimismMintableERC721: only bridge can call this function"); _; } /// @notice Semantic version. /// @custom:semver 1.3.0 string public constant version = "1.3.0"; /// @param _bridge Address of the bridge on this network. /// @param _remoteChainId Chain ID where the remote token is deployed. /// @param _remoteToken Address of the corresponding token on the other network. /// @param _name ERC721 name. /// @param _symbol ERC721 symbol. constructor( address _bridge, uint256 _remoteChainId, address _remoteToken, string memory _name, string memory _symbol ) ERC721(_name, _symbol) { require(_bridge != address(0), "OptimismMintableERC721: bridge cannot be address(0)"); require(_remoteChainId != 0, "OptimismMintableERC721: remote chain id cannot be zero"); require(_remoteToken != address(0), "OptimismMintableERC721: remote token cannot be address(0)"); REMOTE_CHAIN_ID = _remoteChainId; REMOTE_TOKEN = _remoteToken; BRIDGE = _bridge; // Creates a base URI in the format specified by EIP-681: // https://eips.ethereum.org/EIPS/eip-681 baseTokenURI = string( abi.encodePacked( "ethereum:", Strings.toHexString(uint160(_remoteToken), 20), "@", Strings.toString(_remoteChainId), "/tokenURI?uint256=" ) ); } /// @inheritdoc IOptimismMintableERC721 function remoteChainId() external view returns (uint256) { return REMOTE_CHAIN_ID; } /// @inheritdoc IOptimismMintableERC721 function remoteToken() external view returns (address) { return REMOTE_TOKEN; } /// @inheritdoc IOptimismMintableERC721 function bridge() external view returns (address) { return BRIDGE; } /// @inheritdoc IOptimismMintableERC721 function safeMint(address _to, uint256 _tokenId) external virtual onlyBridge { _safeMint(_to, _tokenId); emit Mint(_to, _tokenId); } /// @inheritdoc IOptimismMintableERC721 function burn(address _from, uint256 _tokenId) external virtual onlyBridge { _burn(_tokenId); emit Burn(_from, _tokenId); } /// @notice Checks if a given interface ID is supported by this contract. /// @param _interfaceId The interface ID to check. /// @return True if the interface ID is supported, false otherwise. function supportsInterface(bytes4 _interfaceId) public view override(ERC721Enumerable, IERC165) returns (bool) { bytes4 iface = type(IOptimismMintableERC721).interfaceId; return _interfaceId == iface || super.supportsInterface(_interfaceId); } /// @notice Returns the base token URI. /// @return Base token URI. function _baseURI() internal view virtual override returns (string memory) { return baseTokenURI; } }