// File: /contracts/Homies.sol



/*     +%%#-                           ##.        =+.    .+#%#+:       *%%#:    .**+-      =+
 *   .%@@*#*:                          @@: *%-   #%*=  .*@@=.  =%.   .%@@*%*   +@@=+=%   .%##
 *  .%@@- -=+                         *@% :@@-  #@=#  -@@*     +@-  :@@@: ==* -%%. ***   #@=*
 *  %@@:  -.*  :.                    +@@-.#@#  =@%#.   :.     -@*  :@@@.  -:# .%. *@#   *@#*
 * *%@-   +++ +@#.-- .*%*. .#@@*@#  %@@%*#@@: .@@=-.         -%-   #%@:   +*-   =*@*   -@%=:
 * @@%   =##  +@@#-..%%:%.-@@=-@@+  ..   +@%  #@#*+@:      .*=     @@%   =#*   -*. +#. %@#+*@
 * @@#  +@*   #@#  +@@. -+@@+#*@% =#:    #@= :@@-.%#      -=.  :   @@# .*@*  =@=  :*@:=@@-:@+
 * -#%+@#-  :@#@@+%++@*@*:=%+..%%#=      *@  *@++##.    =%@%@%%#-  =#%+@#-   :*+**+=: %%++%*
 *
 * @title: [EIP721] HOW NFT
 * @author: Max Flow O2 -> @MaxFlowO2 on bird app/GitHub
 * @notice ERC721 Implementation with:
 *         Enhanced EIP173 - Ownership via roles
 *         EIP2981 - NFT Royalties
 *         PaymentSplitter v2 - For "ETH" payments
 */

// SPDX-License-Identifier: Apache-2.0

/******************************************************************************
 * Copyright 2022 Max Flow O2                                                 *
 *                                                                            *
 * Licensed under the Apache License, Version 2.0 (the "License");            *
 * you may not use this file except in compliance with the License.           *
 * You may obtain a copy of the License at                                    *
 *                                                                            *
 *     http://www.apache.org/licenses/LICENSE-2.0                             *
 *                                                                            *
 * Unless required by applicable law or agreed to in writing, software        *
 * distributed under the License is distributed on an "AS IS" BASIS,          *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
 * See the License for the specific language governing permissions and        *
 * limitations under the License.                                             *
 ******************************************************************************/

pragma solidity >=0.8.17 <0.9.0;

import "./Max-721-2981-PSv2.sol";
import "./lib/721.sol";
import "./lib/Lists.sol";
import "./lib/CountersV2.sol";
import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import "@openzeppelin/contracts/utils/Base64.sol";

contract HOW is Max721 {

  using Lib721 for Lib721.Token;
  using Lists for Lists.Access;
  using CountersV2 for CountersV2.Counter;

  CountersV2.Counter internal theTokenID;

  event Claimed(uint256 index, address account, uint256 amount, string what);

  constructor(
    string memory _name
  , string memory _symbol
  , address _admin
  , address _dev
  , address _owner
  , uint256 _start
  ) {
      __Max721_init(_name, _symbol, _admin, _dev, _owner);
      theTokenID.set(_start);
  }

  // set time block
  modifier preSale() {
    if (block.timestamp < startTime + period && block.timestamp >= startTime) {
      _;
    } else {
    revert Unauthorized();
    }
  }

  modifier Sale() {
    if (block.timestamp >= startTime + period) {
      _;
    } else {
    revert Unauthorized();
    }
  }

  function setTime(
    uint256 _start
  , uint256 _period
  ) external
    onlyDev() {
    startTime = _start;
    period = _period;
  }

  function showTimes()
    external
    view
    returns (uint256, uint256) {
    return (startTime, startTime + period);
  }

  function setCap(
    uint256 _amount
  ) external
    onlyDev() {
    maxCap = _amount;
  }

  function setMerks(
    bytes32 _admin
  , bytes32 _homies
  , bytes32 _normies
  ) external
    onlyDev() {
    admin = _admin;
    homies = _homies;
    normies = _normies;
  }

  function isClaimedAdmin(
    uint256 index
  ) public
    view
    returns (bool) {
    uint256 claimedWordIndex = index / 256;
    uint256 claimedBitIndex = index % 256;
    uint256 claimedWord = claimedAdmin[claimedWordIndex];
    uint256 mask = (1 << claimedBitIndex);
    return claimedWord & mask == mask;
  }

  function isClaimedHomies(
    uint256 index
  ) public
    view
    returns (bool) {
    uint256 claimedWordIndex = index / 256;
    uint256 claimedBitIndex = index % 256;
    uint256 claimedWord = claimedHomies[claimedWordIndex];
    uint256 mask = (1 << claimedBitIndex);
    return claimedWord & mask == mask;
  }

  function isClaimedNormies(
    uint256 index
  ) public
    view
    returns (bool) {
    uint256 claimedWordIndex = index / 256;
    uint256 claimedBitIndex = index % 256;
    uint256 claimedWord = claimedNormies[claimedWordIndex];
    uint256 mask = (1 << claimedBitIndex);
    return claimedWord & mask == mask;
  }

  function _setClaimedAdmin(
    uint256 index
  ) internal {
    uint256 claimedWordIndex = index / 256;
    uint256 claimedBitIndex = index % 256;
    claimedAdmin[claimedWordIndex] = claimedAdmin[claimedWordIndex] | (1 << claimedBitIndex);
  }

  function _setClaimedHomies(
    uint256 index
  ) internal {
    uint256 claimedWordIndex = index / 256;
    uint256 claimedBitIndex = index % 256;
    claimedHomies[claimedWordIndex] = claimedHomies[claimedWordIndex] | (1 << claimedBitIndex);
  }

  function _setClaimedNormies(
    uint256 index
  ) internal {
    uint256 claimedWordIndex = index / 256;
    uint256 claimedBitIndex = index % 256;
    claimedNormies[claimedWordIndex] = claimedNormies[claimedWordIndex] | (1 << claimedBitIndex);
  }

  function adminMint(
    uint256 index
  , address account
  , uint256 amount
  , bytes32[] calldata merkleProof
  ) external
    preSale() {
    if (startTime == 0) {
      revert Unauthorized();
    }
    if (isClaimedAdmin(index)) {
      revert MaxSplaining({
        reason: "Already Claimed"
      });
    }

    // Verify the merkle proof.
    bytes32 node = keccak256(abi.encodePacked(index, account, amount));
    if (!MerkleProof.verify(merkleProof, admin, node)) {
      revert MaxSplaining({
        reason: "Invalid Proof"
      });
    }

    // Mark it claimed and send the token.
    _setClaimedAdmin(index);
    if (token721.getSupply() + amount > maxCap) {
      revert Unauthorized();
    } else {
      for (uint c = 0; c < amount;) {
        // mint each
        token721.mint(account, theTokenID.current());
        emit Transfer(address(0), account, theTokenID.current());
        theTokenID.increment();
        unchecked { ++c; }
      }
    }
    emit Claimed(index, account, amount, "Admin");
  }

  function homiesMint(
    uint256 index
  , address account
  , bytes32[] calldata merkleProof
  ) external
    preSale() {
    if (startTime == 0) {
      revert Unauthorized();
    }
    if (isClaimedHomies(index)) {
      revert MaxSplaining({
        reason: "Already Claimed"
      });
    }

    // Verify the merkle proof.
    bytes32 node = keccak256(abi.encodePacked(index, account));
    if (!MerkleProof.verify(merkleProof, homies, node)) {
      revert MaxSplaining({
        reason: "Invalid Proof"
      });
    }

    // Mark it claimed and send the token.
    _setClaimedHomies(index);
    if (token721.getSupply() + 1 > maxCap) {
      revert Unauthorized();
    } else {
      token721.mint(account, theTokenID.current());
      emit Transfer(address(0), account, theTokenID.current());
      theTokenID.increment();
    }
    emit Claimed(index, account, 1, "Homies");
  }

  function normiesMint(
    uint256 index
  , address account
  , bytes32[] calldata merkleProof
  ) external
    payable
    preSale() {
    if (startTime == 0) {
      revert Unauthorized();
    }
    if (msg.value != normiesCost) {
      revert MaxSplaining ({
        reason: "msg.value too low"
      });
    }
    if (isClaimedNormies(index)) {
      revert MaxSplaining({
        reason: "Already Claimed"
      });
    }

    // Verify the merkle proof.
    bytes32 node = keccak256(abi.encodePacked(index, account));
    if (!MerkleProof.verify(merkleProof, normies, node)) {
      revert MaxSplaining({
        reason: "Invalid Proof"
      });
    }

    // Mark it claimed and send the token.
    _setClaimedNormies(index);
    if (token721.getSupply() + 1 > maxCap) {
      revert Unauthorized();
    } else {
      token721.mint(account, theTokenID.current());
      emit Transfer(address(0), account, theTokenID.current());
      theTokenID.increment();
    }
    emit Claimed(index, account, 1, "Normies");
  }

  function publicMint(
    uint256 quant
  ) external
    payable
    Sale() {
    if (quant > 5) {
      revert Unauthorized();
    }
    if (startTime == 0) {
      revert Unauthorized();
    }
    if (msg.value != publicCost * quant) {
      revert MaxSplaining ({
        reason: "msg.value too low"
      });
    }
    if (token721.getSupply() + quant > maxCap) {
      revert Unauthorized();
    } else {
      for (uint c = 0; c < quant;) {
        // mint each
        token721.mint(msg.sender, theTokenID.current());
        emit Transfer(address(0), msg.sender, theTokenID.current());
        theTokenID.increment();
        unchecked { ++c; }
      }
    }
  }

  function setFees(
    uint256 _normies
  , uint256 _public
  ) external
    onlyDev() {
    normiesCost = _normies;
    publicCost = _public;
  }

  function setContractURI(
    string memory newURI
  ) external
    onlyDev() {
    contractURL = newURI;
  }

  function contractURI()
    public
    view
    returns (string memory) {
    return contractURL;
  }

  function setJSON(
    string memory _description
  , string memory _image
  , string memory _animation
  ) external
    onlyDev() {
    description = _description;
    image = _image;
    animationURI = _animation;
  }

  function tokenURI(
    uint256 tokenId
  ) public
    view
    virtual
    override
    returns (string memory) {
    bytes memory json = abi.encodePacked(
      "{",
      '"name": "Homies Genesis #',
      Strings.toString(uint256(tokenId)),
      '",',
      '"description": "',
      description,
      '",',
      '"image": "',
      image,
      '",',
      '"animation_url": "',
      animationURI,
      '"',
      "}"
    );

    return string(
      abi.encodePacked(
        "data:application/json;base64,",
        Base64.encode(json)
      )
    );
  }

  /// @dev Function to receive ether, msg.data must be empty
  receive()
    external
    payable {
    // From PaymentSplitter.sol
    emit PaymentReceived(msg.sender, msg.value);
  }

  /// @dev Function to receive ether, msg.data is not empty
  fallback()
    external
    payable {
    // From PaymentSplitter.sol
    emit PaymentReceived(msg.sender, msg.value);
  }

  /// @dev this is a public getter for ETH blance on contract
  function getBalance()
    external
    view
    returns (uint) {
    return address(this).balance;
  }
}

// File: /contracts/modules/splitter/ISplitter.sol



/*     +%%#-                           ##.        =+.    .+#%#+:       *%%#:    .**+-      =+
 *   .%@@*#*:                          @@: *%-   #%*=  .*@@=.  =%.   .%@@*%*   +@@=+=%   .%##
 *  .%@@- -=+                         *@% :@@-  #@=#  -@@*     +@-  :@@@: ==* -%%. ***   #@=*
 *  %@@:  -.*  :.                    +@@-.#@#  =@%#.   :.     -@*  :@@@.  -:# .%. *@#   *@#*
 * *%@-   +++ +@#.-- .*%*. .#@@*@#  %@@%*#@@: .@@=-.         -%-   #%@:   +*-   =*@*   -@%=:
 * @@%   =##  +@@#-..%%:%.-@@=-@@+  ..   +@%  #@#*+@:      .*=     @@%   =#*   -*. +#. %@#+*@
 * @@#  +@*   #@#  +@@. -+@@+#*@% =#:    #@= :@@-.%#      -=.  :   @@# .*@*  =@=  :*@:=@@-:@+
 * -#%+@#-  :@#@@+%++@*@*:=%+..%%#=      *@  *@++##.    =%@%@%%#-  =#%+@#-   :*+**+=: %%++%*
 *
 * @title: [Not an EIP] Payment Splitter, interface for ether payments
 * @author: Max Flow O2 -> @MaxFlowO2 on bird app/GitHub
 * @notice: Interface for Payment Splitter
 */

// SPDX-License-Identifier: Apache-2.0

/******************************************************************************
 * Copyright 2022 Max Flow O2                                                 *
 *                                                                            *
 * Licensed under the Apache License, Version 2.0 (the "License");            *
 * you may not use this file except in compliance with the License.           *
 * You may obtain a copy of the License at                                    *
 *                                                                            *
 *     http://www.apache.org/licenses/LICENSE-2.0                             *
 *                                                                            *
 * Unless required by applicable law or agreed to in writing, software        *
 * distributed under the License is distributed on an "AS IS" BASIS,          *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
 * See the License for the specific language governing permissions and        *
 * limitations under the License.                                             *
 ******************************************************************************/

pragma solidity >=0.8.0 <0.9.0;

import "../../eip/165/IERC165.sol";

interface ISplitter is IERC165 {

  /// @dev returns total shares
  /// @return uint256 of all shares on contract
  function totalShares()
    external
    view
    returns (uint256);

  /// @dev returns shares of an address
  /// @param payee address of payee to return
  /// @return mapping(address => uint) of _shares
  function shares(
    address payee
  ) external
    view
    returns (uint256);

  /// @dev returns total releases in "eth"
  /// @return uint256 of all "eth" released in wei
  function totalReleased()
    external
    view
    returns (uint256);

  /// @dev returns released "eth" of an payee
  /// @param payee address of payee to look up
  /// @return mapping(address => uint) of _released
  function released(
    address payee
  ) external
    view
    returns (uint256);

  /// @dev returns amount of "eth" that can be released to payee
  /// @param payee address of payee to look up
  /// @return uint in wei of "eth" to release
  function releasable(
    address payee
  ) external
    view
    returns (uint256);

  /// @dev returns index number of payee
  /// @param payee number of index
  /// @return address at _payees[index]
  function payeeIndex(
    address payee
  ) external
    view
    returns (uint256);

  /// @dev this returns the array of payees[]
  /// @return address[] payees
  function payees()
    external
    view
    returns (address[] memory);

  /// @dev this claims all "eth" on contract for msg.sender
  function claim()
    external;

  /// @dev This pays all payees
  function payClaims()
    external;

  /// @dev This adds a payee
  /// @param payee Address of payee
  /// @param _shares Shares to send user
  function addPayee(
    address payee
  , uint256 _shares
  ) external;

  /// @dev This removes a payee
  /// @param payee Address of payee to remove
  /// @dev use payPayees() prior to use if anything is on the contract
  function removePayee(
    address payee
  ) external;

  /// @dev This removes all payees
  /// @dev use payPayees() prior to use if anything is on the contract
  function clearPayees()
    external;
}


// File: /contracts/modules/access/MaxAccess.sol



