181 lines
5.9 KiB
Solidity
181 lines
5.9 KiB
Solidity
|
// SPDX-License-Identifier: MIT
|
||
|
|
||
|
pragma solidity ^0.8.0;
|
||
|
|
||
|
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
|
||
|
import "@openzeppelin/contracts/utils/Address.sol";
|
||
|
import "@openzeppelin/contracts/utils/Context.sol";
|
||
|
import "@openzeppelin/contracts/utils/Strings.sol";
|
||
|
import "@openzeppelin/contracts/utils/Counters.sol";
|
||
|
import "@openzeppelin/contracts/access/Ownable.sol";
|
||
|
|
||
|
library AddressUtil {
|
||
|
/**
|
||
|
* @dev Returns true if `account` is a contract.
|
||
|
*
|
||
|
* [IMPORTANT]
|
||
|
* ====
|
||
|
* It is unsafe to assume that an address for which this function returns
|
||
|
* false is an externally-owned account (EOA) and not a contract.
|
||
|
*
|
||
|
* Among others, `isContract` will return false for the following
|
||
|
* types of addresses:
|
||
|
*
|
||
|
* - an externally-owned account
|
||
|
* - a contract in construction
|
||
|
* - an address where a contract will be created
|
||
|
* - an address where a contract lived, but was destroyed
|
||
|
* ====
|
||
|
*/
|
||
|
function isContract(address account) internal view returns (bool) {
|
||
|
// This method relies on extcodesize, which returns 0 for contracts in
|
||
|
// construction, since the code is only stored at the end of the
|
||
|
// constructor execution.
|
||
|
|
||
|
uint256 size;
|
||
|
// solhint-disable-next-line no-inline-assembly
|
||
|
assembly { size := extcodesize(account) }
|
||
|
return size > 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
abstract contract MultiOwnable is Ownable {
|
||
|
mapping(address => bool) private _admins;
|
||
|
|
||
|
/**
|
||
|
* @dev Initializes the contract setting the deployer as the initial owner.
|
||
|
*/
|
||
|
constructor() Ownable() {
|
||
|
_admins[_msgSender()] = true;
|
||
|
}
|
||
|
|
||
|
function addAdmin(address newAdmin) public onlyOwner {
|
||
|
_admins[newAdmin] = true;
|
||
|
}
|
||
|
|
||
|
function revokeAdmin(address currentAdmin) public onlyOwner {
|
||
|
delete _admins[currentAdmin];
|
||
|
}
|
||
|
|
||
|
function isAdmin(address sender) public view returns(bool) {
|
||
|
return _admins[sender];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @dev Throws if called by a non-admin
|
||
|
*/
|
||
|
modifier onlyAdmins() {
|
||
|
require(_admins[_msgSender()] == true, "Ownable: caller is not an admin");
|
||
|
_;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
interface IERC5169 {
|
||
|
/// @dev This event emits when the scriptURI is updated,
|
||
|
/// so wallets implementing this interface can update a cached script
|
||
|
event ScriptUpdate(string newScriptURI);
|
||
|
|
||
|
/// @notice Get the scriptURI for the contract
|
||
|
/// @return The scriptURI
|
||
|
function scriptURI() external view returns(string memory);
|
||
|
|
||
|
/// @notice Update the scriptURI
|
||
|
/// emits event ScriptUpdate(string memory newScriptURI);
|
||
|
function updateScriptURI(string memory newScriptURI) external;
|
||
|
}
|
||
|
|
||
|
contract STLDoor is ERC721, MultiOwnable, IERC5169 {
|
||
|
using AddressUtil for address;
|
||
|
using Strings for uint256;
|
||
|
using Counters for Counters.Counter;
|
||
|
|
||
|
Counters.Counter public _tokenIdCounter;
|
||
|
Counters.Counter public _topTokenIdCounter;
|
||
|
Counters.Counter public _stlTokenIdCounter;
|
||
|
|
||
|
uint256 private constant _topTokenId = 10000;
|
||
|
|
||
|
string private _scriptURI;
|
||
|
|
||
|
constructor() ERC721("STL HQ Door", "OFFICE") {
|
||
|
_tokenIdCounter.increment();
|
||
|
_scriptURI = "ipfs://QmXXLFBeSjXAwAhbo1344wJSjLgoUrfUK9LE57oVubaRRp";
|
||
|
mintUsingSequentialTokenId();
|
||
|
}
|
||
|
|
||
|
function contractURI() public pure returns (string memory) {
|
||
|
return "ipfs://QmUgdLvPvjuHGfMsuK1H2jFpg5r1QNc8JeWyXyRwKP8pTf";
|
||
|
}
|
||
|
|
||
|
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
|
||
|
require(_exists(tokenId), "tokenURI: URI query for nonexistent token");
|
||
|
if (tokenId < _topTokenId) {
|
||
|
return "ipfs://QmW948aN4Tjh4eLkAAo8os1AcM2FJjA46qtaEfFAnyNYzY";
|
||
|
} else if (tokenId < _topTokenId * 2) {
|
||
|
return "ipfs://QmR31f2AUokC5QyLXzDYUjy5tVibkjbW4voVuMBZfrNVU8";
|
||
|
} else {
|
||
|
return "ipfs://QmdaSTaF6WXpYWiL5ck7csmTy5EWHzYVGykJZN7TR95dSS";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function scriptURI() public view override returns (string memory) {
|
||
|
return _scriptURI;
|
||
|
}
|
||
|
|
||
|
function updateScriptURI(string memory newScriptURI) public override onlyAdmins {
|
||
|
_scriptURI = newScriptURI;
|
||
|
emit ScriptUpdate(newScriptURI);
|
||
|
}
|
||
|
|
||
|
function mintUsingSequentialTokenId() public onlyAdmins returns (uint256 tokenId) {
|
||
|
tokenId = _tokenIdCounter.current();
|
||
|
require(tokenId < _topTokenId, "Hit upper mint limit");
|
||
|
_mint(msg.sender, tokenId);
|
||
|
_tokenIdCounter.increment();
|
||
|
}
|
||
|
|
||
|
function topMintUsingSequentialTokenId() public onlyAdmins returns (uint256 tokenId) {
|
||
|
tokenId = _topTokenIdCounter.current() + _topTokenId;
|
||
|
require(tokenId < _topTokenId*2, "Hit upper mint limit");
|
||
|
_mint(msg.sender, tokenId);
|
||
|
_topTokenIdCounter.increment();
|
||
|
}
|
||
|
|
||
|
function stlMintUsingSequentialTokenId() public onlyAdmins returns (uint256 tokenId) {
|
||
|
tokenId = _stlTokenIdCounter.current() + _topTokenId*2;
|
||
|
_mint(msg.sender, tokenId);
|
||
|
_stlTokenIdCounter.increment();
|
||
|
}
|
||
|
|
||
|
function burnToken(uint256 tokenId) public onlyAdmins {
|
||
|
require(_exists(tokenId), "burn: nonexistent token");
|
||
|
_burn(tokenId);
|
||
|
}
|
||
|
|
||
|
// Only allow owners to transfer tokens
|
||
|
function safeTransferFrom(
|
||
|
address from,
|
||
|
address to,
|
||
|
uint256 tokenId,
|
||
|
bytes memory _data
|
||
|
) public override onlyAdmins {
|
||
|
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");
|
||
|
_safeTransfer(from, to, tokenId, _data);
|
||
|
}
|
||
|
|
||
|
function transferFrom(
|
||
|
address from,
|
||
|
address to,
|
||
|
uint256 tokenId
|
||
|
) public override onlyAdmins {
|
||
|
//solhint-disable-next-line max-line-length
|
||
|
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");
|
||
|
|
||
|
_transfer(from, to, tokenId);
|
||
|
}
|
||
|
|
||
|
function selfDestruct() public payable onlyOwner {
|
||
|
selfdestruct(payable(owner()));
|
||
|
}
|
||
|
}
|