// SPDX-License-Identifier: MIT pragma solidity 0.8.10; import "@openzeppelin/contracts/utils/Strings.sol"; import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/access/AccessControl.sol"; abstract contract BEBase1155 is ERC1155, Ownable, AccessControl { using Strings for uint256; bytes32 public constant URI_SETTER_ROLE = keccak256("URI_SETTER_ROLE"); bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); bytes32 public constant BURN_ROLE = keccak256("BURN_ROLE"); bytes32 public constant LOCK_ROLE = keccak256("LOCK_ROLE"); mapping(uint256 => bool) public lockedTokens; mapping(address => uint256[]) private _ownedTokens; // for 0 means not exists, value stored = index + 1; mapping(address => mapping(uint256 => uint256)) private _ownedTokensIndex; mapping(uint256 => uint256) private _totalSupply; uint256 public maxSupply = 0; struct TokenStruct { uint256 tokenId; uint256 amount; } constructor() ERC1155("https://market.cebg.games/api/nft/info/{id}") { _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); _grantRole(URI_SETTER_ROLE, msg.sender); } function setURI(string memory newuri) external onlyRole(URI_SETTER_ROLE) { _setURI(newuri); } function mintBatch( address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data ) external onlyRole(MINTER_ROLE) { _mintBatch(to, ids, amounts, data); } function burnBatch( address account, uint256[] memory ids, uint256[] memory values ) external onlyRole(BURN_ROLE) { _burnBatch(account, ids, values); } // The following functions are overrides required by Solidity. function supportsInterface(bytes4 interfaceId) public view override(ERC1155, AccessControl) returns (bool) { return super.supportsInterface(interfaceId); } /** * @dev Add factory to mint/burn item */ function setMintFactory(address factory) external onlyOwner { _grantRole(MINTER_ROLE, factory); } /** * @dev Remove factory */ function removeMintFactory(address factory) external onlyOwner { _revokeRole(MINTER_ROLE, factory); } function grantLockRole(address account) external onlyOwner { _grantRole(LOCK_ROLE, account); } function revokeLockRole(address account) external onlyOwner { _revokeRole(LOCK_ROLE, account); } /** * @dev Add factory to burn item */ function grantBurnRole(address proxy) external onlyOwner { _grantRole(BURN_ROLE, proxy); } /** * @dev Remove proxy */ function revokeBurnRole(address proxy) external onlyOwner { _revokeRole(BURN_ROLE, proxy); } /** * @dev Total amount of tokens in with a given id. */ function totalSupply(uint256 id) external view virtual returns (uint256) { return _totalSupply[id]; } /** * @dev Indicates whether any token exist with a given id, or not. */ function exists(uint256 id) public view virtual returns (bool) { return _totalSupply[id] > 0; } function canMint( uint256 /*id*/ ) external view virtual returns (bool) { return true; } /** * @dev Lock token to use in game or for rental */ function lock(uint256 id) external onlyRole(LOCK_ROLE) { require(_totalSupply[id] > 0, "Must be valid tokenId"); require(!lockedTokens[id], "Token has already locked"); lockedTokens[id] = true; } function lockBatch(uint256[] memory ids) external onlyRole(LOCK_ROLE) { uint256 len = ids.length; for (uint256 i = 0; i < len; ++i) { uint256 id = ids[i]; require(_totalSupply[id] > 0, "Must be valid tokenId"); require(!lockedTokens[id], "Token has already locked"); lockedTokens[id] = true; } } /** * @dev Unlock token to use blockchain or sale on marketplace */ function unlock(uint256 id) external onlyRole(LOCK_ROLE) { require(_totalSupply[id] > 0, "Must be valid tokenId"); require(lockedTokens[id], "Token has already unlocked"); lockedTokens[id] = false; } function unlockBatch(uint256[] memory ids) external onlyRole(LOCK_ROLE) { uint256 len = ids.length; for (uint256 i = 0; i < len; ++i) { uint256 id = ids[i]; require(_totalSupply[id] > 0, "Must be valid tokenId"); require(lockedTokens[id], "Token has already unlocked"); lockedTokens[id] = false; } } /** * @dev Get lock status */ function isLocked(uint256 id) external view returns (bool) { return lockedTokens[id]; } function isLockedBatch(uint256[] memory ids) external view returns (bool[] memory) { uint256 len = ids.length; bool[] memory results = new bool[](len); for (uint256 i = 0; i < len; ++i) { results[i] = lockedTokens[ids[i]]; } return results; } /** * @dev See {ERC1155-_beforeTokenTransfer}. */ function _beforeTokenTransfer( address operator, address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data ) internal virtual override { super._beforeTokenTransfer(operator, from, to, ids, amounts, data); uint256 len = ids.length; for (uint256 i = 0; i < len; ++i) { require(!lockedTokens[ids[i]], "Can not transfer locked token"); } if (from == address(0)) { // mint nft for (uint256 i = 0; i < len; ++i) { _totalSupply[ids[i]] += amounts[i]; if (maxSupply > 0) { require( _totalSupply[ids[i]] <= maxSupply, "Can not mint for exceeds max supply" ); } } } else if (from != to) { // transfer from -> to for (uint256 i = 0; i < len; ++i) { _removeTokenFromOwnerEnumeration(from, ids[i], amounts[i]); } } if (to == address(0)) { // burn nft for (uint256 i = 0; i < len; ++i) { _totalSupply[ids[i]] -= amounts[i]; } } else if (to != from) { // mint or transfer from -> to for (uint256 i = 0; i < len; ++i) { _addTokenToOwnerEnumeration(to, ids[i]); } } } function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private { if (_ownedTokensIndex[to][tokenId] == 0 && balanceOf(to, tokenId) == 0) { _ownedTokensIndex[to][tokenId] = _ownedTokens[to].length + 1; _ownedTokens[to].push(tokenId); } } function _removeTokenFromOwnerEnumeration( address from, uint256 tokenId, uint256 amount ) private { uint256 balance = balanceOf(from, tokenId); if (balance == amount) { uint256 lastTokenIndex = _ownedTokens[from].length - 1; uint256 tokenIndex = _ownedTokensIndex[from][tokenId] - 1; uint256 lastTokenId = _ownedTokens[from][lastTokenIndex]; // Move the last token to the slot of the to-delete token _ownedTokens[from][tokenIndex] = lastTokenId; // Update the moved token's index _ownedTokensIndex[from][lastTokenId] = tokenIndex + 1; // This also deletes the contents at the last position of the array delete _ownedTokensIndex[from][tokenId]; _ownedTokens[from].pop(); } } function userTokens( address user, uint256 start, uint256 page ) external view returns (TokenStruct[] memory) { uint256 size = _ownedTokens[user].length; uint256 max = size; uint256 pageSize = 0; if (start < size) { if (start + page < size) { pageSize = page; max = start + page; } else { pageSize = size - start; } } TokenStruct[] memory results = new TokenStruct[](pageSize); if (pageSize > 0) { for (uint256 i = start; i < max; ++i) { TokenStruct memory dataObj; uint256 tokenId = _ownedTokens[user][i]; dataObj.tokenId = tokenId; dataObj.amount = balanceOf(user, tokenId); results[i - start] = dataObj; } } return results; } function tokenTypes(address user) external view returns (uint256) { return _ownedTokens[user].length; } }