// File: /run/media/bill/Rekoner-0/protocols/iyusdi-contracts/contracts/iyusdi/IIyusdiBondingCurve.sol



// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.5.0;

interface IIyusdiBondingCurve {
  function getPrintPrice(uint256 curve, uint256 printNumber, uint256[] calldata parms) external view returns (uint256 price);
}

// File: /run/media/bill/Rekoner-0/protocols/iyusdi-contracts/contracts/iyusdi/IyusdiCollectionsV3.sol



// SPDX-License-Identifier: BUSL-1.1

pragma solidity >=0.8.0;

import "./IyusdiCollectionsV3Base.sol";

contract IyusdiCollectionsV3 is IyusdiCollectionsV3Base {

  constructor (address _protocol, uint256 _protocolMintPercent, uint256 _protocolBurnPercent, uint256 _curatorMintPercent, uint256 _curatorBurnPercent) {
    require(_protocol != address(0), '!protocol');
    require(_protocolMintPercent + _curatorMintPercent <= IyusdiCollectionsV3Base.PERCENT_BASE, '!mintPercent');
    require(_protocolBurnPercent + _curatorBurnPercent <= IyusdiCollectionsV3Base.PERCENT_BASE, '!burnPercent');
    owner = msg.sender;
    protocol = _protocol;
    protocolMintPercent = _protocolMintPercent;
    protocolBurnPercent = _protocolBurnPercent;
    curatorMintPercent = _curatorMintPercent;
    curatorBurnPercent = _curatorBurnPercent;
  }

}

// File: /run/media/bill/Rekoner-0/protocols/iyusdi-contracts/contracts/iyusdi/IyusdiCollectionsV3Base.sol



// SPDX-License-Identifier: BUSL-1.1

pragma solidity >=0.8.0;

import "../utils/Console.sol";
import "./IyusdiNftV3.sol";
import "./IIyusdiBondingCurve.sol";

