mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-05-25 12:04:22 +00:00
6138c8e66b
* tokens: ERC1155 contract for tokens * simplify 1155 * NFT and minter contracts * update NFT * update * update * update * update * NFT metadata JSON * update token metadata * flattened contracts
122 lines
4.4 KiB
Solidity
122 lines
4.4 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.27;
|
|
|
|
import "@openzeppelin/contracts@5.4.0/token/ERC721/extensions/ERC721Enumerable.sol";
|
|
import {IERC721} from "@openzeppelin/contracts@5.4.0/token/ERC721/IERC721.sol";
|
|
import "@openzeppelin/contracts@5.4.0/access/Ownable.sol";
|
|
|
|
/// @title NFTNumbered
|
|
/// @notice ERC721 contract with sequential variants for immutable metadata for minted tokens, allowing to change metadata for future tokens.
|
|
/// @dev Non-upgradeable. Minter address is settable by owner. Global sequential token ID.
|
|
contract NFTNumbered is ERC721Enumerable, Ownable {
|
|
address public minter;
|
|
bool public mintingLocked;
|
|
TokenInfo[] private tokenInfos;
|
|
uint public nextTokenId = 1;
|
|
|
|
struct TokenInfo {
|
|
uint tokenId;
|
|
string tokenUri;
|
|
}
|
|
|
|
constructor(
|
|
string memory _name,
|
|
string memory _symbol,
|
|
string memory _tokenUri
|
|
) ERC721(_name, _symbol) Ownable(msg.sender) {
|
|
minter = msg.sender;
|
|
mintingLocked = false;
|
|
setNextTokenURI(_tokenUri);
|
|
}
|
|
|
|
event MinterUpdated(address indexed newMinter);
|
|
event MintingLocked();
|
|
event NextTokenURI(uint nextTokenId, string nextTokenUri);
|
|
|
|
/// @notice Updates the minter address.
|
|
function setMinter(address newMinter) external onlyOwner whenNotLocked {
|
|
minter = newMinter;
|
|
emit MinterUpdated(newMinter);
|
|
}
|
|
|
|
/// @notice Permanently disable minting and changes.
|
|
function lockMintingPermanently() external onlyOwner {
|
|
mintingLocked = true;
|
|
emit MintingLocked();
|
|
}
|
|
|
|
modifier whenNotLocked() {
|
|
require(!mintingLocked, "Contract permanently locked for changes and minting");
|
|
_;
|
|
}
|
|
|
|
/// @notice Sets tokenUri for future mints only.
|
|
function setNextTokenURI(string memory newTokenUri) public onlyOwner whenNotLocked {
|
|
uint len = tokenInfos.length;
|
|
TokenInfo memory newInfo = TokenInfo({tokenId: nextTokenId, tokenUri: newTokenUri});
|
|
if (len == 0 || tokenInfos[len - 1].tokenId < nextTokenId) { // add
|
|
tokenInfos.push(newInfo);
|
|
} else { // replace
|
|
tokenInfos[len - 1] = newInfo;
|
|
}
|
|
emit NextTokenURI(nextTokenId, newTokenUri);
|
|
}
|
|
|
|
/// @notice metadata URI of the token that will be minted next
|
|
function nextTokenURI() external view returns (string memory) {
|
|
return tokenInfos[tokenInfos.length - 1].tokenUri;
|
|
}
|
|
|
|
/// @notice Mints a new token.
|
|
/// @param to The recipient of the token.
|
|
function mint(address to) external whenNotLocked {
|
|
require(msg.sender == owner() || msg.sender == minter, "Caller must be the owner or minter");
|
|
require(to != address(0), "Cannot mint to zero address");
|
|
uint tokenId = nextTokenId++;
|
|
_safeMint(to, tokenId);
|
|
}
|
|
|
|
/// @notice Burn owned token.
|
|
function burn(uint tokenId) external {
|
|
_burn(tokenId);
|
|
}
|
|
|
|
/// @notice prohibit approvals
|
|
// address to, uint256 tokenId
|
|
function approve(address, uint256) public virtual override (ERC721, IERC721) {
|
|
revert("Soulbound token: approvals prohibited");
|
|
}
|
|
|
|
/// @notice prohibit approvals
|
|
// address operator, bool approved
|
|
function setApprovalForAll(address, bool) public virtual override (ERC721, IERC721) {
|
|
revert("Soulbound token: approvals prohibited");
|
|
}
|
|
|
|
/// @notice Limits ownership to 1 token.
|
|
function _update(address to, uint tokenId, address auth) internal virtual override returns (address) {
|
|
require(to == address(0) || balanceOf(to) == 0, "Soulbound token: only 1 per address");
|
|
require(to == address(0) || _ownerOf(tokenId) == address(0), "Soulbound token: transfers prohibited");
|
|
return super._update(to, tokenId, auth);
|
|
}
|
|
|
|
/// @notice Returns embedded JSON metadata URI.
|
|
/// @param id Token ID.
|
|
function tokenURI(uint id) public view virtual override returns (string memory) {
|
|
_requireOwned(id);
|
|
uint len = tokenInfos.length;
|
|
for (uint i = len; i > 0; ) {
|
|
unchecked { i--; }
|
|
if (tokenInfos[i].tokenId > id) continue;
|
|
return tokenInfos[i].tokenUri;
|
|
}
|
|
revert("Unknown token ID");
|
|
}
|
|
|
|
/// @notice Withdraw any accidental ETH.
|
|
function withdraw() external onlyOwner {
|
|
(bool success, ) = payable(owner()).call{value: address(this).balance}("");
|
|
require(success, "Withdraw failed");
|
|
}
|
|
}
|