/*     +%%#-                           ##.        =+.    .+#%#+:       *%%#:    .**+-      =+
 *   .%@@*#*:                          @@: *%-   #%*=  .*@@=.  =%.   .%@@*%*   +@@=+=%   .%##
 *  .%@@- -=+                         *@% :@@-  #@=#  -@@*     +@-  :@@@: ==* -%%. ***   #@=*
 *  %@@:  -.*  :.                    +@@-.#@#  =@%#.   :.     -@*  :@@@.  -:# .%. *@#   *@#*
 * *%@-   +++ +@#.-- .*%*. .#@@*@#  %@@%*#@@: .@@=-.         -%-   #%@:   +*-   =*@*   -@%=:
 * @@%   =##  +@@#-..%%:%.-@@=-@@+  ..   +@%  #@#*+@:      .*=     @@%   =#*   -*. +#. %@#+*@
 * @@#  +@*   #@#  +@@. -+@@+#*@% =#:    #@= :@@-.%#      -=.  :   @@# .*@*  =@=  :*@:=@@-:@+
 * -#%+@#-  :@#@@+%++@*@*:=%+..%%#=      *@  *@++##.    =%@%@%%#-  =#%+@#-   :*+**+=: %%++%*
 *
 * @title: [Not an EIP]: MaxFlow's 173/Dev/Roles Interface
 * @author: Max Flow O2 -> @MaxFlowO2 on bird app/GitHub
 * @notice: Interface for MaxAccess
 */

// SPDX-License-Identifier: Apache-2.0

/******************************************************************************
 * Copyright 2022 Max Flow O2                                                 *
 *                                                                            *
 * Licensed under the Apache License, Version 2.0 (the "License");            *
 * you may not use this file except in compliance with the License.           *
 * You may obtain a copy of the License at                                    *
 *                                                                            *
 *     http://www.apache.org/licenses/LICENSE-2.0                             *
 *                                                                            *
 * Unless required by applicable law or agreed to in writing, software        *
 * distributed under the License is distributed on an "AS IS" BASIS,          *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
 * See the License for the specific language governing permissions and        *
 * limitations under the License.                                             *
 ******************************************************************************/

pragma solidity >=0.8.0 <0.9.0;

import "./IMAX173.sol";
import "./IMAXDEV.sol";
import "./IRoles.sol";

interface MaxAccess is IMAX173
                     , IMAXDEV
                     , IRoles {

  ///@dev this just imports all 3 and pushes to Implementation

}

// File: /contracts/modules/access/IRoles.sol



/*     +%%#-                           ##.        =+.    .+#%#+:       *%%#:    .**+-      =+
 *   .%@@*#*:                          @@: *%-   #%*=  .*@@=.  =%.   .%@@*%*   +@@=+=%   .%##
 *  .%@@- -=+                         *@% :@@-  #@=#  -@@*     +@-  :@@@: ==* -%%. ***   #@=*
 *  %@@:  -.*  :.                    +@@-.#@#  =@%#.   :.     -@*  :@@@.  -:# .%. *@#   *@#*
 * *%@-   +++ +@#.-- .*%*. .#@@*@#  %@@%*#@@: .@@=-.         -%-   #%@:   +*-   =*@*   -@%=:
 * @@%   =##  +@@#-..%%:%.-@@=-@@+  ..   +@%  #@#*+@:      .*=     @@%   =#*   -*. +#. %@#+*@
 * @@#  +@*   #@#  +@@. -+@@+#*@% =#:    #@= :@@-.%#      -=.  :   @@# .*@*  =@=  :*@:=@@-:@+
 * -#%+@#-  :@#@@+%++@*@*:=%+..%%#=      *@  *@++##.    =%@%@%%#-  =#%+@#-   :*+**+=: %%++%*
 *
 * @title: [Not an EIP]: Contract Roles Standard
 * @author: Max Flow O2 -> @MaxFlowO2 on bird app/GitHub
 * @notice: Interface for MaxAccess version of Roles
 */

// SPDX-License-Identifier: Apache-2.0

/******************************************************************************
 * Copyright 2022 Max Flow O2                                                 *
 *                                                                            *
 * Licensed under the Apache License, Version 2.0 (the "License");            *
 * you may not use this file except in compliance with the License.           *
 * You may obtain a copy of the License at                                    *
 *                                                                            *
 *     http://www.apache.org/licenses/LICENSE-2.0                             *
 *                                                                            *
 * Unless required by applicable law or agreed to in writing, software        *
 * distributed under the License is distributed on an "AS IS" BASIS,          *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
 * See the License for the specific language governing permissions and        *
 * limitations under the License.                                             *
 ******************************************************************************/

pragma solidity >=0.8.0 <0.9.0;

import "../../eip/165/IERC165.sol";

interface IRoles is IERC165 {

  /// @dev Returns `true` if `account` has been granted `role`.
  /// @param role: Bytes4 of a role
  /// @param account: Address to check
  /// @return bool true/false if account has role
  function hasRole(
    bytes4 role
  , address account
  ) external
    view
    returns (bool);

  /// @dev Returns the admin role that controls a role
  /// @param role: Role to check
  /// @return admin role
  function getRoleAdmin(
    bytes4 role
  ) external
    view 
    returns (bytes4);

  /// @dev Grants `role` to `account`
  /// @param role: Bytes4 of a role
  /// @param account: account to give role to
  function grantRole(
    bytes4 role
  , address account
  ) external;

  /// @dev Revokes `role` from `account`
  /// @param role: Bytes4 of a role
  /// @param account: account to revoke role from
  function revokeRole(
    bytes4 role
  , address account
  ) external;

  /// @dev Renounces `role` from `account`
  /// @param role: Bytes4 of a role
  function renounceRole(
    bytes4 role
  ) external;
}


// File: /contracts/modules/access/IMAXDEV.sol



/*     +%%#-                           ##.        =+.    .+#%#+:       *%%#:    .**+-      =+
 *   .%@@*#*:                          @@: *%-   #%*=  .*@@=.  =%.   .%@@*%*   +@@=+=%   .%##
 *  .%@@- -=+                         *@% :@@-  #@=#  -@@*     +@-  :@@@: ==* -%%. ***   #@=*
 *  %@@:  -.*  :.                    +@@-.#@#  =@%#.   :.     -@*  :@@@.  -:# .%. *@#   *@#* 
 * *%@-   +++ +@#.-- .*%*. .#@@*@#  %@@%*#@@: .@@=-.         -%-   #%@:   +*-   =*@*   -@%=: 
 * @@%   =##  +@@#-..%%:%.-@@=-@@+  ..   +@%  #@#*+@:      .*=     @@%   =#*   -*. +#. %@#+*@
 * @@#  +@*   #@#  +@@. -+@@+#*@% =#:    #@= :@@-.%#      -=.  :   @@# .*@*  =@=  :*@:=@@-:@+
 * -#%+@#-  :@#@@+%++@*@*:=%+..%%#=      *@  *@++##.    =%@%@%%#-  =#%+@#-   :*+**+=: %%++%* 
 *
 * @title: [Not an EIP]: Contract Developer Standard
 * @author: Max Flow O2 -> @MaxFlowO2 on bird app/GitHub
 * @notice: Interface for onlyDev() role
 */

// SPDX-License-Identifier: Apache-2.0

/******************************************************************************
 * Copyright 2022 Max Flow O2                                                 *
 *                                                                            *
 * Licensed under the Apache License, Version 2.0 (the "License");            *
 * you may not use this file except in compliance with the License.           *
 * You may obtain a copy of the License at                                    *
 *                                                                            *
 *     http://www.apache.org/licenses/LICENSE-2.0                             *
 *                                                                            *
 * Unless required by applicable law or agreed to in writing, software        *
 * distributed under the License is distributed on an "AS IS" BASIS,          *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
 * See the License for the specific language governing permissions and        *
 * limitations under the License.                                             *
 ******************************************************************************/

pragma solidity >=0.8.0 <0.9.0;

import "../../eip/165/IERC165.sol";

interface IMAXDEV is IERC165 {

  /// @dev Classic "EIP-173" but for onlyDev()
  /// @return Developer of contract
  function developer()
    external
    view
    returns (address);

  /// @dev This renounces your role as onlyDev()
  function renounceDeveloper()
    external;

  /// @dev Classic "EIP-173" but for onlyDev()
  /// @param newDeveloper: addres of new pending Developer role
  function transferDeveloper(
    address newDeveloper
  ) external;

  /// @dev This accepts the push-pull method of onlyDev()
  function acceptDeveloper()
    external;

  /// @dev This declines the push-pull method of onlyDev()
  function declineDeveloper()
    external;

  /// @dev This starts the push-pull method of onlyDev()
  /// @param newDeveloper: addres of new pending developer role
  function pushDeveloper(
    address newDeveloper
  ) external;

}

// File: /contracts/modules/access/IMAX173.sol



/*     +%%#-                           ##.        =+.    .+#%#+:       *%%#:    .**+-      =+
 *   .%@@*#*:                          @@: *%-   #%*=  .*@@=.  =%.   .%@@*%*   +@@=+=%   .%##
 *  .%@@- -=+                         *@% :@@-  #@=#  -@@*     +@-  :@@@: ==* -%%. ***   #@=*
 *  %@@:  -.*  :.                    +@@-.#@#  =@%#.   :.     -@*  :@@@.  -:# .%. *@#   *@#* 
 * *%@-   +++ +@#.-- .*%*. .#@@*@#  %@@%*#@@: .@@=-.         -%-   #%@:   +*-   =*@*   -@%=: 
 * @@%   =##  +@@#-..%%:%.-@@=-@@+  ..   +@%  #@#*+@:      .*=     @@%   =#*   -*. +#. %@#+*@
 * @@#  +@*   #@#  +@@. -+@@+#*@% =#:    #@= :@@-.%#      -=.  :   @@# .*@*  =@=  :*@:=@@-:@+
 * -#%+@#-  :@#@@+%++@*@*:=%+..%%#=      *@  *@++##.    =%@%@%%#-  =#%+@#-   :*+**+=: %%++%* 
 *
 * @title:  EIP-173: Contract Ownership Standard, MaxFlowO2's extension
 * @author: Max Flow O2 -> @MaxFlowO2 on bird app/GitHub
 * @notice: Interface for enhancing EIP-173
 * @custom:change-log UUPS Upgradable
 */

// SPDX-License-Identifier: Apache-2.0

/******************************************************************************
 * Copyright 2022 Max Flow O2                                                 *
 *                                                                            *
 * Licensed under the Apache License, Version 2.0 (the "License");            *
 * you may not use this file except in compliance with the License.           *
 * You may obtain a copy of the License at                                    *
 *                                                                            *
 *     http://www.apache.org/licenses/LICENSE-2.0                             *
 *                                                                            *
 * Unless required by applicable law or agreed to in writing, software        *
 * distributed under the License is distributed on an "AS IS" BASIS,          *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
 * See the License for the specific language governing permissions and        *
 * limitations under the License.                                             *
 ******************************************************************************/

pragma solidity >=0.8.0 <0.9.0;

import "../../eip/173/IERC173.sol";

interface IMAX173 is IERC173 {

  /// @dev This is the classic "EIP-173" method of renouncing onlyOwner()  
  function renounceOwnership()
    external;

  /// @dev This accepts the push-pull method of onlyOwner()
  function acceptOwnership()
    external;

  /// @dev This declines the push-pull method of onlyOwner()
  function declineOwnership()
    external;

  /// @dev This starts the push-pull method of onlyOwner()
  /// @param newOwner: addres of new pending owner role
  function pushOwnership(
    address newOwner
  ) external;

}


// File: /contracts/modules/2981/IERC2981Admin.sol



/*     +%%#-                           ##.        =+.    .+#%#+:       *%%#:    .**+-      =+
 *   .%@@*#*:                          @@: *%-   #%*=  .*@@=.  =%.   .%@@*%*   +@@=+=%   .%##
 *  .%@@- -=+                         *@% :@@-  #@=#  -@@*     +@-  :@@@: ==* -%%. ***   #@=*
 *  %@@:  -.*  :.                    +@@-.#@#  =@%#.   :.     -@*  :@@@.  -:# .%. *@#   *@#*
 * *%@-   +++ +@#.-- .*%*. .#@@*@#  %@@%*#@@: .@@=-.         -%-   #%@:   +*-   =*@*   -@%=:
 * @@%   =##  +@@#-..%%:%.-@@=-@@+  ..   +@%  #@#*+@:      .*=     @@%   =#*   -*. +#. %@#+*@
 * @@#  +@*   #@#  +@@. -+@@+#*@% =#:    #@= :@@-.%#      -=.  :   @@# .*@*  =@=  :*@:=@@-:@+
 * -#%+@#-  :@#@@+%++@*@*:=%+..%%#=      *@  *@++##.    =%@%@%%#-  =#%+@#-   :*+**+=: %%++%*
 *
 * @title: EIP-2981: NFT Royalty Standard, admin extension
 * @author: Max Flow O2 -> @MaxFlowO2 on bird app/GitHub
 * @notice: the ERC-165 identifier for this interface is unknown.
 * @custom:source https://eips.ethereum.org/EIPS/eip-2981
 * @custom:change-log MIT -> Apache-2.0
 *
 */

// SPDX-License-Identifier: Apache-2.0

/******************************************************************************
 * Copyright and related rights waived via CC0.                               *
 *                                                                            *
 * Licensed under the Apache License, Version 2.0 (the "License");            *
 * you may not use this file except in compliance with the License.           *
 * You may obtain a copy of the License at                                    *
 *                                                                            *
 *     http://www.apache.org/licenses/LICENSE-2.0                             *
 *                                                                            *
 * Unless required by applicable law or agreed to in writing, software        *
 * distributed under the License is distributed on an "AS IS" BASIS,          *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
 * See the License for the specific language governing permissions and        *
 * limitations under the License.                                             *
 ******************************************************************************/

pragma solidity >=0.8.0 <0.9.0;

import "../../eip/2981/IERC2981.sol";

interface IERC2981Admin is IERC2981 {

  /// @dev function (state storage) sets the royalty data for a token
  /// @param tokenId uint256 for the token
  /// @param receiver address for the royalty reciever for token
  /// @param permille uint16 for the permille of royalties 20 -> 2.0%
  function setRoyalties(
    uint256 tokenId
  , address receiver
  , uint16 permille
  ) external;

  /// @dev function (state storage) revokes the royalty data for a token
  /// @param tokenId uint256 for the token
  function revokeRoyalties(
    uint256 tokenId
  ) external;

  /// @dev function (state storage) sets the royalty data for a collection
  /// @param receiver address for the royalty reciever for token
  /// @param permille uint16 for the permille of royalties 20 -> 2.0%
  function setRoyalties(
    address receiver
  , uint16 permille
  ) external;

  /// @dev function (state storage) revokes the royalty data for a collection
  function revokeRoyalties()
    external;
}

// File: /contracts/lib/Strings.sol