contract IyusdiCollectionsV3Base {

  struct RequestMintOriginal {
    uint256 mintPercent;
    uint256 burnPercent;
    BondingCurve bondingCurve;
    bytes data;
  }

  struct BondingCurve {
    address curve;
    uint256 curveType;
    uint256[] params;
    uint256 MaxPrints;
  }

  event OriginalMinted (
    uint256 indexed og,
    address bondingCurve,
    uint256[] bondingCurveParms,
    uint32 bondingCurveType,
    uint32 maxPrints,
    uint32 mintPercent,
    uint32 burnPercent
  );

  event PrintBurned(
    uint256 indexed id,
    uint256 price,
    uint256 protocolFee,
    uint256 curatorFee,
    uint256 ogFee,
    uint256 printNumber
  );

  event PrintMinted(
    uint256 indexed id,
    uint256 price,
    uint256 protocolFee,
    uint256 curatorFee,
    uint256 ogFee,
    uint256 printNumber
  );

  address public nft;
  address public owner;
  address public protocol;
  
  uint256 public curatorMintPercent;
  uint256 public curatorBurnPercent;
  uint256 public protocolMintPercent;
  uint256 public protocolBurnPercent;
  mapping (uint256 => uint256) reserves;
  mapping (uint256 => uint256) public ogMintPercent;
  mapping (uint256 => uint256) public ogBurnPercent;
  mapping (uint256 => BondingCurve) public bondingCurves;
  mapping (address => bool) public approveMintOriginals;
  mapping (address => RequestMintOriginal) public requestMintOriginals;
  
  uint256 constant PERCENT_BASE = 10000;

  modifier onlyOwner() {
    require(owner != address(0) && (msg.sender == owner || msg.sender == _getCurator()), "!owner");
    _;
  }

  modifier onlyCurator() {
    require(msg.sender == _getCurator(), "!curator");
    _;
  }

  modifier onlyProtocol() {
    require(msg.sender == protocol, "!protocol");
    _;
  }

  function setNft(address _nft) onlyOwner external {
    require(_nft != address(0), '!nft');
    nft = _nft;
  }

  function transferProtocol(address _protocol) onlyProtocol external {
    require(_protocol != address(0), '!protocol');
    protocol = _protocol;
  }

  function transferOwner(address _owner) onlyOwner external {
    owner = _owner;
  }

  function removeRequestMintOriginal() external {
    _removeRequestMintOriginal();
  }

  function _removeRequestMintOriginal() internal {
    delete requestMintOriginals[msg.sender];
    approveMintOriginals[msg.sender] = false;
  }

  function _validateBondingCurve(BondingCurve memory curve) internal view returns(bool) {
    IIyusdiBondingCurve(curve.curve).getPrintPrice(curve.curveType, 1, curve.params);
    return true;
  }

  function requestMintOriginal(uint256 mintPercent, uint256 burnPercent, BondingCurve memory bondingCurve, bytes memory data) external {
    require(protocolMintPercent + curatorMintPercent + mintPercent <= PERCENT_BASE, '!mintPercent');
    require(protocolBurnPercent + curatorBurnPercent + burnPercent <= PERCENT_BASE, '!burnPercent');
    _validateBondingCurve(bondingCurve);
    requestMintOriginals[msg.sender].mintPercent = mintPercent;
    requestMintOriginals[msg.sender].burnPercent = burnPercent;
    requestMintOriginals[msg.sender].bondingCurve = bondingCurve;
    requestMintOriginals[msg.sender].data = data;
    approveMintOriginals[msg.sender] = false;
  }

  function _getCurator() internal view returns(address) {
    return IyusdiNftV3(nft).curator();
  }

  function _getOgOwner(uint256 og) internal view returns(address) {
    return IyusdiNftV3(nft).originalOwner(og);
  }

  function approveMintOriginal(address user, bool approve) external onlyCurator {
    approveMintOriginals[user] = approve;
  }

  function mintApprovedOriginal() external returns(uint256 id) {
    require(approveMintOriginals[msg.sender], '!approved');
    uint256 mintPercent = requestMintOriginals[msg.sender].mintPercent;
    uint256 burnPercent = requestMintOriginals[msg.sender].burnPercent;
    BondingCurve memory curve = requestMintOriginals[msg.sender].bondingCurve;
    bytes memory data = requestMintOriginals[msg.sender].data;
    id = IyusdiNftV3(nft).mintOriginal(msg.sender, data);
    ogMintPercent[id] = mintPercent;
    ogBurnPercent[id] = burnPercent;
    bondingCurves[id] = curve;
    _removeRequestMintOriginal();
    emit OriginalMinted(id, curve.curve, curve.params, uint32(curve.curveType), uint32(curve.MaxPrints), uint32(mintPercent), uint32(burnPercent));
  }

  function mintPrint(uint256 og, bytes memory data) payable external returns(uint256 id) {
    id = _mintPrintFor(og, msg.sender, data);
  }

  function mintPrintFor(uint256 og, address to, bytes memory data) payable external returns(uint256 id) {
    require(to != address(0), '!for');
    id = _mintPrintFor(og, to, data);
  }

  function _getPrintNumber(uint256 og) internal view returns (uint256) {
    return IyusdiNftV3(nft).originalMintedPrints(og);
  }

  function _getOgId(uint256 og) internal view returns (uint256) {
    return IyusdiNftV3(nft).getOgId(og);
  }

  function getPrintPrice(uint256 og, uint256 printNumber) external view returns(uint256) {
    address ogOwner = _getOgOwner(og);
    require(ogOwner != address(0), '!og');
    BondingCurve storage curve = bondingCurves[og];
    return _getPrintPrice(printNumber, curve);
  }

  function getBurnPrice(uint256 og, uint256 printNumber) external view returns(uint256) {
    address ogOwner = _getOgOwner(og);
    require(ogOwner != address(0), '!og');
    BondingCurve storage curve = bondingCurves[og];
    return _getBurnPrice(og, printNumber, curve);
  }

  function _getBurnPrice(uint256 og, uint256 printNumber, BondingCurve storage curve) internal view returns(uint256) {
    uint256 printPrice = _getPrintPrice(printNumber, curve);
    uint256 protocolFee = printPrice * protocolMintPercent / PERCENT_BASE;
    uint256 curatorFee = printPrice * curatorMintPercent / PERCENT_BASE;
    uint256 ownerFee = printPrice * ogMintPercent[og] / PERCENT_BASE;
    return printPrice - protocolFee - curatorFee - ownerFee;
  }

  function getPrintNumber(uint256 og) external view returns(uint256 printNumber) {
    address ogOwner = _getOgOwner(og);
    require(ogOwner != address(0), '!og');
    printNumber = _getPrintNumber(og);
  }

  function _sendFee(address to, uint256 price, uint256 percent) internal returns(uint256 fee) {
    fee = price * percent / PERCENT_BASE;
    if (fee > 0) {
      (bool success, ) = to.call{value: fee}("");
      require(success, '!_sendFee');
    }
  }

  function _getPrintPrice(uint256 printNumber, BondingCurve storage curve) internal view returns(uint256) {
    return IIyusdiBondingCurve(curve.curve).getPrintPrice(curve.curveType, printNumber, curve.params);
  }

  function _mintPrintFor(uint256 og, address to, bytes memory data) internal returns(uint256 id) {
    address ogOwner = _getOgOwner(og);
    require(ogOwner != address(0), '!og');
    uint256 printNumber = _getPrintNumber(og) + 1;
    BondingCurve storage curve = bondingCurves[og];
    require(printNumber <= curve.MaxPrints, '!maxPrints');
    uint256 printPrice = _getPrintPrice(printNumber, curve);
    require(msg.value >= printPrice, '!printPrice');
    uint256 protocolFee = _sendFee(protocol, printPrice, protocolMintPercent);
    uint256 curatorFee = _sendFee(_getCurator(), printPrice, curatorMintPercent);
    uint256 ownerFee = _sendFee(ogOwner, printPrice, ogMintPercent[og]);
    if (msg.value > printPrice) {
      uint256 refund =  msg.value - printPrice;
      (bool rsuccess, ) = msg.sender.call{value: refund}("");
      require(rsuccess, '!refund');
    }
    reserves[og] += printPrice - protocolFee - curatorFee - ownerFee; // isolate ETH for each og
    id = IyusdiNftV3(nft).mintPrint(og, to, data);
    emit PrintMinted(id, printPrice, protocolFee, curatorFee, ownerFee, printNumber);
  }

  function burnPrint(uint256 id, uint256 minPrintNumber) external {
    require(IyusdiNftV3(nft).isPrintId(id), '!printId');
    _burnPrint(id, minPrintNumber);
  }

  function _burnPrint(uint256 id, uint256 minPrintNumber) internal {
    uint256 og = _getOgId(id);
    address ogOwner = _getOgOwner(og);
    require(ogOwner != address(0), '!og');
    uint256 printNumber = _getPrintNumber(og);
    require(printNumber >= minPrintNumber, '!minPrintNumber');
    BondingCurve storage curve = bondingCurves[og];
    uint256 burnPrice = _getBurnPrice(og, printNumber, curve);
    reserves[og] -= burnPrice; // will fail if underflows
    uint256 protocolFee = _sendFee(protocol, burnPrice, protocolBurnPercent);
    uint256 curatorFee = _sendFee(_getCurator(), burnPrice, curatorBurnPercent);
    uint256 ogFee = _sendFee(ogOwner, burnPrice, ogBurnPercent[og]);
    uint256 refund = burnPrice - protocolFee - curatorFee - ogFee;
    if (refund > 0) {
      (bool success, ) = msg.sender.call{value: refund}("");
      require(success, '!refund');
    }
    IyusdiNftV3(nft).burnPrint(msg.sender, id);
    emit PrintBurned(id, burnPrice, protocolFee, curatorFee, ogFee, printNumber);
  }

  function post(uint256 og, uint256 hash, bytes memory data) external {
    address ogOwner = _getOgOwner(og);
    require(msg.sender == ogOwner, '!owner');
    IyusdiNftV3(nft).post(og, hash, data);
  }

  function allowTransfers(uint256 og, bool allow) external {
    address ogOwner = _getOgOwner(og);
    require(msg.sender == ogOwner, '!owner');
    IyusdiNftV3(nft).allowTransfers(og, allow);
  }

}

