// SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity ^0.8.17; import "./ERC1155.sol"; import "./IMastersFedi.sol"; import "./Address.sol"; import "./IERC1155Metadata.sol"; import "./ERC1155Metadata.sol"; import "@openzeppelin/contracts/utils/Strings.sol"; import "@openzeppelin/contracts/utils/Base64.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/security/Pausable.sol"; contract CurioERC1155Wrapper is ERC1155, ERC1155Metadata_URI, ERC1155Metadata, Ownable, Pausable { using Address for address; mapping (uint256 => address) public contracts; mapping (uint256 => string) public urls; /** @notice Initialize an nft id's data */ function create(uint256 _id, address _contract, string memory _url) internal { require(contracts[_id] == address(0), "id already exists"); contracts[_id] = _contract; urls[_id] = _url; // mint 0 just to let explorers know it exists emit TransferSingle(msg.sender, address(0), msg.sender, _id, 0); } constructor() { create(1, 0x2f873FCc3F4B84E9A62AFf28E9a897ce1BC8814B, "https://shitposter.club"); create(2, 0x13a9914Ad2e0be57eB2Abb3E159021Eab6D7a80E, "https://tuusin.misono-ya.info/users/hakui"); create(3, 0xdeCAa5B6901dc465FBf90f9C0c70c96132aF51Db, "https://shitposter.club/users/augustus"); create(4, 0xc1de7E95663FB3A0e8F8C6E6a64297d7AbcBF7f7, "https://mk.toast.cafe/@xjix"); create(5, 0xB70F9A809693B8c6a4c331342B96F15252521dC7, "https://varishangout.net/users/nepfag"); create(6, 0x3f2592136d90dE35615A409B4fe710B3764366F4, "https://shitposter.club/dokidoki@pl.smuglo.li"); create(7, 0x5e7318f75b177a0F27A31CB20bB26bd0C049620c, "https://twitter.com/sonyasupposedly"); create(8, 0x5539907D45a608828756765429f2B4e6311c295c, "https://shpposter.club/users/shpuld"); create(9, 0x0a0e64067B1F7aDfbF876Dde4322633Ff7Df9702, "https://bbs.kawa-kun.com/users/tk"); } /** @notice Returns URI of token metadata @param _id NFT ID @return URI data URI of metadata JSON */ function uri(uint256 _id) external override view returns (string memory) { IMastersFedi curio = IMastersFedi(contracts[_id]); return string(abi.encodePacked("data:application/json;base64,", Base64.encode(abi.encodePacked( '{', '"name":"', curio.name(), '",', '"description":"', curio.description(), '",', '"image":"ipfs://', curio.ipfs_hash(), '",', '"external_url":"', urls[_id], '",', '"properties":{', '"symbol":"', curio.symbol(), '",', '"erc20_contract":"', Strings.toHexString(contracts[_id]), '"', '}', '}' )))); } /** @notice Change the external URL for a token @param _id NFT ID @param _url URL pointing to user or site */ function setExternalUrl(uint256 _id, string memory _url) external onlyOwner { require(contracts[_id] != address(0), "id must exist"); urls[_id] = _url; } /** @notice Query if a contract implements an interface @param _interfaceId The interface identifier, as specified in ERC-165 @return `true` if the contract implements `_interfaceId` */ function supportsInterface(bytes4 _interfaceId) public pure override(ERC1155, ERC1155Metadata) returns (bool) { return ERC1155.supportsInterface(_interfaceId) || ERC1155Metadata.supportsInterface(_interfaceId); } /** @notice If NFT ID exists @dev Makes OpenSea happy @param _id NFT ID @return exists if NFT ID exists. */ function exists(uint256 _id) external view returns(bool) { return contracts[_id] != address(0); } bool public destroyable = true; /** @notice Destroy the contract @dev Only if it hasn't been disabled */ function destroy() public onlyOwner whenPaused { require(destroyable, "destroy() has been disabled"); selfdestruct(payable(msg.sender)); } function disableDestroy() public onlyOwner { destroyable = false; } /** @notice Converts old-style NFT to new-style @dev For an NFT ID, queries and transfers tokens from the appropriate curio contract to itself, and mints and transfers corresponding new ERC-1155 tokens to caller @param _id NFT ID @param _quantity how many to wrap */ function wrap(uint256 _id, uint256 _quantity) external whenNotPaused { address tokenContract = contracts[_id]; require(tokenContract != address(0), "invalid id"); IMastersFedi curio = IMastersFedi(tokenContract); // these are here for convenience because curio contract doesn't throw meaningful exceptions require(curio.balanceOf(msg.sender) >= _quantity, "insufficient curio balance"); require(curio.allowance(msg.sender, address(this)) >= _quantity, "insufficient curio allowance"); curio.transferFrom(msg.sender, address(this), _quantity); balances[_id][msg.sender] += _quantity; // mint emit TransferSingle(msg.sender, address(0), msg.sender, _id, _quantity); address _to = msg.sender; if (_to.isContract()) { _doSafeTransferAcceptanceCheck(msg.sender, msg.sender, msg.sender, _id, _quantity, ''); } } /** @notice Batch version of wrap function @param _ids array of NFT IDs @param _quantities how many to wrap of each */ function wrapBatch(uint256[] calldata _ids, uint256[] calldata _quantities) external whenNotPaused { require(_ids.length == _quantities.length, "ids and quantities must match"); address _to = msg.sender; bool callerIsContract = _to.isContract(); for (uint256 i=0; i < _ids.length; ++i) { uint256 _id = _ids[i]; uint256 _quantity = _quantities[i]; address tokenContract = contracts[_id]; require(tokenContract != address(0), "invalid id"); IMastersFedi curio = IMastersFedi(tokenContract); require(curio.balanceOf(msg.sender) >= _quantity, "insufficient curio balance"); require(curio.allowance(msg.sender, address(this)) >= _quantity, "insufficient curio allowance"); curio.transferFrom(msg.sender, address(this), _quantity); balances[_id][msg.sender] += _quantity; if (callerIsContract) { _doSafeTransferAcceptanceCheck(msg.sender, msg.sender, msg.sender, _id, _quantity, ''); } } // mint emit TransferBatch(msg.sender, address(0), _to, _ids, _quantities); } /** @notice Unwrap new-style NFTs back to old-style @dev For an NFT ID, burns ERC-1155 quantity and transfers curio ERC-20 tokens to caller @param _id NFT ID @param _quantity how many to unwrap */ function unwrap(uint256 _id, uint256 _quantity) external whenNotPaused { address tokenContract = contracts[_id]; require(tokenContract != address(0), "invalid id"); IMastersFedi curio = IMastersFedi(tokenContract); require(balances[_id][msg.sender] >= _quantity, "insufficient balance"); balances[_id][msg.sender] -= _quantity; curio.transfer(msg.sender, _quantity); // burn emit TransferSingle(msg.sender, msg.sender, address(0), _id, _quantity); } /** @notice Batch version of unwrap @param _ids array of NFT IDs @param _quantities how many to unwrap of each */ function unwrapBatch(uint256[] calldata _ids, uint256[] calldata _quantities) external whenNotPaused { require(_ids.length == _quantities.length, "ids and quantities must match"); for (uint256 i=0; i < _ids.length; ++i) { uint256 _id = _ids[i]; uint256 _quantity = _quantities[i]; address tokenContract = contracts[_id]; require(tokenContract != address(0), "invalid id"); IMastersFedi curio = IMastersFedi(tokenContract); require(balances[_id][msg.sender] >= _quantity, "insufficient balance"); balances[_id][msg.sender] -= _quantity; curio.transfer(msg.sender, _quantity); } // burn emit TransferBatch(msg.sender, msg.sender, address(0), _ids, _quantities); } }