/*     +%%#-                           ##.        =+.    .+#%#+:       *%%#:    .**+-      =+
 *   .%@@*#*:                          @@: *%-   #%*=  .*@@=.  =%.   .%@@*%*   +@@=+=%   .%##
 *  .%@@- -=+                         *@% :@@-  #@=#  -@@*     +@-  :@@@: ==* -%%. ***   #@=*
 *  %@@:  -.*  :.                    +@@-.#@#  =@%#.   :.     -@*  :@@@.  -:# .%. *@#   *@#*
 * *%@-   +++ +@#.-- .*%*. .#@@*@#  %@@%*#@@: .@@=-.         -%-   #%@:   +*-   =*@*   -@%=:
 * @@%   =##  +@@#-..%%:%.-@@=-@@+  ..   +@%  #@#*+@:      .*=     @@%   =#*   -*. +#. %@#+*@
 * @@#  +@*   #@#  +@@. -+@@+#*@% =#:    #@= :@@-.%#      -=.  :   @@# .*@*  =@=  :*@:=@@-:@+
 * -#%+@#-  :@#@@+%++@*@*:=%+..%%#=      *@  *@++##.    =%@%@%%#-  =#%+@#-   :*+**+=: %%++%*
 *
 * @title: Strings
 * @author: OpenZeppelin
 * @notice: Strings Library
 * @custom:source https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts-upgradeable/v4.7.3/contracts/utils/StringsUpgradeable.sol
 * @custom:change-log Readable, External/Public, Removed code comments, MIT -> Apache-2.0
 * @custom:change-log Added MaxSplaining
 * @custom:error-code Str:1 "hex length insufficient"
 *
 * Include with 'using Strings for <insert type>'
 */

// SPDX-License-Identifier: Apache-2.0

/******************************************************************************
 * Copyright 2022 Max Flow O2                                                 *
 *                                                                            *
 * Licensed under the Apache License, Version 2.0 (the "License");            *
 * you may not use this file except in compliance with the License.           *
 * You may obtain a copy of the License at                                    *
 *                                                                            *
 *     http://www.apache.org/licenses/LICENSE-2.0                             *
 *                                                                            *
 * Unless required by applicable law or agreed to in writing, software        *
 * distributed under the License is distributed on an "AS IS" BASIS,          *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
 * See the License for the specific language governing permissions and        *
 * limitations under the License.                                             *
 ******************************************************************************/

pragma solidity >=0.8.0 <0.9.0;

library Strings {

  bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
  uint8 private constant _ADDRESS_LENGTH = 20;

  error MaxSplaining(string reason);

  function toString(
    uint256 value
  ) internal
    pure
    returns (string memory) {
    // Inspired by OraclizeAPI's implementation - MIT licence
    // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol

    if (value == 0) {
      return "0";
    }
    uint256 temp = value;
    uint256 digits;
    while (temp != 0) {
      digits++;
      temp /= 10;
    }
    bytes memory buffer = new bytes(digits);
    while (value != 0) {
      digits -= 1;
      buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
      value /= 10;
    }
    return string(buffer);
  }

  function toHexString(
    uint256 value
  ) internal
    pure
    returns (string memory) {
    if (value == 0) {
      return "0x00";
    }
    uint256 temp = value;
    uint256 length = 0;
    while (temp != 0) {
      length++;
      temp >>= 8;
    }
    return toHexString(value, 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] = _HEX_SYMBOLS[value & 0xf];
      value >>= 4;
    }
    if (value != 0) {
      revert MaxSplaining({
        reason: "Str:1"
      });
    }
    return string(buffer);
  }

  function toHexString(
    address addr
  ) internal
    pure
    returns (string memory) {
    return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
  }
}


// File: /contracts/lib/Roles.sol



/*     +%%#-                           ##.        =+.    .+#%#+:       *%%#:    .**+-      =+
 *   .%@@*#*:                          @@: *%-   #%*=  .*@@=.  =%.   .%@@*%*   +@@=+=%   .%##
 *  .%@@- -=+                         *@% :@@-  #@=#  -@@*     +@-  :@@@: ==* -%%. ***   #@=*
 *  %@@:  -.*  :.                    +@@-.#@#  =@%#.   :.     -@*  :@@@.  -:# .%. *@#   *@#*
 * *%@-   +++ +@#.-- .*%*. .#@@*@#  %@@%*#@@: .@@=-.         -%-   #%@:   +*-   =*@*   -@%=:
 * @@%   =##  +@@#-..%%:%.-@@=-@@+  ..   +@%  #@#*+@:      .*=     @@%   =#*   -*. +#. %@#+*@
 * @@#  +@*   #@#  +@@. -+@@+#*@% =#:    #@= :@@-.%#      -=.  :   @@# .*@*  =@=  :*@:=@@-:@+
 * -#%+@#-  :@#@@+%++@*@*:=%+..%%#=      *@  *@++##.    =%@%@%%#-  =#%+@#-   :*+**+=: %%++%*
 *
 * @title: Roles.sol
 * @author: Max Flow O2 -> @MaxFlowO2 on bird app/GitHub
 * @notice: Library for MaxAcess.sol
 * @custom:change-log cleaned up variables
 *
 * Include with 'using Roles for Roles.Role;'
 */

// SPDX-License-Identifier: Apache-2.0

/******************************************************************************
 * Copyright 2022 Max Flow O2                                                 *
 *                                                                            *
 * Licensed under the Apache License, Version 2.0 (the "License");            *
 * you may not use this file except in compliance with the License.           *
 * You may obtain a copy of the License at                                    *
 *                                                                            *
 *     http://www.apache.org/licenses/LICENSE-2.0                             *
 *                                                                            *
 * Unless required by applicable law or agreed to in writing, software        *
 * distributed under the License is distributed on an "AS IS" BASIS,          *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
 * See the License for the specific language governing permissions and        *
 * limitations under the License.                                             *
 ******************************************************************************/

pragma solidity >=0.8.0 <0.9.0;

library Roles {

  bytes4 constant internal DEVS = 0xca4b208b;
  bytes4 constant internal OWNERS = 0x8da5cb5b;
  bytes4 constant internal ADMIN = 0xf851a440;

  struct Role {
    mapping(address => mapping(bytes4 => bool)) bearer;
    address owner;
    address developer;
    address admin;
  }

  event RoleChanged(bytes4 _role, address _user, bool _status);
  event AdminTransferred(address indexed previousAdmin, address indexed newAdmin);
  event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
  event DeveloperTransferred(address indexed previousDeveloper, address indexed newDeveloper);

  error Unauthorized();

  function add(
    Role storage role
  , bytes4 userRole
  , address account
  ) internal {
    if (account == address(0)) {
      revert Unauthorized();
    } else if (has(role, userRole, account)) {
      revert Unauthorized();
    }
    role.bearer[account][userRole] = true;
    emit RoleChanged(userRole, account, true);
  }

  function remove(
    Role storage role
  , bytes4 userRole
  , address account
  ) internal {
    if (account == address(0)) {
      revert Unauthorized();
    } else if (!has(role, userRole, account)) {
      revert Unauthorized();
    }
    role.bearer[account][userRole] = false;
    emit RoleChanged(userRole, account, false);
  }

  function has(
    Role storage role
  , bytes4  userRole
  , address account
  ) internal
    view
    returns (bool) {
    if (account == address(0)) {
      revert Unauthorized();
    }
    return role.bearer[account][userRole];
  }

  function setAdmin(
    Role storage role
  , address account
  ) internal {
    if (has(role, ADMIN, account)) {
      address old = role.admin;
      role.admin = account;
      emit AdminTransferred(old, role.admin);
    } else if (account == address(0)) {
      address old = role.admin;
      role.admin = account;
      emit AdminTransferred(old, role.admin);
    } else {
      revert Unauthorized();
    }
  }

  function setDeveloper(
    Role storage role
  , address account
  ) internal {
    if (has(role, DEVS, account)) {
      address old = role.developer;
      role.developer = account;
      emit DeveloperTransferred(old, role.developer);
    } else if (account == address(0)) {
      address old = role.admin;
      role.admin = account;
      emit AdminTransferred(old, role.admin);
    } else {
      revert Unauthorized();
    }
  }

  function setOwner(
    Role storage role
  , address account
  ) internal {
    if (has(role, OWNERS, account)) {
      address old = role.owner;
      role.owner = account;
      emit OwnershipTransferred(old, role.owner);
    } else if (account == address(0)) {
      address old = role.admin;
      role.admin = account;
      emit AdminTransferred(old, role.admin);
    } else {
      revert Unauthorized();
    }
  }

  function getAdmin(
    Role storage role
  ) internal 
    view
    returns (address) {
    return role.admin;
  }

  function getDeveloper(
    Role storage role
  ) internal
    view
    returns (address) {
    return role.developer;
  }

  function getOwner(
    Role storage role
  ) internal
    view
    returns (address) {
    return role.owner;
  }
}

// File: /contracts/lib/Payments.sol



/*     +%%#-                           ##.        =+.    .+#%#+:       *%%#:    .**+-      =+
 *   .%@@*#*:                          @@: *%-   #%*=  .*@@=.  =%.   .%@@*%*   +@@=+=%   .%##
 *  .%@@- -=+                         *@% :@@-  #@=#  -@@*     +@-  :@@@: ==* -%%. ***   #@=*
 *  %@@:  -.*  :.                    +@@-.#@#  =@%#.   :.     -@*  :@@@.  -:# .%. *@#   *@#*
 * *%@-   +++ +@#.-- .*%*. .#@@*@#  %@@%*#@@: .@@=-.         -%-   #%@:   +*-   =*@*   -@%=:
 * @@%   =##  +@@#-..%%:%.-@@=-@@+  ..   +@%  #@#*+@:      .*=     @@%   =#*   -*. +#. %@#+*@
 * @@#  +@*   #@#  +@@. -+@@+#*@% =#:    #@= :@@-.%#      -=.  :   @@# .*@*  =@=  :*@:=@@-:@+
 * -#%+@#-  :@#@@+%++@*@*:=%+..%%#=      *@  *@++##.    =%@%@%%#-  =#%+@#-   :*+**+=: %%++%*
 *
 * @title: [Not an EIP] Payment Splitter
 * @author: Max Flow O2 -> @MaxFlowO2 on bird app/GitHub
 * @notice: Library for two structs one with "ERC-20's" and one without
 * @custom:error-code PS:1 No Shares for address
 * @custom:error-code PS:2 No payment due for address
 * @custom:error-code PS:3 Can not use address(0)
 * @custom:error-code PS:4 Shares can not be 0
 * @custom:error-code PS:5 User has shares already
 * @custom:error-code PS:6 User not in payees
 * @custom:change-log added custom error-codes above
 */

// SPDX-License-Identifier: Apache-2.0

/******************************************************************************
 * Copyright 2022 Max Flow O2                                                 *
 *                                                                            *
 * Licensed under the Apache License, Version 2.0 (the "License");            *
 * you may not use this file except in compliance with the License.           *
 * You may obtain a copy of the License at                                    *
 *                                                                            *
 *     http://www.apache.org/licenses/LICENSE-2.0                             *
 *                                                                            *
 * Unless required by applicable law or agreed to in writing, software        *
 * distributed under the License is distributed on an "AS IS" BASIS,          *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
 * See the License for the specific language governing permissions and        *
 * limitations under the License.                                             *
 ******************************************************************************/

pragma solidity >=0.8.0 <0.9.0;

library Payments {

  struct GasTokens {
    uint256 totalShares;
    uint256 totalReleased;
    mapping(address => uint256) shares;
    mapping(address => uint256) released;
    address[] payees;
  }

  event PayeeAdded(address account, uint256 _shares);
  event PayeeRemoved(address account, uint256 _shares);
  event PayeesReset();
  event PaymentReleased(address to, uint256 amount);

  error MaxSplaining(string reason);

  function findIndex(
    address[] memory array
  , address query
  ) internal
    pure
    returns (bool found, uint256 index) {
    uint256 len = array.length;
    for (uint x = 0; x < len;) {
      if (array[x] == query) {
        found = true;
        index = x;
      }
      unchecked { ++x; }
    }
  }

  function getTotalReleased(
    GasTokens storage gasTokens
  ) internal
    view
    returns (uint256) {
    return gasTokens.totalReleased;
  }

  function getTotalShares(
    GasTokens storage gasTokens
  ) internal
    view
    returns (uint256) {
    return gasTokens.totalShares;
  }

  function payeeShares(
    GasTokens storage gasTokens
  , address payee
  ) internal
    view
    returns (uint256) {
    return gasTokens.shares[payee];
  }

  function payeeReleased(
    GasTokens storage gasTokens
  , address payee
  ) internal
    view
    returns (uint256) {
    return gasTokens.released[payee];
  }

  function payeeIndex(
    GasTokens storage gasTokens
  , address payee
  ) internal
    view
    returns (uint256) {
    (bool found, uint256 index) = findIndex(gasTokens.payees, payee);
    if (found) {
      return index;
    } else {
      revert MaxSplaining({
        reason: "PS:6"
      });
    }
  }

  function allPayees(
    GasTokens storage gasTokens
  ) internal
    view
    returns (address[] memory) {
    return gasTokens.payees;
  }

  function addPayee(
    GasTokens storage gasTokens
  , address payee
  , uint256 _shares
  ) internal {
    if (payee == address(0)) {
      revert MaxSplaining({
        reason: "PS:3"
      });
    } else if (_shares == 0) {
      revert MaxSplaining({
        reason: "PS:4"
      });
    } else if (gasTokens.shares[payee] > 0) {
      revert MaxSplaining({
        reason: "PS:5"
      });
    }
    gasTokens.payees.push(payee);
    gasTokens.shares[payee] = _shares;
    gasTokens.totalShares += _shares;
    emit PayeeAdded(payee, _shares);
  }

  function getPayees(
    GasTokens storage gasTokens
  ) internal
    view
    returns (address[] memory) {
    return gasTokens.payees;
  }

  function removePayee(
    GasTokens storage gasTokens
  , address payee
  ) internal {
    if (payee == address(0)) {
      revert MaxSplaining({
        reason: "PS:3"
      });
    }
    uint256 whacked = payeeIndex(gasTokens, payee);
    address last = gasTokens.payees[gasTokens.payees.length -1];
    gasTokens.payees[whacked] = last;
    gasTokens.payees.pop();
    uint256 whackedShares = gasTokens.shares[payee];
    delete gasTokens.shares[payee];
    gasTokens.totalShares -= whackedShares;
    emit PayeeRemoved(payee, whackedShares);
  }

  function clearPayees(
    GasTokens storage gasTokens
  ) internal {
    uint256 len = gasTokens.payees.length;
    for (uint x = 0; x < len;) {
      address whacked = gasTokens.payees[x];
      delete gasTokens.shares[whacked];
      unchecked { ++x; }
    }
    delete gasTokens.totalShares;
    delete gasTokens.payees;
    emit PayeesReset();
  }

  function processPayment(
    GasTokens storage gasTokens
  , address payee
  , uint256 payment
  ) internal {
    gasTokens.totalReleased += payment;
    gasTokens.released[payee] += payment;
    emit PaymentReleased(payee, payment);
  }
}

// File: /contracts/lib/Lists.sol



/*     +%%#-                           ##.        =+.    .+#%#+:       *%%#:    .**+-      =+
 *   .%@@*#*:                          @@: *%-   #%*=  .*@@=.  =%.   .%@@*%*   +@@=+=%   .%##
 *  .%@@- -=+                         *@% :@@-  #@=#  -@@*     +@-  :@@@: ==* -%%. ***   #@=*
 *  %@@:  -.*  :.                    +@@-.#@#  =@%#.   :.     -@*  :@@@.  -:# .%. *@#   *@#*
 * *%@-   +++ +@#.-- .*%*. .#@@*@#  %@@%*#@@: .@@=-.         -%-   #%@:   +*-   =*@*   -@%=:
 * @@%   =##  +@@#-..%%:%.-@@=-@@+  ..   +@%  #@#*+@:      .*=     @@%   =#*   -*. +#. %@#+*@
 * @@#  +@*   #@#  +@@. -+@@+#*@% =#:    #@= :@@-.%#      -=.  :   @@# .*@*  =@=  :*@:=@@-:@+
 * -#%+@#-  :@#@@+%++@*@*:=%+..%%#=      *@  *@++##.    =%@%@%%#-  =#%+@#-   :*+**+=: %%++%*
 *
 * @title: [Not an EIP]: Access lists
 * @author: @MaxFlowO2 on bird app/GitHub
 * @notice: Formerly whitelists, now allowlist, or whatever it's called.
 * @custom:change-log removed end variable/functions (un-needed)
 * @custom:change-log variables renamed from lib whitelist
 * @custom:change-log internal -> internal
 * @custom:error-code A:1 "(user) is already whitelisted."
 * @custom:error-code A:2 "(user) is not whitelisted."
 * @custom:error-code A:3 "Whitelist already enabled."
 * @custom:error-code A:4 "Whitelist already disabled."
 * @custom:change-log added custom error codes
 * @custom:change-log removed import "./Strings.sol"; (un-needed)
 *
 * Include with 'using Lists for Lists.Access;'
 */

