// 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; 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[] ids, uint256[] 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[3] memory types, uint256 saltNonce, bytes calldata signature ) external { require(ids.length == types.length, "LuckyBoxProxy: ids and types length mismatch"); require( !usedSignatures[signature], "LuckyBoxProxy: signature used. please send another transaction with new signature" ); bytes32 criteriaMessageHash = getMessageHash( boxId, ids[0], ids[1], ids[2], types[0], types[1], types[2], saltNonce ); checkSigner(executor, criteriaMessageHash, signature); usedSignatures[signature] = true; address owner = msg.sender; // open box box.burn(owner, boxId, 1); // random count uint256 val = rand(owner, saltNonce, 100); // mint nft uint256[] memory results; uint256[] memory resultTypes; mint721WithType(owner, ids[0], types[0]); results[0] = ids[0]; resultTypes[0] = types[0]; if (val > 80) { mint721WithType(owner, ids[1], types[1]); results[1] = ids[1]; resultTypes[1] = types[1]; } if (val > 90) { mint721WithType(owner, ids[2], types[2]); results[2] = ids[2]; resultTypes[2] = types[2]; } emit BoxOpened( owner, boxId, results, resultTypes ); } function mint721WithType(address to, uint256 tokenId, uint256 typeNum) private { if (typeNum == 0) { hero.mint(to, tokenId); } else if (typeNum == 1) { equip.mint(to, tokenId); } else { 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 _type1, uint256 _type2, uint256 _type3, uint256 _saltNonce ) public pure returns (bytes32) { return keccak256( abi.encodePacked( _boxId, _firstToken, _secondToken, _thirdToken, _type1, _type2, _type3, _saltNonce ) ); } }