// File: /contracts/NinfaMarketplace.sol



/*----------------------------------------------------------*|
|*          ███    ██ ██ ███    ██ ███████  █████           *|
|*          ████   ██ ██ ████   ██ ██      ██   ██          *|
|*          ██ ██  ██ ██ ██ ██  ██ █████   ███████          *|
|*          ██  ██ ██ ██ ██  ██ ██ ██      ██   ██          *|
|*          ██   ████ ██ ██   ████ ██      ██   ██          *|
|*----------------------------------------------------------*/

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import "./utils/Counters.sol";
import "./utils/RoyaltyEngineV1.sol";
import "./access/Ownable.sol";

/*************************************************************
 * @title NinfaMarketplace                                   *
 *                                                           *
 * @notice On-chain NFT marketplace                          *
 *                                                           *
 * @custom:security-contact tech@ninfa.io                    *
 ************************************************************/

contract NinfaMarketplace is Ownable, RoyaltyEngineV1 {
    /*----------------------------------------------------------*|
    |*  # VARIABLES                                             *|
    |*----------------------------------------------------------*/

    using Counters for Counters.Counter;
    /// @notice Orders counter
    Counters.Counter private _orderCount;
    /// @notice Offers counter
    Counters.Counter private _offerCount;
    /// @notice address of admin multisig contract for receiving fees generated by the marketplace
    address private _feeRecipient;
    /// @notice factory contract for deploying self-sovreign collections.
    address private _factory;
    /// @notice Ninfa's external registry, mapping Ninfa's ERC721 sovreign collection's tokenIds to a boolean value indicating if the token has been sold on any of Ninfa's contracts, such as this marketplace or an auction contract.
    address private immutable _PRIMARY_SALES_REGISTRY;
    ///@notice constant 10,000 BPS = 100% shares sale price
    uint256 private constant _BPS_DENOMINATOR = 10000;
    /// @notice Ninfa Marketplace fee percentage on primary sales from orders, expressed in basis points
    uint256 private _primaryOrdersFee;
    /// @notice Ninfa Marketplace fee percentage on primary sales from offers, expressed in basis points
    uint256 private _primaryOffersFee;
    /// @notice Ninfa Marketplace fee percentage on all secondary sales, expressed in basis points
    uint256 private _secondaryMarketFee;
    /// @notice collections MUST be manually whitelisted by Ninfa's admin or multisig account, in order to be traded on Ninfa marketplace
    mapping(address => bool) private _isWhitelisted;
    /// @notice whitelist of address codehashes of Ninfa's sovreign ERC721 collections, in order to determine if it is a primary market sale
    mapping(bytes32 => bool) private _isERC721Sovreign;
    /// @notice `_orderCount` counter to `_Order` struct mapping
    mapping(uint256 => _Order) public orders;
    /// @notice `_offerCount` counter to `_Offer` struct mapping
    mapping(uint256 => _Offer) public offers;

    /*----------------------------------------------------------*|
    |*  # STRUCTS                                               *|
    |*----------------------------------------------------------*/

    /**
     * @dev the `Order` struct is used both for storing order information, as well as trade information when passed as a function parameter to the private `_trade` function
     * @param tokenId the NFT id, for now we only allow trading NINFA NFT's so no erc721 address is needed
     * @param unitPrice ERC-1155 unit price in ETH, or total price if ERC-721 since there is only 1 unit of each token.
     * @dev when the `Order` struct is passed as a function parameter to `_trade`, `unitPrice` always refers to the total price of ERC-1155 tokens, i.e. token value * unit price
     * @param collection address of the ERC721 or ERC1155 contract. Doesn't require any access control besides the collection being whitelisted, i.e. msg.sender may be any address.
     * @param erc1155Value the NFT amount, _amount == 0 for ERC721 and _amount > 0 for ERC1155
     * @param commissionBps commission amount, expressed in basis points 0 - 10000
     * @param commissionReceiver receiver of commission on sales (primary AND secondary)
     * @param collection address of the collection being sold
     * @param from always refers to the seller, who either creater an order or is accepting an offer.
     * @param operator address of an authorized operator, such as a gallery managing an artist; the operator is allowed to change or cancel an order they created on the Marketplace.
     * @dev operator address usually also corresponds to the commission receiver in order to receive sale commissions, unless `commissionReceiver` is set to yet another address, e.g. a payment splitter.
     */
    struct _Order {
        uint256 tokenId;
        uint256 unitPrice;
        uint256 erc1155Value;
        uint256 commissionBps;
        address commissionReceiver;
        address collection;
        address from;
        address operator;
    }

    struct _Offer {
        uint256 tokenId;
        uint256 unitPrice;
        uint256 erc1155Value;
        address collection;
        address from; // buyer
    }

    /*----------------------------------------------------------*|
    |*  # EVENTS                                                *|
    |*----------------------------------------------------------*/

    event OrderCreated(uint256 orderId);

    event OrderUpdated(uint256 orderId);

    event OrderDeleted(uint256 orderId);

    event OfferCreated(uint256 offerId);

    event OfferUpdated(uint256 offerId);

    event OfferDeleted(uint256 offerId);

    event Trade(
        address indexed collection,
        uint256 indexed tokenId,
        address indexed from, // seller
        uint256 id, // we have order/offer id and all the related data stored in db.
        uint256 price,
        uint256 erc1155Value
    );

    /*----------------------------------------------------------*|
    |*  # MODIFIERS                                             *|
    |*----------------------------------------------------------*/

    /**
     * @notice ERC721 and ERC1155 collections must be whitelisted.
     * @dev if the collection has not been whitelisted, check if it is one of Ninfa's factory clones (Ninfa's self-sovreign collections)
     * @dev by checking the whitelist first with nested `if`s avoids having to make an external call unnecessarily
     */
    modifier isWhitelisted(address _collection) {
        if (_isWhitelisted[_collection] == false) {
            (, bytes memory authorized) = _factory.staticcall(
                abi.encodeWithSelector(0xf6a3d24e, _collection) // function selector: 0xf6a3d24e or bytes4(keccak256("exists(address)"));
            );
            if (abi.decode(authorized, (bool)) == false) revert Unauthorized();
        }
        _;
    }

    /*----------------------------------------------------------*|
    |*  # ORDERS                                                *|
    |*----------------------------------------------------------*/

    /**
     * @notice create a new order on the marketplace by transfering an NFT to it.
     * @dev Will create a new order by sending tokens with data bytes containing function parameters
     *
     * Require:
     *
     * - can only be called by an NFT smart contract transfering an NFT to the marketplace
     * - collection must be whitelisted
     *
     */
    function _createOrder(
        address _operator, //  either the previous owner or operator, i.e. whichever address called safeTransferFrom on the ERC1155 contract
        address _from, // previous owner, i.e. seller
        uint256 _id,
        uint256 _value,
        uint256 _unitPrice,
        uint256 _commissionBps,
        address _commissionRecipient
    ) private isWhitelisted(msg.sender) {
        // `_orderCount` starts at 1
        _orderCount.increment();
        uint256 _orderId = _orderCount.current();

        // create order with new `_orderId` in orders mapping
        orders[_orderId] = _Order(
            _id,
            _unitPrice,
            _value,
            _commissionBps,
            _commissionRecipient,
            msg.sender, // collection
            _from,
            _operator
        );
        emit OrderCreated(_orderId);
    }

    /// @dev only for 1155 for updating the price and LOWER the amount
    function updateOrder(
        uint256 _erc1155RedeemAmount,
        uint256 _unitPrice,
        uint256 _orderId
    ) external {
        lowerOrderErc1155Amount(_orderId, _erc1155RedeemAmount);

        orders[_orderId].unitPrice = _unitPrice;
    }

    /**
     * @notice cancels order and transfers NFT back to owner
     * @param _orderId the Id of the order
     * @dev delete `orders[_orderId]` from storage BEFORE making external calls for transfering the NFT back to the seller (check-effects pattern)
     *
     *
     * SHOULD:
     *
     * This function does not check whether the order exists or not
     *
     */
    function deleteOrder(uint256 _orderId) external {
        _Order memory order = orders[_orderId];
        require(msg.sender == order.operator);

        delete orders[_orderId];

        _transferNFT(
            order.collection,
            address(this),
            msg.sender,
            order.tokenId,
            order.erc1155Value
        );

        emit OrderDeleted(_orderId);
    }

    /**
     * @param _unitPrice will override the old price, note that it may be 0 although this could only mean that a mistake was made
     * @dev this function doesn't enforce a positive value for `_unitPrice` in order to save a little gas for the users.
     * @dev if `_unitPrice` is set to 0, the order will be deleted from the database (not from the smart contract), see {NinfaMarketplace-OrderUpdated} modifier
     */
    function updateOrderPrice(uint256 _orderId, uint256 _unitPrice) external {
        _Order storage order = orders[_orderId];
        require(msg.sender == order.operator);

        order.unitPrice = _unitPrice;

        emit OrderUpdated(_orderId);
    }

    /**
     * @notice function to lower an order's amount of the ERC-1155 tokenId on sale, doesn't apply to ERC-721 because it is non-fungible
     * @param _erc1155RedeemAmount is the (negative) difference of tokens to be withdrawn by the seller. Should be different from 0, although not strictly required.
     */
    function lowerOrderErc1155Amount(
        uint256 _orderId,
        uint256 _erc1155RedeemAmount
    ) public {
        _Order storage order = orders[_orderId];
        require(msg.sender == order.operator);

        /// @dev warning, make changes to storage BEFORE making external calls for transfering the NFT back to the seller (check-effects-interactions pattern)
        order.erc1155Value -= _erc1155RedeemAmount;

        (bool success, ) = order.collection.call(
            abi.encodeWithSelector(
                0xf242432a, // bytes4(keccak256('safeTransferFrom(address,address,uint256,uint256,bytes)')) == 0xf242432a
                address(this),
                msg.sender,
                order.tokenId,
                _erc1155RedeemAmount,
                ""
            )
        );
        require(success);

        if (order.erc1155Value == 0) {
            delete orders[_orderId]; // it is not possible to delete using storage pointers https://docs.soliditylang.org/en/develop/types.html#data-location
            emit OrderDeleted(_orderId);
        } else {
            emit OrderUpdated(_orderId);
        }
    }

    function setOrderCommission(
        uint256 _orderId,
        uint256 _commissionBps,
        address _commissionRecipient
    ) external {
        require(msg.sender == orders[_orderId].operator);
        orders[_orderId].commissionBps = _commissionBps;
        orders[_orderId].commissionReceiver = _commissionRecipient;
        emit OrderUpdated(_orderId);
    }

    /*----------------------------------------------------------*|
    |*  # OFFERS                                                *|
    |*----------------------------------------------------------*/

    /**
     * @dev offers can be made independently of whether the token is on sale or not, the msg.value is used to determine the offer amount, so no function parameter is needed for that
     * @dev there is no require to check that an offer or offer doesn't already exist and if so, that the offer amount is not greater than the order itself, this was omitted in order to save gas; the frontend should check this in order to prevent mistakes from the user
     * @param _collection address of the erc721 implementation contract or proxy contract
     * @param _tokenId the token Id to make an offer to
     * @param _amount the NFT amount, _amount == 0 for ERC721 and _amount > 0 for ERC1155
     * @param _from needed in order to integrate Wert payment solution, because in every txn Wert is the `msg.sender`.
     *      using _msgSender does not represent a security risk, on the other hand, it is possible for the buyer to use this parameter simply in order to transfer the NFT to an address other than their own, this can be useful for external contract buying NFTs.
     */
    function createOffer(
        address _collection,
        uint256 _tokenId,
        uint256 _amount,
        address _from,
        uint256 _unitPrice
    ) external payable isWhitelisted(_collection) {
        _offerCount.increment(); // start count at 1

        if (_amount == 0) require(msg.value == _unitPrice);
        else require(msg.value == _unitPrice * _amount);

        offers[_offerCount.current()] = _Offer(
            _tokenId, // uint256 tokenId;
            _unitPrice, // uint256 unitPrice;
            _amount, // uint256 amount;
            _collection, // address collection;
            _from // address from;
        );

        emit OfferCreated(_offerCount.current());
    }

    /**
     * @dev cancels offer and refunds ETH back to bidder. When an order gets filled, the offer isn't marked as cancelled, in order to allow users to claim back their money.
     * @param _offerId the Id of the offer.
     */
    function deleteOffer(uint256 _offerId) external {
        // in memory copy needed so that it is possible to delete the struct inside the storage offers mapping, while keeping check effects interact pattern intact
        _Offer memory offer = offers[_offerId];

        uint256 refund;

        if (offer.erc1155Value == 0) {
            refund = offer.unitPrice;
        } else {
            refund = offer.unitPrice * offer.erc1155Value;
        }
        require(msg.sender == offer.from);
        // mark offer as cancelled forever, updating offer price before external call, Checks Effects Interactions pattern
        delete offers[_offerId];
        // transfer the offer amount back to bidder
        _sendValue(offer.from, refund);

        emit OfferDeleted(_offerId);
    }

    /**
     * @dev this is one of two functions called by a buyer in order to modify their offer, there are two functions, `raiseOffer()` and `lowerOffer()`, because they expect different parameters depending on whether the offer is being raised or lowerd.
     *      A `msg.value` is required, this function will add the amount sent to the old offer amount. The frontend needs to calculate the difference between the old and new offer.
     *      E.g. A buyer calls createOffer() and pays 0.1 ETH. The same buyer later wants to raise the offer to 0.3 ETH, therefore they now need to send 0.2 ETH, because 0.1 was was sent before.
     * @dev anyone can call this function, i.e. requiring that caller is offer creator is not needed
     * @param _offerId the id of the offer
     * call this function only if the new total price is greater than the old total price
     */
    function raiseOfferPrice(
        uint256 _offerId,
        uint256 _erc1155Value,
        uint256 _unitPrice
    ) external payable {
        _Offer storage _offer = offers[_offerId];

        require(msg.sender == _offer.from);

        if (_offer.erc1155Value == 0)
            require(msg.value == _unitPrice - _offer.unitPrice);
        else
            require(
                msg.value ==
                    (_unitPrice * _erc1155Value) -
                        (_offer.unitPrice * _offer.erc1155Value)
            );

        _offer.unitPrice = _unitPrice; // transfer extra amount needed on top of older offer
        _offer.erc1155Value = _erc1155Value;

        emit OfferUpdated(_offerId);
    }

    /**
     * @dev this is one of two functions called by a buyer in order to modify their offer, there are two functions, `raiseOffer()` and `lowerOffer()`, because they expect different parameters depending on whether the offer is being raised or lowerd.
     *      In contrast with `raiseOffer()`, instead of `msg.value` this function expects a uint parameter representing the new (lower) offer; the buyer will get refunded the difference.
     *      E.g. A buyer calls createOffer() and pays 0.3 ETH. The same buyer later wants to lower the offer to 0.1 ETH, therefore they will get refunded 0.2 ETH. I.e. The amount expected by the `_newAmount` paramer is 0.1 ETH (1^17).
     * @param _offerId the id of the offer
     */
    function lowerOfferPrice(
        uint256 _offerId,
        uint256 _erc1155Amount,
        uint256 _unitPrice
    ) external {
        _Offer storage _offer = offers[_offerId];

        require(msg.sender == _offer.from, "hwy");

        uint256 refund;

        if (_erc1155Amount == 0) {
            refund = _offer.unitPrice - _unitPrice;
        } else {
            refund =
                (_offer.unitPrice * _offer.erc1155Value) -
                (_unitPrice * _erc1155Amount);
            _offer.erc1155Value = _erc1155Amount;
        }

        _offer.unitPrice = _unitPrice; // needed to store result before offer price is updated
        // transfer the difference between old and new lower offer to the user
        _sendValue(msg.sender, refund);

        emit OfferUpdated(_offerId);
    }

    function acceptListedTokenOffer(
        uint256 _orderId,
        uint256 _offerId,
        uint256 _commissionBps,
        address _commissionRecipient
    ) external {
        _Order memory order = orders[_orderId];
        _Offer memory offer = offers[_offerId];

        require(
            order.operator == msg.sender &&
                order.tokenId == offer.tokenId &&
                order.collection == offer.collection
        );

        delete orders[_orderId];
        delete offers[_offerId];

        _trade(
            _Order(
                order.tokenId, // uint256 tokenId
                offer.unitPrice, // offer price, not order price
                0,
                _commissionBps, // uint256 commissionBps
                _commissionRecipient,
                order.collection, // address collection
                msg.sender, // seller
                offer.from // buyer / nft recipient
            ),
            _orderId,
            _primaryOffersFee
        );
    }

    /*----------------------------------------------------------*|
    |*  # TRADING                                               *|
    |*----------------------------------------------------------*/

    /**
     * @notice the collector calls this function to buy an NFT at the ask price, only if an order exists
     * @notice if someone has an open offer but calls fillOrder, the offer will remain open, meaning they will need to call cancelOffer() to get a refund. This is unlikely, as users will likely be aware of this and use the refund in order to pay for part of the order.
     * @param _id avoids having to store a mapping to order id like the deprecated `mapping(address => mapping(uint256 => uint256)) private _tokenToOrderId` which would have not worked for erc1155 as each token has a supply.
     * _orderId does not constitute a vulnerability as it is user provided, since A) a regular user will go through the frontend which gets orderId from events
     * @param _buyer needed in order to integrate Wert payment solution, because in every txn Wert is the msg.sender, although using msg.sender would cost less gas.
     * using _msgSender does not represent a security risk, on the other hand, it is possible for the buyer to use this parameter simply in order to transfer the NFT to an address other than their own, this can be useful for external contract buying NFTs.
     + @param _erc1155Value market order amount (total or partial fill).
     * `_erc1155Value == 0` corresponds to one erc721 tokenId, `_erc1155Value > 0` for erc1155 tokenIds
     *
     * MUST:
     *
     * - `msg.value` must be equal to `orders[_orderId]der.unitPrice * buyAmount`
     * - `orders[_orderId].sellAmount >= buyAmount`
     *
     */
    function fillOrder(
        uint256 _id,
        address _buyer,
        uint256 _erc1155Value
    ) external payable {
        _Order memory order = orders[_id];

        require(
            msg.value ==
                order.unitPrice * (_erc1155Value == 0 ? 1 : _erc1155Value)
        );

        // subtracting user-suplied `_erc1155Value` from order amount, transaction will revert if underflow, implicitly requiring `orders[_id]._erc1155Value >= _erc1155Value`
        if (orders[_id].erc1155Value - _erc1155Value == 0) delete orders[_id];
        else orders[_id].erc1155Value -= _erc1155Value;

        _trade(
            _Order(
                order.tokenId, // uint256 tokenId
                msg.value, // price
                _erc1155Value,
                order.commissionBps, // uint256 commissionBps
                order.commissionReceiver, // address commissionReceiver
                order.collection, // address collection
                order.from, // seller or from
                _buyer // address buyer used for nft transfer
            ),
            _id,
            _primaryOrdersFee // uint256 primaryFee
        );
    }

    /**
     *
     * @dev Handles the receipt of a single ERC1155 token type. This function is
     * called at the end of a `safeTransferFrom` after the balance has been updated.
     *
     * NOTE: To accept the transfer, this must return
     * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
     * (i.e. 0xf23a6e61, or its own function selector).
     *
     * @param _operator The address which initiated the transfer (i.e. msg.sender)
     * @param _from     The address which previously owned the token
     * @param _tokenId  The ID of the token being transferred
     * @param _value    The amount of tokens being transferred
     * @param _data     Additional data with no specified format
     * @param _data     `uint256 id` corresponding to an order to be updated or an offer to be accepted, if the `id` parameter is 0 create a new order.
     * @param _data     `uint256 unitPrice` is only required for updating an order, i.e. if `unitPrice == 0` `id` an offer id, if `unitPrice > 0` `id` is an order id
     * @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed
     */
    function onERC1155Received(
        address _operator, //  either the previous owner or operator, whoever called safeTransferFrom on the ERC1155 contract
        address _from, // previous owner
        uint256 _tokenId,
        uint256 _value,
        bytes memory _data
    ) external returns (bytes4) {
        (
            uint256 id,
            uint256 unitPrice,
            uint256 commissionBps,
            address commissionReceiver
        ) = abi.decode(_data, (uint256, uint256, uint256, address));

        if (unitPrice == 0) {
            /*----------------------------------------------------------*|
            |*  # ACCEPT OFFER                                          *|
            |*----------------------------------------------------------*/
            // if `_value` is more than the amount the following if check will revert due to underflow,
            // intended to stop someone from sending more erc1155 tokens then there are available in the order

            _Offer memory offer = offers[id];

            require(
                offer.collection == msg.sender && offer.tokenId == _tokenId
            );

            // subtracting `_value` from offer amount, transaction will revert if underflow, implicitly requiring `offers[id]._erc1155Value >= _value`
            if (offers[id].erc1155Value - _value == 0) delete offers[id];
            else offers[id].erc1155Value -= _value;

            _trade(
                _Order(
                    _tokenId, // uint256 tokenId
                    offer.unitPrice * _value, // uint256 price (unitPrice * value)
                    _value,
                    commissionBps, // uint256 commissionBps
                    commissionReceiver, // address commissionReceiver
                    msg.sender, // address collection
                    _from, // seller
                    offer.from // buyer or operator
                ),
                id,
                _primaryOffersFee
            );
        } else if (id == 0) {
            /*----------------------------------------------------------*|
            |*  # CREATE ORDER                                          *|
            |*----------------------------------------------------------*/
            // if the order/offer id parameter is 0, create a new order

            _createOrder(
                _operator,
                _from,
                _tokenId,
                _value,
                unitPrice,
                commissionBps,
                commissionReceiver
            );
        } else {
            /*----------------------------------------------------------*|
            |*  # UPDATE ORDER                                          *|
            |*----------------------------------------------------------*/
            // if the user supplied a non-zero value for `unitPrice` then the `id` parameter must correspond to an order that needs to be updated
            // the operator, collection and tokenId of the NFT received by the marketplace must match the ones stored at the id provided by the operator
            // in order to avoid operators increasing allowance for orders with different (more valuable) NFTs
            _Order storage order = orders[id];

            require(
                order.operator == _operator &&
                    order.collection == msg.sender &&
                    order.tokenId == _tokenId
            );
            order.erc1155Value += _value;
            order.unitPrice = unitPrice;

            emit OrderUpdated(id);
        }

        return 0xf23a6e61;
    }

    /**
     * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
     * by `operator` from `from`, this function is called.
     *
     * It must return its Solidity selector to confirm the token transfer.
     * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
     *
     * The selector can be obtained in Solidity with `IERC721.onERC721Received.selector`.
     * @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
     */
    function onERC721Received(
        address _operator,
        address _from,
        uint256 _tokenId,
        bytes calldata _data
    ) external returns (bytes4) {
        (
            uint256 id,
            uint256 unitPrice,
            uint256 commissionBps,
            address commissionReceiver
        ) = abi.decode(_data, (uint256, uint256, uint256, address));

        if (id == 0) {
            /*----------------------------------------------------------*|
            |*  # CREATE ORDER                                          *|
            |*----------------------------------------------------------*/
            // if the order/offer id parameter is 0, create a new order
            _createOrder(
                _operator,
                _from,
                _tokenId,
                0,
                unitPrice,
                commissionBps,
                commissionReceiver
            );
        } else {
            /*----------------------------------------------------------*|
            |*  # ACCEPT OFFER                                          *|
            |*----------------------------------------------------------*/
            // if `_value` is more than the amount the following if check will revert due to underflow,
            // intended to stop someone from sending more erc1155 tokens then there are available in the order
            _Offer memory offer = offers[id];

            require(
                offer.collection == msg.sender && offer.tokenId == _tokenId
            );

            delete offers[id]; // ERC-721 doesn't have any supply therefore the offer may be deleted after accepting the offer

            _trade(
                _Order(
                    _tokenId, // uint256 tokenId
                    offer.unitPrice, // uint256 price
                    0,
                    commissionBps, // uint256 commissionBps
                    commissionReceiver,
                    msg.sender, // address collection
                    _from, // address buyer or from
                    offer.from // seller
                ),
                id,
                _primaryOffersFee //uint256 primaryFee
            );
        }

        return 0x150b7a02;
    }

    /**
     * @dev ERC-721 tokens are transferred to the buyer via `transferFrom` rather than `safeTransferFrom`
     *      i.e. the caller is responsible to confirm that the recipient is capable of receiving ERC721
     */
    function _transferNFT(
        address _collection,
        address _from,
        address _to,
        uint256 _tokenId,
        uint256 _erc1155Value
    ) private {
        bool success;
        if (_erc1155Value == 0)
            (success, ) = _collection.call(
                abi.encodeWithSelector(
                    0x42842e0e, // bytes4(keccak256('transferFrom(address,address,uint256)')) == 0x23b872dd
                    _from,
                    _to,
                    _tokenId
                )
            );
        else
            (success, ) = _collection.call(
                abi.encodeWithSelector(
                    0xf242432a, // bytes4(keccak256('safeTransferFrom(address,address,uint256,uint256,bytes)')) == 0xf242432a
                    _from,
                    _to,
                    _tokenId,
                    _erc1155Value,
                    ""
                )
            );

        require(success);
    }

    function _trade(
        _Order memory _order,
        uint256 _id,
        uint256 _primaryMarketFee
    ) private {
        uint256 marketplaceAmount; // declare `marketplaceAmount`, its value will be calculated based on whether it is a primary or secondary sale
        uint256 sellerAmount = _order.unitPrice; // sellerAmount is set equal to price and reduced at each step by subtracting fees, royalties and commissions, if any.
        address payable[] memory royaltyRecipients; // declare `royaltyRecipients`, its value will be calculated based on whether it is a primary or secondary sale
        uint256[] memory royaltyAmounts; // declare `royaltyAmounts`, its value will be calculated based on whether it is a primary or secondary sale

        if (_isERC721Sovreign[_order.collection.codehash]) {
            /**
             * @dev The following code is equivalent to: `bool secondaryMarket = IERC721(_order.collection).secondaryMarketInfo(_order.tokenId);`
             * @dev checking the return value of `bool success` is not required because if the call reverts, the value of `secondaryMarket` will be `false`.
             * The call is not expected to fail as the function is a view function from a trusted contract.
             */
            (, bytes memory secondaryMarket) = _PRIMARY_SALES_REGISTRY.call(
                abi.encodeWithSelector(
                    0x7abab711,
                    _order.collection,
                    _order.tokenId
                ) // bytes4(keccak256("secondaryMarketInfo(address,uint256)")) == 0x7abab711
            );

            if (abi.decode(secondaryMarket, (bool)) == false) {
                marketplaceAmount =
                    (_order.unitPrice * _primaryMarketFee) /
                    _BPS_DENOMINATOR;
                sellerAmount -= marketplaceAmount; // subtract before external call
                _sendValue(_feeRecipient, marketplaceAmount);
            }
        }

        if (marketplaceAmount == 0) {
            /*----------------------------------------------------------*|
             *  # IF SECONDARY MARKET                                   *|
            |*----------------------------------------------------------*/
            // if marketplaceAmount is not already set, then it is a secondary sale

            /**
             * @dev `getRoyalty()` is a getter function from a modified implementation of `RoyaltyEngineV1`, it only checks that `royaltyAmount < price`.
             * @dev arrays are returned so that their length may be checked before accessing their elements at index 0; an empty array is returned when royalties are not supported.
             *      if the collection does not support royalties, the arrays returned by `getRoyalty()` will be empty. If the collection supports royalties, the arrays will have a length of 1.
             */
            (royaltyRecipients, royaltyAmounts) = getRoyalty(
                _order.collection,
                _order.tokenId,
                _order.unitPrice
            );

            if (_secondaryMarketFee > 0) {
                /*----------------------------------------------------------*|
                |*  # PAY MARKETPLACE FEE                                   *|
                |*----------------------------------------------------------*/
                marketplaceAmount =
                    (_order.unitPrice * _secondaryMarketFee) /
                    _BPS_DENOMINATOR;
                // subtracting primary or secondary fee amount from seller amount, this is a security check (will revert on underflow) as well as a variable assignment.
                sellerAmount -= marketplaceAmount; // subtract before external call
                _sendValue(_feeRecipient, marketplaceAmount);
            }
        }

        /*----------------------------------------------------------*|
        |*  # PAY ROYALTIES                                         *|
        |*----------------------------------------------------------*/
        // > "Marketplaces that support this standard MUST pay royalties no matter where the sale occurred or in what currency" - https://eips.ethereum.org/EIPS/eip-2981.

        /*----------------------------------------------------------*|
        |*  # IF ROYALTIES SUPPORTED                                *|
        |*----------------------------------------------------------*/
        // The collection implements some royalty standard, otherwise the length of the arrays returned would be 0.
        uint256 royaltyRecipientsLength = royaltyRecipients.length; // assign to memory variable to save gas
        if (royaltyRecipientsLength > 0) {
            if (royaltyRecipientsLength > 1) {
                for (uint256 i = 0; i < royaltyRecipientsLength; i++) {
                    // subtracting royalty amount from seller amount, this is a security check (will revert on underflow) as well as a variable assignment.
                    sellerAmount -= royaltyAmounts[i]; // subtract before external call
                    _sendValue(royaltyRecipients[i], royaltyAmounts[i]);
                }
            } else {
                /// @dev this case is only triggered if the collection supports royalties and the length of the arrays returned is 1.
                ///         There is no check such as `if (royaltyAmounts[0] > 0)` as we assume that any collection that supports royalties will return a non-zero royalty amount.
                sellerAmount -= royaltyAmounts[0]; // subtracting royalty amount from seller amount, this is a security check (will revert on underflow) as well as a variable assignment.
                _sendValue(royaltyRecipients[0], royaltyAmounts[0]);
            }
        }

        /*----------------------------------------------------------*|
        |*  # PAY ORDER COMMISSIONS (if any)                        *|
        |*----------------------------------------------------------*/
        if (_order.commissionBps > 0) {
            uint256 commissionAmount = (_order.commissionBps *
                _order.unitPrice) / _BPS_DENOMINATOR; // calculate commission amount
            // subtracting sale commission amount from seller amount, this is a security check (will revert on underflow) as well as a variable assignment.
            sellerAmount -= commissionAmount; // subtract before external call
            _sendValue(_order.commissionReceiver, commissionAmount);
        }

        /*----------------------------------------------------------*|
        |*  # PAY SELLER                                            *|
        |*----------------------------------------------------------*/
        _sendValue(_order.from, sellerAmount);

        /*----------------------------------------------------------*|
        |*  # TRANSFER NFT                                          *|
        |*----------------------------------------------------------*/

        _transferNFT(
            _order.collection,
            address(this),
            _order.operator, // buyer
            _order.tokenId,
            _order.erc1155Value
        );

        emit Trade(
            _order.collection,
            _order.tokenId,
            _order.from, // seller
            _id,
            _order.unitPrice,
            _order.erc1155Value
        );
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `_amount` wei to
     * `_receiver`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] raises the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {_sendValue} removes this limitation.
     *
     * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `_receiver`, 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 _receiver, uint256 _amount) private {
        // solhint-disable-next-line avoid-low-level-calls
        (bool success, ) = payable(_receiver).call{value: _amount}("");
        require(success);
    }

    /*----------------------------------------------------------*|
    |*  # ADMIN FUNCTIONS                                       *|
    |*----------------------------------------------------------*/

    function setPrimaryMarketCodehash(
        address master,
        bool _isPrimaryMarket
    ) external onlyOwner {
        _isERC721Sovreign[master.codehash] = _isPrimaryMarket;
    }

    /**
     * @param feeRecipient_ address (multisig) controlled by Ninfa that will receive any market fees
     */
    function setFeeRecipient(address feeRecipient_) external onlyOwner {
        _feeRecipient = feeRecipient_;
    }

    /**
     * @notice whitelist collection to be traded on marketplace
     * @dev this function needs to be called immediately after deployment in order to set marketplace fees for NINFA_ERC721_V2 to 500bps or 5%. Required for access control by `isWhitelisted` modifier, also required in order to set secondary sales fees for NINFA_ERC721_V2 and all NinfaERC1155 to 5%.
     * @param _collection address of collection ERC721 or ERC1155
     */
    function whitelistCollection(
        address _collection,
        bool isWhitelisted_
    ) external onlyOwner {
        _isWhitelisted[_collection] = isWhitelisted_;
    }

    /**
     * @notice sets primary sale fees for NINFA_ERC721_V2 communal collection.
     * @param primaryOrdersFee_ fee BPS for primary market orders, set to 500 BPS (5% shares) at deployment.
     * @param primaryOffersFee_ fee BPS for primary market offers, set to 500 BPS (5% shares) at deployment.
     */
    function setMarketFees(
        uint256 primaryOrdersFee_,
        uint256 primaryOffersFee_,
        uint256 secondaryMarketFee_
    ) external onlyOwner {
        _primaryOrdersFee = primaryOrdersFee_;
        _primaryOffersFee = primaryOffersFee_;
        _secondaryMarketFee = secondaryMarketFee_;
    }

    /*----------------------------------------------------------*|
    |*  # VIEW FUNCTIONS                                        *|
    |*----------------------------------------------------------*/

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(
        bytes4 interfaceId
    ) external pure returns (bool) {
        return
            // Interface ID for IERC165
            interfaceId == 0x01ffc9a7 ||
            // Return value from `onERC1155Received` call if a contract accepts receipt (i.e `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`).
            // In all other cases the ERC1155TokenReceiver rules MUST be followed as appropriate for the implementation (i.e. safe, custom and/or hybrid).
            interfaceId == 0xf23a6e61 ||
            // Interface ID for IERC721Receiver. A wallet/broker/auction application MUST implement the wallet interface if it will accept safe transfers.
            interfaceId == 0x150b7a02;
    }

    /*----------------------------------------------------------*|
    |*  # CONSTRUCTOR                                           *|
    |*----------------------------------------------------------*/

    /**
     * @dev Grants `DEFAULT_ADMIN_ROLE`
     * @dev after deployment admin needs to manually whitelist collections.
     * @param _royaltyRegistry see https://royaltyregistry.xyz/lookup for public addresses
     */
    constructor(
        address _royaltyRegistry,
        address _primarySalesRegistry,
        address factory_
    ) RoyaltyEngineV1(_royaltyRegistry) {
        _PRIMARY_SALES_REGISTRY = _primarySalesRegistry;
        _factory = factory_;
    }
}

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



// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

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

// File: /contracts/utils/RoyaltyEngineV1.sol



/*----------------------------------------------------------*|
|*          ███    ██ ██ ███    ██ ███████  █████           *|
|*          ████   ██ ██ ████   ██ ██      ██   ██          *|
|*          ██ ██  ██ ██ ██ ██  ██ █████   ███████          *|
|*          ██  ██ ██ ██ ██  ██ ██ ██      ██   ██          *|
|*          ██   ████ ██ ██   ████ ██      ██   ██          *|
|*----------------------------------------------------------*/

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import "../specs/IRoyaltyRegistry.sol";
import "../specs/IManifold.sol";
import "../specs/IRarible.sol";
import "../specs/IFoundation.sol";
import "../specs/ISuperRare.sol";
import "../specs/IEIP2981.sol";
import "../specs/IZoraOverride.sol";
import "../specs/IArtBlocksOverride.sol";
import "../specs/IKODAV2Override.sol";
import "../specs/IRoyaltySplitter.sol";

/*************************************************************
 * @title RoyaltyEngineV1                                    *
 *                                                           *
 * @notice Custom implementation of Manifold RoyaltyEngineV1 *
 *                                                           *
 * @dev > "Marketplaces may choose to directly inherit the   *
 * Royalty Engine to save a bit of gas".                     *
 *                                                           *
 * @dev ERC165 was removed because interface selector will   *
 * be different from Manifold's (0xcb23f816) and this engine *
 * implementation is not meant for used by other contracts   *
 *                                                           *
 * @author Fork of Manifold's RoyaltyRegistryV1              *
 *                                                           *
 * @custom:security-contact tech@ninfa.io                    *
 ************************************************************/