// SPDX-License-Identifier: Apache-2.0

/******************************************************************************
 * Copyright 2022 Max Flow O2                                                 *
 *                                                                            *
 * Licensed under the Apache License, Version 2.0 (the "License");            *
 * you may not use this file except in compliance with the License.           *
 * You may obtain a copy of the License at                                    *
 *                                                                            *
 *     http://www.apache.org/licenses/LICENSE-2.0                             *
 *                                                                            *
 * Unless required by applicable law or agreed to in writing, software        *
 * distributed under the License is distributed on an "AS IS" BASIS,          *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
 * See the License for the specific language governing permissions and        *
 * limitations under the License.                                             *
 ******************************************************************************/

pragma solidity >=0.8.0 <0.9.0;

import "./CountersV2.sol";

library Lists {

  using CountersV2 for CountersV2.Counter;

  event ListChanged(bool _old, bool _new, address _address);
  event ListStatus(bool _old, bool _new);

  error MaxSplaining(string reason);

  struct Access {
    bool _status;
    CountersV2.Counter added;
    CountersV2.Counter removed;
    mapping(address => bool) allowed;
  }

  function add(
    Access storage list
  , address user
  ) internal {
    if (list.allowed[user]) {
      revert  MaxSplaining({
        reason : "A:1"
      });
    }
    // since now all previous values are false no need for another variable
    // and add them to the list!
    list.allowed[user] = true;
    // increment counter
    list.added.increment();
    // emit event
    emit ListChanged(false, list.allowed[user], user);
  }

  function remove(
    Access storage list
  , address user
  ) internal {
    if (!list.allowed[user]) {
      revert  MaxSplaining({
        reason : "A:2"
      });
    }
    // since now all previous values are true no need for another variable
    // and remove them from the list!
    list.allowed[user] = false;
    // increment counter
    list.removed.increment();
    // emit event
    emit ListChanged(true, list.allowed[user], user);
  }

  function enable(
    Access storage list
  ) internal {
    if (list._status) {
      revert  MaxSplaining({
        reason : "A:3"
      });
    }
    list._status = true;
    emit ListStatus(false, list._status);
  }

  function disable(
    Access storage list
  ) internal {
    if (!list._status) {
      revert  MaxSplaining({
        reason : "A:4"
      });
    }
    list._status = false;
    emit ListStatus(true, list._status);
  }

  function status(
    Access storage list
  ) internal
    view
    returns (bool) {
    return list._status;
  }

  function totalAdded(
    Access storage list
  ) internal
    view
    returns (uint) {
    return list.added.current();
  }

  function totalRemoved(
    Access storage list
  ) internal
    view
    returns (uint) {
    return list.removed.current();
  }

  function onList(
    Access storage list
  , address user
  ) internal
    view
    returns (bool) {
    return list.allowed[user];
  }
}

// File: /contracts/lib/CountersV2.sol



/*     +%%#-                           ##.        =+.    .+#%#+:       *%%#:    .**+-      =+
 *   .%@@*#*:                          @@: *%-   #%*=  .*@@=.  =%.   .%@@*%*   +@@=+=%   .%##
 *  .%@@- -=+                         *@% :@@-  #@=#  -@@*     +@-  :@@@: ==* -%%. ***   #@=*
 *  %@@:  -.*  :.                    +@@-.#@#  =@%#.   :.     -@*  :@@@.  -:# .%. *@#   *@#*
 * *%@-   +++ +@#.-- .*%*. .#@@*@#  %@@%*#@@: .@@=-.         -%-   #%@:   +*-   =*@*   -@%=:
 * @@%   =##  +@@#-..%%:%.-@@=-@@+  ..   +@%  #@#*+@:      .*=     @@%   =#*   -*. +#. %@#+*@
 * @@#  +@*   #@#  +@@. -+@@+#*@% =#:    #@= :@@-.%#      -=.  :   @@# .*@*  =@=  :*@:=@@-:@+
 * -#%+@#-  :@#@@+%++@*@*:=%+..%%#=      *@  *@++##.    =%@%@%%#-  =#%+@#-   :*+**+=: %%++%*
 *
 * @title: CountersV2.sol
 * @author Matt Condon (@shrugs)
 * @notice Provides counters that can only be incremented, decremented, reset or set. 
 * This can be used e.g. to track the number of elements in a mapping, issuing ERC721 ids
 * or counting request ids.
 * @custom:change-log MIT -> Apache-2.0
 * @custom:change-log Edited for more NFT functionality added .set(uint)
 * @custom:change-log added event CounterNumberChangedTo(uint _number).
 * @custom:change-log added error MaxSplaining(string reason).
 * @custom:change-log internal -> internal functions
 * @custom:error-code C2:1 "No negatives in uints" - overflow protection
 *
 * Include with `using CountersV2 for CountersV2.Counter;`
 */

// SPDX-License-Identifier: Apache-2.0

/******************************************************************************
 * Copyright 2022 Max Flow O2                                                 *
 *                                                                            *
 * Licensed under the Apache License, Version 2.0 (the "License");            *
 * you may not use this file except in compliance with the License.           *
 * You may obtain a copy of the License at                                    *
 *                                                                            *
 *     http://www.apache.org/licenses/LICENSE-2.0                             *
 *                                                                            *
 * Unless required by applicable law or agreed to in writing, software        *
 * distributed under the License is distributed on an "AS IS" BASIS,          *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
 * See the License for the specific language governing permissions and        *
 * limitations under the License.                                             *
 ******************************************************************************/

pragma solidity >=0.8.0 <0.9.0;

library CountersV2 {

  struct Counter {
    uint256 value;
  }

  event CounterNumberChangedTo(uint _number);

  error MaxSplaining(string reason);

  function current(
    Counter storage counter
  ) internal
    view
    returns (uint256) {
    return counter.value;
  }

  function increment(
    Counter storage counter
  ) internal {
    unchecked {
      ++counter.value;
    }
  }

  function decrement(
    Counter storage counter
  ) internal {
    if (counter.value == 0) {
      revert MaxSplaining({
        reason : "C2:1"
      });
    }
    unchecked {
      --counter.value;
    }
  }

  function reset(
    Counter storage counter
  ) internal {
    counter.value = 0;
    emit CounterNumberChangedTo(counter.value);
  }

  function set(
    Counter storage counter
  , uint number
  ) internal {
    counter.value = number;
    emit CounterNumberChangedTo(counter.value);
  }  
}

// File: /contracts/lib/Address.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)

pragma solidity ^0.8.1;

/**
 * @notice : Collection of functions related to the address type
 */