// File: /run/media/bill/Rekoner-0/protocols/iyusdi-contracts/contracts/iyusdi/IyusdiNftV3.sol



// SPDX-License-Identifier: BUSL-1.1

pragma solidity >=0.8.0;

import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "./IyusdiNftV3Base.sol";
import "../utils/Console.sol";

contract OwnableDelegateProxy {}
contract ProxyRegistry {
  mapping(address => OwnableDelegateProxy) public proxies;
}

contract IyusdiNftV3 is IyusdiNftV3Base, ERC1155 {

  address proxyRegistryAddress;

  constructor (address _operator, address _curator, string memory _uri, address _proxyRegistryAddress) ERC1155(_uri) {
    require(_curator != address(0) && _operator != address(0), '!param');
    proxyRegistryAddress = _proxyRegistryAddress;
    curator = _curator;
    operators[_operator] = true;
    setApprovalForAll(_operator, true);
    _mint(_curator, CURATOR_ID, 1, "");
    emit CuratorMinted(_curator, CURATOR_ID);
  }

  function isApprovedForAll(address _owner, address _operator) public view virtual override returns(bool isOperator) {
    ProxyRegistry proxyRegistry = ProxyRegistry(proxyRegistryAddress);
    if (address(proxyRegistry.proxies(_owner)) == _operator) {
      return true;
    }
    return ERC1155.isApprovedForAll(_owner, _operator);
  }

  function setOperator(address operator, bool set) onlyCurator external {
    require(operator != address(0), '!operator');
    operators[operator] = set;
    setApprovalForAll(operator, set);
  }

  function owns(uint256 id, address owner) external view returns(bool) {
    return balanceOf(owner, id) > 0;
  }

  function mintOriginal(address owner, bytes memory data) external returns(uint256 id) {
    id = _mintOriginal(owner, data);
    _mint(owner, id, 1, "");
  }

  function mintPrint(uint256 og, address to, bytes memory data) external returns(uint256 id) {
    id = _mintPrint(og, to, data);
    _mint(to, id, 1, "");
  }

  function burnPrint(address from, uint256 id) external {
    _burnPrint(from, id);
    _burn(from, id, 1);
  }

  function _isPrintOwner(uint256 id) internal view returns(bool) {
    return balanceOf(msg.sender, id) > 0;
  }

  function _canTransfer(uint256 id, address _operator) internal view returns(bool) {
    if (operators[_operator] || _isOgId(id) || _isPrintOwner(id)) {
      return true;
    } else {
      uint256 og = _getOgId(id);
      return canTransfer[og];
    }
  }

  /***********************************|
  |        Hooks                      |
  |__________________________________*/
  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);

    for (uint256 i = 0; i < ids.length; i++) {
      uint256 id = ids[i];
      require(_canTransfer(id, _operator), '!transfer');
      if (id == CURATOR_ID) {
        curator = to;
      } else if (_isOgId(id)) {
        originalOwner[id] = to;   
      }
    }
  }

  /**
      * @dev See {IERC165-supportsInterface}.
      */
  function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
    return ERC1155.supportsInterface(interfaceId);
  }

}
// File: /run/media/bill/Rekoner-0/protocols/iyusdi-contracts/contracts/iyusdi/IyusdiNftV3Base.sol