contract RoyaltyEngineV1 {
    /**
     * @dev ROYALTY_REGISTRY could be a hardcoded constant, however using an immutable variable
     * is useful for deploying the engine onto other (test) networks where its address differs from Mainnet
     */
    address internal immutable ROYALTY_REGISTRY;
    address internal constant SUPERRARE_REGISTRY =
        0x17B0C8564E53f22364A6C8de6F7ca5CE9BEa4e5D;
    address internal constant SUPERRARE_V1 =
        0x41A322b28D0fF354040e2CbC676F0320d8c8850d;
    address internal constant SUPERRARE_V2 =
        0xb932a70A57673d89f4acfFBE830E8ed7f75Fb9e0;

    error Unauthorized();
    error InvalidAmount(uint256 amount);
    error LengthMismatch(uint256 recipients, uint256 bps); // only used in RoyaltyEngineV1

    /**
     * Get the royalties for a given token and sale amount.
     *
     * @param tokenAddress - address of token
     * @param tokenId - id of token
     * @param value - sale value of token
     * Returns two arrays, first is the list of royalty recipients, second is the amounts for each recipient.
     */
    function getRoyalty(
        address tokenAddress,
        uint256 tokenId,
        uint256 value
    )
        internal
        view
        returns (address payable[] memory recipients, uint256[] memory amounts)
    {
        try
            /**
             * @dev Control-flow hijack and gas griefing vulnerability within Manifold's RoyaltyEngine, mitigated in https://github.com/manifoldxyz/royalty-registry-solidity/commit/c5ba6db3e04e0b364f7afd7aae853a25542a7439.
             *      "To mitigate the griefing vector and other potential vulnerabilities, limit the gas by default that _getRoyalty is given to at most 50,000 gas, but certainly no more than 100,000 gas."
             *      - https://githubrecord.com/issue/manifoldxyz/royalty-registry-solidity/17/1067105243
             *      However, Ninfa's ERC-2981 implementation needs to write to storage upon primary sales, this consumes 800,000 at most gas,
             *      while it only reds from storage upon secondary sales, see {ERC721N-rotaltyInfo}
             *      Other implementations of ERC721N may require even more gas in the future, therefore the limit has been raised to 1,000,000 gas.
             */
            this._getRoyalty{gas: 100000}(tokenAddress, tokenId, value)
        returns (
            address payable[] memory _recipients,
            uint256[] memory _amounts
        ) {
            return (_recipients, _amounts);
        } catch {
            revert("Royalty lookup failed");
        }
    }

    /**
     * @dev Get the royalty for a given token
     * @dev the original RoyaltyEngineV1 has been modified by removing the _specCache and the associated code,
     * using try catch statements is very cheap, no need to store `_specCache` mapping, see {RoyaltyEngineV1-_specCache}.
     * - https://www.reddit.com/r/ethdev/comments/szot8r/comment/hy5vsxb/
     * @dev EIP-2981 standard lookup is performed first unlike Manifold's implementation, as it is the most prevalent
     * royalty standard as well as the one being used by Ninfa's collections
     * @return recipients array and amounts array, if no royalty standard has been found, the returned arrays will be empty
     */
    function _getRoyalty(
        address tokenAddress,
        uint256 tokenId,
        uint256 value
    )
        external
        view
        returns (address payable[] memory recipients, uint256[] memory amounts)
    {
        address royaltyAddress = IRoyaltyRegistry(ROYALTY_REGISTRY)
            .getRoyaltyLookupAddress(tokenAddress);

        try IEIP2981(royaltyAddress).royaltyInfo(tokenId, value) returns (
            address recipient,
            uint256 amount
        ) {
            // Supports EIP2981. Return amounts
            if (amount > value) revert InvalidAmount(amount); // note doesn't revert if amount == value

            try
                IRoyaltySplitter(royaltyAddress).getRecipients(tokenId)
            returns (Recipient[] memory royaltyInfo) {
                uint256 splitRecipientsLength = royaltyInfo.length;
                recipients = new address payable[](splitRecipientsLength);
                amounts = new uint256[](splitRecipientsLength);
                uint256 sum;

                for (uint256 i; i < splitRecipientsLength; ) {
                    Recipient memory splitRecipient = royaltyInfo[i];
                    recipients[i] = payable(splitRecipient.recipient);
                    uint256 splitAmount = (splitRecipient.bps * amount) / 10000;
                    amounts[i] = splitAmount;
                    sum += splitAmount;
                    unchecked {
                        ++i;
                    }
                }
                // sum can be less than amount, otherwise small-value listings can break
                require(sum <= amount, "Invalid split");

                return (recipients, amounts);
            } catch {
                // Supports EIP2981.  Return amounts
                recipients = new address payable[](1);
                amounts = new uint256[](1);
                recipients[0] = payable(recipient);
                amounts[0] = amount;
                return (recipients, amounts);
            }
        } catch {}

        // SuperRare handling
        if (tokenAddress == SUPERRARE_V1 || tokenAddress == SUPERRARE_V2) {
            try
                ISuperRareRegistry(SUPERRARE_REGISTRY).tokenCreator(
                    tokenAddress,
                    tokenId
                )
            returns (address payable creator) {
                try
                    ISuperRareRegistry(SUPERRARE_REGISTRY).calculateRoyaltyFee(
                        tokenAddress,
                        tokenId,
                        value
                    )
                returns (uint256 amount) {
                    recipients = new address payable[](1);
                    amounts = new uint256[](1);
                    recipients[0] = creator;
                    amounts[0] = amount;
                    return (recipients, amounts);
                } catch {}
            } catch {}
        }

        try IManifold(royaltyAddress).getRoyalties(tokenId) returns (
            address payable[] memory recipients_,
            uint256[] memory bps
        ) {
            // Supports manifold interface.  Compute amounts
            if (recipients_.length != bps.length)
                revert LengthMismatch(recipients_.length, bps.length);
            return (recipients_, _computeAmounts(value, bps));
        } catch {}

        try IRaribleV2(royaltyAddress).getRaribleV2Royalties(tokenId) returns (
            IRaribleV2.Part[] memory royalties
        ) {
            // Supports rarible v2 interface. Compute amounts
            recipients = new address payable[](royalties.length);
            amounts = new uint256[](royalties.length);
            uint256 totalAmount;
            for (uint256 i = 0; i < royalties.length; i++) {
                recipients[i] = royalties[i].account;
                amounts[i] = (value * royalties[i].value) / 10000;
                totalAmount += amounts[i];
            }
            if (totalAmount > value) revert InvalidAmount(totalAmount);
            return (recipients, amounts);
        } catch {}
        try IRaribleV1(royaltyAddress).getFeeRecipients(tokenId) returns (
            address payable[] memory recipients_
        ) {
            // Supports rarible v1 interface. Compute amounts
            recipients_ = IRaribleV1(royaltyAddress).getFeeRecipients(tokenId);
            try IRaribleV1(royaltyAddress).getFeeBps(tokenId) returns (
                uint256[] memory bps
            ) {
                if (recipients_.length != bps.length)
                    revert LengthMismatch(recipients_.length, bps.length);
                return (recipients_, _computeAmounts(value, bps));
            } catch {}
        } catch {}
        try IFoundation(royaltyAddress).getFees(tokenId) returns (
            address payable[] memory recipients_,
            uint256[] memory bps
        ) {
            // Supports foundation interface.  Compute amounts
            if (recipients_.length != bps.length)
                revert LengthMismatch(recipients_.length, bps.length);
            return (recipients_, _computeAmounts(value, bps));
        } catch {}
        try
            IZoraOverride(royaltyAddress).convertBidShares(
                tokenAddress,
                tokenId
            )
        returns (address payable[] memory recipients_, uint256[] memory bps) {
            // Support Zora override
            if (recipients_.length != bps.length)
                revert LengthMismatch(recipients_.length, bps.length);
            return (recipients_, _computeAmounts(value, bps));
        } catch {}
        try
            IArtBlocksOverride(royaltyAddress).getRoyalties(
                tokenAddress,
                tokenId
            )
        returns (address payable[] memory recipients_, uint256[] memory bps) {
            // Support Art Blocks override
            if (recipients_.length != bps.length)
                revert LengthMismatch(recipients_.length, bps.length);
            return (recipients_, _computeAmounts(value, bps));
        } catch {}
        try
            IKODAV2Override(royaltyAddress).getKODAV2RoyaltyInfo(
                tokenAddress,
                tokenId,
                value
            )
        returns (
            address payable[] memory _recipients,
            uint256[] memory _amounts
        ) {
            // Support KODA V2 override
            if (_recipients.length != _amounts.length)
                revert LengthMismatch(_recipients.length, _amounts.length);
            return (_recipients, _amounts);
        } catch {}

        // No supported royalties configured
        return (recipients, amounts);
    }

    /**
     * Compute royalty amounts
     */
    function _computeAmounts(
        uint256 value,
        uint256[] memory bps
    ) private pure returns (uint256[] memory amounts) {
        amounts = new uint256[](bps.length);
        uint256 totalAmount;
        for (uint256 i = 0; i < bps.length; i++) {
            amounts[i] = (value * bps[i]) / 10000;
            totalAmount += amounts[i];
        }
        if (totalAmount > value) revert InvalidAmount(totalAmount);
        return amounts;
    }

    constructor(address _royaltyRegistry) {
        ROYALTY_REGISTRY = _royaltyRegistry;
    }
}