library Address {
    /**
     * @notice : 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
     * ====
     *
     * [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;
    }

    /**
     * @notice : 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.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @notice : 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");
    }

    /**
     * @notice : 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);
    }

    /**
     * @notice : 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");
    }

    /**
     * @notice : 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);
    }

    /**
     * @notice : 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");
    }

    /**
     * @notice : 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);
    }

    /**
     * @notice : 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");
    }

    /**
     * @notice : 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);
    }

    /**
     * @notice : 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);
        }
    }

    /**
     * @notice : 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: /contracts/lib/721.sol



/*     +%%#-                           ##.        =+.    .+#%#+:       *%%#:    .**+-      =+
 *   .%@@*#*:                          @@: *%-   #%*=  .*@@=.  =%.   .%@@*%*   +@@=+=%   .%##
 *  .%@@- -=+                         *@% :@@-  #@=#  -@@*     +@-  :@@@: ==* -%%. ***   #@=*
 *  %@@:  -.*  :.                    +@@-.#@#  =@%#.   :.     -@*  :@@@.  -:# .%. *@#   *@#*
 * *%@-   +++ +@#.-- .*%*. .#@@*@#  %@@%*#@@: .@@=-.         -%-   #%@:   +*-   =*@*   -@%=:
 * @@%   =##  +@@#-..%%:%.-@@=-@@+  ..   +@%  #@#*+@:      .*=     @@%   =#*   -*. +#. %@#+*@
 * @@#  +@*   #@#  +@@. -+@@+#*@% =#:    #@= :@@-.%#      -=.  :   @@# .*@*  =@=  :*@:=@@-:@+
 * -#%+@#-  :@#@@+%++@*@*:=%+..%%#=      *@  *@++##.    =%@%@%%#-  =#%+@#-   :*+**+=: %%++%*
 *
 * @title: Library 721
 * @author: Max Flow O2 -> @MaxFlowO2 on bird app/GitHub
 * @notice: Library for EIP 721
 * @custom:error-code L:1 "non-existent tokenId" 
 * @custom:error-code L:2 "approval to current owner"
 * @custom:error-code L:3 "approve caller is not token owner nor approved for all"
 * @custom:error-code L:4 "approve to caller"
 * @custom:error-code L:5 "caller is not token owner nor approved"
 * @custom:error-code L:6 "transfer from incorrect owner"
 * @custom:error-code L:7 "transfer to the zero address"
 * @custom:error-code L:8 "mint to the zero address"
 * @custom:error-code L:9 "token already minted"
 * @custom:change-log Custom errors added above
 *
 * Include with 'using Lib721 for Lib721.Token;'
 */

// SPDX-License-Identifier: Apache-2.0

/******************************************************************************
 * Copyright 2022 Max Flow O2                                                 *
 *                                                                            *
 * Licensed under the Apache License, Version 2.0 (the "License");            *
 * you may not use this file except in compliance with the License.           *
 * You may obtain a copy of the License at                                    *
 *                                                                            *
 *     http://www.apache.org/licenses/LICENSE-2.0                             *
 *                                                                            *
 * Unless required by applicable law or agreed to in writing, software        *
 * distributed under the License is distributed on an "AS IS" BASIS,          *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
 * See the License for the specific language governing permissions and        *
 * limitations under the License.                                             *
 ******************************************************************************/

pragma solidity >=0.8.0 <0.9.0;

import "./Strings.sol";
import "./CountersV2.sol";

library Lib721 {

  using Strings for uint256;
  using CountersV2 for CountersV2.Counter;

  struct Token {
    mapping(uint256 => address) owners;
    mapping(address => uint256) balances;
    mapping(uint256 => address) tokenApprovals;
    mapping(address => mapping(address => bool)) operatorApprovals;
    string name;
    string symbol;
    string baseURI;
    CountersV2.Counter supply;
  }

  event NameSet(string name);
  event SymbolSet(string symbol);
  event NewBaseURI(string baseURI);

  error MaxSplaining(string reason);

  function getBalanceOf(
    Token storage token
  , address owner
  ) internal
    view
    returns (uint256) {
    return token.balances[owner];
  }

  function getOwnerOf(
    Token storage token
  , uint256 tokenId
  ) internal 
    view
    returns (address) {
    return token.owners[tokenId];
  }

  function setName(
    Token storage token
  , string memory newName
  ) internal {
    token.name = newName;
    emit NameSet(newName);
  }

  function getName(
   Token storage token
  ) internal
    view
    returns (string memory) {
    return token.name;
  }

  function setSymbol(
    Token storage token
  , string memory newSymbol
  ) internal {
    token.symbol = newSymbol;
    emit SymbolSet(newSymbol);
  }

  function getSymbol(
   Token storage token
  ) internal
    view
    returns (string memory) {
    return token.symbol;
  }

  function getSupply(
   Token storage token
  ) internal
    view
    returns (uint256) {
    return token.supply.current();
  }

  function setBaseURI(
    Token storage token
  , string memory newURI
  ) internal {
    token.baseURI = newURI;
    emit NewBaseURI(newURI);
  }

  function getTokenURI(
    Token storage token
  , uint256 tokenId
  ) internal
    view
    returns (string memory) {
    if (getOwnerOf(token, tokenId) == address(0)) {
      revert MaxSplaining({
        reason: "L:1"
      });
    }
    return bytes(token.baseURI).length > 0 ? string(abi.encodePacked(token.baseURI, tokenId.toString())) : "";
  }

  function setApprove(
    Token storage token
  , address to
  , address by
  , uint256 tokenId
  ) internal {
    address owner = getOwnerOf(token, tokenId);
    if (to == owner) {
      revert MaxSplaining({
        reason: "L:2"
      });
    } else if (!isApprovedOrOwner(token, by, tokenId)) {
      revert MaxSplaining({
        reason: "L:3"
      });
    }
    token.tokenApprovals[tokenId] = to;
  }

  function getApproved(
    Token storage token
  , uint256 tokenId
  ) internal
    view
    returns (address) {
    if (getOwnerOf(token, tokenId) == address(0)) {
      revert MaxSplaining({
        reason: "L:1"
      });
    }
    return token.tokenApprovals[tokenId];
  }

  function setApprovalForAll(
    Token storage token
  , address operator
  , address from
  , bool approved
  ) internal {
    if (from == operator) {
      revert MaxSplaining({
        reason: "L:4"
      });
    }
    token.operatorApprovals[from][operator] = approved;
  }

  function isApprovedForAll(
    Token storage token
  , address owner
  , address operator
  ) internal
    view
    returns (bool) {
    return token.operatorApprovals[owner][operator];
  }

  function isApprovedOrOwner(
    Token storage token
  , address spender
  , uint256 tokenId
  ) internal
    view
    returns (bool) {
    address owner = getOwnerOf(token, tokenId);
    return (
      spender == owner ||
      isApprovedForAll(token, owner, spender) ||
      getApproved(token, tokenId) == spender
    );
  }

  function doTransferFrom(
    Token storage token
  , address from
  , address to
  , address by
  , uint256 tokenId
  ) internal {
    if (!isApprovedOrOwner(token, by, tokenId)) {
      revert MaxSplaining({
        reason: "L:5"
      });
    }
    address owner = getOwnerOf(token, tokenId);
    if (owner != from) {
      revert MaxSplaining({
        reason: "L:6"
      });
    } else if (to == address(0)) {
      revert MaxSplaining({
        reason: "L:7"
      });
    }
    // Clear approvals from the previous owner
    setApprove(token, address(0), by, tokenId);
    // Change balances
    token.balances[from] -= 1;
    token.balances[to] += 1;
    // Move tokenId
    token.owners[tokenId] = to;
  }

  function mint(
    Token storage token
  , address to
  , uint256 tokenId
  ) internal {
    if (to == address(0)) {
      revert MaxSplaining({
        reason: "L:8"
      });
    } else if (getOwnerOf(token, tokenId) != address(0)) {
      revert MaxSplaining({
        reason: "L:9"
      });
    }
    token.balances[to] += 1;
    token.owners[tokenId] = to;
    token.supply.increment();
  }

  function burn(
    Token storage token
  , address by
  , uint256 tokenId
  ) internal {
    address owner = getOwnerOf(token, tokenId);
    // Clear approvals
    setApprove(token, address(0), by, tokenId);
    // Change balances
    token.balances[owner] -= 1;
    delete token.owners[tokenId];
    token.supply.decrement();
  }
}

// File: /contracts/lib/2981c.sol



/*     +%%#-                           ##.        =+.    .+#%#+:       *%%#:    .**+-      =+
 *   .%@@*#*:                          @@: *%-   #%*=  .*@@=.  =%.   .%@@*%*   +@@=+=%   .%##
 *  .%@@- -=+                         *@% :@@-  #@=#  -@@*     +@-  :@@@: ==* -%%. ***   #@=*
 *  %@@:  -.*  :.                    +@@-.#@#  =@%#.   :.     -@*  :@@@.  -:# .%. *@#   *@#*
 * *%@-   +++ +@#.-- .*%*. .#@@*@#  %@@%*#@@: .@@=-.         -%-   #%@:   +*-   =*@*   -@%=:
 * @@%   =##  +@@#-..%%:%.-@@=-@@+  ..   +@%  #@#*+@:      .*=     @@%   =#*   -*. +#. %@#+*@
 * @@#  +@*   #@#  +@@. -+@@+#*@% =#:    #@= :@@-.%#      -=.  :   @@# .*@*  =@=  :*@:=@@-:@+
 * -#%+@#-  :@#@@+%++@*@*:=%+..%%#=      *@  *@++##.    =%@%@%%#-  =#%+@#-   :*+**+=: %%++%*
 *
 * @title: Library 2981
 * @author: Max Flow O2 -> @MaxFlowO2 on bird app/GitHub
 * @notice: Library for EIP 2981
 * @custom:error-code R:1 Permille out of bounds
 * @custom:change-log Custom errors added above
 *
 * Include with 'using Lib2981 for Lib2981.Royalties;' -- unique per collection
 * Include with 'using Lib2981 for Lib2981.MappedRoyalties;' -- unique per token
 */

// SPDX-License-Identifier: Apache-2.0

/******************************************************************************
 * Copyright 2022 Max Flow O2                                                 *
 *                                                                            *
 * Licensed under the Apache License, Version 2.0 (the "License");            *
 * you may not use this file except in compliance with the License.           *
 * You may obtain a copy of the License at                                    *
 *                                                                            *
 *     http://www.apache.org/licenses/LICENSE-2.0                             *
 *                                                                            *
 * Unless required by applicable law or agreed to in writing, software        *
 * distributed under the License is distributed on an "AS IS" BASIS,          *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
 * See the License for the specific language governing permissions and        *
 * limitations under the License.                                             *
 ******************************************************************************/

pragma solidity >=0.8.0 <0.9.0;

library Lib2981c {

  struct Royalties {
    address receiver;
    uint16 permille;
  }

  event RoyaltiesSet(uint256 token, address recipient, uint16 value);
  event RoyaltiesSet(address recipient, uint16 value);

  error MaxSplaining(string reason);

  function setRoyalties(
    Royalties storage royalties
  , address receiver
  , uint16 permille
  ) internal {
    if (permille >= 1000 ||  permille == 0) {
      revert MaxSplaining({
        reason: "R:1"
      });
    }
    royalties.receiver = receiver;
    royalties.permille = permille;
    emit RoyaltiesSet(
           royalties.receiver
         , royalties.permille
         );
  }

  function revokeRoyalties(
    Royalties storage royalties
  ) internal {
    delete royalties.receiver;
    delete royalties.permille;
    emit RoyaltiesSet(
           royalties.receiver
         , royalties.permille
         );
  }

  function royaltyInfo(
    Royalties storage royalties
  , uint256 tokenId
  , uint256 salePrice
  ) internal
    view
    returns (
      address receiver
    , uint256 royaltyAmount
    ) {
    receiver = royalties.receiver;
    royaltyAmount = salePrice * royalties.permille / 1000;
  }
}

// File: /contracts/eip/721/IERC721TokenReceiver.sol



/*     +%%#-                           ##.        =+.    .+#%#+:       *%%#:    .**+-      =+
 *   .%@@*#*:                          @@: *%-   #%*=  .*@@=.  =%.   .%@@*%*   +@@=+=%   .%##
 *  .%@@- -=+                         *@% :@@-  #@=#  -@@*     +@-  :@@@: ==* -%%. ***   #@=*
 *  %@@:  -.*  :.                    +@@-.#@#  =@%#.   :.     -@*  :@@@.  -:# .%. *@#   *@#*
 * *%@-   +++ +@#.-- .*%*. .#@@*@#  %@@%*#@@: .@@=-.         -%-   #%@:   +*-   =*@*   -@%=:
 * @@%   =##  +@@#-..%%:%.-@@=-@@+  ..   +@%  #@#*+@:      .*=     @@%   =#*   -*. +#. %@#+*@
 * @@#  +@*   #@#  +@@. -+@@+#*@% =#:    #@= :@@-.%#      -=.  :   @@# .*@*  =@=  :*@:=@@-:@+
 * -#%+@#-  :@#@@+%++@*@*:=%+..%%#=      *@  *@++##.    =%@%@%%#-  =#%+@#-   :*+**+=: %%++%*
 *
 * @title: ERC-721 Non-Fungible Token Standard, required wallet interface
 * @author: William Entriken, Dieter Shirley, Jacob Evans, Nastassia Sachs
 * @notice: the ERC-165 identifier for this interface is 0x150b7a02.
 * @custom:source https://eips.ethereum.org/EIPS/eip-721
 * @custom:change-log interface ERC721TokenReceiver -> interface IERC721TokenReceiver
 * @custom:change-log readability enhanced
 * @custom:change-log MIT -> Apache-2.0
 * @custom:change-log TypeError: Data location must be "memory" or "calldata" for parameter (line 60)
 *
 */

// SPDX-License-Identifier: Apache-2.0

/******************************************************************************
 * Copyright and related rights waived via CC0.                               *
 *                                                                            *
 * Licensed under the Apache License, Version 2.0 (the "License");            *
 * you may not use this file except in compliance with the License.           *
 * You may obtain a copy of the License at                                    *
 *                                                                            *
 *     http://www.apache.org/licenses/LICENSE-2.0                             *
 *                                                                            *
 * Unless required by applicable law or agreed to in writing, software        *
 * distributed under the License is distributed on an "AS IS" BASIS,          *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
 * See the License for the specific language governing permissions and        *
 * limitations under the License.                                             *
 ******************************************************************************/

pragma solidity >=0.8.0 <0.9.0;

import "../165/IERC165.sol";

interface IERC721TokenReceiver is IERC165 {

  /// @notice Handle the receipt of an NFT
  /// @notice The ERC721 smart contract calls this function on the recipient
  ///  after a `transfer`. This function MAY throw to revert and reject the
  ///  transfer. Return of other than the magic value MUST result in the
  ///  transaction being reverted.
  ///  Note: the contract address is always the message sender.
  /// @param _operator The address which called `safeTransferFrom` function
  /// @param _from The address which previously owned the token
  /// @param _tokenId The NFT identifier which is being transferred
  /// @param _data Additional data with no specified format
  /// @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
  ///  unless throwing
  function onERC721Received(
    address _operator
  , address _from
  , uint256 _tokenId
  , bytes calldata _data
  ) external
    returns(bytes4);
}

// File: /contracts/eip/721/IERC721Metadata.sol



/*     +%%#-                           ##.        =+.    .+#%#+:       *%%#:    .**+-      =+
 *   .%@@*#*:                          @@: *%-   #%*=  .*@@=.  =%.   .%@@*%*   +@@=+=%   .%##
 *  .%@@- -=+                         *@% :@@-  #@=#  -@@*     +@-  :@@@: ==* -%%. ***   #@=*
 *  %@@:  -.*  :.                    +@@-.#@#  =@%#.   :.     -@*  :@@@.  -:# .%. *@#   *@#*
 * *%@-   +++ +@#.-- .*%*. .#@@*@#  %@@%*#@@: .@@=-.         -%-   #%@:   +*-   =*@*   -@%=:
 * @@%   =##  +@@#-..%%:%.-@@=-@@+  ..   +@%  #@#*+@:      .*=     @@%   =#*   -*. +#. %@#+*@
 * @@#  +@*   #@#  +@@. -+@@+#*@% =#:    #@= :@@-.%#      -=.  :   @@# .*@*  =@=  :*@:=@@-:@+
 * -#%+@#-  :@#@@+%++@*@*:=%+..%%#=      *@  *@++##.    =%@%@%%#-  =#%+@#-   :*+**+=: %%++%*
 *
 * @title: ERC-721 Non-Fungible Token Standard, optional metadata extension
 * @author: William Entriken, Dieter Shirley, Jacob Evans, Nastassia Sachs
 * @notice: the ERC-165 identifier for this interface is 0x5b5e139f.
 * @custom:source https://eips.ethereum.org/EIPS/eip-721
 * @custom:change-log interface ERC721Metadata * is ERC721 * -> interface IERC721Metadata
 * @custom:change-log readability enhanced
 * @custom:change-log MIT -> Apache-2.0
 * @custom:change-log  Data location must be "memory" or "calldata" for return parameter (lines 48, 54, 64)
 *
 */

// SPDX-License-Identifier: Apache-2.0

/******************************************************************************
 * Copyright and related rights waived via CC0.                               *
 *                                                                            *
 * Licensed under the Apache License, Version 2.0 (the "License");            *
 * you may not use this file except in compliance with the License.           *
 * You may obtain a copy of the License at                                    *
 *                                                                            *
 *     http://www.apache.org/licenses/LICENSE-2.0                             *
 *                                                                            *
 * Unless required by applicable law or agreed to in writing, software        *
 * distributed under the License is distributed on an "AS IS" BASIS,          *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
 * See the License for the specific language governing permissions and        *
 * limitations under the License.                                             *
 ******************************************************************************/

pragma solidity >=0.8.0 <0.9.0;

import "./IERC721.sol";

interface IERC721Metadata is IERC721 {

  /// @notice A descriptive name for a collection of NFTs in this contract
  function name()
    external
    view
    returns (string memory _name);

  /// @notice An abbreviated name for NFTs in this contract
  function symbol()
    external
    view
    returns (string memory _symbol);

  /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.
  /// @notice Throws if `_tokenId` is not a valid NFT. URIs are defined in RFC
  ///  3986. The URI may point to a JSON file that conforms to the "ERC721
  ///  Metadata JSON Schema".
  function tokenURI(
    uint256 _tokenId
  ) external
    view
    returns (string memory);
}

// File: /contracts/eip/721/IERC721.sol



/*     +%%#-                           ##.        =+.    .+#%#+:       *%%#:    .**+-      =+
 *   .%@@*#*:                          @@: *%-   #%*=  .*@@=.  =%.   .%@@*%*   +@@=+=%   .%##
 *  .%@@- -=+                         *@% :@@-  #@=#  -@@*     +@-  :@@@: ==* -%%. ***   #@=*
 *  %@@:  -.*  :.                    +@@-.#@#  =@%#.   :.     -@*  :@@@.  -:# .%. *@#   *@#*
 * *%@-   +++ +@#.-- .*%*. .#@@*@#  %@@%*#@@: .@@=-.         -%-   #%@:   +*-   =*@*   -@%=:
 * @@%   =##  +@@#-..%%:%.-@@=-@@+  ..   +@%  #@#*+@:      .*=     @@%   =#*   -*. +#. %@#+*@
 * @@#  +@*   #@#  +@@. -+@@+#*@% =#:    #@= :@@-.%#      -=.  :   @@# .*@*  =@=  :*@:=@@-:@+
 * -#%+@#-  :@#@@+%++@*@*:=%+..%%#=      *@  *@++##.    =%@%@%%#-  =#%+@#-   :*+**+=: %%++%*
 *
 * @title: ERC-721 Non-Fungible Token Standard
 * @author: William Entriken, Dieter Shirley, Jacob Evans, Nastassia Sachs
 * @notice: the ERC-165 identifier for this interface is 0x80ac58cd.
 * @custom:source https://eips.ethereum.org/EIPS/eip-721
 * @custom:change-log interface ERC721 * is ERC165 * -> interface IERC721
 * @custom:change-log removed payable from IERC721 
 * @custom:change-log removed events from IERC721 (handled in Lib721)
 * @custom:change-log readability enhanced
 * @custom:change-log MIT -> Apache-2.0
 * @custom:change-log TypeError: Data location must be "memory" or "calldata" for parameter (line 84)
 *
 */

// SPDX-License-Identifier: Apache-2.0

/******************************************************************************
 * Copyright and related rights waived via CC0.                               *
 *                                                                            *
 * Licensed under the Apache License, Version 2.0 (the "License");            *
 * you may not use this file except in compliance with the License.           *
 * You may obtain a copy of the License at                                    *
 *                                                                            *
 *     http://www.apache.org/licenses/LICENSE-2.0                             *
 *                                                                            *
 * Unless required by applicable law or agreed to in writing, software        *
 * distributed under the License is distributed on an "AS IS" BASIS,          *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
 * See the License for the specific language governing permissions and        *
 * limitations under the License.                                             *
 ******************************************************************************/

pragma solidity >=0.8.0 <0.9.0;

import "../165/IERC165.sol";

interface IERC721 is IERC165 {

  /// @dev This emits when ownership of any NFT changes by any mechanism.
  ///  This event emits when NFTs are created (`from` == 0) and destroyed
  ///  (`to` == 0). Exception: during contract creation, any number of NFTs
  ///  may be created and assigned without emitting Transfer. At the time of
  ///  any transfer, the approved address for that NFT (if any) is reset to none.
  event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);

  /// @dev This emits when the approved address for an NFT is changed or
  ///  reaffirmed. The zero address indicates there is no approved address.
  ///  When a Transfer event emits, this also indicates that the approved
  ///  address for that NFT (if any) is reset to none.
  event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);

  /// @dev This emits when an operator is enabled or disabled for an owner.
  ///  The operator can manage all NFTs of the owner.
  event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);

  /// @notice Count all NFTs assigned to an owner
  /// @notice NFTs assigned to the zero address are considered invalid, and this
  ///  function throws for queries about the zero address.
  /// @param _owner An address for whom to query the balance
  /// @return The number of NFTs owned by `_owner`, possibly zero
  function balanceOf(
    address _owner
  ) external
    view
    returns (uint256);

  /// @notice Find the owner of an NFT
  /// @notice NFTs assigned to zero address are considered invalid, and queries
  ///  about them do throw.
  /// @param _tokenId The identifier for an NFT
  /// @return The address of the owner of the NFT
  function ownerOf(
    uint256 _tokenId
  ) external
    view
    returns (address);

  /// @notice Transfers the ownership of an NFT from one address to another address
  /// @notice Throws unless `msg.sender` is the current owner, an authorized
  ///  operator, or the approved address for this NFT. Throws if `_from` is
  ///  not the current owner. Throws if `_to` is the zero address. Throws if
  ///  `_tokenId` is not a valid NFT. When transfer is complete, this function
  ///  checks if `_to` is a smart contract (code size > 0). If so, it calls
  ///  `onERC721Received` on `_to` and throws if the return value is not
  ///  `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
  /// @param _from The current owner of the NFT
  /// @param _to The new owner
  /// @param _tokenId The NFT to transfer
  /// @param data Additional data with no specified format, sent in call to `_to`
  function safeTransferFrom(
    address _from
  , address _to
  , uint256 _tokenId
  , bytes calldata data
  ) external;

  /// @notice Transfers the ownership of an NFT from one address to another address
  /// @notice This works identically to the other function with an extra data parameter,
  ///  except this function just sets data to "".
  /// @param _from The current owner of the NFT
  /// @param _to The new owner
  /// @param _tokenId The NFT to transfer
  function safeTransferFrom(
    address _from
  , address _to
  , uint256 _tokenId
  ) external;

  /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE
  ///  TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE
  ///  THEY MAY BE PERMANENTLY LOST
  /// @notice Throws unless `msg.sender` is the current owner, an authorized
  ///  operator, or the approved address for this NFT. Throws if `_from` is
  ///  not the current owner. Throws if `_to` is the zero address. Throws if
  ///  `_tokenId` is not a valid NFT.
  /// @param _from The current owner of the NFT
  /// @param _to The new owner
  /// @param _tokenId The NFT to transfer
  function transferFrom(
    address _from
  , address _to
  , uint256 _tokenId
  ) external;

  /// @notice Change or reaffirm the approved address for an NFT
  /// @notice The zero address indicates there is no approved address.
  ///  Throws unless `msg.sender` is the current NFT owner, or an authorized
  ///  operator of the current owner.
  /// @param _approved The new approved NFT controller
  /// @param _tokenId The NFT to approve
  function approve(
    address _approved
  , uint256 _tokenId
  ) external;

  /// @notice Enable or disable approval for a third party ("operator") to manage
  ///  all of `msg.sender`'s assets
  /// @notice Emits the ApprovalForAll event. The contract MUST allow
  ///  multiple operators per owner.
  /// @param _operator Address to add to the set of authorized operators
  /// @param _approved True if the operator is approved, false to revoke approval
  function setApprovalForAll(
    address _operator
  , bool _approved
  ) external;

  /// @notice Get the approved address for a single NFT
  /// @notice Throws if `_tokenId` is not a valid NFT.
  /// @param _tokenId The NFT to find the approved address for
  /// @return The approved address for this NFT, or the zero address if there is none
  function getApproved(
    uint256 _tokenId
  ) external
    view
    returns (address);

  /// @notice Query if an address is an authorized operator for another address
  /// @param _owner The address that owns the NFTs
  /// @param _operator The address that acts on behalf of the owner
  /// @return True if `_operator` is an approved operator for `_owner`, false otherwise
  function isApprovedForAll(
    address _owner
  , address _operator
  ) external
    view
    returns (bool);
}

