/*----------------------------------------------------------*|
|* ███ ██ ██ ███ ██ ███████ █████ *|
|* ████ ██ ██ ████ ██ ██ ██ ██ *|
|* ██ ██ ██ ██ ██ ██ ██ █████ ███████ *|
|* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ *|
|* ██ ████ ██ ██ ████ ██ ██ ██ *|
|*----------------------------------------------------------*/
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
/**
*
* @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;
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
/**
* 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);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
/**
* EIP-2981
*/
interface IEIP2981 {
/**
* bytes4(keccak256("royaltyInfo(uint256,uint256)")) == 0x2a55205a
*
* => 0x2a55205a = 0x2a55205a
*/
function royaltyInfo(uint256 tokenId, uint256 value) external view returns (address, uint256);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
/**
* @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);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
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);
}
// SPDX-License-Identifier: MIT
/// @author: knownorigin.io
pragma solidity 0.8.20;
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;
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
/// @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);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
/**
* EIP-2981
*/
interface INinfaRoyalty {
/**
* bytes4(keccak256("royaltyInfo(uint256,uint256)")) == 0x2a55205a
*
* => 0x2a55205a = 0x2a55205a
*/
function ninfaRoyaltyInfo(
uint256 tokenId,
uint256 value
)
external
returns (address payable[] memory, uint256[] memory);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
interface IRaribleV1 {
/*
* bytes4(keccak256('getFeeBps(uint256)')) == 0x0ebd4c7f
* bytes4(keccak256('getFeeRecipients(uint256)')) == 0xb9c4d9fb
*
* => 0x0ebd4c7f ^ 0xb9c4d9fb == 0xb7799584
*/
function getFeeBps(uint256 id) external view returns (uint256[] 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);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
/// @author: manifold.xyz
import "../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);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
/// @author: manifold.xyz
struct Recipient {
address payable recipient;
uint16 bps;
}
interface IRoyaltySplitter {
/**
* @dev Get the splitter recipients;
*/
function getRecipients() external view returns (Recipient[] memory);
function getRecipients(
uint256 tokenId
) external view returns (Recipient[] memory);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
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);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
/**
* 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);
}
/*----------------------------------------------------------*|
|* ███ ██ ██ ███ ██ ███████ █████ *|
|* ████ ██ ██ ████ ██ ██ ██ ██ *|
|* ██ ██ ██ ██ ██ ██ ██ █████ ███████ *|
|* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ *|
|* ██ ████ ██ ██ ████ ██ ██ ██ *|
|*----------------------------------------------------------*/
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
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 sovereign
/// 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_MARKET_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) private _auctions;
/// @notice whitelist of address codehashes of Ninfa's sovreign ERC721
/// collections, in order to determine if it is a
/// primary market sale
bytes32 private _ERC721SovreignV1CodeHash;
/**
* @notice _feeRecipient multisig for receiving trading fees
*/
address payable private _feeRecipient;
/// @notice factory contract for deploying self-sovereign collections.
address private _factory;
address private _whitelist;
/**
* @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 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;
uint256 tokenId;
uint256 bidPrice;
uint256 end;
uint256[] commissionBps;
address[] commissionReceivers;
}
/**
* @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-sovereign collections)
* @dev by checking the whitelist first with nested `if`s avoids having to
* make an external call unnecessarily
*/
modifier isWhitelisted(address _collection) {
bytes memory authorized;
(, authorized) = _factory.staticcall(
abi.encodeWithSelector(0xf6a3d24e, _collection)
);
if (abi.decode(authorized, (bool)) == false) {
(, authorized) = _whitelist.staticcall(
abi.encodeWithSelector(0x3af32abf, _collection)
);
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 _commissionReceivers 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[] memory _commissionBps,
address[] memory _commissionReceivers
)
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
_id,
_reservePrice,
0,
_commissionBps,
_commissionReceivers
);
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[] memory commissionBps, address[] memory commissionReceivers) =
abi.decode(_data, (uint256, uint256[], address[]));
_createAuction(_operator, _from, _tokenId, reservePrice, commissionBps, commissionReceivers);
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_);
}
function setCommissions(
uint256 auctionId_,
uint256[] memory _commissionBps,
address[] memory _commissionReceivers) external {
require(msg.sender == _auctions[auctionId_].operator);
_auctions[auctionId_].commissionBps = _commissionBps;
_auctions[auctionId_].commissionReceivers = _commissionReceivers;
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_];
bool checkSecondaryMarket = true;
if (auction.collection.codehash == _ERC721SovreignV1CodeHash) {
// it's a v1 721 token, check market registry
(, bytes memory secondaryMarket) = _PRIMARY_MARKET_REGISTRY.call(
abi.encodeWithSelector(
0x7abab711,
auction.collection,
auction.tokenId
) // bytes4(keccak256("secondaryMarketInfo(address,uint256)")) == 0x7abab711
);
checkSecondaryMarket = abi.decode(secondaryMarket, (bool));
}
/*----------------------------------------------------------*|
|* # 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.
if (checkSecondaryMarket) {
(royaltyRecipients, royaltyAmounts) = getRoyalty(auction.collection, auction.tokenId, sellerAmount);
}
uint256 royaltyRecipientsLength = royaltyRecipients.length; // assign to
if (royaltyRecipientsLength > 0) {
if (_secondaryMarketFee > 0) {
/*----------------------------------------------------------*|
|* # PAY MARKETPLACE FEE *|
|*----------------------------------------------------------*/
marketplaceAmount = (auction.bidPrice * _secondaryMarketFee) / 10_000;
// 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);
}
do {
royaltyRecipientsLength--;
// subtracting royalty amount from seller amount, this is a
// security check (will revert on
// underflow) as well as a variable assignment.
if(royaltyAmounts[royaltyRecipientsLength] > 0){
sellerAmount -= royaltyAmounts[royaltyRecipientsLength]; // subtract
// before external call
_sendValue(royaltyRecipients[royaltyRecipientsLength], royaltyAmounts[royaltyRecipientsLength]);
}
} while (royaltyRecipientsLength > 0);
} else {
//case primary
marketplaceAmount = (auction.bidPrice * _primaryMarketFee) / 10_000;
sellerAmount -= marketplaceAmount; // subtract before external
// call
_sendValue(_feeRecipient, marketplaceAmount);
}
/**
*
* Pay seller commissions *
*
*/
uint256 commissionReceiversLength = auction.commissionReceivers.length;
if (commissionReceiversLength > 0) {
do {
commissionReceiversLength--;
if(auction.commissionBps[commissionReceiversLength] > 0){
uint256 commissionAmount = (auction.commissionBps[commissionReceiversLength] * auction.bidPrice) / 10_000; // calculate
sellerAmount -= commissionAmount; // subtract before external
_sendValue(auction.commissionReceivers[commissionReceiversLength], commissionAmount);
}
} while (commissionReceiversLength > 0);
}
/**
*
* 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;
}
function setWhitelist(address whitelist_) external onlyOwner {
_whitelist = whitelist_;
}
/**
* @notice sets primary sale fees for NINFA_ERC721_V2 communal collection.
*/
function setMarketFees(uint256 primaryMarketFee_, uint256 secondaryMarketFee_) external onlyOwner {
_primaryMarketFee = primaryMarketFee_;
_secondaryMarketFee = secondaryMarketFee_;
}
function auctions(uint256 auctionId_) external view returns (_Auction memory) {
return _auctions[auctionId_];
}
/**
* @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_,
address whitelist_,
bytes32 ERC721SovreignV1CodeHash
)
RoyaltyEngineV1(_royaltyRegistry)
{
_PRIMARY_MARKET_REGISTRY = _primarySalesRegistry;
_factory = factory_;
_whitelist = whitelist_;
_ERC721SovreignV1CodeHash = ERC721SovreignV1CodeHash;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
/**
* @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);
}
}
/*----------------------------------------------------------*|
|* ███ ██ ██ ███ ██ ███████ █████ *|
|* ████ ██ ██ ████ ██ ██ ██ ██ *|
|* ██ ██ ██ ██ ██ ██ ██ █████ ███████ *|
|* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ *|
|* ██ ████ ██ ██ ████ ██ ██ ██ *|
|*----------------------------------------------------------*/
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import "./specs/IRoyaltyRegistry.sol";
import "./specs/IRoyaltySplitter.sol";
import "./specs/IManifold.sol";
import "./specs/IRarible.sol";
import "./specs/IFoundation.sol";
import "./specs/ISuperRare.sol";
import "./specs/IEIP2981.sol";
import "./specs/INinfaRoyalty.sol";
import "./specs/IZoraOverride.sol";
import "./specs/IArtBlocksOverride.sol";
import "./specs/IKODAV2Override.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
returns (address payable[] memory recipients, uint256[] memory amounts)
{
/**
* @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 (ERC2981N) needs to write to
* storage upon primary sales, this consumes
* 800,000 at most gas,
* while it only reads from storage upon secondary sales, see
* {ERC2981N-rotaltyInfo}
*/
try this._getRoyalty{ gas: 1_000_000 }(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
returns (address payable[] memory recipients, uint256[] memory amounts)
{
address royaltyAddress = IRoyaltyRegistry(ROYALTY_REGISTRY).getRoyaltyLookupAddress(tokenAddress);
try INinfaRoyalty(royaltyAddress).ninfaRoyaltyInfo(tokenId, value) returns (
address payable[] memory recipients_, uint256[] memory bps_
) {
require(recipients_.length == bps_.length);
return (recipients_, _computeAmounts(value, bps_));
} catch { }
try IEIP2981(royaltyAddress).royaltyInfo(tokenId, value) returns (address recipient, uint256 amount) {
if (amount > value) revert InvalidAmount(amount);
uint32 recipientSize;
assembly {
recipientSize := extcodesize(recipient)
}
if (recipientSize > 0) {
try IRoyaltySplitter(recipient).getRecipients() returns (Recipient[] memory splitRecipients) {
recipients = new address payable[](splitRecipients.length);
amounts = new uint256[](splitRecipients.length);
uint256 sum = 0;
uint256 splitRecipientsLength = splitRecipients.length;
for (uint256 i = 0; i < splitRecipientsLength;) {
Recipient memory splitRecipient = splitRecipients[i];
recipients[i] = payable(splitRecipient.recipient);
uint256 splitAmount = splitRecipient.bps * amount / 10_000;
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 {}
}
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 { }
try IManifold(royaltyAddress).getRoyalties(tokenId) returns (
address payable[] memory recipients_, uint256[] memory bps
) {
// Supports manifold interface. Compute amounts
require(recipients_.length == bps.length);
return (recipients_, _computeAmounts(value, bps));
} 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 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) / 10_000;
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]) / 10_000;
totalAmount += amounts[i];
}
if (totalAmount > value) revert InvalidAmount(totalAmount);
return amounts;
}
constructor(address _royaltyRegistry) {
ROYALTY_REGISTRY = _royaltyRegistry;
}
}
{
"compilationTarget": {
"src/NinfaEnglishAuction.sol": "NinfaEnglishAuction"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"appendCBOR": false,
"bytecodeHash": "none"
},
"optimizer": {
"details": {
"constantOptimizer": true,
"cse": true,
"deduplicate": true,
"inliner": true,
"jumpdestRemover": true,
"orderLiterals": true,
"peephole": true,
"yul": true,
"yulDetails": {
"optimizerSteps": "dhfoDgvulfnTUtnIf:fDnTOc",
"stackAllocation": true
}
},
"runs": 4194304
},
"remappings": [
":ds-test/=lib/forge-std/lib/ds-test/src/",
":forge-std/=lib/forge-std/src/",
":openzeppelin-contracts/=lib/openzeppelin-contracts/",
":openzeppelin/=lib/openzeppelin-contracts/contracts/",
":solmate/=lib/solmate/src/",
":src/=src/",
":test/=test/"
],
"viaIR": true
}
[{"inputs":[{"internalType":"address","name":"_royaltyRegistry","type":"address"},{"internalType":"address","name":"_primarySalesRegistry","type":"address"},{"internalType":"address","name":"factory_","type":"address"},{"internalType":"address","name":"whitelist_","type":"address"},{"internalType":"bytes32","name":"ERC721SovreignV1CodeHash","type":"bytes32"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"InvalidAmount","type":"error"},{"inputs":[{"internalType":"uint256","name":"recipients","type":"uint256"},{"internalType":"uint256","name":"bps","type":"uint256"}],"name":"LengthMismatch","type":"error"},{"inputs":[],"name":"Unauthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"auctionId","type":"uint256"}],"name":"AuctionCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"auctionId","type":"uint256"}],"name":"AuctionCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"auctionId","type":"uint256"}],"name":"AuctionFinalized","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"auctionId","type":"uint256"}],"name":"AuctionUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"auctionId","type":"uint256"}],"name":"Bid","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":false,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"_getRoyalty","outputs":[{"internalType":"address payable[]","name":"recipients","type":"address[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"auctionId_","type":"uint256"}],"name":"auctions","outputs":[{"components":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"address","name":"seller","type":"address"},{"internalType":"address","name":"collection","type":"address"},{"internalType":"address","name":"bidder","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"bidPrice","type":"uint256"},{"internalType":"uint256","name":"end","type":"uint256"},{"internalType":"uint256[]","name":"commissionBps","type":"uint256[]"},{"internalType":"address[]","name":"commissionReceivers","type":"address[]"}],"internalType":"struct NinfaEnglishAuction._Auction","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"auctionId_","type":"uint256"}],"name":"bid","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"auctionId_","type":"uint256"}],"name":"cancelAuction","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"auctionId_","type":"uint256"}],"name":"finalize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"auctionId_","type":"uint256"}],"name":"firstBid","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"_operator","type":"address"},{"internalType":"address","name":"_from","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"auctionId_","type":"uint256"},{"internalType":"uint256[]","name":"_commissionBps","type":"uint256[]"},{"internalType":"address[]","name":"_commissionReceivers","type":"address[]"}],"name":"setCommissions","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"_newFeeAccount","type":"address"}],"name":"setFeeRecipient","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"primaryMarketFee_","type":"uint256"},{"internalType":"uint256","name":"secondaryMarketFee_","type":"uint256"}],"name":"setMarketFees","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"whitelist_","type":"address"}],"name":"setWhitelist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"auctionId_","type":"uint256"},{"internalType":"uint256","name":"_newReservePrice","type":"uint256"}],"name":"updateReservePrice","outputs":[],"stateMutability":"nonpayable","type":"function"}]