// File: /contracts/utils/Counters.sol



/*----------------------------------------------------------*|
|*          ███    ██ ██ ███    ██ ███████  █████           *|
|*          ████   ██ ██ ████   ██ ██      ██   ██          *|
|*          ██ ██  ██ ██ ██ ██  ██ █████   ███████          *|
|*          ██  ██ ██ ██ ██  ██ ██ ██      ██   ██          *|
|*          ██   ████ ██ ██   ████ ██      ██   ██          *|
|*----------------------------------------------------------*/

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

/*************************************************************
 * @title Counters                                           *
 *                                                           *
 * @dev Stripped down version of OpenZeppelin Contracts       *
 * v4.4.1 (utils/Counters.sol), identical to                 *
 * CountersUpgradeable.sol being a library. Provides         *
 * counters that can only be incremented.                    *
 * Used to track the total supply of ERC721 ids.             *
 * @dev Include with `using Counters for Counters.Counter;`  *
 *                                                           *
 * @custom:security-contact tech@ninfa.io                    *
 ************************************************************/
/**
 * @title Counters
 * @dev Stripped down version of OpenZeppelin Contracts v4.4.1 (utils/Counters.sol), identical to CountersUpgradeable.sol being a library. Provides counters that can only be incremented. Used to track the total supply of ERC721 ids.
 * @dev Include with `using Counters for Counters.Counter;`
 */