// File: /contracts/eip/2981/IERC2981.sol



/*     +%%#-                           ##.        =+.    .+#%#+:       *%%#:    .**+-      =+
 *   .%@@*#*:                          @@: *%-   #%*=  .*@@=.  =%.   .%@@*%*   +@@=+=%   .%##
 *  .%@@- -=+                         *@% :@@-  #@=#  -@@*     +@-  :@@@: ==* -%%. ***   #@=*
 *  %@@:  -.*  :.                    +@@-.#@#  =@%#.   :.     -@*  :@@@.  -:# .%. *@#   *@#*
 * *%@-   +++ +@#.-- .*%*. .#@@*@#  %@@%*#@@: .@@=-.         -%-   #%@:   +*-   =*@*   -@%=:
 * @@%   =##  +@@#-..%%:%.-@@=-@@+  ..   +@%  #@#*+@:      .*=     @@%   =#*   -*. +#. %@#+*@
 * @@#  +@*   #@#  +@@. -+@@+#*@% =#:    #@= :@@-.%#      -=.  :   @@# .*@*  =@=  :*@:=@@-:@+
 * -#%+@#-  :@#@@+%++@*@*:=%+..%%#=      *@  *@++##.    =%@%@%%#-  =#%+@#-   :*+**+=: %%++%*
 *
 * @title: EIP-2981: NFT Royalty Standard
 * @author: Zach Burks, James Morgan, Blaine Malone, James Seibel
 * @notice: the ERC-165 identifier for this interface is 0x2a55205a.
 * @custom:source https://eips.ethereum.org/EIPS/eip-2981
 * @custom:change-log MIT -> Apache-2.0
 *
 */

// SPDX-License-Identifier: Apache-2.0

/******************************************************************************
 * Copyright and related rights waived via CC0.                               *
 *                                                                            *
 * Licensed under the Apache License, Version 2.0 (the "License");            *
 * you may not use this file except in compliance with the License.           *
 * You may obtain a copy of the License at                                    *
 *                                                                            *
 *     http://www.apache.org/licenses/LICENSE-2.0                             *
 *                                                                            *
 * Unless required by applicable law or agreed to in writing, software        *
 * distributed under the License is distributed on an "AS IS" BASIS,          *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
 * See the License for the specific language governing permissions and        *
 * limitations under the License.                                             *
 ******************************************************************************/

pragma solidity >=0.8.0 <0.9.0;

import "../165/IERC165.sol";

interface IERC2981 is IERC165 {

  /// ERC165 bytes to add to interface array - set in parent contract
  /// implementing this standard

  /// bytes4(keccak256("royaltyInfo(uint256,uint256)")) == 0x2a55205a
  /// bytes4 private constant _INTERFACE_ID_ERC2981 = 0x2a55205a;
  /// _registerInterface(_INTERFACE_ID_ERC2981);

  /// @notice Called with the sale price to determine how much royalty
  ///         is owed and to whom.
  /// @param _tokenId - the NFT asset queried for royalty information
  /// @param _salePrice - the sale price of the NFT asset specified by _tokenId
  /// @return receiver - address of who should be sent the royalty payment
  /// @return royaltyAmount - the royalty payment amount for _salePrice
  function royaltyInfo(
    uint256 _tokenId,
    uint256 _salePrice
  ) external
    view
    returns (
      address receiver,
      uint256 royaltyAmount
    );
}

// File: /contracts/eip/173/IERC173.sol



/*     +%%#-                           ##.        =+.    .+#%#+:       *%%#:    .**+-      =+
 *   .%@@*#*:                          @@: *%-   #%*=  .*@@=.  =%.   .%@@*%*   +@@=+=%   .%##
 *  .%@@- -=+                         *@% :@@-  #@=#  -@@*     +@-  :@@@: ==* -%%. ***   #@=*
 *  %@@:  -.*  :.                    +@@-.#@#  =@%#.   :.     -@*  :@@@.  -:# .%. *@#   *@#*
 * *%@-   +++ +@#.-- .*%*. .#@@*@#  %@@%*#@@: .@@=-.         -%-   #%@:   +*-   =*@*   -@%=:
 * @@%   =##  +@@#-..%%:%.-@@=-@@+  ..   +@%  #@#*+@:      .*=     @@%   =#*   -*. +#. %@#+*@
 * @@#  +@*   #@#  +@@. -+@@+#*@% =#:    #@= :@@-.%#      -=.  :   @@# .*@*  =@=  :*@:=@@-:@+
 * -#%+@#-  :@#@@+%++@*@*:=%+..%%#=      *@  *@++##.    =%@%@%%#-  =#%+@#-   :*+**+=: %%++%*
 *
 * @title: EIP-173: Contract Ownership Standard
 * @author: Nick Mudge, Dan Finlay
 * @notice: This specification defines standard functions for owning or controlling a contract.
 *          the ERC-165 identifier for this interface is 0x7f5828d0
 * @custom:URI https://eips.ethereum.org/EIPS/eip-173
 * @custom:change-log MIT -> Apache-2.0
 * @custom:change-log readability modification
 */

// SPDX-License-Identifier: Apache-2.0

/******************************************************************************
 * Copyright and related rights waived via CC0.                               *
 *                                                                            *
 * Licensed under the Apache License, Version 2.0 (the "License");            *
 * you may not use this file except in compliance with the License.           *
 * You may obtain a copy of the License at                                    *
 *                                                                            *
 *     http://www.apache.org/licenses/LICENSE-2.0                             *
 *                                                                            *
 * Unless required by applicable law or agreed to in writing, software        *
 * distributed under the License is distributed on an "AS IS" BASIS,          *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
 * See the License for the specific language governing permissions and        *
 * limitations under the License.                                             *
 ******************************************************************************/

pragma solidity >=0.8.0 <0.9.0;

import "../../eip/165/IERC165.sol";

interface IERC173 is IERC165 {

  /// @dev This emits when ownership of a contract changes.    
  event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

  /// @notice Get the address of the owner    
  /// @return The address of the owner.
  function owner()
    view
    external
    returns(address);
	
  /// @notice Set the address of the new owner of the contract
  /// @dev Set _newOwner to address(0) to renounce any ownership.
  /// @param _newOwner The address of the new owner of the contract    
  function transferOwnership(
    address _newOwner
  ) external;	
}

// File: /contracts/eip/165/IERC165.sol



/*     +%%#-                           ##.        =+.    .+#%#+:       *%%#:    .**+-      =+
 *   .%@@*#*:                          @@: *%-   #%*=  .*@@=.  =%.   .%@@*%*   +@@=+=%   .%##
 *  .%@@- -=+                         *@% :@@-  #@=#  -@@*     +@-  :@@@: ==* -%%. ***   #@=*
 *  %@@:  -.*  :.                    +@@-.#@#  =@%#.   :.     -@*  :@@@.  -:# .%. *@#   *@#*
 * *%@-   +++ +@#.-- .*%*. .#@@*@#  %@@%*#@@: .@@=-.         -%-   #%@:   +*-   =*@*   -@%=:
 * @@%   =##  +@@#-..%%:%.-@@=-@@+  ..   +@%  #@#*+@:      .*=     @@%   =#*   -*. +#. %@#+*@
 * @@#  +@*   #@#  +@@. -+@@+#*@% =#:    #@= :@@-.%#      -=.  :   @@# .*@*  =@=  :*@:=@@-:@+
 * -#%+@#-  :@#@@+%++@*@*:=%+..%%#=      *@  *@++##.    =%@%@%%#-  =#%+@#-   :*+**+=: %%++%*
 *
 * @title: EIP-165: Standard Interface Detection
 * @author: Christian Reitwießner, Nick Johnson, Fabian Vogelsteller, Jordi Baylina, Konrad Feldmeier, William Entriken
 * @notice: Creates a standard method to publish and detect what interfaces a smart contract implements.
 * @custom:source https://eips.ethereum.org/EIPS/eip-165
 * @custom:change-log interface ERC165 -> interface IERC165
 * @custom:change-log readability enhanced
 * @custom:change-log MIT -> Apache-2.0

// SPDX-License-Identifier: Apache-2.0

/******************************************************************************
 * Copyright and related rights waived via CC0.                               *
 *                                                                            *
 * Licensed under the Apache License, Version 2.0 (the "License");            *
 * you may not use this file except in compliance with the License.           *
 * You may obtain a copy of the License at                                    *
 *                                                                            *
 *     http://www.apache.org/licenses/LICENSE-2.0                             *
 *                                                                            *
 * Unless required by applicable law or agreed to in writing, software        *
 * distributed under the License is distributed on an "AS IS" BASIS,          *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
 * See the License for the specific language governing permissions and        *
 * limitations under the License.                                             *
 ******************************************************************************/

pragma solidity >=0.8.0 <0.9.0;

interface IERC165 {

  /// @notice Query if a contract implements an interface
  /// @param interfaceID The interface identifier, as specified in ERC-165
  /// @notice Interface identification is specified in ERC-165. This function
  ///  uses less than 30,000 gas.
  /// @return `true` if the contract implements `interfaceID` and
  ///  `interfaceID` is not 0xffffffff, `false` otherwise
  function supportsInterface(
    bytes4 interfaceID
  ) external
    view
    returns (bool);
}

// File: /contracts/Max-721-2981-PSv2.sol



/*     +%%#-                           ##.        =+.    .+#%#+:       *%%#:    .**+-      =+
 *   .%@@*#*:                          @@: *%-   #%*=  .*@@=.  =%.   .%@@*%*   +@@=+=%   .%##
 *  .%@@- -=+                         *@% :@@-  #@=#  -@@*     +@-  :@@@: ==* -%%. ***   #@=*
 *  %@@:  -.*  :.                    +@@-.#@#  =@%#.   :.     -@*  :@@@.  -:# .%. *@#   *@#*
 * *%@-   +++ +@#.-- .*%*. .#@@*@#  %@@%*#@@: .@@=-.         -%-   #%@:   +*-   =*@*   -@%=:
 * @@%   =##  +@@#-..%%:%.-@@=-@@+  ..   +@%  #@#*+@:      .*=     @@%   =#*   -*. +#. %@#+*@
 * @@#  +@*   #@#  +@@. -+@@+#*@% =#:    #@= :@@-.%#      -=.  :   @@# .*@*  =@=  :*@:=@@-:@+
 * -#%+@#-  :@#@@+%++@*@*:=%+..%%#=      *@  *@++##.    =%@%@%%#-  =#%+@#-   :*+**+=: %%++%*
 *
 * @title: [EIP721] Max-721 Implementation, using EIP 1822
 * @author: Max Flow O2 -> @MaxFlowO2 on bird app/GitHub
 * @notice ERC721 Implementation with:
 *         Enhanced EIP173 - Ownership via roles
 *         EIP2981 - NFT Royalties
 *         PsuedoRandom Engine - Expansion of BAYC engine
 *         TimeCop + Lists - For presales
 *         PaymentSplitter v2 - For "ETH" payments
 */

// SPDX-License-Identifier: Apache-2.0

/******************************************************************************
 * Copyright 2022 Max Flow O2                                                 *
 *                                                                            *
 * Licensed under the Apache License, Version 2.0 (the "License");            *
 * you may not use this file except in compliance with the License.           *
 * You may obtain a copy of the License at                                    *
 *                                                                            *
 *     http://www.apache.org/licenses/LICENSE-2.0                             *
 *                                                                            *
 * Unless required by applicable law or agreed to in writing, software        *
 * distributed under the License is distributed on an "AS IS" BASIS,          *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
 * See the License for the specific language governing permissions and        *
 * limitations under the License.                                             *
 ******************************************************************************/

pragma solidity >=0.8.17 <0.9.0;

import "./eip/721/IERC721TokenReceiver.sol";//
import "./eip/721/IERC721Metadata.sol";//
import "./eip/721/IERC721.sol";//
import "./modules/2981/IERC2981Admin.sol";//
import "./modules/access/MaxAccess.sol";//
import "./modules/splitter/ISplitter.sol";//
import "./lib/Address.sol";//
import "./lib/721.sol";//
import "./lib/Roles.sol";//
import "./lib/2981c.sol";//
import "./lib/Payments.sol";//

