// SPDX-License-Identifier: MIT pragma solidity 0.8.10; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import "../interfaces/IBEERC721.sol"; import "../core/HasSignature.sol"; contract MysteryBoxProxy is Ownable, Initializable, HasSignature { IBEERC721 public box; IBEERC721 public hero; IBEERC721 public equip; IBEERC721 public chip; uint8 public constant TYPE_NONE = 0; uint8 public constant TYPE_HERO = 1; uint8 public constant TYPE_EQUIP = 2; uint8 public constant TYPE_CHIP = 3; address public executor; mapping(bytes => bool) public usedSignatures; event TokenMinted( address contractAddress, address to, uint256 indexed tokenId ); event BoxOpened( address indexed to, uint256 indexed boxId, uint256 val, uint256[3] ids, uint8[3] types ); constructor() HasSignature("MysteryBoxProxy", "1") {} function init(address[4] calldata _erc721s) external initializer onlyOwner { hero = IBEERC721(_erc721s[0]); equip = IBEERC721(_erc721s[1]); chip = IBEERC721(_erc721s[2]); box = IBEERC721(_erc721s[3]); } /** * @dev update executor */ function updateExecutor(address account) external onlyOwner { require(account != address(0), "address can not be zero"); executor = account; } function mintBoxTo(address to, uint256 tokenId) external onlyOwner { require(to != address(0), "to address can not be zero"); box.mint(to, tokenId); emit TokenMinted(address(box), to, tokenId); } function openBox( uint256 boxId, uint256[3] calldata ids, uint256 saltNonce, bytes calldata signature ) external { require(ids.length == 3, "MysteryBoxProxy: amount of token id mismatch"); require( !usedSignatures[signature], "MysteryBoxProxy: signature used. please send another transaction with new signature" ); address owner = msg.sender; require( box.ownerOf(boxId) == owner, "MysteryBoxProxy: only owner can open this box" ); bytes32 criteriaMessageHash = getMessageHash( boxId, ids[0], ids[1], ids[2], saltNonce ); checkSigner(executor, criteriaMessageHash, signature); // open box box.burn(owner, boxId); usedSignatures[signature] = true; uint256[3] memory results = [ids[0], 0, 0]; uint8[3] memory types = [TYPE_HERO, TYPE_NONE, TYPE_NONE]; mint721WithType(owner, ids[0], types[0]); results[0] = ids[0]; uint256 val = rand(owner, saltNonce, 100); if (val >= 70 && val < 90) { types[1] = TYPE_CHIP; mint721WithType(owner, ids[1], types[1]); results[1] = ids[1]; } else if (val >= 90 && val < 98) { types[1] = TYPE_EQUIP; mint721WithType(owner, ids[1], types[1]); results[1] = ids[1]; } else if (val >= 98) { types[1] = TYPE_EQUIP; mint721WithType(owner, ids[1], types[1]); results[1] = ids[1]; types[2] = TYPE_CHIP; mint721WithType(owner, ids[2], types[2]); results[2] = ids[2]; } emit BoxOpened(owner, boxId, val, results, types); } function mint721WithType( address to, uint256 tokenId, uint256 typeNum ) private { if (typeNum == 1) { hero.mint(to, tokenId); } else if (typeNum == 2) { equip.mint(to, tokenId); } else if (typeNum == 3) { chip.mint(to, tokenId); } } function rand( address owner, uint256 nonce, uint256 _length ) internal view returns (uint256) { uint256 random = uint256( keccak256( abi.encodePacked(owner, nonce, block.difficulty, block.timestamp) ) ); return random % _length; } function getMessageHash( uint256 _boxId, uint256 _firstToken, uint256 _secondToken, uint256 _thirdToken, uint256 _saltNonce ) public pure returns (bytes32) { return keccak256( abi.encodePacked( _boxId, _firstToken, _secondToken, _thirdToken, _saltNonce ) ); } }