// 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/ERC1155/IERC1155.sol"; import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import "../HasSignature.sol"; interface IMintableERC1155 is IERC1155 { function mintBatch( address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data ) external; function burn( address owner, uint256 tokenId, uint256 amount ) external; } interface IMintableERC721 is IERC721 { function mint(address to, uint256 tokenId) external; } contract LuckyBoxProxy is Ownable, Initializable, HasSignature { IMintableERC1155 public box; IMintableERC721 public hero; IMintableERC721 public equip; IMintableERC721 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 BoxMinted( address contractAddress, address indexed to, uint256[] ids, uint256[] amounts ); event BoxOpened( address indexed to, uint256 indexed boxId, uint256 val, uint256[3] ids, uint8[3] types ); constructor() HasSignature("LuckyBoxProxy", "1") {} function init(address _erc1155, address[3] calldata _erc721s) external initializer onlyOwner { box = IMintableERC1155(_erc1155); hero = IMintableERC721(_erc721s[0]); equip = IMintableERC721(_erc721s[1]); chip = IMintableERC721(_erc721s[2]); } /** * @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[] memory ids, uint256[] memory amounts ) external onlyOwner { require(to != address(0), "to address can not be zero"); box.mintBatch(to, ids, amounts, ""); emit BoxMinted(address(box), to, ids, amounts); } function openBox( uint256 boxId, uint256[3] memory ids, uint256 saltNonce, bytes calldata signature ) external { require( !usedSignatures[signature], "LuckyBoxProxy: signature used. please send another transaction with new signature" ); address owner = msg.sender; bytes32 criteriaMessageHash = getMessageHash( boxId, ids[0], ids[1], ids[2], saltNonce ); checkSigner(executor, criteriaMessageHash, signature); usedSignatures[signature] = true; // open box box.burn(owner, boxId, 1); 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 ) ); } }