abstract contract Max721 is MaxAccess
                          , IERC721
                          , IERC721Metadata
                          , IERC721TokenReceiver
                          , IERC2981Admin
                          , ISplitter {

  using Lib721 for Lib721.Token;
  using Roles for Roles.Role;
  using Lib2981c for Lib2981c.Royalties;
  using Payments for Payments.GasTokens;
  using Address for address;

  // The Structs...
  Lib721.Token internal token721;
  Roles.Role internal contractRoles;
  Lib2981c.Royalties internal royalties;
  Payments.GasTokens internal splitter;

  // The rest (got to have a few)
  bytes4 constant internal DEVS = 0xca4b208b;
  bytes4 constant internal PENDING_DEVS = 0xca4b208a; // DEVS - 1
  bytes4 constant internal OWNERS = 0x8da5cb5b;
  bytes4 constant internal PENDING_OWNERS = 0x8da5cb5a; // OWNERS - 1
  bytes4 constant internal ADMIN = 0xf851a440;

  uint256 startTime; // Set to opening (can +48h for secondary)
  uint256 period; // Set to the period
  uint256 maxCap; // Cpacity of minter
  bytes32 internal admin;
  mapping(uint256 => uint256) internal claimedAdmin;
  bytes32 internal homies;
  mapping(uint256 => uint256) internal claimedHomies;
  bytes32 internal normies;
  mapping(uint256 => uint256) internal claimedNormies;
  string internal contractURL;
  string internal image;
  string internal description;
  string internal animationURI;
  uint256 public normiesCost = 0.069 ether;
  uint256 public publicCost = 0.1 ether;

  event PaymentReceived(address indexed _payee, uint256 _amount);

  /// @dev this is Unauthorized(), basically a catch all, zero description
  /// @notice 0x82b42900 bytes4 of this
  error Unauthorized();

  /// @dev this is MaxSplaining(), giving you a reason, aka require(param, "reason")
  /// @param reason: Use the "Contract name: error"
  /// @notice 0x0661b792 bytes4 of this
  error MaxSplaining(
    string reason
  );

  /// @dev this is TooSoonJunior(), using times
  /// @param yourTime: should almost always be block.timestamp
  /// @param hitTime: the time you should have started
  /// @notice 0xf3f82ac5 bytes4 of this
  error TooSoonJunior(
    uint yourTime
  , uint hitTime
  );

  /// @dev this is TooLateBoomer(), using times
  /// @param yourTime: should almost always be block.timestamp
  /// @param hitTime: the time you should have ended
  /// @notice 0x43c540ef bytes4 of this
  error TooLateBoomer(
    uint yourTime
  , uint hitTime
  );

  ///////////////////////
  /// MAX-721: Modifiers
  ///////////////////////

  modifier onlyRole(bytes4 role) {
    if (contractRoles.has(role, msg.sender) || contractRoles.has(ADMIN, msg.sender)) {
      _;
    } else {
    revert Unauthorized();
    }
  }

  modifier onlyOwner() {
    if (contractRoles.has(OWNERS, msg.sender)) {
      _;
    } else {
    revert Unauthorized();
    }
  }

  modifier onlyDev() {
    if (contractRoles.has(DEVS, msg.sender)) {
      _;
    } else {
    revert Unauthorized();
    }
  }

  ///////////////////////
  /// MAX-721: Internals
  ///////////////////////

  function __Max721_init(
    string memory _name
  , string memory _symbol
  , address _admin
  , address _dev
  , address _owner
  ) internal {
    token721.setName(_name);
    token721.setSymbol(_symbol);
    contractRoles.add(ADMIN, _admin);
    contractRoles.setAdmin(_admin);
    contractRoles.add(DEVS, _dev);
    contractRoles.setDeveloper(_dev);
    contractRoles.add(OWNERS, _owner);
    contractRoles.setOwner(_owner);
  }

  function safeHook(
    address from,
    address to,
    uint256 tokenId,
    bytes memory data
  ) internal
    returns (bool) {
    if (to.isContract()) {
      try IERC721TokenReceiver(to).onERC721Received(msg.sender, from, tokenId, data)
        returns (bytes4 retval) {
        return retval == IERC721TokenReceiver.onERC721Received.selector;
      } catch (bytes memory reason) {
        if (reason.length == 0) {
          revert Unauthorized();
        } else {
          assembly {
            revert(add(32, reason), mload(reason))
          }
        }
      }
    } else {
      return true;
    }
  }

  /////////////////////////////////////////
  /// EIP-173: Contract Ownership Standard
  /////////////////////////////////////////

  /// @notice Get the address of the owner    
  /// @return The address of the owner.
  function owner()
    view
    external
    returns(address) {
    return contractRoles.getOwner();
  }
	
  /// @notice Set the address of the new owner of the contract
  /// @dev Set _newOwner to address(0) to renounce any ownership.
  /// @param _newOwner The address of the new owner of the contract    
  function transferOwnership(
    address _newOwner
  ) external
    onlyRole(OWNERS) {
    contractRoles.add(OWNERS, _newOwner);
    contractRoles.setOwner(_newOwner);
    contractRoles.remove(OWNERS, msg.sender);
  }

  ////////////////////////////////////////////////////////////////
  /// EIP-173: Contract Ownership Standard, MaxFlowO2's extension
  ////////////////////////////////////////////////////////////////

  /// @dev This is the classic "EIP-173" method of renouncing onlyOwner()  
  function renounceOwnership()
    external 
    onlyRole(OWNERS) {
    contractRoles.setOwner(address(0));
    contractRoles.remove(OWNERS, msg.sender);
  }

  /// @dev This accepts the push-pull method of onlyOwner()
  function acceptOwnership()
    external
    onlyRole(PENDING_OWNERS) {
    contractRoles.add(OWNERS, msg.sender);
    contractRoles.setOwner(msg.sender);
    contractRoles.remove(PENDING_OWNERS, msg.sender);
  }

  /// @dev This declines the push-pull method of onlyOwner()
  function declineOwnership()
    external
    onlyRole(PENDING_OWNERS) {
    contractRoles.remove(PENDING_OWNERS, msg.sender);
  }

  /// @dev This starts the push-pull method of onlyOwner()
  /// @param newOwner: addres of new pending owner role
  function pushOwnership(
    address newOwner
  ) external
    onlyRole(OWNERS) {
    contractRoles.add(PENDING_OWNERS, newOwner);
  }

  //////////////////////////////////////////////
  /// [Not an EIP]: Contract Developer Standard
  //////////////////////////////////////////////

  /// @dev Classic "EIP-173" but for onlyDev()
  /// @return Developer of contract
  function developer()
    external
    view
    returns (address) {
    return contractRoles.getDeveloper();
  }

  /// @dev This renounces your role as onlyDev()
  function renounceDeveloper()
    external
    onlyRole(DEVS) {
    contractRoles.setDeveloper(address(0));
    contractRoles.remove(DEVS, msg.sender);
  }

  /// @dev Classic "EIP-173" but for onlyDev()
  /// @param newDeveloper: addres of new pending Developer role
  function transferDeveloper(
    address newDeveloper
  ) external
    onlyRole(DEVS) {
    contractRoles.add(DEVS, newDeveloper);
    contractRoles.setDeveloper(newDeveloper);
    contractRoles.remove(DEVS, msg.sender);
  }

  /// @dev This accepts the push-pull method of onlyDev()
  function acceptDeveloper()
    external
    onlyRole(PENDING_DEVS) {
    contractRoles.add(DEVS, msg.sender);
    contractRoles.setDeveloper(msg.sender);
    contractRoles.remove(PENDING_DEVS, msg.sender);
  }

  /// @dev This declines the push-pull method of onlyDev()
  function declineDeveloper()
    external
    onlyRole(PENDING_DEVS) {
    contractRoles.remove(PENDING_DEVS, msg.sender);
  }

  /// @dev This starts the push-pull method of onlyDev()
  /// @param newDeveloper: addres of new pending developer role
  function pushDeveloper(
    address newDeveloper
  ) external
    onlyRole(DEVS) {
    contractRoles.add(PENDING_DEVS, newDeveloper);
  }

  //////////////////////////////////////////
  /// [Not an EIP]: Contract Roles Standard
  //////////////////////////////////////////

  /// @dev Returns `true` if `account` has been granted `role`.
  /// @param role: Bytes4 of a role
  /// @param account: Address to check
  /// @return bool true/false if account has role
  function hasRole(
    bytes4 role
  , address account
  ) external
    view
    returns (bool) {
    return contractRoles.has(role, account);
  }

  /// @dev Returns the admin role that controls a role
  /// @param role: Role to check
  /// @return admin role
  function getRoleAdmin(
    bytes4 role
  ) external
    view 
    returns (bytes4) {
    return ADMIN;
  }

  /// @dev Grants `role` to `account`
  /// @param role: Bytes4 of a role
  /// @param account: account to give role to
  function grantRole(
    bytes4 role
  , address account
  ) external
    onlyRole(role) {
    if (role == PENDING_DEVS || role == PENDING_OWNERS) {
      revert Unauthorized();
    } else {
      contractRoles.add(role, account);
    }
  }

  /// @dev Revokes `role` from `account`
  /// @param role: Bytes4 of a role
  /// @param account: account to revoke role from
  function revokeRole(
    bytes4 role
  , address account
  ) external
    onlyRole(role) {
    if (role == PENDING_DEVS || role == PENDING_OWNERS) {
      if (account == msg.sender) {
        contractRoles.remove(role, account);
      } else {
        revert Unauthorized();
      }
    } else {
      contractRoles.remove(role, account);
    }
  }

  /// @dev Renounces `role` from `account`
  /// @param role: Bytes4 of a role
  function renounceRole(
    bytes4 role
  ) external
    onlyRole(role) {
    contractRoles.remove(role, msg.sender);
  }

  ////////////////////////////////////////////////////////////////////////
  /// ERC-721 Non-Fungible Token Standard, optional enumeration extension
  /// @dev may be added, but not fully supported see ERC-165 below
  ////////////////////////////////////////////////////////////////////////

  /// @notice Count NFTs tracked by this contract
  /// @return A count of valid NFTs tracked by this contract, where each one of
  ///  them has an assigned and queryable owner not equal to the zero address
  function totalSupply()
    external
    view
    virtual
    returns (uint256) {
    return token721.getSupply();
  }

  /////////////////////////////////////////////////
  /// ERC721 Metadata, optional metadata extension
  /////////////////////////////////////////////////

  /// @notice A descriptive name for a collection of NFTs in this contract
  function name()
    external
    view
    virtual
    override
    returns (string memory _name) {
    return token721.getName();
  }

  /// @notice An abbreviated name for NFTs in this contract
  function symbol()
    external
    view
    virtual
    override
    returns (string memory _symbol) {
    return token721.getSymbol();
  }

  /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.
  /// @notice Throws if `_tokenId` is not a valid NFT. URIs are defined in RFC
  ///  3986. The URI may point to a JSON file that conforms to the "ERC721
  ///  Metadata JSON Schema".
  function tokenURI(
    uint256 _tokenId
  ) external
    view
    virtual
    override
    returns (string memory) {
    return token721.getTokenURI(_tokenId);
  }

  ////////////////////////////////////////
  /// ERC-721 Non-Fungible Token Standard
  ////////////////////////////////////////

  /// @notice Count all NFTs assigned to an owner
  /// @notice NFTs assigned to the zero address are considered invalid, and this
  ///  function throws for queries about the zero address.
  /// @param _owner An address for whom to query the balance
  /// @return The number of NFTs owned by `_owner`, possibly zero
  function balanceOf(
    address _owner
  ) external
    view
    virtual
    override
    returns (uint256) {
    return token721.getBalanceOf(_owner);
  }

  /// @notice Find the owner of an NFT
  /// @notice NFTs assigned to zero address are considered invalid, and queries
  ///  about them do throw.
  /// @param _tokenId The identifier for an NFT
  /// @return The address of the owner of the NFT
  function ownerOf(
    uint256 _tokenId
  ) external
    view
    virtual
    override
    returns (address) {
    return token721.getOwnerOf(_tokenId);
  }

  /// @notice Transfers the ownership of an NFT from one address to another address
  /// @notice Throws unless `msg.sender` is the current owner, an authorized
  ///  operator, or the approved address for this NFT. Throws if `_from` is
  ///  not the current owner. Throws if `_to` is the zero address. Throws if
  ///  `_tokenId` is not a valid NFT. When transfer is complete, this function
  ///  checks if `_to` is a smart contract (code size > 0). If so, it calls
  ///  `onERC721Received` on `_to` and throws if the return value is not
  ///  `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
  /// @param _from The current owner of the NFT
  /// @param _to The new owner
  /// @param _tokenId The NFT to transfer
  /// @param data Additional data with no specified format, sent in call to `_to`
  function safeTransferFrom(
    address _from
  , address _to
  , uint256 _tokenId
  , bytes calldata data
  ) external
    virtual
    override {
    token721.doTransferFrom(_from, _to, msg.sender, _tokenId);
    safeHook(_from, _to, _tokenId, data);
    emit Transfer(_from, _to, _tokenId);
  }

  /// @notice Transfers the ownership of an NFT from one address to another address
  /// @notice This works identically to the other function with an extra data parameter,
  ///  except this function just sets data to "".
  /// @param _from The current owner of the NFT
  /// @param _to The new owner
  /// @param _tokenId The NFT to transfer
  function safeTransferFrom(
    address _from
  , address _to
  , uint256 _tokenId
  ) external
    virtual
    override {
    this.safeTransferFrom(_from, _to, _tokenId, "");
  }

  /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE
  ///  TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE
  ///  THEY MAY BE PERMANENTLY LOST
  /// @notice Throws unless `msg.sender` is the current owner, an authorized
  ///  operator, or the approved address for this NFT. Throws if `_from` is
  ///  not the current owner. Throws if `_to` is the zero address. Throws if
  ///  `_tokenId` is not a valid NFT.
  /// @param _from The current owner of the NFT
  /// @param _to The new owner
  /// @param _tokenId The NFT to transfer
  function transferFrom(
    address _from
  , address _to
  , uint256 _tokenId
  ) external
    virtual
    override {
    token721.doTransferFrom(_from, _to, msg.sender, _tokenId);
    emit Transfer(_from, _to, _tokenId);
  }

  /// @notice Change or reaffirm the approved address for an NFT
  /// @notice The zero address indicates there is no approved address.
  ///  Throws unless `msg.sender` is the current NFT owner, or an authorized
  ///  operator of the current owner.
  /// @param _approved The new approved NFT controller
  /// @param _tokenId The NFT to approve
  function approve(
    address _approved
  , uint256 _tokenId
  ) external
    virtual
    override {
    token721.setApprove(_approved, msg.sender, _tokenId);
    emit Approval(msg.sender, _approved, _tokenId);
  }

  /// @notice Enable or disable approval for a third party ("operator") to manage
  ///  all of `msg.sender`'s assets
  /// @notice Emits the ApprovalForAll event. The contract MUST allow
  ///  multiple operators per owner.
  /// @param _operator Address to add to the set of authorized operators
  /// @param _approved True if the operator is approved, false to revoke approval
  function setApprovalForAll(
    address _operator
  , bool _approved
  ) external
    virtual
    override {
    token721.setApprovalForAll(_operator, msg.sender, _approved);
    emit ApprovalForAll(msg.sender, _operator, _approved);
  }

  /// @notice Get the approved address for a single NFT
  /// @notice Throws if `_tokenId` is not a valid NFT.
  /// @param _tokenId The NFT to find the approved address for
  /// @return The approved address for this NFT, or the zero address if there is none
  function getApproved(
    uint256 _tokenId
  ) external
    view
    virtual
    override
    returns (address) {
    return token721.getApproved(_tokenId);
  }

  /// @notice Query if an address is an authorized operator for another address
  /// @param _owner The address that owns the NFTs
  /// @param _operator The address that acts on behalf of the owner
  /// @return True if `_operator` is an approved operator for `_owner`, false otherwise
  function isApprovedForAll(
    address _owner
  , address _operator
  ) external
    view
    virtual
    override
    returns (bool) {
    return token721.isApprovedForAll(_owner, _operator);
  }

  ///////////////////////////////////////////////////////////////////
  /// ERC-721 Non-Fungible Token Standard, required wallet interface
  /// @dev This is to disable all safe transfers to this contract
  ///////////////////////////////////////////////////////////////////

  /// @notice Handle the receipt of an NFT
  /// @notice The ERC721 smart contract calls this function on the recipient
  ///  after a `transfer`. This function MAY throw to revert and reject the
  ///  transfer. Return of other than the magic value MUST result in the
  ///  transaction being reverted.
  ///  Note: the contract address is always the message sender.
  /// @param _operator The address which called `safeTransferFrom` function
  /// @param _from The address which previously owned the token
  /// @param _tokenId The NFT identifier which is being transferred
  /// @param _data Additional data with no specified format
  /// @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
  ///  unless throwing
  function onERC721Received(
    address _operator
  , address _from
  , uint256 _tokenId
  , bytes calldata _data
  ) external
    virtual
    override
    returns(bytes4) {
    revert Unauthorized();
  }

  ///////////////////////////////////
  /// EIP-2981: NFT Royalty Standard
  ///////////////////////////////////

  /// @notice Called with the sale price to determine how much royalty
  ///         is owed and to whom.
  /// @param _tokenId - the NFT asset queried for royalty information
  /// @param _salePrice - the sale price of the NFT asset specified by _tokenId
  /// @return receiver - address of who should be sent the royalty payment
  /// @return royaltyAmount - the royalty payment amount for _salePrice
  function royaltyInfo(
    uint256 _tokenId,
    uint256 _salePrice
  ) external
    view
    virtual
    override
    returns (
      address receiver,
      uint256 royaltyAmount
    ) {
    (receiver, royaltyAmount) = royalties.royaltyInfo(_tokenId,_salePrice);
  }

  ////////////////////////////////////////////////////
  /// EIP-2981: NFT Royalty Standard, admin extension
  /// @dev Using the collection standard
  ////////////////////////////////////////////////////

  /// @dev function (state storage) sets the royalty data for a token
  /// @param tokenId uint256 for the token
  /// @param receiver address for the royalty reciever for token
  /// @param permille uint16 for the permille of royalties 20 -> 2.0%
  function setRoyalties(
    uint256 tokenId
  , address receiver
  , uint16 permille
  ) external
    virtual
    override {
    revert Unauthorized();
  }

  /// @dev function (state storage) revokes the royalty data for a token
  /// @param tokenId uint256 for the token
  function revokeRoyalties(
    uint256 tokenId
  ) external
    virtual
    override {
    revert Unauthorized();
  }

  /// @dev function (state storage) sets the royalty data for a collection
  /// @param receiver address for the royalty reciever for token
  /// @param permille uint16 for the permille of royalties 20 -> 2.0%
  function setRoyalties(
    address receiver
  , uint16 permille
  ) external
    virtual
    onlyOwner()
    override {
    royalties.setRoyalties(receiver, permille);
  }

  /// @dev function (state storage) revokes the royalty data for a collection
  function revokeRoyalties()
    external
    virtual
    onlyOwner()
    override {
    royalties.revokeRoyalties();
  }

  ////////////////////////////////////////////////////////////////
  /// [Not an EIP] Payment Splitter, interface for ether payments
  ////////////////////////////////////////////////////////////////

  /// @dev returns total shares
  /// @return uint256 of all shares on contract
  function totalShares()
    external
    view
    virtual
    override
    returns (uint256) {
    return splitter.getTotalShares();
  }

  /// @dev returns shares of an address
  /// @param payee address of payee to return
  /// @return mapping(address => uint) of _shares
  function shares(
    address payee
  ) external
    view
    virtual
    override
    returns (uint256) {
    return splitter.payeeShares(payee);
  }

  /// @dev returns total releases in "eth"
  /// @return uint256 of all "eth" released in wei
  function totalReleased()
    external
    view
    virtual
    override
    returns (uint256) {
    return splitter.getTotalReleased();
  }

  /// @dev returns released "eth" of an payee
  /// @param payee address of payee to look up
  /// @return mapping(address => uint) of _released
  function released(
    address payee
  ) external
    view
    virtual
    override
    returns (uint256) {
    return splitter.payeeReleased(payee);
  }

  /// @dev returns amount of "eth" that can be released to payee
  /// @param payee address of payee to look up
  /// @return uint in wei of "eth" to release
  function releasable(
    address payee
  ) external
    view
    virtual
    override
    returns (uint256) {
    uint totalReceived
      = address(this).balance
      + this.totalReleased();
    return 
      totalReceived
    * this.shares(payee)
    / this.totalShares()
    - this.released(payee);
  }

  /// @dev returns index number of payee
  /// @param payee number of index
  /// @return address at _payees[index]
  function payeeIndex(
    address payee
  ) external
    view
    virtual
    override
    returns (uint256) {
    return splitter.payeeIndex(payee);
  }

  /// @dev this returns the array of payees[]
  /// @return address[] payees
  function payees()
    external
    view
    virtual
    override
    returns (address[] memory) {
    return splitter.getPayees();
  }

  /// @dev this claims all "eth" on contract for msg.sender
  function claim()
    external
    virtual
    override {
    if (this.shares(msg.sender) == 0) {
      revert Unauthorized();
    }
    uint256 payment = this.releasable(msg.sender);
    if (payment == 0) {
      revert Unauthorized();
    }
    splitter.processPayment(msg.sender, payment);
    Address.sendValue(payable(msg.sender), payment);
  }

  /// @dev This pays all payees
  function payClaims()
    external
    virtual
    override {
    address[] memory toPay = splitter.getPayees();
    uint256 len = toPay.length;
    for (uint x = 0 ; x < len ;) {
      uint256 payment = this.releasable(toPay[x]);
      splitter.processPayment(toPay[x], payment);
      Address.sendValue(payable(toPay[x]), payment);
      unchecked { ++x; }
    }
  }

  /// @dev This adds a payee
  /// @param payee Address of payee
  /// @param _shares Shares to send user
  function addPayee(
    address payee
  , uint256 _shares
  ) external
    virtual
    onlyDev()
    override {
    splitter.addPayee(payee, _shares);
  }

  /// @dev This removes a payee
  /// @param payee Address of payee to remove
  /// @notice use payPayees() prior to use if anything is on the contract
  function removePayee(
    address payee
  ) external
    virtual
    onlyDev()
    override {
    splitter.removePayee(payee);
  }

  /// @dev This removes all payees
  /// @notice use payPayees() prior to use if anything is on the contract
  function clearPayees()
    external
    virtual
    onlyDev()
    override {
    splitter.clearPayees();
  }

  //////////////////////////////////////////
  /// EIP-165: Standard Interface Detection
  //////////////////////////////////////////

  /// @dev Query if a contract implements an interface
  /// @param interfaceID The interface identifier, as specified in ERC-165
  /// @notice Interface identification is specified in ERC-165. This function
  ///  uses less than 30,000 gas.
  /// @return `true` if the contract implements `interfaceID` and
  ///  `interfaceID` is not 0xffffffff, `false` otherwise
  function supportsInterface(
    bytes4 interfaceID
  ) external
    view
    virtual
    override
    returns (bool) {
    return (
      interfaceID == type(IERC173).interfaceId  ||
      interfaceID == type(IERC721).interfaceId  ||
      interfaceID == type(IERC2981).interfaceId  ||
      interfaceID == type(IERC721Metadata).interfaceId
    );
  }
}