library Counters {
    struct Counter {
        uint256 _value; // default: 0
    }

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

    /// @dev if implementing ERC721A there could be an overflow risk by removing overflow protection with `unchecked`, unless we limit the amount of tokens that can be minted, or require that totalsupply be less than 2^256 - 1
    function increment(Counter storage counter) internal {
        unchecked {
            counter._value += 1;
        }
    }
}

// File: /contracts/specs/IZoraOverride.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

/**
 * Paired down version of the Zora Market interface
 */
interface IZoraMarket {
    struct ZoraDecimal {
        uint256 value;
    }

    struct ZoraBidShares {
        // % of sale value that goes to the _previous_ owner of the nft
        ZoraDecimal prevOwner;
        // % of sale value that goes to the original creator of the nft
        ZoraDecimal creator;
        // % of sale value that goes to the seller (current owner) of the nft
        ZoraDecimal owner;
    }

    function bidSharesForToken(
        uint256 tokenId
    ) external view returns (ZoraBidShares memory);
}

/**
 * Paired down version of the Zora Media interface
 */
interface IZoraMedia {
    /**
     * Auto-generated accessors of public variables
     */
    function marketContract() external view returns (address);

    function previousTokenOwners(
        uint256 tokenId
    ) external view returns (address);

    function tokenCreators(uint256 tokenId) external view returns (address);

