creation_sourcecode: // Sources flattened with hardhat v2.17.0 https://hardhat.org
  
  // File @openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol@v4.9.2
  
  // SPDX-License-Identifier: MIT
  // OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
  
  pragma solidity ^0.8.1;
  
  /**
   * @dev Collection of functions related to the address type
   */
  library AddressUpgradeable {
      /**
       * @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
       *
       * Furthermore, `isContract` will also return true if the target contract within
       * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
       * which only has an effect at the end of a transaction.
       * ====
       *
       * [IMPORTANT]
       * ====
       * You shouldn't rely on `isContract` to protect against flash loan attacks!
       *
       * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
       * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
       * constructor.
       * ====
       */
      function isContract(address account) internal view returns (bool) {
          // This method relies on extcodesize/address.code.length, which returns 0
          // for contracts in construction, since the code is only stored at the end
          // of the constructor execution.
  
          return account.code.length > 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://consensys.net/diligence/blog/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.8.0/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");
  
          (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 functionCallWithValue(target, data, 0, "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");
          (bool success, bytes memory returndata) = target.call{value: value}(data);
          return verifyCallResultFromTarget(target, 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) {
          (bool success, bytes memory returndata) = target.staticcall(data);
          return verifyCallResultFromTarget(target, 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) {
          (bool success, bytes memory returndata) = target.delegatecall(data);
          return verifyCallResultFromTarget(target, success, returndata, errorMessage);
      }
  
      /**
       * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
       * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
       *
       * _Available since v4.8._
       */
      function verifyCallResultFromTarget(
          address target,
          bool success,
          bytes memory returndata,
          string memory errorMessage
      ) internal view returns (bytes memory) {
          if (success) {
              if (returndata.length == 0) {
                  // only check isContract if the call was successful and the return data is empty
                  // otherwise we already know that it was a contract
                  require(isContract(target), "Address: call to non-contract");
              }
              return returndata;
          } else {
              _revert(returndata, errorMessage);
          }
      }
  
      /**
       * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
       * revert reason or using the provided one.
       *
       * _Available since v4.3._
       */
      function verifyCallResult(
          bool success,
          bytes memory returndata,
          string memory errorMessage
      ) internal pure returns (bytes memory) {
          if (success) {
              return returndata;
          } else {
              _revert(returndata, errorMessage);
          }
      }
  
      function _revert(bytes memory returndata, string memory errorMessage) private pure {
          // 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
              /// @solidity memory-safe-assembly
              assembly {
                  let returndata_size := mload(returndata)
                  revert(add(32, returndata), returndata_size)
              }
          } else {
              revert(errorMessage);
          }
      }
  }
  
  
  // File @openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol@v4.9.2
  
  // OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)
  
  pragma solidity ^0.8.2;
  
  /**
   * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
   * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
   * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
   * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
   *
   * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
   * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
   * case an upgrade adds a module that needs to be initialized.
   *
   * For example:
   *
   * [.hljs-theme-light.nopadding]
   * ```solidity
   * contract MyToken is ERC20Upgradeable {
   *     function initialize() initializer public {
   *         __ERC20_init("MyToken", "MTK");
   *     }
   * }
   *
   * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
   *     function initializeV2() reinitializer(2) public {
   *         __ERC20Permit_init("MyToken");
   *     }
   * }
   * ```
   *
   * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
   * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
   *
   * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
   * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
   *
   * [CAUTION]
   * ====
   * Avoid leaving a contract uninitialized.
   *
   * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
   * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
   * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
   *
   * [.hljs-theme-light.nopadding]
   * ```
   * /// @custom:oz-upgrades-unsafe-allow constructor
   * constructor() {
   *     _disableInitializers();
   * }
   * ```
   * ====
   */
  abstract contract Initializable {
      /**
       * @dev Indicates that the contract has been initialized.
       * @custom:oz-retyped-from bool
       */
      uint8 private _initialized;
  
      /**
       * @dev Indicates that the contract is in the process of being initialized.
       */
      bool private _initializing;
  
      /**
       * @dev Triggered when the contract has been initialized or reinitialized.
       */
      event Initialized(uint8 version);
  
      /**
       * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
       * `onlyInitializing` functions can be used to initialize parent contracts.
       *
       * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
       * constructor.
       *
       * Emits an {Initialized} event.
       */
      modifier initializer() {
          bool isTopLevelCall = !_initializing;
          require(
              (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
              "Initializable: contract is already initialized"
          );
          _initialized = 1;
          if (isTopLevelCall) {
              _initializing = true;
          }
          _;
          if (isTopLevelCall) {
              _initializing = false;
              emit Initialized(1);
          }
      }
  
      /**
       * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
       * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
       * used to initialize parent contracts.
       *
       * A reinitializer may be used after the original initialization step. This is essential to configure modules that
       * are added through upgrades and that require initialization.
       *
       * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
       * cannot be nested. If one is invoked in the context of another, execution will revert.
       *
       * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
       * a contract, executing them in the right order is up to the developer or operator.
       *
       * WARNING: setting the version to 255 will prevent any future reinitialization.
       *
       * Emits an {Initialized} event.
       */
      modifier reinitializer(uint8 version) {
          require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
          _initialized = version;
          _initializing = true;
          _;
          _initializing = false;
          emit Initialized(version);
      }
  
      /**
       * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
       * {initializer} and {reinitializer} modifiers, directly or indirectly.
       */
      modifier onlyInitializing() {
          require(_initializing, "Initializable: contract is not initializing");
          _;
      }
  
      /**
       * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
       * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
       * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
       * through proxies.
       *
       * Emits an {Initialized} event the first time it is successfully executed.
       */
      function _disableInitializers() internal virtual {
          require(!_initializing, "Initializable: contract is initializing");
          if (_initialized != type(uint8).max) {
              _initialized = type(uint8).max;
              emit Initialized(type(uint8).max);
          }
      }
  
      /**
       * @dev Returns the highest version that has been initialized. See {reinitializer}.
       */
      function _getInitializedVersion() internal view returns (uint8) {
          return _initialized;
      }
  
      /**
       * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
       */
      function _isInitializing() internal view returns (bool) {
          return _initializing;
      }
  }
  
  
  // File @openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol@v4.9.2
  
  // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
  
  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 ContextUpgradeable is Initializable {
      function __Context_init() internal onlyInitializing {
      }
  
      function __Context_init_unchained() internal onlyInitializing {
      }
      function _msgSender() internal view virtual returns (address) {
          return msg.sender;
      }
  
      function _msgData() internal view virtual returns (bytes calldata) {
          return msg.data;
      }
  
      /**
       * @dev This empty reserved space is put in place to allow future versions to add new
       * variables without shifting down storage in the inheritance chain.
       * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
       */
      uint256[50] private __gap;
  }
  
  
  // File @openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol@v4.9.2
  
  // OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
  
  pragma solidity ^0.8.0;
  
  
  /**
   * @dev Contract module which provides a basic access control mechanism, where
   * there is an account (an owner) that can be granted exclusive access to
   * specific functions.
   *
   * By default, the owner account will be the one that deploys the contract. This
   * can later be changed with {transferOwnership}.
   *
   * This module is used through inheritance. It will make available the modifier
   * `onlyOwner`, which can be applied to your functions to restrict their use to
   * the owner.
   */
  abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
      address private _owner;
  
      event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
  
      /**
       * @dev Initializes the contract setting the deployer as the initial owner.
       */
      function __Ownable_init() internal onlyInitializing {
          __Ownable_init_unchained();
      }
  
      function __Ownable_init_unchained() internal onlyInitializing {
          _transferOwnership(_msgSender());
      }
  
      /**
       * @dev Throws if called by any account other than the owner.
       */
      modifier onlyOwner() {
          _checkOwner();
          _;
      }
  
      /**
       * @dev Returns the address of the current owner.
       */
      function owner() public view virtual returns (address) {
          return _owner;
      }
  
      /**
       * @dev Throws if the sender is not the owner.
       */
      function _checkOwner() internal view virtual {
          require(owner() == _msgSender(), "Ownable: caller is not the owner");
      }
  
      /**
       * @dev Leaves the contract without owner. It will not be possible to call
       * `onlyOwner` functions. Can only be called by the current owner.
       *
       * NOTE: Renouncing ownership will leave the contract without an owner,
       * thereby disabling any functionality that is only available to the owner.
       */
      function renounceOwnership() public virtual onlyOwner {
          _transferOwnership(address(0));
      }
  
      /**
       * @dev Transfers ownership of the contract to a new account (`newOwner`).
       * Can only be called by the current owner.
       */
      function transferOwnership(address newOwner) public virtual onlyOwner {
          require(newOwner != address(0), "Ownable: new owner is the zero address");
          _transferOwnership(newOwner);
      }
  
      /**
       * @dev Transfers ownership of the contract to a new account (`newOwner`).
       * Internal function without access restriction.
       */
      function _transferOwnership(address newOwner) internal virtual {
          address oldOwner = _owner;
          _owner = newOwner;
          emit OwnershipTransferred(oldOwner, newOwner);
      }
  
      /**
       * @dev This empty reserved space is put in place to allow future versions to add new
       * variables without shifting down storage in the inheritance chain.
       * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
       */
      uint256[49] private __gap;
  }
  
  
  // File @openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol@v4.9.2
  
  // OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)
  
  pragma solidity ^0.8.0;
  
  
  /**
   * @dev Contract module which allows children to implement an emergency stop
   * mechanism that can be triggered by an authorized account.
   *
   * This module is used through inheritance. It will make available the
   * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
   * the functions of your contract. Note that they will not be pausable by
   * simply including this module, only once the modifiers are put in place.
   */
  abstract contract PausableUpgradeable is Initializable, ContextUpgradeable {
      /**
       * @dev Emitted when the pause is triggered by `account`.
       */
      event Paused(address account);
  
      /**
       * @dev Emitted when the pause is lifted by `account`.
       */
      event Unpaused(address account);
  
      bool private _paused;
  
      /**
       * @dev Initializes the contract in unpaused state.
       */
      function __Pausable_init() internal onlyInitializing {
          __Pausable_init_unchained();
      }
  
      function __Pausable_init_unchained() internal onlyInitializing {
          _paused = false;
      }
  
      /**
       * @dev Modifier to make a function callable only when the contract is not paused.
       *
       * Requirements:
       *
       * - The contract must not be paused.
       */
      modifier whenNotPaused() {
          _requireNotPaused();
          _;
      }
  
      /**
       * @dev Modifier to make a function callable only when the contract is paused.
       *
       * Requirements:
       *
       * - The contract must be paused.
       */
      modifier whenPaused() {
          _requirePaused();
          _;
      }
  
      /**
       * @dev Returns true if the contract is paused, and false otherwise.
       */
      function paused() public view virtual returns (bool) {
          return _paused;
      }
  
      /**
       * @dev Throws if the contract is paused.
       */
      function _requireNotPaused() internal view virtual {
          require(!paused(), "Pausable: paused");
      }
  
      /**
       * @dev Throws if the contract is not paused.
       */
      function _requirePaused() internal view virtual {
          require(paused(), "Pausable: not paused");
      }
  
      /**
       * @dev Triggers stopped state.
       *
       * Requirements:
       *
       * - The contract must not be paused.
       */
      function _pause() internal virtual whenNotPaused {
          _paused = true;
          emit Paused(_msgSender());
      }
  
      /**
       * @dev Returns to normal state.
       *
       * Requirements:
       *
       * - The contract must be paused.
       */
      function _unpause() internal virtual whenPaused {
          _paused = false;
          emit Unpaused(_msgSender());
      }
  
      /**
       * @dev This empty reserved space is put in place to allow future versions to add new
       * variables without shifting down storage in the inheritance chain.
       * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
       */
      uint256[49] private __gap;
  }
  
  
  // File @openzeppelin/contracts-upgradeable/utils/introspection/IERC165Upgradeable.sol@v4.9.2
  
  // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
  
  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 IERC165Upgradeable {
      /**
       * @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);
  }
  
  
  // File @openzeppelin/contracts-upgradeable/token/ERC1155/IERC1155Upgradeable.sol@v4.9.2
  
  // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC1155/IERC1155.sol)
  
  pragma solidity ^0.8.0;
  
  /**
   * @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 IERC1155Upgradeable is IERC165Upgradeable {
      /**
       * @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 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-upgradeable/token/ERC1155/extensions/IERC1155MetadataURIUpgradeable.sol@v4.9.2
  
  // OpenZeppelin Contracts v4.4.1 (token/ERC1155/extensions/IERC1155MetadataURI.sol)
  
  pragma solidity ^0.8.0;
  
  /**
   * @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 IERC1155MetadataURIUpgradeable is IERC1155Upgradeable {
      /**
       * @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-upgradeable/token/ERC1155/IERC1155ReceiverUpgradeable.sol@v4.9.2
  
  // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/IERC1155Receiver.sol)
  
  pragma solidity ^0.8.0;
  
  /**
   * @dev _Available since v3.1._
   */
  interface IERC1155ReceiverUpgradeable is IERC165Upgradeable {
      /**
       * @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.
       *
       * NOTE: 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.
       *
       * NOTE: 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-upgradeable/utils/introspection/ERC165Upgradeable.sol@v4.9.2
  
  // OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
  
  pragma solidity ^0.8.0;
  
  
  /**
   * @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 ERC165Upgradeable is Initializable, IERC165Upgradeable {
      function __ERC165_init() internal onlyInitializing {
      }
  
      function __ERC165_init_unchained() internal onlyInitializing {
      }
      /**
       * @dev See {IERC165-supportsInterface}.
       */
      function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
          return interfaceId == type(IERC165Upgradeable).interfaceId;
      }
  
      /**
       * @dev This empty reserved space is put in place to allow future versions to add new
       * variables without shifting down storage in the inheritance chain.
       * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
       */
      uint256[50] private __gap;
  }
  
  
  // File @openzeppelin/contracts-upgradeable/token/ERC1155/ERC1155Upgradeable.sol@v4.9.2
  
  // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC1155/ERC1155.sol)
  
  pragma solidity ^0.8.0;
  
  
  
  
  
  
  
  /**
   * @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 ERC1155Upgradeable is Initializable, ContextUpgradeable, ERC165Upgradeable, IERC1155Upgradeable, IERC1155MetadataURIUpgradeable {
      using AddressUpgradeable 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}.
       */
      function __ERC1155_init(string memory uri_) internal onlyInitializing {
          __ERC1155_init_unchained(uri_);
      }
  
      function __ERC1155_init_unchained(string memory uri_) internal onlyInitializing {
          _setURI(uri_);
      }
  
      /**
       * @dev See {IERC165-supportsInterface}.
       */
      function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165Upgradeable, IERC165Upgradeable) returns (bool) {
          return
              interfaceId == type(IERC1155Upgradeable).interfaceId ||
              interfaceId == type(IERC1155MetadataURIUpgradeable).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: address zero is not a valid owner");
          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 {
          _setApprovalForAll(_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(
              from == _msgSender() || isApprovedForAll(from, _msgSender()),
              "ERC1155: caller is not token owner or approved"
          );
          _safeTransferFrom(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(
              from == _msgSender() || isApprovedForAll(from, _msgSender()),
              "ERC1155: caller is not token owner or approved"
          );
          _safeBatchTransferFrom(from, to, ids, amounts, data);
      }
  
      /**
       * @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
       *
       * Emits a {TransferSingle} event.
       *
       * Requirements:
       *
       * - `to` cannot be the zero address.
       * - `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 memory data
      ) internal virtual {
          require(to != address(0), "ERC1155: transfer to the zero address");
  
          address operator = _msgSender();
          uint256[] memory ids = _asSingletonArray(id);
          uint256[] memory amounts = _asSingletonArray(amount);
  
          _beforeTokenTransfer(operator, from, to, ids, amounts, data);
  
          uint256 fromBalance = _balances[id][from];
          require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
          unchecked {
              _balances[id][from] = fromBalance - amount;
          }
          _balances[id][to] += amount;
  
          emit TransferSingle(operator, from, to, id, amount);
  
          _afterTokenTransfer(operator, from, to, ids, amounts, data);
  
          _doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data);
      }
  
      /**
       * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_safeTransferFrom}.
       *
       * Emits a {TransferBatch} event.
       *
       * Requirements:
       *
       * - 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[] memory ids,
          uint256[] memory amounts,
          bytes memory data
      ) internal virtual {
          require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
          require(to != address(0), "ERC1155: transfer to the zero address");
  
          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");
              unchecked {
                  _balances[id][from] = fromBalance - amount;
              }
              _balances[id][to] += amount;
          }
  
          emit TransferBatch(operator, from, to, ids, amounts);
  
          _afterTokenTransfer(operator, from, to, ids, amounts, data);
  
          _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 `to`.
       *
       * Emits a {TransferSingle} event.
       *
       * Requirements:
       *
       * - `to` cannot be the zero address.
       * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
       * acceptance magic value.
       */
      function _mint(address to, uint256 id, uint256 amount, bytes memory data) internal virtual {
          require(to != address(0), "ERC1155: mint to the zero address");
  
          address operator = _msgSender();
          uint256[] memory ids = _asSingletonArray(id);
          uint256[] memory amounts = _asSingletonArray(amount);
  
          _beforeTokenTransfer(operator, address(0), to, ids, amounts, data);
  
          _balances[id][to] += amount;
          emit TransferSingle(operator, address(0), to, id, amount);
  
          _afterTokenTransfer(operator, address(0), to, ids, amounts, data);
  
          _doSafeTransferAcceptanceCheck(operator, address(0), to, id, amount, data);
      }
  
      /**
       * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_mint}.
       *
       * 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 _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 (uint256 i = 0; i < ids.length; i++) {
              _balances[ids[i]][to] += amounts[i];
          }
  
          emit TransferBatch(operator, address(0), to, ids, amounts);
  
          _afterTokenTransfer(operator, address(0), to, ids, amounts, data);
  
          _doSafeBatchTransferAcceptanceCheck(operator, address(0), to, ids, amounts, data);
      }
  
      /**
       * @dev Destroys `amount` tokens of token type `id` from `from`
       *
       * Emits a {TransferSingle} event.
       *
       * Requirements:
       *
       * - `from` cannot be the zero address.
       * - `from` must have at least `amount` tokens of token type `id`.
       */
      function _burn(address from, uint256 id, uint256 amount) internal virtual {
          require(from != address(0), "ERC1155: burn from the zero address");
  
          address operator = _msgSender();
          uint256[] memory ids = _asSingletonArray(id);
          uint256[] memory amounts = _asSingletonArray(amount);
  
          _beforeTokenTransfer(operator, from, address(0), ids, amounts, "");
  
          uint256 fromBalance = _balances[id][from];
          require(fromBalance >= amount, "ERC1155: burn amount exceeds balance");
          unchecked {
              _balances[id][from] = fromBalance - amount;
          }
  
          emit TransferSingle(operator, from, address(0), id, amount);
  
          _afterTokenTransfer(operator, from, address(0), ids, amounts, "");
      }
  
      /**
       * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_burn}.
       *
       * Emits a {TransferBatch} event.
       *
       * Requirements:
       *
       * - `ids` and `amounts` must have the same length.
       */
      function _burnBatch(address from, uint256[] memory ids, uint256[] memory amounts) internal virtual {
          require(from != address(0), "ERC1155: burn from the zero address");
          require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
  
          address operator = _msgSender();
  
          _beforeTokenTransfer(operator, from, address(0), ids, amounts, "");
  
          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: burn amount exceeds balance");
              unchecked {
                  _balances[id][from] = fromBalance - amount;
              }
          }
  
          emit TransferBatch(operator, from, address(0), ids, amounts);
  
          _afterTokenTransfer(operator, from, address(0), ids, amounts, "");
      }
  
      /**
       * @dev Approve `operator` to operate on all of `owner` tokens
       *
       * Emits an {ApprovalForAll} event.
       */
      function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
          require(owner != operator, "ERC1155: setting approval status for self");
          _operatorApprovals[owner][operator] = approved;
          emit ApprovalForAll(owner, operator, approved);
      }
  
      /**
       * @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 `ids` and `amounts` 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 {}
  
      /**
       * @dev Hook that is called after 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 _afterTokenTransfer(
          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 IERC1155ReceiverUpgradeable(to).onERC1155Received(operator, from, id, amount, data) returns (bytes4 response) {
                  if (response != IERC1155ReceiverUpgradeable.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 IERC1155ReceiverUpgradeable(to).onERC1155BatchReceived(operator, from, ids, amounts, data) returns (
                  bytes4 response
              ) {
                  if (response != IERC1155ReceiverUpgradeable.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;
      }
  
      /**
       * @dev This empty reserved space is put in place to allow future versions to add new
       * variables without shifting down storage in the inheritance chain.
       * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
       */
      uint256[47] private __gap;
  }
  
  
  // File @openzeppelin/contracts-upgradeable/token/ERC1155/extensions/ERC1155PausableUpgradeable.sol@v4.9.2
  
  // OpenZeppelin Contracts (last updated v4.8.2) (token/ERC1155/extensions/ERC1155Pausable.sol)
  
  pragma solidity ^0.8.0;
  
  
  
  /**
   * @dev ERC1155 token with pausable token transfers, minting and burning.
   *
   * Useful for scenarios such as preventing trades until the end of an evaluation
   * period, or having an emergency switch for freezing all token transfers in the
   * event of a large bug.
   *
   * IMPORTANT: This contract does not include public pause and unpause functions. In
   * addition to inheriting this contract, you must define both functions, invoking the
   * {Pausable-_pause} and {Pausable-_unpause} internal functions, with appropriate
   * access control, e.g. using {AccessControl} or {Ownable}. Not doing so will
   * make the contract unpausable.
   *
   * _Available since v3.1._
   */
  abstract contract ERC1155PausableUpgradeable is Initializable, ERC1155Upgradeable, PausableUpgradeable {
      function __ERC1155Pausable_init() internal onlyInitializing {
          __Pausable_init_unchained();
      }
  
      function __ERC1155Pausable_init_unchained() internal onlyInitializing {
      }
      /**
       * @dev See {ERC1155-_beforeTokenTransfer}.
       *
       * Requirements:
       *
       * - the contract must not be paused.
       */
      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);
  
          require(!paused(), "ERC1155Pausable: token transfer while paused");
      }
  
      /**
       * @dev This empty reserved space is put in place to allow future versions to add new
       * variables without shifting down storage in the inheritance chain.
       * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
       */
      uint256[50] private __gap;
  }
  
  
  // File @openzeppelin/contracts/utils/introspection/IERC165.sol@v4.9.2
  
  // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
  
  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);
  }
  
  
  // File @openzeppelin/contracts/token/ERC1155/IERC1155.sol@v4.9.2
  
  // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC1155/IERC1155.sol)
  
  pragma solidity ^0.8.0;
  
  /**
   * @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 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/extensions/IERC1155MetadataURI.sol@v4.9.2
  
  // OpenZeppelin Contracts v4.4.1 (token/ERC1155/extensions/IERC1155MetadataURI.sol)
  
  pragma solidity ^0.8.0;
  
  /**
   * @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/ERC1155/IERC1155Receiver.sol@v4.9.2
  
  // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/IERC1155Receiver.sol)
  
  pragma solidity ^0.8.0;
  
  /**
   * @dev _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.
       *
       * NOTE: 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.
       *
       * NOTE: 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/utils/Address.sol@v4.9.2
  
  // OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
  
  pragma solidity ^0.8.1;
  
  /**
   * @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
       *
       * Furthermore, `isContract` will also return true if the target contract within
       * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
       * which only has an effect at the end of a transaction.
       * ====
       *
       * [IMPORTANT]
       * ====
       * You shouldn't rely on `isContract` to protect against flash loan attacks!
       *
       * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
       * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
       * constructor.
       * ====
       */
      function isContract(address account) internal view returns (bool) {
          // This method relies on extcodesize/address.code.length, which returns 0
          // for contracts in construction, since the code is only stored at the end
          // of the constructor execution.
  
          return account.code.length > 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://consensys.net/diligence/blog/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.8.0/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");
  
          (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 functionCallWithValue(target, data, 0, "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");
          (bool success, bytes memory returndata) = target.call{value: value}(data);
          return verifyCallResultFromTarget(target, 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) {
          (bool success, bytes memory returndata) = target.staticcall(data);
          return verifyCallResultFromTarget(target, 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) {
          (bool success, bytes memory returndata) = target.delegatecall(data);
          return verifyCallResultFromTarget(target, success, returndata, errorMessage);
      }
  
      /**
       * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
       * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
       *
       * _Available since v4.8._
       */
      function verifyCallResultFromTarget(
          address target,
          bool success,
          bytes memory returndata,
          string memory errorMessage
      ) internal view returns (bytes memory) {
          if (success) {
              if (returndata.length == 0) {
                  // only check isContract if the call was successful and the return data is empty
                  // otherwise we already know that it was a contract
                  require(isContract(target), "Address: call to non-contract");
              }
              return returndata;
          } else {
              _revert(returndata, errorMessage);
          }
      }
  
      /**
       * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
       * revert reason or using the provided one.
       *
       * _Available since v4.3._
       */
      function verifyCallResult(
          bool success,
          bytes memory returndata,
          string memory errorMessage
      ) internal pure returns (bytes memory) {
          if (success) {
              return returndata;
          } else {
              _revert(returndata, errorMessage);
          }
      }
  
      function _revert(bytes memory returndata, string memory errorMessage) private pure {
          // 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
              /// @solidity memory-safe-assembly
              assembly {
                  let returndata_size := mload(returndata)
                  revert(add(32, returndata), returndata_size)
              }
          } else {
              revert(errorMessage);
          }
      }
  }
  
  
  // File @openzeppelin/contracts/utils/Context.sol@v4.9.2
  
  // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
  
  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) {
          return msg.data;
      }
  }
  
  
  // File @openzeppelin/contracts/utils/introspection/ERC165.sol@v4.9.2
  
  // OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
  
  pragma solidity ^0.8.0;
  
  /**
   * @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/token/ERC1155/ERC1155.sol@v4.9.2
  
  // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC1155/ERC1155.sol)
  
  pragma solidity ^0.8.0;
  
  
  
  
  
  
  /**
   * @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: address zero is not a valid owner");
          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 {
          _setApprovalForAll(_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(
              from == _msgSender() || isApprovedForAll(from, _msgSender()),
              "ERC1155: caller is not token owner or approved"
          );
          _safeTransferFrom(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(
              from == _msgSender() || isApprovedForAll(from, _msgSender()),
              "ERC1155: caller is not token owner or approved"
          );
          _safeBatchTransferFrom(from, to, ids, amounts, data);
      }
  
      /**
       * @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
       *
       * Emits a {TransferSingle} event.
       *
       * Requirements:
       *
       * - `to` cannot be the zero address.
       * - `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 memory data
      ) internal virtual {
          require(to != address(0), "ERC1155: transfer to the zero address");
  
          address operator = _msgSender();
          uint256[] memory ids = _asSingletonArray(id);
          uint256[] memory amounts = _asSingletonArray(amount);
  
          _beforeTokenTransfer(operator, from, to, ids, amounts, data);
  
          uint256 fromBalance = _balances[id][from];
          require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
          unchecked {
              _balances[id][from] = fromBalance - amount;
          }
          _balances[id][to] += amount;
  
          emit TransferSingle(operator, from, to, id, amount);
  
          _afterTokenTransfer(operator, from, to, ids, amounts, data);
  
          _doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data);
      }
  
      /**
       * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_safeTransferFrom}.
       *
       * Emits a {TransferBatch} event.
       *
       * Requirements:
       *
       * - 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[] memory ids,
          uint256[] memory amounts,
          bytes memory data
      ) internal virtual {
          require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
          require(to != address(0), "ERC1155: transfer to the zero address");
  
          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");
              unchecked {
                  _balances[id][from] = fromBalance - amount;
              }
              _balances[id][to] += amount;
          }
  
          emit TransferBatch(operator, from, to, ids, amounts);
  
          _afterTokenTransfer(operator, from, to, ids, amounts, data);
  
          _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 `to`.
       *
       * Emits a {TransferSingle} event.
       *
       * Requirements:
       *
       * - `to` cannot be the zero address.
       * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
       * acceptance magic value.
       */
      function _mint(address to, uint256 id, uint256 amount, bytes memory data) internal virtual {
          require(to != address(0), "ERC1155: mint to the zero address");
  
          address operator = _msgSender();
          uint256[] memory ids = _asSingletonArray(id);
          uint256[] memory amounts = _asSingletonArray(amount);
  
          _beforeTokenTransfer(operator, address(0), to, ids, amounts, data);
  
          _balances[id][to] += amount;
          emit TransferSingle(operator, address(0), to, id, amount);
  
          _afterTokenTransfer(operator, address(0), to, ids, amounts, data);
  
          _doSafeTransferAcceptanceCheck(operator, address(0), to, id, amount, data);
      }
  
      /**
       * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_mint}.
       *
       * 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 _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 (uint256 i = 0; i < ids.length; i++) {
              _balances[ids[i]][to] += amounts[i];
          }
  
          emit TransferBatch(operator, address(0), to, ids, amounts);
  
          _afterTokenTransfer(operator, address(0), to, ids, amounts, data);
  
          _doSafeBatchTransferAcceptanceCheck(operator, address(0), to, ids, amounts, data);
      }
  
      /**
       * @dev Destroys `amount` tokens of token type `id` from `from`
       *
       * Emits a {TransferSingle} event.
       *
       * Requirements:
       *
       * - `from` cannot be the zero address.
       * - `from` must have at least `amount` tokens of token type `id`.
       */
      function _burn(address from, uint256 id, uint256 amount) internal virtual {
          require(from != address(0), "ERC1155: burn from the zero address");
  
          address operator = _msgSender();
          uint256[] memory ids = _asSingletonArray(id);
          uint256[] memory amounts = _asSingletonArray(amount);
  
          _beforeTokenTransfer(operator, from, address(0), ids, amounts, "");
  
          uint256 fromBalance = _balances[id][from];
          require(fromBalance >= amount, "ERC1155: burn amount exceeds balance");
          unchecked {
              _balances[id][from] = fromBalance - amount;
          }
  
          emit TransferSingle(operator, from, address(0), id, amount);
  
          _afterTokenTransfer(operator, from, address(0), ids, amounts, "");
      }
  
      /**
       * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_burn}.
       *
       * Emits a {TransferBatch} event.
       *
       * Requirements:
       *
       * - `ids` and `amounts` must have the same length.
       */
      function _burnBatch(address from, uint256[] memory ids, uint256[] memory amounts) internal virtual {
          require(from != address(0), "ERC1155: burn from the zero address");
          require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
  
          address operator = _msgSender();
  
          _beforeTokenTransfer(operator, from, address(0), ids, amounts, "");
  
          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: burn amount exceeds balance");
              unchecked {
                  _balances[id][from] = fromBalance - amount;
              }
          }
  
          emit TransferBatch(operator, from, address(0), ids, amounts);
  
          _afterTokenTransfer(operator, from, address(0), ids, amounts, "");
      }
  
      /**
       * @dev Approve `operator` to operate on all of `owner` tokens
       *
       * Emits an {ApprovalForAll} event.
       */
      function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
          require(owner != operator, "ERC1155: setting approval status for self");
          _operatorApprovals[owner][operator] = approved;
          emit ApprovalForAll(owner, operator, approved);
      }
  
      /**
       * @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 `ids` and `amounts` 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 {}
  
      /**
       * @dev Hook that is called after 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 _afterTokenTransfer(
          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.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.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/ERC20/IERC20.sol@v4.9.2
  
  // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
  
  pragma solidity ^0.8.0;
  
  /**
   * @dev Interface of the ERC20 standard as defined in the EIP.
   */
  interface IERC20 {
      /**
       * @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);
  
      /**
       * @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 `to`.
       *
       * Returns a boolean value indicating whether the operation succeeded.
       *
       * Emits a {Transfer} event.
       */
      function transfer(address to, 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 `from` to `to` 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 from, address to, uint256 amount) external returns (bool);
  }
  
  
  // File @openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol@v4.9.2
  
  // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
  
  pragma solidity ^0.8.0;
  
  /**
   * @dev Interface for the optional metadata functions from the ERC20 standard.
   *
   * _Available since v4.1._
   */
  interface IERC20Metadata is IERC20 {
      /**
       * @dev Returns the name of the token.
       */
      function name() external view returns (string memory);
  
      /**
       * @dev Returns the symbol of the token.
       */
      function symbol() external view returns (string memory);
  
      /**
       * @dev Returns the decimals places of the token.
       */
      function decimals() external view returns (uint8);
  }
  
  
  // File @openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol@v4.9.2
  
  // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol)
  
  pragma solidity ^0.8.0;
  
  /**
   * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
   * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
   *
   * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
   * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
   * need to send a transaction, and thus is not required to hold Ether at all.
   */
  interface IERC20Permit {
      /**
       * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
       * given ``owner``'s signed approval.
       *
       * IMPORTANT: The same issues {IERC20-approve} has related to transaction
       * ordering also apply here.
       *
       * Emits an {Approval} event.
       *
       * Requirements:
       *
       * - `spender` cannot be the zero address.
       * - `deadline` must be a timestamp in the future.
       * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
       * over the EIP712-formatted function arguments.
       * - the signature must use ``owner``'s current nonce (see {nonces}).
       *
       * For more information on the signature format, see the
       * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
       * section].
       */
      function permit(
          address owner,
          address spender,
          uint256 value,
          uint256 deadline,
          uint8 v,
          bytes32 r,
          bytes32 s
      ) external;
  
      /**
       * @dev Returns the current nonce for `owner`. This value must be
       * included whenever a signature is generated for {permit}.
       *
       * Every successful call to {permit} increases ``owner``'s nonce by one. This
       * prevents a signature from being used multiple times.
       */
      function nonces(address owner) external view returns (uint256);
  
      /**
       * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
       */
      // solhint-disable-next-line func-name-mixedcase
      function DOMAIN_SEPARATOR() external view returns (bytes32);
  }
  
  
  // File @openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol@v4.9.2
  
  // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/utils/SafeERC20.sol)
  
  pragma solidity ^0.8.0;
  
  
  
  /**
   * @title SafeERC20
   * @dev Wrappers around ERC20 operations that throw on failure (when the token
   * contract returns false). Tokens that return no value (and instead revert or
   * throw on failure) are also supported, non-reverting calls are assumed to be
   * successful.
   * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
   * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
   */
  library SafeERC20 {
      using Address for address;
  
      /**
       * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
       * non-reverting calls are assumed to be successful.
       */
      function safeTransfer(IERC20 token, address to, uint256 value) internal {
          _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
      }
  
      /**
       * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
       * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
       */
      function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
          _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
      }
  
      /**
       * @dev Deprecated. This function has issues similar to the ones found in
       * {IERC20-approve}, and its usage is discouraged.
       *
       * Whenever possible, use {safeIncreaseAllowance} and
       * {safeDecreaseAllowance} instead.
       */
      function safeApprove(IERC20 token, address spender, uint256 value) internal {
          // safeApprove should only be called when setting an initial allowance,
          // or when resetting it to zero. To increase and decrease it, use
          // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
          require(
              (value == 0) || (token.allowance(address(this), spender) == 0),
              "SafeERC20: approve from non-zero to non-zero allowance"
          );
          _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
      }
  
      /**
       * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
       * non-reverting calls are assumed to be successful.
       */
      function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
          uint256 oldAllowance = token.allowance(address(this), spender);
          _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
      }
  
      /**
       * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
       * non-reverting calls are assumed to be successful.
       */
      function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
          unchecked {
              uint256 oldAllowance = token.allowance(address(this), spender);
              require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
              _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
          }
      }
  
      /**
       * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
       * non-reverting calls are assumed to be successful. Compatible with tokens that require the approval to be set to
       * 0 before setting it to a non-zero value.
       */
      function forceApprove(IERC20 token, address spender, uint256 value) internal {
          bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);
  
          if (!_callOptionalReturnBool(token, approvalCall)) {
              _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
              _callOptionalReturn(token, approvalCall);
          }
      }
  
      /**
       * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
       * Revert on invalid signature.
       */
      function safePermit(
          IERC20Permit token,
          address owner,
          address spender,
          uint256 value,
          uint256 deadline,
          uint8 v,
          bytes32 r,
          bytes32 s
      ) internal {
          uint256 nonceBefore = token.nonces(owner);
          token.permit(owner, spender, value, deadline, v, r, s);
          uint256 nonceAfter = token.nonces(owner);
          require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
      }
  
      /**
       * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
       * on the return value: the return value is optional (but if data is returned, it must not be false).
       * @param token The token targeted by the call.
       * @param data The call data (encoded using abi.encode or one of its variants).
       */
      function _callOptionalReturn(IERC20 token, bytes memory data) private {
          // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
          // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
          // the target address contains contract code and also asserts for success in the low-level call.
  
          bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
          require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
      }
  
      /**
       * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
       * on the return value: the return value is optional (but if data is returned, it must not be false).
       * @param token The token targeted by the call.
       * @param data The call data (encoded using abi.encode or one of its variants).
       *
       * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
       */
      function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
          // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
          // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
          // and not revert is the subcall reverts.
  
          (bool success, bytes memory returndata) = address(token).call(data);
          return
              success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
      }
  }
  
  
  // File @openzeppelin/contracts/utils/math/Math.sol@v4.9.2
  
  // OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)
  
  pragma solidity ^0.8.0;
  
  /**
   * @dev Standard math utilities missing in the Solidity language.
   */
  library Math {
      enum Rounding {
          Down, // Toward negative infinity
          Up, // Toward infinity
          Zero // Toward zero
      }
  
      /**
       * @dev Returns the largest of two numbers.
       */
      function max(uint256 a, uint256 b) internal pure returns (uint256) {
          return a > b ? a : b;
      }
  
      /**
       * @dev Returns the smallest of two numbers.
       */
      function min(uint256 a, uint256 b) internal pure returns (uint256) {
          return a < b ? a : b;
      }
  
      /**
       * @dev Returns the average of two numbers. The result is rounded towards
       * zero.
       */
      function average(uint256 a, uint256 b) internal pure returns (uint256) {
          // (a + b) / 2 can overflow.
          return (a & b) + (a ^ b) / 2;
      }
  
      /**
       * @dev Returns the ceiling of the division of two numbers.
       *
       * This differs from standard division with `/` in that it rounds up instead
       * of rounding down.
       */
      function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
          // (a + b - 1) / b can overflow on addition, so we distribute.
          return a == 0 ? 0 : (a - 1) / b + 1;
      }
  
      /**
       * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
       * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
       * with further edits by Uniswap Labs also under MIT license.
       */
      function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
          unchecked {
              // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
              // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
              // variables such that product = prod1 * 2^256 + prod0.
              uint256 prod0; // Least significant 256 bits of the product
              uint256 prod1; // Most significant 256 bits of the product
              assembly {
                  let mm := mulmod(x, y, not(0))
                  prod0 := mul(x, y)
                  prod1 := sub(sub(mm, prod0), lt(mm, prod0))
              }
  
              // Handle non-overflow cases, 256 by 256 division.
              if (prod1 == 0) {
                  // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                  // The surrounding unchecked block does not change this fact.
                  // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                  return prod0 / denominator;
              }
  
              // Make sure the result is less than 2^256. Also prevents denominator == 0.
              require(denominator > prod1, "Math: mulDiv overflow");
  
              ///////////////////////////////////////////////
              // 512 by 256 division.
              ///////////////////////////////////////////////
  
              // Make division exact by subtracting the remainder from [prod1 prod0].
              uint256 remainder;
              assembly {
                  // Compute remainder using mulmod.
                  remainder := mulmod(x, y, denominator)
  
                  // Subtract 256 bit number from 512 bit number.
                  prod1 := sub(prod1, gt(remainder, prod0))
                  prod0 := sub(prod0, remainder)
              }
  
              // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
              // See https://cs.stackexchange.com/q/138556/92363.
  
              // Does not overflow because the denominator cannot be zero at this stage in the function.
              uint256 twos = denominator & (~denominator + 1);
              assembly {
                  // Divide denominator by twos.
                  denominator := div(denominator, twos)
  
                  // Divide [prod1 prod0] by twos.
                  prod0 := div(prod0, twos)
  
                  // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                  twos := add(div(sub(0, twos), twos), 1)
              }
  
              // Shift in bits from prod1 into prod0.
              prod0 |= prod1 * twos;
  
              // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
              // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
              // four bits. That is, denominator * inv = 1 mod 2^4.
              uint256 inverse = (3 * denominator) ^ 2;
  
              // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
              // in modular arithmetic, doubling the correct bits in each step.
              inverse *= 2 - denominator * inverse; // inverse mod 2^8
              inverse *= 2 - denominator * inverse; // inverse mod 2^16
              inverse *= 2 - denominator * inverse; // inverse mod 2^32
              inverse *= 2 - denominator * inverse; // inverse mod 2^64
              inverse *= 2 - denominator * inverse; // inverse mod 2^128
              inverse *= 2 - denominator * inverse; // inverse mod 2^256
  
              // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
              // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
              // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
              // is no longer required.
              result = prod0 * inverse;
              return result;
          }
      }
  
      /**
       * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
       */
      function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
          uint256 result = mulDiv(x, y, denominator);
          if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
              result += 1;
          }
          return result;
      }
  
      /**
       * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
       *
       * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
       */
      function sqrt(uint256 a) internal pure returns (uint256) {
          if (a == 0) {
              return 0;
          }
  
          // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
          //
          // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
          // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
          //
          // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
          // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
          // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
          //
          // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
          uint256 result = 1 << (log2(a) >> 1);
  
          // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
          // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
          // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
          // into the expected uint128 result.
          unchecked {
              result = (result + a / result) >> 1;
              result = (result + a / result) >> 1;
              result = (result + a / result) >> 1;
              result = (result + a / result) >> 1;
              result = (result + a / result) >> 1;
              result = (result + a / result) >> 1;
              result = (result + a / result) >> 1;
              return min(result, a / result);
          }
      }
  
      /**
       * @notice Calculates sqrt(a), following the selected rounding direction.
       */
      function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
          unchecked {
              uint256 result = sqrt(a);
              return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
          }
      }
  
      /**
       * @dev Return the log in base 2, rounded down, of a positive value.
       * Returns 0 if given 0.
       */
      function log2(uint256 value) internal pure returns (uint256) {
          uint256 result = 0;
          unchecked {
              if (value >> 128 > 0) {
                  value >>= 128;
                  result += 128;
              }
              if (value >> 64 > 0) {
                  value >>= 64;
                  result += 64;
              }
              if (value >> 32 > 0) {
                  value >>= 32;
                  result += 32;
              }
              if (value >> 16 > 0) {
                  value >>= 16;
                  result += 16;
              }
              if (value >> 8 > 0) {
                  value >>= 8;
                  result += 8;
              }
              if (value >> 4 > 0) {
                  value >>= 4;
                  result += 4;
              }
              if (value >> 2 > 0) {
                  value >>= 2;
                  result += 2;
              }
              if (value >> 1 > 0) {
                  result += 1;
              }
          }
          return result;
      }
  
      /**
       * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
       * Returns 0 if given 0.
       */
      function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
          unchecked {
              uint256 result = log2(value);
              return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
          }
      }
  
      /**
       * @dev Return the log in base 10, rounded down, of a positive value.
       * Returns 0 if given 0.
       */
      function log10(uint256 value) internal pure returns (uint256) {
          uint256 result = 0;
          unchecked {
              if (value >= 10 ** 64) {
                  value /= 10 ** 64;
                  result += 64;
              }
              if (value >= 10 ** 32) {
                  value /= 10 ** 32;
                  result += 32;
              }
              if (value >= 10 ** 16) {
                  value /= 10 ** 16;
                  result += 16;
              }
              if (value >= 10 ** 8) {
                  value /= 10 ** 8;
                  result += 8;
              }
              if (value >= 10 ** 4) {
                  value /= 10 ** 4;
                  result += 4;
              }
              if (value >= 10 ** 2) {
                  value /= 10 ** 2;
                  result += 2;
              }
              if (value >= 10 ** 1) {
                  result += 1;
              }
          }
          return result;
      }
  
      /**
       * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
       * Returns 0 if given 0.
       */
      function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
          unchecked {
              uint256 result = log10(value);
              return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
          }
      }
  
      /**
       * @dev Return the log in base 256, rounded down, of a positive value.
       * Returns 0 if given 0.
       *
       * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
       */
      function log256(uint256 value) internal pure returns (uint256) {
          uint256 result = 0;
          unchecked {
              if (value >> 128 > 0) {
                  value >>= 128;
                  result += 16;
              }
              if (value >> 64 > 0) {
                  value >>= 64;
                  result += 8;
              }
              if (value >> 32 > 0) {
                  value >>= 32;
                  result += 4;
              }
              if (value >> 16 > 0) {
                  value >>= 16;
                  result += 2;
              }
              if (value >> 8 > 0) {
                  result += 1;
              }
          }
          return result;
      }
  
      /**
       * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
       * Returns 0 if given 0.
       */
      function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
          unchecked {
              uint256 result = log256(value);
              return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
          }
      }
  }
  
  
  // File @openzeppelin/contracts/utils/math/SignedMath.sol@v4.9.2
  
  // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)
  
  pragma solidity ^0.8.0;
  
  /**
   * @dev Standard signed math utilities missing in the Solidity language.
   */
  library SignedMath {
      /**
       * @dev Returns the largest of two signed numbers.
       */
      function max(int256 a, int256 b) internal pure returns (int256) {
          return a > b ? a : b;
      }
  
      /**
       * @dev Returns the smallest of two signed numbers.
       */
      function min(int256 a, int256 b) internal pure returns (int256) {
          return a < b ? a : b;
      }
  
      /**
       * @dev Returns the average of two signed numbers without overflow.
       * The result is rounded towards zero.
       */
      function average(int256 a, int256 b) internal pure returns (int256) {
          // Formula from the book "Hacker's Delight"
          int256 x = (a & b) + ((a ^ b) >> 1);
          return x + (int256(uint256(x) >> 255) & (a ^ b));
      }
  
      /**
       * @dev Returns the absolute unsigned value of a signed value.
       */
      function abs(int256 n) internal pure returns (uint256) {
          unchecked {
              // must be unchecked in order to support `n = type(int256).min`
              return uint256(n >= 0 ? n : -n);
          }
      }
  }
  
  
  // File @openzeppelin/contracts/utils/Strings.sol@v4.9.2
  
  // OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)
  
  pragma solidity ^0.8.0;
  
  
  /**
   * @dev String operations.
   */
  library Strings {
      bytes16 private constant _SYMBOLS = "0123456789abcdef";
      uint8 private constant _ADDRESS_LENGTH = 20;
  
      /**
       * @dev Converts a `uint256` to its ASCII `string` decimal representation.
       */
      function toString(uint256 value) internal pure returns (string memory) {
          unchecked {
              uint256 length = Math.log10(value) + 1;
              string memory buffer = new string(length);
              uint256 ptr;
              /// @solidity memory-safe-assembly
              assembly {
                  ptr := add(buffer, add(32, length))
              }
              while (true) {
                  ptr--;
                  /// @solidity memory-safe-assembly
                  assembly {
                      mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                  }
                  value /= 10;
                  if (value == 0) break;
              }
              return buffer;
          }
      }
  
      /**
       * @dev Converts a `int256` to its ASCII `string` decimal representation.
       */
      function toString(int256 value) internal pure returns (string memory) {
          return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value))));
      }
  
      /**
       * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
       */
      function toHexString(uint256 value) internal pure returns (string memory) {
          unchecked {
              return toHexString(value, Math.log256(value) + 1);
          }
      }
  
      /**
       * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
       */
      function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
          bytes memory buffer = new bytes(2 * length + 2);
          buffer[0] = "0";
          buffer[1] = "x";
          for (uint256 i = 2 * length + 1; i > 1; --i) {
              buffer[i] = _SYMBOLS[value & 0xf];
              value >>= 4;
          }
          require(value == 0, "Strings: hex length insufficient");
          return string(buffer);
      }
  
      /**
       * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
       */
      function toHexString(address addr) internal pure returns (string memory) {
          return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
      }
  
      /**
       * @dev Returns true if the two strings are equal.
       */
      function equal(string memory a, string memory b) internal pure returns (bool) {
          return keccak256(bytes(a)) == keccak256(bytes(b));
      }
  }
  
  
  // File contracts/interfaces/IOracle.sol
  
  
  pragma solidity 0.8.17;
  
  interface IOracle {
      function viewPriceInUSD() external view returns (uint256);
  }
  
  
  // File contracts/interfaces/IPriceOracleAggregator.sol
  
  
  pragma solidity 0.8.17;
  
  interface IPriceOracleAggregator {
      event UpdateOracle(address token, IOracle oracle);
  
      function updateOracleForAsset(address _asset, IOracle _oracle) external;
  
      function viewPriceInUSD(address _token) external view returns (uint256);
  }
  
  
  // File contracts/interfaces/IUniswapV2Router01.sol
  
  
  pragma solidity 0.8.17;
  
  interface IUniswapV2Router01 {
      function factory() external pure returns (address);
  
      function WETH() external pure returns (address);
  
      function addLiquidity(
          address tokenA,
          address tokenB,
          uint amountADesired,
          uint amountBDesired,
          uint amountAMin,
          uint amountBMin,
          address to,
          uint deadline
      ) external returns (uint amountA, uint amountB, uint liquidity);
  
      function addLiquidityETH(
          address token,
          uint amountTokenDesired,
          uint amountTokenMin,
          uint amountETHMin,
          address to,
          uint deadline
      )
          external
          payable
          returns (uint amountToken, uint amountETH, uint liquidity);
  
      function removeLiquidity(
          address tokenA,
          address tokenB,
          uint liquidity,
          uint amountAMin,
          uint amountBMin,
          address to,
          uint deadline
      ) external returns (uint amountA, uint amountB);
  
      function removeLiquidityETH(
          address token,
          uint liquidity,
          uint amountTokenMin,
          uint amountETHMin,
          address to,
          uint deadline
      ) external returns (uint amountToken, uint amountETH);
  
      function removeLiquidityWithPermit(
          address tokenA,
          address tokenB,
          uint liquidity,
          uint amountAMin,
          uint amountBMin,
          address to,
          uint deadline,
          bool approveMax,
          uint8 v,
          bytes32 r,
          bytes32 s
      ) external returns (uint amountA, uint amountB);
  
      function removeLiquidityETHWithPermit(
          address token,
          uint liquidity,
          uint amountTokenMin,
          uint amountETHMin,
          address to,
          uint deadline,
          bool approveMax,
          uint8 v,
          bytes32 r,
          bytes32 s
      ) external returns (uint amountToken, uint amountETH);
  
      function swapExactTokensForTokens(
          uint amountIn,
          uint amountOutMin,
          address[] calldata path,
          address to,
          uint deadline
      ) external returns (uint[] memory amounts);
  
      function swapTokensForExactTokens(
          uint amountOut,
          uint amountInMax,
          address[] calldata path,
          address to,
          uint deadline
      ) external returns (uint[] memory amounts);
  
      function swapExactETHForTokens(
          uint amountOutMin,
          address[] calldata path,
          address to,
          uint deadline
      ) external payable returns (uint[] memory amounts);
  
      function swapTokensForExactETH(
          uint amountOut,
          uint amountInMax,
          address[] calldata path,
          address to,
          uint deadline
      ) external returns (uint[] memory amounts);
  
      function swapExactTokensForETH(
          uint amountIn,
          uint amountOutMin,
          address[] calldata path,
          address to,
          uint deadline
      ) external returns (uint[] memory amounts);
  
      function swapETHForExactTokens(
          uint amountOut,
          address[] calldata path,
          address to,
          uint deadline
      ) external payable returns (uint[] memory amounts);
  
      function quote(
          uint amountA,
          uint reserveA,
          uint reserveB
      ) external pure returns (uint amountB);
  
      function getAmountOut(
          uint amountIn,
          uint reserveIn,
          uint reserveOut
      ) external pure returns (uint amountOut);
  
      function getAmountIn(
          uint amountOut,
          uint reserveIn,
          uint reserveOut
      ) external pure returns (uint amountIn);
  
      function getAmountsOut(
          uint amountIn,
          address[] calldata path
      ) external view returns (uint[] memory amounts);
  
      function getAmountsIn(
          uint amountOut,
          address[] calldata path
      ) external view returns (uint[] memory amounts);
  }
  
  
  // File contracts/interfaces/IUniswapV2Router02.sol
  
  
  pragma solidity 0.8.17;
  
  interface IUniswapV2Router02 is IUniswapV2Router01 {
      function removeLiquidityETHSupportingFeeOnTransferTokens(
          address token,
          uint liquidity,
          uint amountTokenMin,
          uint amountETHMin,
          address to,
          uint deadline
      ) external returns (uint amountETH);
  
      function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
          address token,
          uint liquidity,
          uint amountTokenMin,
          uint amountETHMin,
          address to,
          uint deadline,
          bool approveMax,
          uint8 v,
          bytes32 r,
          bytes32 s
      ) external returns (uint amountETH);
  
      function swapExactTokensForTokensSupportingFeeOnTransferTokens(
          uint amountIn,
          uint amountOutMin,
          address[] calldata path,
          address to,
          uint deadline
      ) external;
  
      function swapExactETHForTokensSupportingFeeOnTransferTokens(
          uint amountOutMin,
          address[] calldata path,
          address to,
          uint deadline
      ) external payable;
  
      function swapExactTokensForETHSupportingFeeOnTransferTokens(
          uint amountIn,
          uint amountOutMin,
          address[] calldata path,
          address to,
          uint deadline
      ) external;
  }
  
  
  // File @openzeppelin/contracts/utils/structs/EnumerableSet.sol@v4.9.2
  
  // OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/EnumerableSet.sol)
  // This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
  
  pragma solidity ^0.8.0;
  
  /**
   * @dev Library for managing
   * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
   * types.
   *
   * Sets have the following properties:
   *
   * - Elements are added, removed, and checked for existence in constant time
   * (O(1)).
   * - Elements are enumerated in O(n). No guarantees are made on the ordering.
   *
   * ```solidity
   * contract Example {
   *     // Add the library methods
   *     using EnumerableSet for EnumerableSet.AddressSet;
   *
   *     // Declare a set state variable
   *     EnumerableSet.AddressSet private mySet;
   * }
   * ```
   *
   * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
   * and `uint256` (`UintSet`) are supported.
   *
   * [WARNING]
   * ====
   * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
   * unusable.
   * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
   *
   * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
   * array of EnumerableSet.
   * ====
   */
  library EnumerableSet {
      // To implement this library for multiple types with as little code
      // repetition as possible, we write it in terms of a generic Set type with
      // bytes32 values.
      // The Set implementation uses private functions, and user-facing
      // implementations (such as AddressSet) are just wrappers around the
      // underlying Set.
      // This means that we can only create new EnumerableSets for types that fit
      // in bytes32.
  
      struct Set {
          // Storage of set values
          bytes32[] _values;
          // Position of the value in the `values` array, plus 1 because index 0
          // means a value is not in the set.
          mapping(bytes32 => uint256) _indexes;
      }
  
      /**
       * @dev Add a value to a set. O(1).
       *
       * Returns true if the value was added to the set, that is if it was not
       * already present.
       */
      function _add(Set storage set, bytes32 value) private returns (bool) {
          if (!_contains(set, value)) {
              set._values.push(value);
              // The value is stored at length-1, but we add 1 to all indexes
              // and use 0 as a sentinel value
              set._indexes[value] = set._values.length;
              return true;
          } else {
              return false;
          }
      }
  
      /**
       * @dev Removes a value from a set. O(1).
       *
       * Returns true if the value was removed from the set, that is if it was
       * present.
       */
      function _remove(Set storage set, bytes32 value) private returns (bool) {
          // We read and store the value's index to prevent multiple reads from the same storage slot
          uint256 valueIndex = set._indexes[value];
  
          if (valueIndex != 0) {
              // Equivalent to contains(set, value)
              // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
              // the array, and then remove the last element (sometimes called as 'swap and pop').
              // This modifies the order of the array, as noted in {at}.
  
              uint256 toDeleteIndex = valueIndex - 1;
              uint256 lastIndex = set._values.length - 1;
  
              if (lastIndex != toDeleteIndex) {
                  bytes32 lastValue = set._values[lastIndex];
  
                  // Move the last value to the index where the value to delete is
                  set._values[toDeleteIndex] = lastValue;
                  // Update the index for the moved value
                  set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
              }
  
              // Delete the slot where the moved value was stored
              set._values.pop();
  
              // Delete the index for the deleted slot
              delete set._indexes[value];
  
              return true;
          } else {
              return false;
          }
      }
  
      /**
       * @dev Returns true if the value is in the set. O(1).
       */
      function _contains(Set storage set, bytes32 value) private view returns (bool) {
          return set._indexes[value] != 0;
      }
  
      /**
       * @dev Returns the number of values on the set. O(1).
       */
      function _length(Set storage set) private view returns (uint256) {
          return set._values.length;
      }
  
      /**
       * @dev Returns the value stored at position `index` in the set. O(1).
       *
       * Note that there are no guarantees on the ordering of values inside the
       * array, and it may change when more values are added or removed.
       *
       * Requirements:
       *
       * - `index` must be strictly less than {length}.
       */
      function _at(Set storage set, uint256 index) private view returns (bytes32) {
          return set._values[index];
      }
  
      /**
       * @dev Return the entire set in an array
       *
       * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
       * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
       * this function has an unbounded cost, and using it as part of a state-changing function may render the function
       * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
       */
      function _values(Set storage set) private view returns (bytes32[] memory) {
          return set._values;
      }
  
      // Bytes32Set
  
      struct Bytes32Set {
          Set _inner;
      }
  
      /**
       * @dev Add a value to a set. O(1).
       *
       * Returns true if the value was added to the set, that is if it was not
       * already present.
       */
      function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
          return _add(set._inner, value);
      }
  
      /**
       * @dev Removes a value from a set. O(1).
       *
       * Returns true if the value was removed from the set, that is if it was
       * present.
       */
      function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
          return _remove(set._inner, value);
      }
  
      /**
       * @dev Returns true if the value is in the set. O(1).
       */
      function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
          return _contains(set._inner, value);
      }
  
      /**
       * @dev Returns the number of values in the set. O(1).
       */
      function length(Bytes32Set storage set) internal view returns (uint256) {
          return _length(set._inner);
      }
  
      /**
       * @dev Returns the value stored at position `index` in the set. O(1).
       *
       * Note that there are no guarantees on the ordering of values inside the
       * array, and it may change when more values are added or removed.
       *
       * Requirements:
       *
       * - `index` must be strictly less than {length}.
       */
      function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
          return _at(set._inner, index);
      }
  
      /**
       * @dev Return the entire set in an array
       *
       * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
       * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
       * this function has an unbounded cost, and using it as part of a state-changing function may render the function
       * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
       */
      function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
          bytes32[] memory store = _values(set._inner);
          bytes32[] memory result;
  
          /// @solidity memory-safe-assembly
          assembly {
              result := store
          }
  
          return result;
      }
  
      // AddressSet
  
      struct AddressSet {
          Set _inner;
      }
  
      /**
       * @dev Add a value to a set. O(1).
       *
       * Returns true if the value was added to the set, that is if it was not
       * already present.
       */
      function add(AddressSet storage set, address value) internal returns (bool) {
          return _add(set._inner, bytes32(uint256(uint160(value))));
      }
  
      /**
       * @dev Removes a value from a set. O(1).
       *
       * Returns true if the value was removed from the set, that is if it was
       * present.
       */
      function remove(AddressSet storage set, address value) internal returns (bool) {
          return _remove(set._inner, bytes32(uint256(uint160(value))));
      }
  
      /**
       * @dev Returns true if the value is in the set. O(1).
       */
      function contains(AddressSet storage set, address value) internal view returns (bool) {
          return _contains(set._inner, bytes32(uint256(uint160(value))));
      }
  
      /**
       * @dev Returns the number of values in the set. O(1).
       */
      function length(AddressSet storage set) internal view returns (uint256) {
          return _length(set._inner);
      }
  
      /**
       * @dev Returns the value stored at position `index` in the set. O(1).
       *
       * Note that there are no guarantees on the ordering of values inside the
       * array, and it may change when more values are added or removed.
       *
       * Requirements:
       *
       * - `index` must be strictly less than {length}.
       */
      function at(AddressSet storage set, uint256 index) internal view returns (address) {
          return address(uint160(uint256(_at(set._inner, index))));
      }
  
      /**
       * @dev Return the entire set in an array
       *
       * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
       * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
       * this function has an unbounded cost, and using it as part of a state-changing function may render the function
       * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
       */
      function values(AddressSet storage set) internal view returns (address[] memory) {
          bytes32[] memory store = _values(set._inner);
          address[] memory result;
  
          /// @solidity memory-safe-assembly
          assembly {
              result := store
          }
  
          return result;
      }
  
      // UintSet
  
      struct UintSet {
          Set _inner;
      }
  
      /**
       * @dev Add a value to a set. O(1).
       *
       * Returns true if the value was added to the set, that is if it was not
       * already present.
       */
      function add(UintSet storage set, uint256 value) internal returns (bool) {
          return _add(set._inner, bytes32(value));
      }
  
      /**
       * @dev Removes a value from a set. O(1).
       *
       * Returns true if the value was removed from the set, that is if it was
       * present.
       */
      function remove(UintSet storage set, uint256 value) internal returns (bool) {
          return _remove(set._inner, bytes32(value));
      }
  
      /**
       * @dev Returns true if the value is in the set. O(1).
       */
      function contains(UintSet storage set, uint256 value) internal view returns (bool) {
          return _contains(set._inner, bytes32(value));
      }
  
      /**
       * @dev Returns the number of values in the set. O(1).
       */
      function length(UintSet storage set) internal view returns (uint256) {
          return _length(set._inner);
      }
  
      /**
       * @dev Returns the value stored at position `index` in the set. O(1).
       *
       * Note that there are no guarantees on the ordering of values inside the
       * array, and it may change when more values are added or removed.
       *
       * Requirements:
       *
       * - `index` must be strictly less than {length}.
       */
      function at(UintSet storage set, uint256 index) internal view returns (uint256) {
          return uint256(_at(set._inner, index));
      }
  
      /**
       * @dev Return the entire set in an array
       *
       * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
       * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
       * this function has an unbounded cost, and using it as part of a state-changing function may render the function
       * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
       */
      function values(UintSet storage set) internal view returns (uint256[] memory) {
          bytes32[] memory store = _values(set._inner);
          uint256[] memory result;
  
          /// @solidity memory-safe-assembly
          assembly {
              result := store
          }
  
          return result;
      }
  }
  
  
  // File contracts/interfaces/IAddressProvider.sol
  
  
  pragma solidity 0.8.17;
  
  interface IAddressProvider {
      function getTreasury() external view returns (address);
  
      function getShezmu() external view returns (address);
  
      function getGuardian() external view returns (address);
  
      function getPriceOracleAggregator() external view returns (address);
  
      function getBond() external view returns (address);
  
      function getObelisk() external view returns (address);
  
      function getOptionShezmu() external view returns (address);
  
      function getFarming() external view returns (address);
  }
  
  
  // File contracts/interfaces/IERC20MintableBurnable.sol
  
  
  pragma solidity 0.8.17;
  
  interface IERC20MintableBurnable {
      function mint(address to, uint256 amount) external;
  
      function burn(uint256 amount) external;
  
      function burnFrom(address account, uint256 amount) external;
  }
  
  
  // File contracts/interfaces/IObelisk.sol
  
  
  pragma solidity 0.8.17;
  
  interface IObelisk {
      function getMultiplierOf(
          address account
      ) external view returns (uint256 multiplier, uint256 precision);
  }
  
  
  // File contracts/interfaces/IRewardRecipient.sol
  
  
  pragma solidity 0.8.17;
  
  interface IRewardRecipient {
      function receiveReward() external payable;
  }
  
  
  // File contracts/interfaces/IZap.sol
  
  
  pragma solidity 0.8.17;
  
  interface IZap {
      function zapInToken(
          address token,
          uint amount,
          address receiver
      )
          external
          returns (uint256 amountShezmu, uint256 amountETH, uint256 liquidity);
  }
  
  
  // File contracts/token/Guardian.sol
  
  
  pragma solidity 0.8.17;
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  error INVALID_ADDRESS();
  error INVALID_AMOUNT();
  error INVALID_PARAM();
  error INVALID_FEE_TOKEN();
  error ONLY_BOND();
  error ONLY_OBELISK();
  
  contract Guardian is
      ERC1155PausableUpgradeable,
      OwnableUpgradeable,
      IRewardRecipient
  {
      using SafeERC20 for IERC20;
      using EnumerableSet for EnumerableSet.AddressSet;
      using Strings for uint256;
  
      /* ======== STORAGE ======== */
      struct RewardRate {
          uint256 rewardPerSec;
          uint256 numberOfGuardians;
      }
  
      struct RewardInfo {
          uint256 debt;
          uint256 pending;
      }
  
      /// @dev BASE URI
      string private constant BASE_URI = 'https://metadata.shezmu.io/guardian/';
  
      /// @notice name
      string public constant name = 'Shezmu Guardian';
  
      /// @notice percent multiplier (100%)
      uint256 public constant PRECISION = 10000;
  
      /// @notice SHEZMU
      IERC20MintableBurnable public constant SHEZMU =
          IERC20MintableBurnable(0x5fE72ed557d8a02FFf49B3B826792c765d5cE162);
  
      /// @notice Fee Token (USDC)
      IERC20 public constant USDC =
          IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
  
      /// @notice Uniswap Router
      IUniswapV2Router02 public constant ROUTER =
          IUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
  
      /// @notice price per guardian
      uint256 public pricePerGuardian;
  
      /// @notice treasury wallet
      address public treasury;
  
      /// @notice txn fee (price)
      uint256 public txnFee;
  
      /// @notice claim fee (percentage)
      uint256 public claimFee;
  
      /// @notice mint limit in one txn
      uint256 public mintLimit;
  
      /// @notice mapping account => total balance
      mapping(address => uint256) public totalBalanceOf;
  
      /// @notice total supply
      uint256 public totalSupply;
  
      /// @notice reward rate
      RewardRate public rewardRate;
  
      /// @dev feeToken tokens (stable coin for txn fee payment)
      EnumerableSet.AddressSet private feeTokens;
  
      /// @dev TYPE
      uint8 private constant TYPE = 6;
  
      /// @dev SIZE for each TYPE
      uint8[6] private SIZES;
  
      /// @dev reward accTokenPerShare
      uint256 private accTokenPerShare;
  
      /// @dev reward lastUpdate
      uint256 private lastUpdate;
  
      /// @dev mapping account => reward info
      mapping(address => RewardInfo) private rewardInfoOf;
  
      /// @dev USDC dividendsPerShare
      uint256 private dividendsPerShare;
  
      /// @dev mapping account => dividends info
      mapping(address => RewardInfo) private dividendsInfoOf;
  
      /// @dev dividends multiplier
      uint256 private constant MULTIPLIER = 1e18;
  
      /// @notice address provider
      IAddressProvider public addressProvider;
  
      /// @notice tributeFee for Shezmu (percentage)
      uint256 public tributeFeeForShezmu;
  
      /// @notice tributeFee for ETH (percentage)
      uint256 public tributeFeeForETH;
  
      address public lpFeeReceiver;
      IZap public zap;
  
      /* ======== EVENTS ======== */
  
      event MintLimit(uint256 limit);
      event Treasury(address treasury);
      event TxnFee(uint256 fee);
      event ClaimFee(uint256 fee);
      event AddFeeTokens(address[] tokens);
      event RemoveFeeTokens(address[] tokens);
      event Mint(address indexed from, address indexed to, uint256 amount);
      event Compound(address indexed from, address indexed to, uint256 amount);
      event Split(address indexed from, address indexed to, uint256 amount);
      event Claim(address indexed from, uint256 reward, uint256 dividends);
      event Bond(address indexed to, uint256 amount);
      event TributeFee(uint256 tributeFeeForShezmu, uint256 tributeFeeForETH);
  
      /* ======== INITIALIZATION ======== */
  
      /// @custom:oz-upgrades-unsafe-allow constructor
      constructor() {
          _disableInitializers();
      }
  
      function initialize(address treasury_) external initializer {
          if (treasury_ == address(0)) revert INVALID_ADDRESS();
          treasury = treasury_;
  
          // 1 Guardian: Craftsman
          // 5 Guardian: Scribe
          // 10 Guardian: High Priest
          // 25 Guardian: Nobles
          // 50 Guardians: Viziers
          // 100 Guardian: Pharaoh
          SIZES = [1, 5, 10, 25, 50, 100];
  
          // 12 Shezmu per Guardian
          pricePerGuardian = 12 ether;
  
          // txn fee $15
          txnFee = 15 ether;
          feeTokens.add(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); // USDC
          feeTokens.add(0xdAC17F958D2ee523a2206206994597C13D831ec7); // USDT
          feeTokens.add(0x6B175474E89094C44Da98b954EedeAC495271d0F); // DAI
  
          // claim fee 20%
          claimFee = 2000;
  
          // option how many can mint in one txn
          mintLimit = 100;
  
          // 0.1 Shezmu per day for first 250,000 guardians
          rewardRate.rewardPerSec = uint256(0.1 ether) / uint256(1 days);
          rewardRate.numberOfGuardians = 250000;
  
          // init
          __ERC1155Pausable_init();
          __ERC1155_init(BASE_URI);
          __Ownable_init();
      }
  
      /* ======== MODIFIERS ======== */
  
      modifier onlyBond() {
          if (msg.sender != _bond()) revert ONLY_BOND();
          _;
      }
  
      modifier onlyObelisk() {
          if (msg.sender != _obelisk()) revert ONLY_OBELISK();
          _;
      }
  
      modifier update() {
          if (totalSupply > 0) {
              accTokenPerShare +=
                  rewardRate.rewardPerSec *
                  (block.timestamp - lastUpdate);
          }
          lastUpdate = block.timestamp;
  
          _;
      }
  
      /* ======== POLICY FUNCTIONS ======== */
  
      function setAddressProvider(address _addressProvider) external onlyOwner {
          if (_addressProvider == address(0)) revert INVALID_ADDRESS();
          addressProvider = IAddressProvider(_addressProvider);
      }
  
      function setMintLimit(uint256 limit) external onlyOwner {
          if (limit == 0) revert INVALID_AMOUNT();
  
          mintLimit = limit;
  
          emit MintLimit(limit);
      }
  
      function setTreasury(address treasury_) external onlyOwner {
          if (treasury_ == address(0)) revert INVALID_ADDRESS();
  
          treasury = treasury_;
  
          emit Treasury(treasury_);
      }
  
      function setTxnFee(uint256 fee) external onlyOwner {
          if (fee == 0) revert INVALID_AMOUNT();
  
          txnFee = fee;
  
          emit TxnFee(fee);
      }
  
      function setClaimFee(uint256 fee) external onlyOwner {
          if (fee >= PRECISION / 2) revert INVALID_AMOUNT();
  
          claimFee = fee;
  
          emit ClaimFee(fee);
      }
  
      function setTributeFee(
          uint256 feeForShezmu,
          uint256 feeForETH
      ) external onlyOwner {
          if ((feeForShezmu + feeForETH) >= PRECISION / 2)
              revert INVALID_AMOUNT();
  
          tributeFeeForShezmu = feeForShezmu;
          tributeFeeForETH = feeForETH;
  
          emit TributeFee(feeForShezmu, feeForETH);
      }
  
      function setZap(address zap_) external onlyOwner {
          zap = IZap(zap_);
      }
  
      function setLpFeeReceiver(address receiver) external onlyOwner {
          lpFeeReceiver = receiver;
      }
  
      function setPricePerGuardian(uint price) external onlyOwner {
          pricePerGuardian = price;
      }
  
      function setRewardRate(
          RewardRate memory _rewardRate
      ) external onlyOwner update {
          rewardRate = _rewardRate;
      }
  
      function addFeeTokens(address[] calldata tokens) external onlyOwner {
          uint256 length = tokens.length;
  
          for (uint256 i = 0; i < length; ) {
              feeTokens.add(tokens[i]);
              unchecked {
                  ++i;
              }
          }
  
          emit AddFeeTokens(tokens);
      }
  
      function removeFeeTokens(address[] calldata tokens) external onlyOwner {
          uint256 length = tokens.length;
  
          for (uint256 i = 0; i < length; ) {
              feeTokens.remove(tokens[i]);
              unchecked {
                  ++i;
              }
          }
  
          emit RemoveFeeTokens(tokens);
      }
  
      function pause() external onlyOwner {
          _pause();
      }
  
      function unpause() external onlyOwner {
          _unpause();
      }
  
      function recoverERC20(IERC20 token) external onlyOwner {
          token.safeTransfer(_msgSender(), token.balanceOf(address(this)));
      }
  
      function airdrop(
          address[] calldata tos,
          uint256[] calldata amounts
      ) external onlyOwner update {
          uint256 length = tos.length;
          if (length != amounts.length) revert INVALID_PARAM();
  
          for (uint256 i = 0; i < length; ) {
              _simpleMint(tos[i], amounts[i]);
  
              unchecked {
                  ++i;
              }
          }
      }
  
      /* ======== INTERNAL FUNCTIONS ======== */
  
      function _shezmu() internal view returns (address) {
          return addressProvider.getShezmu();
      }
  
      function _bond() internal view returns (address) {
          return addressProvider.getBond();
      }
  
      function _obelisk() internal view returns (address) {
          return addressProvider.getObelisk();
      }
  
      function _treasury() internal view returns (address) {
          return addressProvider.getTreasury();
      }
  
      function _viewPriceInUSD(address token) internal view returns (uint256) {
          return
              IPriceOracleAggregator(addressProvider.getPriceOracleAggregator())
                  .viewPriceInUSD(token);
      }
  
      function _getMultiplierOf(
          address account
      ) internal view returns (uint256, uint256) {
          return IObelisk(addressProvider.getObelisk()).getMultiplierOf(account);
      }
  
      function _min(uint256 a, uint256 b) internal pure returns (uint256) {
          return a < b ? a : b;
      }
  
      function _sync(address account) internal {
          uint256 totalBalance = totalBalanceOf[account];
  
          unchecked {
              for (uint256 i = 0; i < TYPE; i++) {
                  uint256 index = TYPE - i - 1;
                  uint256 newBalance = totalBalance / SIZES[index];
                  uint256 oldBalance = balanceOf(account, index);
  
                  if (newBalance > oldBalance) {
                      super._mint(account, index, newBalance - oldBalance, '');
                  } else if (newBalance < oldBalance) {
                      super._burn(account, index, oldBalance - newBalance);
                  }
  
                  totalBalance = totalBalance % SIZES[index];
              }
          }
      }
  
      function _updateReward(
          address account
      )
          internal
          returns (
              RewardInfo storage rewardInfo,
              RewardInfo storage dividendsInfo
          )
      {
          uint256 totalBalance = totalBalanceOf[account];
  
          // Shezmu
          rewardInfo = rewardInfoOf[account];
          (uint256 multiplier, uint256 precision) = _getMultiplierOf(account);
          uint256 reward = ((accTokenPerShare * totalBalance - rewardInfo.debt) *
              (precision + multiplier)) / precision;
          uint256 fee = (reward * claimFee) / PRECISION;
          rewardInfo.pending += reward - fee;
          if (fee > 0) {
              SHEZMU.mint(treasury, fee);
          }
  
          // USDC
          dividendsInfo = dividendsInfoOf[account];
          dividendsInfo.pending +=
              (dividendsPerShare * totalBalance) /
              MULTIPLIER -
              dividendsInfo.debt;
      }
  
      function _mint(address to, address feeToken, uint256 amount) internal {
          if (to == address(0)) revert INVALID_ADDRESS();
          if (amount == 0 || amount > mintLimit) revert INVALID_AMOUNT();
          if (!feeTokens.contains(feeToken)) revert INVALID_FEE_TOKEN();
  
          // pay txn fee
          RewardInfo storage dividendsInfo = dividendsInfoOf[to];
          uint256 usdcFee = (txnFee *
              10 ** IERC20Metadata(address(USDC)).decimals()) / MULTIPLIER;
  
          if (dividendsInfo.pending >= usdcFee) {
              dividendsInfo.pending -= usdcFee;
  
              USDC.approve(address(zap), usdcFee);
              zap.zapInToken(address(USDC), usdcFee, lpFeeReceiver);
          } else {
              uint fee = (txnFee * 10 ** IERC20Metadata(feeToken).decimals()) /
                  MULTIPLIER;
              IERC20(feeToken).safeTransferFrom(_msgSender(), address(this), fee);
              IERC20(feeToken).approve(address(zap), fee);
              zap.zapInToken(feeToken, fee, lpFeeReceiver);
          }
  
          _simpleMint(to, amount);
      }
  
      function _simpleMint(address to, uint256 amount) internal {
          // update reward
          (
              RewardInfo storage rewardInfo,
              RewardInfo storage dividendsInfo
          ) = _updateReward(to);
  
          // mint Guardian
          unchecked {
              totalBalanceOf[to] += amount;
              totalSupply += amount;
          }
  
          // update reward rate if exceeds the Guardians number
          if (totalSupply > rewardRate.numberOfGuardians) {
              rewardRate.rewardPerSec /= 2;
              rewardRate.numberOfGuardians *= 2;
          }
  
          // update reward debt
          rewardInfo.debt = accTokenPerShare * totalBalanceOf[to];
          dividendsInfo.debt =
              (dividendsPerShare * totalBalanceOf[to]) /
              MULTIPLIER;
  
          // sync Guardians
          _sync(to);
      }
  
      function _beforeTokenTransfer(
          address operator,
          address from,
          address to,
          uint256[] memory ids,
          uint256[] memory amounts,
          bytes memory data
      ) internal virtual override update {
          super._beforeTokenTransfer(operator, from, to, ids, amounts, data);
  
          if (from == address(0) || to == address(0)) {
              return;
          }
  
          // update reward
          (
              RewardInfo storage fromRewardInfo,
              RewardInfo storage fromDividendsInfo
          ) = _updateReward(from);
          (
              RewardInfo storage toRewardInfo,
              RewardInfo storage toDividendsInfo
          ) = _updateReward(to);
  
          // calculate number of Guardians
          uint256 amount;
          unchecked {
              for (uint256 i = 0; i < ids.length; ++i) {
                  amount += SIZES[ids[i]] * amounts[i];
              }
          }
  
          // update total balance
          unchecked {
              totalBalanceOf[from] -= amount;
              totalBalanceOf[to] += amount;
          }
  
          // update reward debt
          fromRewardInfo.debt = accTokenPerShare * totalBalanceOf[from];
          fromDividendsInfo.debt =
              (dividendsPerShare * totalBalanceOf[from]) /
              MULTIPLIER;
          toRewardInfo.debt = accTokenPerShare * totalBalanceOf[to];
          toDividendsInfo.debt =
              (dividendsPerShare * totalBalanceOf[to]) /
              MULTIPLIER;
      }
  
      /* ======== PUBLIC FUNCTIONS ======== */
  
      function mint(
          address to,
          address feeToken,
          uint256 amount
      ) external update {
          address account = _msgSender();
  
          // burn Shezmu
          SHEZMU.burnFrom(account, amount * pricePerGuardian);
  
          // update reward
          (
              RewardInfo storage rewardInfo,
              RewardInfo storage dividendsInfo
          ) = _updateReward(account);
          rewardInfo.debt = accTokenPerShare * totalBalanceOf[account];
          dividendsInfo.debt =
              (dividendsPerShare * totalBalanceOf[account]) /
              MULTIPLIER;
  
          // mint Guardian
          _mint(to, feeToken, amount);
  
          emit Mint(account, to, amount);
      }
  
      function compound(
          address to,
          address feeToken,
          uint256 amount
      ) external update {
          address account = _msgSender();
  
          // update reward
          (
              RewardInfo storage rewardInfo,
              RewardInfo storage dividendsInfo
          ) = _updateReward(account);
          rewardInfo.debt = accTokenPerShare * totalBalanceOf[account];
          dividendsInfo.debt =
              (dividendsPerShare * totalBalanceOf[account]) /
              MULTIPLIER;
  
          // burn Shezmu out of rewards
          if (amount > 0) {
              if (rewardInfo.pending < amount * pricePerGuardian)
                  revert INVALID_AMOUNT();
              unchecked {
                  rewardInfo.pending -= amount * pricePerGuardian;
              }
          } else {
              amount = rewardInfo.pending / pricePerGuardian;
              rewardInfo.pending %= pricePerGuardian;
          }
  
          // mint Guardian
          _mint(to, feeToken, amount);
  
          emit Compound(account, to, amount);
      }
  
      function split(address to, uint256 amount) external update {
          if (to == address(0)) revert INVALID_ADDRESS();
          address from = _msgSender();
          if (totalBalanceOf[from] < amount) revert INVALID_AMOUNT();
  
          // from
          {
              // update reward
              (
                  RewardInfo storage rewardInfo,
                  RewardInfo storage dividendsInfo
              ) = _updateReward(from);
  
              unchecked {
                  totalBalanceOf[from] -= amount;
              }
  
              // update reward debt
              rewardInfo.debt = accTokenPerShare * totalBalanceOf[from];
              dividendsInfo.debt =
                  (dividendsPerShare * totalBalanceOf[from]) /
                  MULTIPLIER;
  
              // sync Guardians
              _sync(from);
          }
  
          // to
          {
              // update reward
              (
                  RewardInfo storage rewardInfo,
                  RewardInfo storage dividendsInfo
              ) = _updateReward(to);
  
              unchecked {
                  totalBalanceOf[to] += amount;
              }
  
              // update reward debt
              rewardInfo.debt = accTokenPerShare * totalBalanceOf[to];
              dividendsInfo.debt =
                  (dividendsPerShare * totalBalanceOf[to]) /
                  MULTIPLIER;
  
              // sync Guardians
              _sync(to);
          }
  
          emit Split(from, to, amount);
      }
  
      function claim() external payable update {
          address account = _msgSender();
          uint256 totalBalance = totalBalanceOf[account];
  
          if (totalBalance == 0) return;
  
          // update reward
          (
              RewardInfo storage rewardInfo,
              RewardInfo storage dividendsInfo
          ) = _updateReward(account);
  
          rewardInfo.debt = accTokenPerShare * totalBalance;
          dividendsInfo.debt = (dividendsPerShare * totalBalance) / MULTIPLIER;
  
          // tribute fee & claim percentage
          uint256 claimPercentage;
  
          if (msg.sender != _bond()) {
              uint256 shezmuPriceInUSD = _viewPriceInUSD(_shezmu());
              uint256 ethPriceInUSD = _viewPriceInUSD(ROUTER.WETH());
              uint256 ethAmountForFee = (rewardInfo.pending *
                  tributeFeeForETH *
                  shezmuPriceInUSD) / (ethPriceInUSD * PRECISION);
  
              if (ethAmountForFee == 0 || ethAmountForFee <= msg.value) {
                  claimPercentage = PRECISION;
              } else {
                  claimPercentage = (msg.value * PRECISION) / ethAmountForFee;
  
                  if (claimPercentage <= 2400) revert INVALID_AMOUNT();
              }
          } else {
              claimPercentage = PRECISION;
          }
  
          if (msg.value > 0) {
              (bool success, ) = payable(_treasury()).call{value: msg.value}('');
              require(success);
          }
  
          // transfer pending (Shezmu)
          uint256 reward = (rewardInfo.pending * claimPercentage) / PRECISION;
          if (reward > 0) {
              unchecked {
                  rewardInfo.pending -= reward;
              }
              SHEZMU.mint(account, reward);
          }
  
          // transfer pending (USDC)
          uint256 dividends = _min(
              (dividendsInfo.pending * claimPercentage) / PRECISION,
              USDC.balanceOf(address(this))
          );
          if (dividends > 0) {
              unchecked {
                  dividendsInfo.pending -= dividends;
              }
              USDC.safeTransfer(account, dividends);
          }
  
          emit Claim(account, reward, dividends);
      }
  
      function receiveReward() external payable override {
          if (msg.value == 0) return;
  
          address[] memory path = new address[](2);
          path[0] = ROUTER.WETH();
          path[1] = address(USDC);
  
          uint256 balanceBefore = USDC.balanceOf(address(this));
          ROUTER.swapExactETHForTokensSupportingFeeOnTransferTokens{
              value: msg.value
          }(0, path, address(this), block.timestamp);
  
          uint256 rewardAmount = USDC.balanceOf(address(this)) - balanceBefore;
  
          if (totalSupply > 0 && rewardAmount > 0) {
              dividendsPerShare += (rewardAmount * MULTIPLIER) / totalSupply;
          }
      }
  
      /* ======== BOND FUNCTIONS ======== */
  
      function bond(
          address account,
          address feeToken,
          uint256 amount
      ) external onlyBond update {
          if (account == address(0)) revert INVALID_ADDRESS();
          if (amount == 0 || amount > mintLimit) revert INVALID_AMOUNT();
          if (!feeTokens.contains(feeToken)) revert INVALID_FEE_TOKEN();
  
          // burn Shezmu from bond
          SHEZMU.burnFrom(_msgSender(), amount * pricePerGuardian);
  
          // update reward
          (
              RewardInfo storage rewardInfo,
              RewardInfo storage dividendsInfo
          ) = _updateReward(account);
          rewardInfo.debt = accTokenPerShare * totalBalanceOf[account];
          dividendsInfo.debt =
              (dividendsPerShare * totalBalanceOf[account]) /
              MULTIPLIER;
  
          // pay txn fee
          uint256 usdcFee = (txnFee *
              10 ** IERC20Metadata(address(USDC)).decimals()) / MULTIPLIER;
  
          if (dividendsInfo.pending >= usdcFee) {
              dividendsInfo.pending -= usdcFee;
  
              USDC.approve(address(zap), usdcFee);
              zap.zapInToken(address(USDC), usdcFee, lpFeeReceiver);
          } else {
              uint fee = (txnFee * 10 ** IERC20Metadata(feeToken).decimals()) /
                  MULTIPLIER;
              IERC20(feeToken).safeTransferFrom(_msgSender(), address(this), fee);
              IERC20(feeToken).approve(address(zap), fee);
              zap.zapInToken(feeToken, fee, lpFeeReceiver);
          }
  
          // mint Guardian
          _simpleMint(_msgSender(), amount);
  
          emit Bond(account, amount);
      }
  
      function sellRewardForBond(
          address account,
          uint256 dividends
      ) external onlyBond update {
          if (account == address(0)) revert INVALID_ADDRESS();
          if (dividends == 0) revert INVALID_AMOUNT();
  
          // update reward
          (
              RewardInfo storage rewardInfo,
              RewardInfo storage dividendsInfo
          ) = _updateReward(account);
          rewardInfo.debt = accTokenPerShare * totalBalanceOf[account];
          dividendsInfo.debt =
              (dividendsPerShare * totalBalanceOf[account]) /
              MULTIPLIER;
  
          // usdc to treasury
          dividendsInfo.pending -= dividends;
  
          USDC.approve(address(zap), dividends);
          zap.zapInToken(address(USDC), dividends, lpFeeReceiver);
      }
  
      /* ======== OBELISK FUNCTIONS ======== */
  
      function updateRewardForObelisk(
          address account
      ) external onlyObelisk update {
          if (account == address(0)) revert INVALID_ADDRESS();
  
          // update reward
          (
              RewardInfo storage rewardInfo,
              RewardInfo storage dividendsInfo
          ) = _updateReward(account);
          rewardInfo.debt = accTokenPerShare * totalBalanceOf[account];
          dividendsInfo.debt =
              (dividendsPerShare * totalBalanceOf[account]) /
              MULTIPLIER;
      }
  
      /* ======== VIEW FUNCTIONS ======== */
  
      function uri(
          uint256 tokenId
      ) public view virtual override returns (string memory) {
          return
              tokenId < TYPE
                  ? string(
                      abi.encodePacked(BASE_URI, tokenId.toString(), '.json')
                  )
                  : super.uri(tokenId);
      }
  
      function allFeeTokens() external view returns (address[] memory) {
          return feeTokens.values();
      }
  
      function pendingReward(
          address account
      )
          external
          view
          returns (
              uint256 reward,
              uint256 dividends,
              uint256 feeInUSD,
              uint256 shezmuAmountForFee,
              uint256 ethAmountForFee
          )
      {
          uint256 totalBalance = totalBalanceOf[account];
  
          if (totalBalance > 0) {
              // Shezmu reward
              {
                  RewardInfo memory rewardInfo = rewardInfoOf[account];
                  uint256 newAccTokenPerShare = accTokenPerShare +
                      rewardRate.rewardPerSec *
                      (block.timestamp - lastUpdate);
                  (uint256 multiplier, uint256 precision) = _getMultiplierOf(
                      account
                  );
                  uint256 newReward = ((newAccTokenPerShare *
                      totalBalance -
                      rewardInfoOf[account].debt) * (precision + multiplier)) /
                      precision;
                  reward =
                      rewardInfo.pending +
                      newReward -
                      (newReward * claimFee) /
                      PRECISION;
              }
  
              // USDC reward
              {
                  RewardInfo memory dividendsInfo = dividendsInfoOf[account];
                  dividends =
                      dividendsInfo.pending +
                      (dividendsPerShare * totalBalance) /
                      MULTIPLIER -
                      dividendsInfo.debt;
              }
  
              // Tribute Fee
              {
                  uint256 shezmuPriceInUSD = _viewPriceInUSD(_shezmu());
                  uint256 ethPriceInUSD = _viewPriceInUSD(ROUTER.WETH());
  
                  feeInUSD =
                      (reward *
                          (tributeFeeForShezmu + tributeFeeForETH) *
                          shezmuPriceInUSD) /
                      PRECISION;
                  shezmuAmountForFee = (reward * tributeFeeForShezmu) / PRECISION;
                  ethAmountForFee =
                      (reward * tributeFeeForETH * shezmuPriceInUSD) /
                      (ethPriceInUSD * PRECISION);
              }
          }
      }
  
      function getTributeFee()
          external
          view
          returns (
              uint256 tributeFeeForShezmu_,
              uint256 tributeFeeForETH_,
              uint256 shezmuPriceInUSD,
              uint256 ethPriceInUSD
          )
      {
          tributeFeeForShezmu_ = tributeFeeForShezmu;
          tributeFeeForETH_ = tributeFeeForETH;
          shezmuPriceInUSD = _viewPriceInUSD(_shezmu());
          ethPriceInUSD = _viewPriceInUSD(ROUTER.WETH());
      }
  
      uint256[49] private __gap;
  }