// SPDX-License-Identifier: BUSL-1.1

pragma solidity >=0.8.0;

contract IyusdiNftV3Base {

  event FeedItem(
    uint256 indexed id,
    uint256 indexed hash,
    uint256 timestamp,
    bytes data
  );

  event CuratorMinted(
    address owner,
    uint256 id
  );

  event OriginalMinted(
    address indexed owner,
    uint256 indexed id
  );

  event PrintMinted(
    address indexed owner,
    uint256 indexed og,
    uint256 indexed id
  );

  event PrintBurned(
    address from,
    uint256 id
  );

  struct Original {
    uint64 mintedPrints;
    uint64 printIndex;
  }

  uint256 public constant OG_MASK     = 0x00000000000000000000000000000000ffffffffffffffffffffffffffffffff;
  uint256 public constant OG_INV_MASK = 0xffffffffffffffffffffffffffffffff00000000000000000000000000000000;
  uint256 public constant CURATOR_ID  = 0x8000000000000000000000000000000000000000000000000000000000000000;

  address public curator;
  Original[] public originals;
  mapping (address => bool) public operators;
  mapping (uint256 => bool) public canTransfer;
  mapping (uint256 => address) public originalOwner;

  modifier onlyCurator() {
    require(msg.sender == curator, "!curator");
    _;
  }

  function _getOgOwner(uint256 id) internal view returns (address) {
    return id == CURATOR_ID ? curator : originalOwner[_getOgId(id)];
  }

  function originalIndex(uint256 id) external pure returns(uint256) {
    uint256 og = _getOgId(id);
    return (og >> 128) - 1;
  }

  function originalMintedPrints(uint256 id) external view returns(uint256) {
    uint256 og = _getOgId(id);
    uint256 idx = (og >> 128) - 1;
    return originals[idx].mintedPrints;
  }

  function post(uint256 id, uint256 hash, bytes memory data) external {
    require(_isOperator(), '!operator');
    uint256 og = _getOgId(id);
    address _owner = _getOgOwner(og);
    require(_owner != address(0), '!owner');
    emit FeedItem(id, hash, block.timestamp, data);
  }

  function getOgId(uint256 id) external pure returns (uint256) {
    return _getOgId(id);
  }

  function _getOgId(uint256 id) internal pure returns (uint256) {
    return id & OG_INV_MASK;
  }

  function isOgId(uint256 id) external pure returns (bool) {
    return _isOgId(id);
  }

  function _isOgId(uint256 id) internal pure returns (bool) {
    return (id & OG_MASK) == 0;
  }

  function isPrintId(uint256 id) external pure returns (bool) {
    return _isPrintId(id);
  }

  function _isPrintId(uint256 id) internal pure returns (bool) {
    return (id & OG_MASK) > 0;
  }

  function _isCurator() internal view returns(bool) {
    return msg.sender == curator;
  }

  function _isOperator() internal view returns(bool) {
    return operators[msg.sender];
  }

  function _mintOriginal(address _owner, bytes memory data) internal returns(uint256 id) {
    require(_isOperator() && _owner != address(0), '!parm');
    originals.push(Original(0, 0));
    id = originals.length << 128;
    originalOwner[id] = _owner;
    emit OriginalMinted(_owner, id);
    emit FeedItem(id, 0, block.timestamp, data);
  }

  function _mintPrint(uint256 og, address to, bytes memory data) internal returns(uint256 id) {
    require(_isOperator() && _isOgId(og), '!ogId');
    uint256 idx = (og >> 128) - 1;
    Original storage original = originals[idx];
    original.mintedPrints++;
    original.printIndex++;
    id = og | original.printIndex;
    emit PrintMinted(to, og, id);
    emit FeedItem(id, 0, block.timestamp, data);
  }

  function _burnPrint(address from, uint256 id) internal {
    require(_isOperator() && _isPrintId(id), '!printId');
    uint256 og = _getOgId(id);
    uint256 idx = (og >> 128) - 1;
    Original storage original = originals[idx];
    original.mintedPrints = original.mintedPrints - 1;
    emit PrintBurned(from, id);
  }

  function allowTransfers(uint256 id, bool can) external {
    require(_isOperator(), '!operator');
    uint256 og = _getOgId(id);
    canTransfer[og] = can;
  }

}
// File: /run/media/bill/Rekoner-0/protocols/iyusdi-contracts/contracts/utils/Console.sol



// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.6.12;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

library Console {
  bool constant PROD = false;

  function concat(string memory a, string memory b) internal pure returns(string memory)
  {
    return string(abi.encodePacked(a, b));
  }

  function concat(string memory a, string memory b, string memory c) internal pure returns(string memory)
  {
    return string(abi.encodePacked(a, b, c));
  }

  event LogBalance(string, uint);
  function logBalance(address token, address to) internal {
    if (PROD) return;
    emit LogBalance(ERC20(token).symbol(), ERC20(token).balanceOf(to));
  }

  function logBalance(string memory s, address token, address to) internal {
    if (PROD) return;
    emit LogBalance(string(abi.encodePacked(s, '/', ERC20(token).symbol())), ERC20(token).balanceOf(to));
  }

  event LogUint(string, uint);
  function log(string memory s, uint x) internal {
    if (PROD) return;
    emit LogUint(s, x);
  }

  function log(string memory s, string memory t, uint x) internal {
    if (PROD) return;
    emit LogUint(concat(s, t), x);
  }
    
  function log(string memory s, string memory t, string memory u, uint x) internal {
    if (PROD) return;
    emit LogUint(concat(s, t, u), x);
  }
    
  event LogInt(string, int);
  function log(string memory s, int x) internal {
    if (PROD) return;
    emit LogInt(s, x);
  }
  
  event LogBytes(string, bytes);
  function log(string memory s, bytes memory x) internal {
    if (PROD) return;
    emit LogBytes(s, x);
  }
  
  event LogBytes32(string, bytes32);
  function log(string memory s, bytes32 x) internal {
    if (PROD) return;
    emit LogBytes32(s, x);
  }

  event LogAddress(string, address);
  function log(string memory s, address x) internal {
    if (PROD) return;
    emit LogAddress(s, x);
  }

  event LogBool(string, bool);
  function log(string memory s, bool x) internal {
    if (PROD) return;
    emit LogBool(s, x);
  }
}

// File: @openzeppelin/contracts/token/ERC1155/ERC1155.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./IERC1155.sol";
import "./IERC1155Receiver.sol";
import "./extensions/IERC1155MetadataURI.sol";
import "../../utils/Address.sol";
import "../../utils/Context.sol";
import "../../utils/introspection/ERC165.sol";

/**
 *
 * @dev Implementation of the basic standard multi-token.
 * See https://eips.ethereum.org/EIPS/eip-1155
 * Originally based on code by Enjin: https://github.com/enjin/erc-1155
 *
 * _Available since v3.1._
 */
contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI {
    using Address for address;

    // Mapping from token ID to account balances
    mapping (uint256 => mapping(address => uint256)) private _balances;

    // Mapping from account to operator approvals
    mapping (address => mapping(address => bool)) private _operatorApprovals;

    // Used as the URI for all token types by relying on ID substitution, e.g. https://token-cdn-domain/{id}.json
    string private _uri;

    /**
     * @dev See {_setURI}.
     */
    constructor (string memory uri_) {
        _setURI(uri_);
    }

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
        return interfaceId == type(IERC1155).interfaceId
            || interfaceId == type(IERC1155MetadataURI).interfaceId
            || super.supportsInterface(interfaceId);
    }

    /**
     * @dev See {IERC1155MetadataURI-uri}.
     *
     * This implementation returns the same URI for *all* token types. It relies
     * on the token type ID substitution mechanism
     * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP].
     *
     * Clients calling this function must replace the `\{id\}` substring with the
     * actual token type ID.
     */
    function uri(uint256) public view virtual override returns (string memory) {
        return _uri;
    }

    /**
     * @dev See {IERC1155-balanceOf}.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function balanceOf(address account, uint256 id) public view virtual override returns (uint256) {
        require(account != address(0), "ERC1155: balance query for the zero address");
        return _balances[id][account];
    }

    /**
     * @dev See {IERC1155-balanceOfBatch}.
     *
     * Requirements:
     *
     * - `accounts` and `ids` must have the same length.
     */
    function balanceOfBatch(
        address[] memory accounts,
        uint256[] memory ids
    )
        public
        view
        virtual
        override
        returns (uint256[] memory)
    {
        require(accounts.length == ids.length, "ERC1155: accounts and ids length mismatch");

        uint256[] memory batchBalances = new uint256[](accounts.length);

        for (uint256 i = 0; i < accounts.length; ++i) {
            batchBalances[i] = balanceOf(accounts[i], ids[i]);
        }

        return batchBalances;
    }

    /**
     * @dev See {IERC1155-setApprovalForAll}.
     */
    function setApprovalForAll(address operator, bool approved) public virtual override {
        require(_msgSender() != operator, "ERC1155: setting approval status for self");

        _operatorApprovals[_msgSender()][operator] = approved;
        emit ApprovalForAll(_msgSender(), operator, approved);
    }

    /**
     * @dev See {IERC1155-isApprovedForAll}.
     */
    function isApprovedForAll(address account, address operator) public view virtual override returns (bool) {
        return _operatorApprovals[account][operator];
    }

    /**
     * @dev See {IERC1155-safeTransferFrom}.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    )
        public
        virtual
        override
    {
        require(to != address(0), "ERC1155: transfer to the zero address");
        require(
            from == _msgSender() || isApprovedForAll(from, _msgSender()),
            "ERC1155: caller is not owner nor approved"
        );

        address operator = _msgSender();

        _beforeTokenTransfer(operator, from, to, _asSingletonArray(id), _asSingletonArray(amount), data);

        uint256 fromBalance = _balances[id][from];
        require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
        _balances[id][from] = fromBalance - amount;
        _balances[id][to] += amount;

        emit TransferSingle(operator, from, to, id, amount);

        _doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data);
    }

    /**
     * @dev See {IERC1155-safeBatchTransferFrom}.
     */
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    )
        public
        virtual
        override
    {
        require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
        require(to != address(0), "ERC1155: transfer to the zero address");
        require(
            from == _msgSender() || isApprovedForAll(from, _msgSender()),
            "ERC1155: transfer caller is not owner nor approved"
        );

        address operator = _msgSender();

        _beforeTokenTransfer(operator, from, to, ids, amounts, data);

        for (uint256 i = 0; i < ids.length; ++i) {
            uint256 id = ids[i];
            uint256 amount = amounts[i];

            uint256 fromBalance = _balances[id][from];
            require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
            _balances[id][from] = fromBalance - amount;
            _balances[id][to] += amount;
        }

        emit TransferBatch(operator, from, to, ids, amounts);

        _doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, amounts, data);
    }

    /**
     * @dev Sets a new URI for all token types, by relying on the token type ID
     * substitution mechanism
     * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP].
     *
     * By this mechanism, any occurrence of the `\{id\}` substring in either the
     * URI or any of the amounts in the JSON file at said URI will be replaced by
     * clients with the token type ID.
     *
     * For example, the `https://token-cdn-domain/\{id\}.json` URI would be
     * interpreted by clients as
     * `https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json`
     * for token type ID 0x4cce0.
     *
     * See {uri}.
     *
     * Because these URIs cannot be meaningfully represented by the {URI} event,
     * this function emits no events.
     */
    function _setURI(string memory newuri) internal virtual {
        _uri = newuri;
    }

    /**
     * @dev Creates `amount` tokens of token type `id`, and assigns them to `account`.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     * - If `account` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function _mint(address account, uint256 id, uint256 amount, bytes memory data) internal virtual {
        require(account != address(0), "ERC1155: mint to the zero address");

        address operator = _msgSender();

        _beforeTokenTransfer(operator, address(0), account, _asSingletonArray(id), _asSingletonArray(amount), data);

        _balances[id][account] += amount;
        emit TransferSingle(operator, address(0), account, id, amount);

        _doSafeTransferAcceptanceCheck(operator, address(0), account, id, amount, data);
    }

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_mint}.
     *
     * Requirements:
     *
     * - `ids` and `amounts` must have the same length.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function _mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) internal virtual {
        require(to != address(0), "ERC1155: mint to the zero address");
        require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");

        address operator = _msgSender();

        _beforeTokenTransfer(operator, address(0), to, ids, amounts, data);

        for (uint i = 0; i < ids.length; i++) {
            _balances[ids[i]][to] += amounts[i];
        }

        emit TransferBatch(operator, address(0), to, ids, amounts);

        _doSafeBatchTransferAcceptanceCheck(operator, address(0), to, ids, amounts, data);
    }

    /**
     * @dev Destroys `amount` tokens of token type `id` from `account`
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens of token type `id`.
     */
    function _burn(address account, uint256 id, uint256 amount) internal virtual {
        require(account != address(0), "ERC1155: burn from the zero address");

        address operator = _msgSender();

        _beforeTokenTransfer(operator, account, address(0), _asSingletonArray(id), _asSingletonArray(amount), "");

        uint256 accountBalance = _balances[id][account];
        require(accountBalance >= amount, "ERC1155: burn amount exceeds balance");
        _balances[id][account] = accountBalance - amount;

        emit TransferSingle(operator, account, address(0), id, amount);
    }

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_burn}.
     *
     * Requirements:
     *
     * - `ids` and `amounts` must have the same length.
     */
    function _burnBatch(address account, uint256[] memory ids, uint256[] memory amounts) internal virtual {
        require(account != address(0), "ERC1155: burn from the zero address");
        require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");

        address operator = _msgSender();

        _beforeTokenTransfer(operator, account, address(0), ids, amounts, "");

        for (uint i = 0; i < ids.length; i++) {
            uint256 id = ids[i];
            uint256 amount = amounts[i];

            uint256 accountBalance = _balances[id][account];
            require(accountBalance >= amount, "ERC1155: burn amount exceeds balance");
            _balances[id][account] = accountBalance - amount;
        }

        emit TransferBatch(operator, account, address(0), ids, amounts);
    }

    /**
     * @dev Hook that is called before any token transfer. This includes minting
     * and burning, as well as batched variants.
     *
     * The same hook is called on both single and batched variants. For single
     * transfers, the length of the `id` and `amount` arrays will be 1.
     *
     * Calling conditions (for each `id` and `amount` pair):
     *
     * - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * of token type `id` will be  transferred to `to`.
     * - When `from` is zero, `amount` tokens of token type `id` will be minted
     * for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens of token type `id`
     * will be burned.
     * - `from` and `to` are never both zero.
     * - `ids` and `amounts` have the same, non-zero length.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(
        address operator,
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    )
        internal
        virtual
    { }

    function _doSafeTransferAcceptanceCheck(
        address operator,
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    )
        private
    {
        if (to.isContract()) {
            try IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns (bytes4 response) {
                if (response != IERC1155Receiver(to).onERC1155Received.selector) {
                    revert("ERC1155: ERC1155Receiver rejected tokens");
                }
            } catch Error(string memory reason) {
                revert(reason);
            } catch {
                revert("ERC1155: transfer to non ERC1155Receiver implementer");
            }
        }
    }

    function _doSafeBatchTransferAcceptanceCheck(
        address operator,
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    )
        private
    {
        if (to.isContract()) {
            try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, amounts, data) returns (bytes4 response) {
                if (response != IERC1155Receiver(to).onERC1155BatchReceived.selector) {
                    revert("ERC1155: ERC1155Receiver rejected tokens");
                }
            } catch Error(string memory reason) {
                revert(reason);
            } catch {
                revert("ERC1155: transfer to non ERC1155Receiver implementer");
            }
        }
    }

    function _asSingletonArray(uint256 element) private pure returns (uint256[] memory) {
        uint256[] memory array = new uint256[](1);
        array[0] = element;

        return array;
    }
}

// File: @openzeppelin/contracts/token/ERC1155/IERC1155.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC1155 compliant contract, as defined in the
 * https://eips.ethereum.org/EIPS/eip-1155[EIP].
 *
 * _Available since v3.1._
 */