    /**
     * ERC721 function
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);
}

/**
 * Interface for a Zora media override
 */
interface IZoraOverride {
    /**
     * @dev Convert bid share configuration of a Zora Media token into an array of receivers and bps values
     *      Does not support prevOwner and sell-on amounts as that is specific to Zora marketplace implementation
     *      and requires updates on the Zora Media and Marketplace to update the sell-on amounts/previous owner values.
     *      An off-Zora marketplace sale will break the sell-on functionality.
     */
    function convertBidShares(
        address media,
        uint256 tokenId
    ) external view returns (address payable[] memory, uint256[] memory);
}

// File: /contracts/specs/ISuperRare.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

interface ISuperRareRegistry {
    /**
     * @dev Get the royalty fee percentage for a specific ERC721 contract.
     * @param _contractAddress address ERC721Contract address.
     * @param _tokenId uint256 token ID.
     * @return uint8 wei royalty fee.
     */
    function getERC721TokenRoyaltyPercentage(
        address _contractAddress,
        uint256 _tokenId
    ) external view returns (uint8);

    /**
     * @dev Utililty function to calculate the royalty fee for a token.
     * @param _contractAddress address ERC721Contract address.
     * @param _tokenId uint256 token ID.
     * @param _amount uint256 wei amount.
     * @return uint256 wei fee.
     */
    function calculateRoyaltyFee(
        address _contractAddress,
        uint256 _tokenId,
        uint256 _amount
    ) external view returns (uint256);