// File: @openzeppelin/contracts/utils/cryptography/MerkleProof.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/MerkleProof.sol)

pragma solidity ^0.8.0;

/**
 * @dev These functions deal with verification of Merkle Tree proofs.
 *
 * The tree and the proofs can be generated using our
 * https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
 * You will find a quickstart guide in the readme.
 *
 * WARNING: You should avoid using leaf values that are 64 bytes long prior to
 * hashing, or use a hash function other than keccak256 for hashing leaves.
 * This is because the concatenation of a sorted pair of internal nodes in
 * the merkle tree could be reinterpreted as a leaf value.
 * OpenZeppelin's JavaScript library generates merkle trees that are safe
 * against this attack out of the box.
 */
library MerkleProof {
    /**
     * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
     * defined by `root`. For this, a `proof` must be provided, containing
     * sibling hashes on the branch from the leaf to the root of the tree. Each
     * pair of leaves and each pair of pre-images are assumed to be sorted.
     */
    function verify(
        bytes32[] memory proof,
        bytes32 root,
        bytes32 leaf
    ) internal pure returns (bool) {
        return processProof(proof, leaf) == root;
    }

    /**
     * @dev Calldata version of {verify}
     *
     * _Available since v4.7._
     */
    function verifyCalldata(
        bytes32[] calldata proof,
        bytes32 root,
        bytes32 leaf
    ) internal pure returns (bool) {
        return processProofCalldata(proof, leaf) == root;
    }

    /**
     * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
     * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
     * hash matches the root of the tree. When processing the proof, the pairs
     * of leafs & pre-images are assumed to be sorted.
     *
     * _Available since v4.4._
     */
    function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = _hashPair(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Calldata version of {processProof}
     *
     * _Available since v4.7._
     */
    function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = _hashPair(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a merkle tree defined by
     * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
     *
     * CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
     *
     * _Available since v4.7._
     */
    function multiProofVerify(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32 root,
        bytes32[] memory leaves
    ) internal pure returns (bool) {
        return processMultiProof(proof, proofFlags, leaves) == root;
    }

    /**
     * @dev Calldata version of {multiProofVerify}
     *
     * CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
     *
     * _Available since v4.7._
     */
    function multiProofVerifyCalldata(
        bytes32[] calldata proof,
        bool[] calldata proofFlags,
        bytes32 root,
        bytes32[] memory leaves
    ) internal pure returns (bool) {
        return processMultiProofCalldata(proof, proofFlags, leaves) == root;
    }

    /**
     * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
     * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
     * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
     * respectively.
     *
     * CAUTION: Not all merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
     * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
     * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
     *
     * _Available since v4.7._
     */
    function processMultiProof(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32[] memory leaves
    ) internal pure returns (bytes32 merkleRoot) {
        // This function rebuild the root hash by traversing the tree up from the leaves. The root is rebuilt by
        // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
        // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
        // the merkle tree.
        uint256 leavesLen = leaves.length;
        uint256 totalHashes = proofFlags.length;

        // Check proof validity.
        require(leavesLen + proof.length - 1 == totalHashes, "MerkleProof: invalid multiproof");

        // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
        // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
        bytes32[] memory hashes = new bytes32[](totalHashes);
        uint256 leafPos = 0;
        uint256 hashPos = 0;
        uint256 proofPos = 0;
        // At each step, we compute the next hash using two values:
        // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
        //   get the next hash.
        // - depending on the flag, either another value for the "main queue" (merging branches) or an element from the
        //   `proof` array.
        for (uint256 i = 0; i < totalHashes; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i] ? leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++] : proof[proofPos++];
            hashes[i] = _hashPair(a, b);
        }

        if (totalHashes > 0) {
            return hashes[totalHashes - 1];
        } else if (leavesLen > 0) {
            return leaves[0];
        } else {
            return proof[0];
        }
    }

    /**
     * @dev Calldata version of {processMultiProof}.
     *
     * CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
     *
     * _Available since v4.7._
     */
    function processMultiProofCalldata(
        bytes32[] calldata proof,
        bool[] calldata proofFlags,
        bytes32[] memory leaves
    ) internal pure returns (bytes32 merkleRoot) {
        // This function rebuild the root hash by traversing the tree up from the leaves. The root is rebuilt by
        // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
        // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
        // the merkle tree.
        uint256 leavesLen = leaves.length;
        uint256 totalHashes = proofFlags.length;

        // Check proof validity.
        require(leavesLen + proof.length - 1 == totalHashes, "MerkleProof: invalid multiproof");

        // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
        // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
        bytes32[] memory hashes = new bytes32[](totalHashes);
        uint256 leafPos = 0;
        uint256 hashPos = 0;
        uint256 proofPos = 0;
        // At each step, we compute the next hash using two values:
        // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
        //   get the next hash.
        // - depending on the flag, either another value for the "main queue" (merging branches) or an element from the
        //   `proof` array.
        for (uint256 i = 0; i < totalHashes; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i] ? leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++] : proof[proofPos++];
            hashes[i] = _hashPair(a, b);
        }

        if (totalHashes > 0) {
            return hashes[totalHashes - 1];
        } else if (leavesLen > 0) {
            return leaves[0];
        } else {
            return proof[0];
        }
    }

    function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
        return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
    }

    function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, a)
            mstore(0x20, b)
            value := keccak256(0x00, 0x40)
        }
    }
}

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



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/Base64.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides a set of functions to operate with Base64 strings.
 *
 * _Available since v4.5._
 */
library Base64 {
    /**
     * @dev Base64 Encoding/Decoding Table
     */
    string internal constant _TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

    /**
     * @dev Converts a `bytes` to its Bytes64 `string` representation.
     */
    function encode(bytes memory data) internal pure returns (string memory) {
        /**
         * Inspired by Brecht Devos (Brechtpd) implementation - MIT licence
         * https://github.com/Brechtpd/base64/blob/e78d9fd951e7b0977ddca77d92dc85183770daf4/base64.sol
         */
        if (data.length == 0) return "";

        // Loads the table into memory
        string memory table = _TABLE;

        // Encoding takes 3 bytes chunks of binary data from `bytes` data parameter
        // and split into 4 numbers of 6 bits.
        // The final Base64 length should be `bytes` data length multiplied by 4/3 rounded up
        // - `data.length + 2`  -> Round up
        // - `/ 3`              -> Number of 3-bytes chunks
        // - `4 *`              -> 4 characters for each chunk
        string memory result = new string(4 * ((data.length + 2) / 3));

        /// @solidity memory-safe-assembly
        assembly {
            // Prepare the lookup table (skip the first "length" byte)
            let tablePtr := add(table, 1)

            // Prepare result pointer, jump over length
            let resultPtr := add(result, 32)

            // Run over the input, 3 bytes at a time
            for {
                let dataPtr := data
                let endPtr := add(data, mload(data))
            } lt(dataPtr, endPtr) {

            } {
                // Advance 3 bytes
                dataPtr := add(dataPtr, 3)
                let input := mload(dataPtr)

                // To write each character, shift the 3 bytes (18 bits) chunk
                // 4 times in blocks of 6 bits for each character (18, 12, 6, 0)
                // and apply logical AND with 0x3F which is the number of
                // the previous character in the ASCII table prior to the Base64 Table
                // The result is then added to the table to get the character to write,
                // and finally write it in the result pointer but with a left shift
                // of 256 (1 byte) - 8 (1 ASCII char) = 248 bits

                mstore8(resultPtr, mload(add(tablePtr, and(shr(18, input), 0x3F))))
                resultPtr := add(resultPtr, 1) // Advance

                mstore8(resultPtr, mload(add(tablePtr, and(shr(12, input), 0x3F))))
                resultPtr := add(resultPtr, 1) // Advance

                mstore8(resultPtr, mload(add(tablePtr, and(shr(6, input), 0x3F))))
                resultPtr := add(resultPtr, 1) // Advance

                mstore8(resultPtr, mload(add(tablePtr, and(input, 0x3F))))
                resultPtr := add(resultPtr, 1) // Advance
            }

            // When data `bytes` is not exactly 3 bytes long
            // it is padded with `=` characters at the end
            switch mod(mload(data), 3)
            case 1 {
                mstore8(sub(resultPtr, 1), 0x3d)
                mstore8(sub(resultPtr, 2), 0x3d)
            }
            case 2 {
                mstore8(sub(resultPtr, 1), 0x3d)
            }
        }

        return result;
    }
}