interface IERC1155 is IERC165 {
    /**
     * @dev Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`.
     */
    event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);

    /**
     * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all
     * transfers.
     */
    event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values);

    /**
     * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to
     * `approved`.
     */
    event ApprovalForAll(address indexed account, address indexed operator, bool approved);

    /**
     * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI.
     *
     * If an {URI} event was emitted for `id`, the standard
     * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value
     * returned by {IERC1155MetadataURI-uri}.
     */
    event URI(string value, uint256 indexed id);

    /**
     * @dev Returns the amount of tokens of token type `id` owned by `account`.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function balanceOf(address account, uint256 id) external view returns (uint256);

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}.
     *
     * Requirements:
     *
     * - `accounts` and `ids` must have the same length.
     */
    function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) external view returns (uint256[] memory);

    /**
     * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`,
     *
     * Emits an {ApprovalForAll} event.
     *
     * Requirements:
     *
     * - `operator` cannot be the caller.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns true if `operator` is approved to transfer ``account``'s tokens.
     *
     * See {setApprovalForAll}.
     */
    function isApprovedForAll(address account, address operator) external view returns (bool);

    /**
     * @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - If the caller is not `from`, it must be have been approved to spend ``from``'s tokens via {setApprovalForAll}.
     * - `from` must have a balance of tokens of type `id` of at least `amount`.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external;

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}.
     *
     * Emits a {TransferBatch} event.
     *
     * Requirements:
     *
     * - `ids` and `amounts` must have the same length.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function safeBatchTransferFrom(address from, address to, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata data) external;
}

// File: @openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../../utils/introspection/IERC165.sol";

/**
 * _Available since v3.1._
 */
interface IERC1155Receiver is IERC165 {

    /**
        @dev Handles the receipt of a single ERC1155 token type. This function is
        called at the end of a `safeTransferFrom` after the balance has been updated.
        To accept the transfer, this must return
        `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
        (i.e. 0xf23a6e61, or its own function selector).
        @param operator The address which initiated the transfer (i.e. msg.sender)
        @param from The address which previously owned the token
        @param id The ID of the token being transferred
        @param value The amount of tokens being transferred
        @param data Additional data with no specified format
        @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed
    */
    function onERC1155Received(
        address operator,
        address from,
        uint256 id,
        uint256 value,
        bytes calldata data
    )
        external
        returns(bytes4);