    /**
     * @dev Get the token creator which will receive royalties of the given token
     * @param _contractAddress address ERC721Contract address.
     * @param _tokenId uint256 token ID.
     */
    function tokenCreator(
        address _contractAddress,
        uint256 _tokenId
    ) external view returns (address payable);
}

// File: /contracts/specs/IRoyaltySplitter.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

/// @author: manifold.xyz
struct Recipient {
    address payable recipient;
    uint16 bps;
}

interface IRoyaltySplitter {
    /**
     * @dev Set the splitter recipients. Total bps must total 10000.
     */
    function setRecipients(Recipient[] calldata recipients) external;

    /**
     * @dev Get the splitter recipients;
     */
    function getRecipients(
        uint256 tokenId
    ) external view returns (Recipient[] memory);
}

// File: /contracts/specs/IRoyaltyRegistry.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

/// @author: manifold.xyz

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

/**
 * @dev Royalty registry interface
 */
interface IRoyaltyRegistry is IERC165 {
    event RoyaltyOverride(
        address owner,
        address tokenAddress,
        address royaltyAddress
    );

    /**
     * Override the location of where to look up royalty information for a given token contract.
     * Allows for backwards compatibility and implementation of royalty logic for contracts that did not previously support them.
     *
     * @param tokenAddress    - The token address you wish to override
     * @param royaltyAddress  - The royalty override address
     */
    function setRoyaltyLookupAddress(
        address tokenAddress,
        address royaltyAddress
    ) external returns (bool);

