// File: /contracts/NinfaEnglishAuction.sol



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

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

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

/*************************************************************
 * @title NinfaEnglishAuction                                *
 *                                                           *
 * @notice On-chain english auction                          *
 *                                                           *
 * @custom:security-contact tech@ninfa.io                    *
 ************************************************************/

contract NinfaEnglishAuction is Ownable, RoyaltyEngineV1 {
    using Counters for Counters.Counter;
    Counters.Counter private _auctionId;
    /// @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 auctions ids mapped to NFT auction data.
     * @dev This is deleted when an auction is finalized or canceled.
     * @dev Visibility needs to be public so that it can be called by a frontend as the auction creation event only emits auction id.
     */
    mapping(uint256 => _Auction) public auctions;
    /// @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 _feeRecipient multisig for receiving trading fees
     */
    address payable private _feeRecipient;
    /// @notice factory contract for deploying self-sovreign collections.
    address private _factory;
    /**
     * @notice How long an auction lasts for once the first bid has been received.
     */
    uint256 private constant _DURATION = 1 days;
    /**
     * @notice The window for auction extensions, any bid placed in the final 15 minutes of an auction will reset the time remaining to 15 minutes.
     */
    uint256 private constant _EXTENSION_DURATION = 15 minutes;
    /**
     * @notice the last highest bid is divided by this number in order to obtain the minimum bid increment. E.g. _MIN_BID_RAISE = 10 is 10% increment, 20 is 5%, 2 is 50%. I.e. 100 / _MIN_BID_RAISE. OpenSea uses a fixed 5% increment while SuperRare between 5-10%
     */
    uint256 private constant _MIN_BID_RAISE = 20;
    /**
     * @notice 5% fee on all secondary sales paid to Ninfa (seller receives the remainder after paying 10% royalties to artist/gallery and 5% Ninfa, i.e. 85%)
     */
    uint256 private constant _MARKETPLACE_FEE = 500;
    /// @notice Ninfa Marketplace fee percentage on primary sales, expressed in basis points
    uint256 private _primaryMarketFee;
    /// @notice Ninfa Marketplace fee percentage on all secondary sales, expressed in basis points
    uint256 private _secondaryMarketFee;

    /**
     * @notice Stores the auction configuration for a specific NFT.
     * @param operator since the order creator may be a gallery, i.e. the commission receiver itself, they would not be able to cancel or update the order as there would be no way to know if the order creator was the seller or the commissionReceiver,
     * @dev therefore an additional parameter is needed to store the address of `msg.sender`
     * @param end the time at which this auction will not accept any new bids. This is `0` until the first bid is placed.
     * @param bidder highest bidder, needs to be payable in order to receive refund in case of being outbid
     * @param price reserve price, highest bid, and all bids in between
     * @param erc1155Amount 0 for erc721, 1> for erc1155
     */
    struct _Auction {
        address operator;
        address seller;
        address collection;
        address bidder;
        address commissionReceiver;
        uint256 commissionBps;
        uint256 tokenId;
        uint256 bidPrice;
        uint256 end;
    }

    /**
     * @notice Emitted when an NFT is listed for auction.
     * @param auctionId The id of the auction that was created.
     * @dev the only parameter needed is auctionId, the emitted event must trigger the backend to retrieve all auction data from a getter function and store it in DB.
     */
    event AuctionCreated(uint256 auctionId);

    /**
     * @notice Emitted when an auction is cancelled.
     * @dev This is only possible if the auction has not received any bids.
     * @param auctionId The id of the auction that was cancelled.
     */
    event AuctionCanceled(uint256 auctionId);

    /**
     * @notice Emitted when the auction's reserve price is updated.
     * @dev This is only possible if the auction has not received any bids.
     * @param auctionId The id of the auction that was updated.
     */
    event AuctionUpdated(uint256 auctionId);

    /**
     * @notice Emitted when an auction that has already ended is finalized,
     * indicating that the NFT has been transferred and revenue from the sale distributed.
     */
    event AuctionFinalized(uint256 auctionId);

    /**
     * @notice Emitted when a bid is placed.
     * @param auctionId The id of the auction this bid was for.
     */
    event Bid(uint256 auctionId);

    /**
     * @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();
        }
        _;
    }

    /**
     * @notice Creates an auction for the given NFT. The NFT is held in escrow until the auction is finalized or canceled.
     * @param _id The id of the NFT.
     * @param _reservePrice The initial reserve price for the auction.
     * @dev reserve price may also be 0, clearly a mistake but not strictly required, only done in order to save gas by removing the need for a condition such as `if (_price == 0) revert InvalidAmount(_price)`
     * @param _commissionReceiver address of sale commissions receiver
     * @dev if `msg.sender` is also the `_commissionReceiver`, e.g. if `msg.sender` is a gallery, they must put their own address as the `_commissionReceiver`, and set the `_seller` parameter with the artist's/collector's address.
     * @dev if there is no commission receiver, it must be set to address(0)
     * @dev it is not required for `_commissionReceiver` and `_seller` addresses to be different (in order to save gas), although it would likely be a mistake, it cannot be exploited as the total amount paid out will never exceed the price set for the order. I.e. in the worst case the same address will receive both principal sale profit and commissions.
     */
    function _createAuction(
        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 _reservePrice,
        uint256 _commissionBps,
        address _commissionReceiver
    ) private {
        _auctionId.increment(); // start counter at 1

        uint256 auctionId_ = _auctionId.current();

        auctions[auctionId_] = _Auction(
            _operator,
            _from, // auction beneficiary, needs to be payable in order to receive funds from the auction sale
            msg.sender,
            address(0), // bidder is only known once a bid has been placed. // highest bidder, needs to be payable in order to receive refund in case of being outbid
            _commissionReceiver,
            _commissionBps,
            _id,
            _reservePrice,
            0
        );

        emit AuctionCreated(auctionId_);
    }

    /**
     * @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 reservePrice,
            uint256 commissionBps,
            address commissionReceiver
        ) = abi.decode(_data, (uint256, uint256, address));

        _createAuction(
            _operator,
            _from,
            _tokenId,
            reservePrice,
            commissionBps,
            commissionReceiver
        );

        return 0x150b7a02;
    }

    function _transferNFT(
        address _collection,
        address _from,
        address _to,
        uint256 _tokenId
    ) private {
        (bool success, ) = _collection.call(
            abi.encodeWithSelector(
                0x42842e0e, // bytes4(keccak256('safeTransferFrom(address,address,uint256)')) == 0x42842e0e
                _from,
                _to,
                _tokenId
            )
        );

        require(success);
    }

    function firstBid(uint256 auctionId_) external payable {
        _Auction storage _auction = auctions[auctionId_];

        // hardcoded 0x0 address in order to avoid reading from storage.
        // there is no need to check whether the auction exists already, because even if someone managed to set end, price and bidder for a (yet) non-existing auction, they would be reser when an auction with the same id gets created
        // the important thing is that no one can reset these variables for auctions that have already started, and can't happen because _auction.bidder would be set after the first bid is placed by calling this function.
        if (
            _auction.bidder != 0x0000000000000000000000000000000000000000 || // if auction has started
            msg.value < _auction.bidPrice
        ) revert Unauthorized();

        // if the auction exists and this is the firsat bid, start the auction timer.
        // On the first bid, set the end to now + duration. `_DURATION` is a constant set to 24hrs therefore the below addition can't overflow.
        unchecked {
            _auction.end = block.timestamp + _DURATION;
            _auction.bidPrice = msg.value; // new highest bid
            _auction.bidder = msg.sender; // new highest bidder
        }

        emit Bid(auctionId_);
    }

    /**
     * @notice Place a bid in an auction.
     * A bidder may place a bid which is at least the amount defined by `getMinBidAmount`.
     * If this is the first bid on the auction, the countdown will begin.
     * If there is already an outstanding bid, the previous bidder will be refunded at this time
     * and if the bid is placed in the final moments of the auction, the countdown may be extended.
     * @dev bids MUST be at least 5% higher than previous bid.
     * @param auctionId_ The id of the auction to bid on.
     * @dev auctionId_ MUST exist, auction MUST have begun and MUST not have ended.
     */
    function bid(uint256 auctionId_) external payable {
        _Auction storage _auction = auctions[auctionId_];
        // if auction hasn't started or doesn't exist, i.e. no one has called firstBid() yet, _auction.end will still be 0,
        // therefore the following require statement implicitly checks that auction has started and explicitly that it has not ended

        if (
            block.timestamp > _auction.end ||
            _auction.end == 0 || // required otherwise calling this function would start a 15 minutes auction rather than 24h
            msg.value - _auction.bidPrice < _auction.bidPrice / _MIN_BID_RAISE
        ) revert Unauthorized();

        // if there is less than 15 minutes left, increment end time by 15 more. _EXTENSION_DURATION is always set to 15 minutes so the below can't overflow.
        // already checking in previous if statement that if `block.timestamp > _auction.end` the tx reverts, meaning that `block.timestamp` must be less than `_auction.end`, i.e. auction hasn't expired,
        // if you combine that with `block.timestamp + _EXTENSION_DURATION > _auction.end` that means that `block.timestamp` must be between `_auction.end` and `_auction.end - 15 minutes`, i.e. it's the last 15 minutes of the auction.
        if (block.timestamp + _EXTENSION_DURATION > _auction.end) {
            unchecked {
                _auction.end += _EXTENSION_DURATION;
            }
        }

        // refund the previous bidder
        _sendValue(_auction.bidder, _auction.bidPrice);

        // does not follow check-effects-interactions pattern so that storing previous bidder and amount in memory is not required, however there is no reentrancy exploit in this case;
        // calling back into `bid()` requires that `msg.value` is 5% higher than previous bid, meaning that the extra 5% would not be refunded because storage has not been updated yet
        // besides the bid() function, there is no other function that can be called back into which represents a security risk, namely `createAuction()` and `firstBid()`, i.e. _auction.bidPrice and _auction.bidder are not read by any other function that may be reentered
        _auction.bidPrice = msg.value; // new highest bid
        _auction.bidder = msg.sender; // new highest bidder

        emit Bid(auctionId_);
    }

    /**
     * @notice If an auction has been created but has not yet received bids, the `reservePrice` may be edited by the seller.
     * @param auctionId_ The id of the auction to change.
     * @param _newReservePrice The new reserve price for this auction, may be higher or lower than the previoius price.
     * @dev `_newReservePrice` may be equal to old price (`auctions[auctionId_].price`); although this doesn't make much sense it isn't a security requirement, hence `require(_auction.bidPrice != _price)` it has been omitted in order to save the user some gas
     * @dev `_newReservePrice` may also be 0, clearly a mistake but not a security requirement,  hence `require(_price > 0)` has been omitted in order to save the user some gas
     */
    function updateReservePrice(
        uint256 auctionId_,
        uint256 _newReservePrice
    ) external {
        _Auction storage _auction = auctions[auctionId_];
        // code duplication because modifiers can't pass variables to functions, meanining that storage pointer cannot be instantiated in modifier
        require(_auction.operator == msg.sender && _auction.end == 0);

        // Update the current reserve price.
        _auction.bidPrice = _newReservePrice;

        emit AuctionUpdated(auctionId_);
    }

    /**
     * @notice If an auction has been created but has not yet received bids, it may be canceled by the seller.
     * @dev The NFT is transferred back to the owner unless there is still a buy price set.
     * @param auctionId_ The id of the auction to cancel.
     */
    function cancelAuction(uint256 auctionId_) external {
        _Auction memory _auction = auctions[auctionId_];

        require(_auction.operator == msg.sender && _auction.end == 0);

        // Delete the _auction.
        delete auctions[auctionId_];

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

        emit AuctionCanceled(auctionId_);
    }

    function finalize(uint256 auctionId_) external {
        _Auction memory auction = auctions[auctionId_];
        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
        // sellerAmount is a security check as well as a variable assignment, because it would revert if there was an underflow
        // sellerAmount may be 0 if royalties are set too high for an external collection. If `royaltyAmount == (auction.bidPrice - marketplaceAmount)` then `sellerAmount == 0`. if royalties amount exceeds price - fees amount the transaction will revert.
        uint256 sellerAmount = auction.bidPrice;
        uint256 marketplaceAmount;

        // there must be at least one bid higher than the reserve price in order to execute the trade, no bids mean no end time
        if (block.timestamp < auction.end || auction.end == 0)
            revert Unauthorized();

        // Remove the auction.
        delete auctions[auctionId_];

        if (_isERC721Sovreign[auction.collection.codehash]) {
            /**
             * @dev The following code is equivalent to: `bool secondaryMarket = IERC721(auction.collection).secondaryMarketInfo(auction.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,
                    auction.collection,
                    auction.tokenId
                ) // bytes4(keccak256("secondaryMarketInfo(address,uint256)")) == 0x7abab711
            );

            if (abi.decode(secondaryMarket, (bool)) == false) {
                marketplaceAmount =
                    (auction.bidPrice * _primaryMarketFee) /
                    10000;
                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(
                auction.collection,
                auction.tokenId,
                auction.bidPrice
            );

            if (_secondaryMarketFee > 0) {
                /*----------------------------------------------------------*|
                |*  # PAY MARKETPLACE FEE                                   *|
                |*----------------------------------------------------------*/
                marketplaceAmount =
                    (auction.bidPrice * _secondaryMarketFee) /
                    10000;
                // 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 seller commissions *
         **************************/

        if (auction.commissionBps > 0) {
            uint256 commissionAmount = (auction.bidPrice *
                auction.commissionBps) / 10000;

            /**
             * Pay commissions, if any
             * since the seller can arbitrarily provide a value for commission shares, should the seller input a BPS/shares value that exceeds the amount they are entitled to for the sale, commissions will not be paid but instead the remaining amount (after royalties and fees) is paid to the seller.
             * this is a security check in order to avoid paying more than the actual price
             * `if` is better than `require` because should the royalties or commissions be set too high by mistake, the nft and eth may get locked in the contract forever because the entire tx would revert
             */

            sellerAmount -= commissionAmount; // sellerAmount may be 0
            /**
             * Pay commissions, if any
             */
            _sendValue(auction.commissionReceiver, commissionAmount);
        }

        /**************
         * Pay seller *
         *************/

        _sendValue(auction.seller, sellerAmount);

        // transfer nft to auction winner
        _transferNFT(
            auction.collection,
            address(this),
            auction.bidder,
            auction.tokenId
        );

        emit AuctionFinalized(auctionId_);
    }

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

    /**
     * @dev setter function only callable by contract admin used to change the address to which fees are paid
     * @param _newFeeAccount is the address owned by NINFA that will collect sales fees
     */
    function setFeeRecipient(
        address payable _newFeeAccount
    ) external onlyOwner {
        _feeRecipient = _newFeeAccount;
    }

    /**
     * @notice sets primary sale fees for NINFA_ERC721_V2 communal collection.
     */
    function setMarketFees(
        uint256 primaryMarketFee_,
        uint256 secondaryMarketFee_
    ) external onlyOwner {
        _primaryMarketFee = primaryMarketFee_;
        _secondaryMarketFee = secondaryMarketFee_;
    }

    /**
     * @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_;
    }

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

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(
        bytes4 interfaceId
    ) external pure returns (bool) {
        return
            // Interface ID for IERC165
            interfaceId == 0x01ffc9a7 ||
            // 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);
    }
}