    /**
        @dev Handles the receipt of a multiple ERC1155 token types. This function
        is called at the end of a `safeBatchTransferFrom` after the balances have
        been updated. To accept the transfer(s), this must return
        `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
        (i.e. 0xbc197c81, or its own function selector).
        @param operator The address which initiated the batch transfer (i.e. msg.sender)
        @param from The address which previously owned the token
        @param ids An array containing ids of each token being transferred (order and length must match values array)
        @param values An array containing amounts of each token being transferred (order and length must match ids array)
        @param data Additional data with no specified format
        @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed
    */
    function onERC1155BatchReceived(
        address operator,
        address from,
        uint256[] calldata ids,
        uint256[] calldata values,
        bytes calldata data
    )
        external
        returns(bytes4);
}

// File: @openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../IERC1155.sol";

/**
 * @dev Interface of the optional ERC1155MetadataExtension interface, as defined
 * in the https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[EIP].
 *
 * _Available since v3.1._
 */
interface IERC1155MetadataURI is IERC1155 {
    /**
     * @dev Returns the URI for token type `id`.
     *
     * If the `\{id\}` substring is present in the URI, it must be replaced by
     * clients with the actual token type ID.
     */
    function uri(uint256 id) external view returns (string memory);
}

// File: @openzeppelin/contracts/token/ERC20/ERC20.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./IERC20.sol";
import "../../utils/Context.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 * For a generic mechanism see {ERC20PresetMinterPauser}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * We have followed general OpenZeppelin guidelines: functions revert instead
 * of returning `false` on failure. This behavior is nonetheless conventional
 * and does not conflict with the expectations of ERC20 applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 *
 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See {IERC20-approve}.
 */
contract ERC20 is Context, IERC20 {
    mapping (address => uint256) private _balances;

    mapping (address => mapping (address => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * The defaut value of {decimals} is 18. To select a different value for
     * {decimals} you should overload it.
     *
     * All three of these values are immutable: they can only be set once during
     * construction.
     */
    constructor (string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5,05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the value {ERC20} uses, unless this function is
     * overloaded;
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual returns (uint8) {
        return 18;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual override returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual override returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `recipient` cannot be the zero address.
     * - the caller must have a balance of at least `amount`.
     */
    function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
        _transfer(_msgSender(), recipient, amount);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * Requirements:
     *
     * - `sender` and `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     * - the caller must have allowance for ``sender``'s tokens of at least
     * `amount`.
     */
    function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
        _transfer(sender, recipient, amount);

        uint256 currentAllowance = _allowances[sender][_msgSender()];
        require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
        _approve(sender, _msgSender(), currentAllowance - amount);

        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender] + addedValue);
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        uint256 currentAllowance = _allowances[_msgSender()][spender];
        require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
        _approve(_msgSender(), spender, currentAllowance - subtractedValue);

        return true;
    }

    /**
     * @dev Moves tokens `amount` from `sender` to `recipient`.
     *
     * This is internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `sender` cannot be the zero address.
     * - `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     */
    function _transfer(address sender, address recipient, uint256 amount) internal virtual {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(sender, recipient, amount);

        uint256 senderBalance = _balances[sender];
        require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
        _balances[sender] = senderBalance - amount;
        _balances[recipient] += amount;

        emit Transfer(sender, recipient, amount);
    }

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply += amount;
        _balances[account] += amount;
        emit Transfer(address(0), account, amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens.
     */
    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        uint256 accountBalance = _balances[account];
        require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
        _balances[account] = accountBalance - amount;
        _totalSupply -= amount;

        emit Transfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(address owner, address spender, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    /**
     * @dev Hook that is called before any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * will be to transferred to `to`.
     * - when `from` is zero, `amount` tokens will be minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { }
}

// File: @openzeppelin/contracts/token/ERC20/IERC20.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

// File: @openzeppelin/contracts/utils/Address.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize, which returns 0 for contracts in
        // construction, since the code is only stored at the end of the
        // constructor execution.

        uint256 size;
        // solhint-disable-next-line no-inline-assembly
        assembly { size := extcodesize(account) }
        return size > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
        (bool success, ) = recipient.call{ value: amount }("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain`call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
      return functionCall(target, data, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        require(isContract(target), "Address: call to non-contract");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = target.call{ value: value }(data);
        return _verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
        require(isContract(target), "Address: static call to non-contract");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = target.staticcall(data);
        return _verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
        require(isContract(target), "Address: delegate call to non-contract");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return _verifyCallResult(success, returndata, errorMessage);
    }

    function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
        if (success) {
            return returndata;
        } else {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly

                // solhint-disable-next-line no-inline-assembly
                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}

// File: @openzeppelin/contracts/utils/Context.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/*
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
        return msg.data;
    }
}

// File: @openzeppelin/contracts/utils/introspection/ERC165.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./IERC165.sol";

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 *
 * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
 */
abstract contract ERC165 is IERC165 {
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IERC165).interfaceId;
    }
}

// File: @openzeppelin/contracts/utils/introspection/IERC165.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}