    /**
     * Returns royalty address location.  Returns the tokenAddress by default, or the override if it exists
     *
     * @param tokenAddress    - The token address you are looking up the royalty for
     */
    function getRoyaltyLookupAddress(
        address tokenAddress
    ) external view returns (address);

    /**
     * Returns the token address that an overrideAddress is set for.
     * Note: will not be accurate if the override was created before this function was added.
     *
     * @param overrideAddress - The override address you are looking up the token for
     */
    function getOverrideLookupTokenAddress(
        address overrideAddress
    ) external view returns (address);

    /**
     * Whether or not the message sender can override the royalty address for the given token address
     *
     * @param tokenAddress    - The token address you are looking up the royalty for
     */
    function overrideAllowed(address tokenAddress) external view returns (bool);
}

// File: /contracts/specs/IRarible.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

interface IRaribleV1 {
    /*
     * bytes4(keccak256('getFeeBps(uint256)')) == 0x0ebd4c7f
     * bytes4(keccak256('getFeeRecipients(uint256)')) == 0xb9c4d9fb
     *
     * => 0x0ebd4c7f ^ 0xb9c4d9fb == 0xb7799584
     */
    function getFeeBps(uint256 id) external view returns (uint[] memory);

    function getFeeRecipients(
        uint256 id
    ) external view returns (address payable[] memory);
}

interface IRaribleV2 {
    /*
     *  bytes4(keccak256('getRaribleV2Royalties(uint256)')) == 0xcad96cca
     */
    struct Part {
        address payable account;
        uint96 value;
    }

    function getRaribleV2Royalties(
        uint256 id
    ) external view returns (Part[] memory);
}

// File: /contracts/specs/IManifold.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

/// @author: manifold.xyz

/**
 * @dev Royalty interface for creator core classes
 */
interface IManifold {
    /**
     * @dev Get royalites of a token.  Returns list of receivers and basisPoints
     *
     *  bytes4(keccak256('getRoyalties(uint256)')) == 0xbb3bafd6
     *
     *  => 0xbb3bafd6 = 0xbb3bafd6
     */
    function getRoyalties(
        uint256 tokenId
    ) external view returns (address payable[] memory, uint256[] memory);
}

// File: /contracts/specs/IKODAV2Override.sol



// SPDX-License-Identifier: MIT

/// @author: knownorigin.io

pragma solidity 0.8.19;

interface IKODAV2 {
    function editionOfTokenId(
        uint256 _tokenId
    ) external view returns (uint256 _editionNumber);

    function artistCommission(
        uint256 _editionNumber
    ) external view returns (address _artistAccount, uint256 _artistCommission);

    function editionOptionalCommission(
        uint256 _editionNumber
    ) external view returns (uint256 _rate, address _recipient);
}

interface IKODAV2Override {
    /// @notice Emitted when the royalties fee changes
    event CreatorRoyaltiesFeeUpdated(
        uint256 _oldCreatorRoyaltiesFee,
        uint256 _newCreatorRoyaltiesFee
    );

    /// @notice For the given KO NFT and token ID, return the addresses and the amounts to pay
    function getKODAV2RoyaltyInfo(
        address _tokenAddress,
        uint256 _id,
        uint256 _amount
    ) external view returns (address payable[] memory, uint256[] memory);

    /// @notice Allows the owner() to update the creator royalties
    function updateCreatorRoyalties(uint256 _creatorRoyaltiesFee) external;
}

// File: /contracts/specs/IFoundation.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

interface IFoundation {
    /*
     *  bytes4(keccak256('getFees(uint256)')) == 0xd5a06d4c
     *
     *  => 0xd5a06d4c = 0xd5a06d4c
     */
    function getFees(
        uint256 tokenId
    ) external view returns (address payable[] memory, uint256[] memory);
}

interface IFoundationTreasuryNode {
    function getFoundationTreasury() external view returns (address payable);
}

interface IFoundationTreasury {
    function isAdmin(address account) external view returns (bool);
}

// File: /contracts/specs/IEIP2981.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

/**
 * EIP-2981
 */
interface IEIP2981 {
    /**
     * bytes4(keccak256("royaltyInfo(uint256,uint256)")) == 0x2a55205a
     *
     * => 0x2a55205a = 0x2a55205a
     */
    function royaltyInfo(
        uint256 tokenId,
        uint256 value
    ) external view returns (address, uint256);
}

// File: /contracts/specs/IArtBlocksOverride.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

/**
 *  Interface for an Art Blocks override
 */
interface IArtBlocksOverride {
    /**
     * @dev Get royalites of a token at a given tokenAddress.
     *      Returns array of receivers and basisPoints.
     *
     *  bytes4(keccak256('getRoyalties(address,uint256)')) == 0x9ca7dc7a
     *
     *  => 0x9ca7dc7a = 0x9ca7dc7a
     */
    function getRoyalties(
        address tokenAddress,
        uint256 tokenId
    ) external view returns (address payable[] memory, uint256[] memory);
}

// File: /contracts/access/Ownable.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable {
    address private _owner;

    event OwnershipTransferred(
        address previousOwner,
        address newOwner
    );

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(msg.sender);
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(owner() == msg.sender);
        _;
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() external onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) external onlyOwner {
        require(
            newOwner != address(0)
        );
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) private {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}