// File: src/AccaAuction.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;
import {SafeTransferLib} from "lib/solady/src/utils/SafeTransferLib.sol";
import {IERC165, ERC165} from "lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol";
import {IERC721} from "lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol";
import {IERC721Receiver} from "lib/openzeppelin-contracts/contracts/token/ERC721/IERC721Receiver.sol";
import {IERC1155} from "lib/openzeppelin-contracts/contracts/token/ERC1155/IERC1155.sol";
import {IERC1155Receiver} from "lib/openzeppelin-contracts/contracts/token/ERC1155/IERC1155Receiver.sol";
import {IERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {IERC2981} from "lib/openzeppelin-contracts/contracts/interfaces/IERC2981.sol";
import {IRoyaltyEngineV1} from "lib/royalty-registry-solidity/contracts/IRoyaltyEngineV1.sol";

interface IAccaAuction {
    error BidTooLow(uint256 listingId, uint256 bidAmount);
    error AlreadyInitialized();
    error ReentrantCall();
    error ListingNotActive(uint256 listingId);
    error NotOwner(address);
    error ListingAlreadyExists(uint256 listingId);
    error NonExistentListing(uint256 listingId);
    error ActiveListingNotReplaceable(uint256 listingId);
    error ActiveListingNotRemoveable(uint256 listingId);
    error ListingNotCompleted(uint256 listingId);
    error ArraysSizesDoNotMatch();
    error NotSupportedToken(address tokenContract);
    error NotOwnedByThis(address tokenContract, uint256 tokenId);
    error NoBids(uint256 listingId);
    error ClaimerNotLatestBidder(uint256 listingId, address claimer, address latestBidder);
    error ListingNotReturnable(uint256 listingId);
    error ListingNotClaimable(uint256 listingId);
    error NoZeroStartPrice();

    event ListingAdded(
        uint256 indexed listingId, 
        address indexed tokenContract, 
        uint256 indexed tokenId,
        address tokenProvider,
        uint256 startPrice,
        uint256 reservePrice
    );
    
    event OwnershipTransferred(address oldOwner, address newOwner);

    event EthTransferFailed(address to, uint256 amount);

    event RoyaltiesRetrieveError(
        address tokenContract, 
        uint256 tokenId, 
        uint256 amount, 
        bytes reason
    );

    event NFTTransferError(
        address tokenContract, 
        uint256 tokenId, 
        address to,
        bytes reason
    );

    struct Bid {
        address bidder;
        bool refunded;
        uint256 bidAmount;
    }

    struct BidStep {
        uint256 nextBidThreshold;
        uint256 stepSize;
    }

    struct ListingDetails {
        address contractAddres;
        uint256 tokenId;
        address tokenProvider;
        uint256 startPrice;
        uint256 reservePrice;
        uint256 extraTime;
    }

    function createListing(
        uint256 listingId,
        ListingDetails calldata listing
    ) external;

    function createListings(
        uint256[] calldata listingIds,
        ListingDetails[] calldata listings
    ) external;

    function replaceListing(
        uint256 listingId,
        ListingDetails memory listing
    ) external;

    function removeListing(uint256 listingId) external;

    function getListing(uint256 listingId) external view returns (ListingDetails memory);

    function isActive(uint256 listingId) external view returns(bool);

    function listingEndTime(uint256 listingId) external view returns(uint256);

    function getMinNextBidAmount(uint256 listingId) external view returns (uint256);

    function placeBid(uint256 listingId) external payable;

    function getLatestBid(uint256 listingId) external view returns (Bid memory);

    function getBids(uint256 listingId) external view returns (Bid[] memory);

    function setBidSteps(BidStep[] memory _bidSteps) external;

    function getAuctionStartTime() external view returns (uint256);

    function changeAuctionStartTimestamp(uint256 _newTimestamp) external;

    function completeListing(uint256 listingId) external;

    function isListingClaimable(uint256 listingId) external view returns (bool);

    function transferOwnership(address _newOwner) external;

    function owner() external view returns (address);

    function emergencyWithdrawETH(address to, uint256 amount) external;

    function emergencyWithdrawERC20(address tokenContract, address to, uint256 amount) external;

    function returnTokenToProvider(uint256 listingId) external;

    function transferNFTFromAuction(
        address tokenContract,
        uint256 tokenId,
        address to
    ) external;
}

contract AccaAuction is IAccaAuction, ERC165, IERC1155Receiver, IERC721Receiver {

    using SafeTransferLib for address;
    
    struct AuctionStorage {
        string name;
        address owner;
        uint256 startTimestamp;
        uint256 auctionDuration;
        uint256 extraTimePeriod;
        address royaltyEngineAddress;

        uint256 _reentrancyStatus;

        // listingId => listingParameters
        mapping (uint256 => ListingDetails) listings;

        // store this as a separate mapping to make creating listings cheaper
        mapping (uint256 => bool) isListingClaimed;

        // listingId => bids
        mapping (uint256 => Bid[]) listingBids;

        // bidSteps
        BidStep[] bidSteps;
    }

    uint256 private constant ACCA_SHARE = 20;
    uint256 private constant NOT_ENTERED = 1;
    uint256 private constant ENTERED = 2;
    uint256 private constant ETH_SEND_GAS_STIPEND = 10_000;

    // keccak256(abi.encode(uint256(keccak256("acca.storage.auction")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant AuctionStorageLocation = 0xf3d3c5373489466780afac49d56e4aa8e5c681cfad75e15483bd09008fc14900;

    function _getAucStorage() private pure returns (AuctionStorage storage _as) {
        assembly {
            _as.slot := AuctionStorageLocation
        }
    }

    /////// Initial Setup ///////

    function init(
        string calldata _name,
        address _owner,
        uint256 _startTimestamp,
        uint256 _auctionDuration,
        uint256 _extraTimePeriod,
        address _royaltyEngineAddress
    ) external {
        AuctionStorage storage _as = _getAucStorage();
        
        if(bytes(_as.name).length != 0) 
            revert AlreadyInitialized();
        
        _as._reentrancyStatus = NOT_ENTERED;        
        _as.name = _name;
        _as.owner = _owner;
        _as.royaltyEngineAddress = _royaltyEngineAddress;

        // set auc parameters
        _as.startTimestamp = _startTimestamp;
        _as.auctionDuration = _auctionDuration;
        _as.extraTimePeriod = _extraTimePeriod;
    }

    /////// Listings ///////

    function createListing(
        uint256 listingId,
        ListingDetails calldata listing
    ) public onlyOwner {
        _createListing(listingId, listing);
    }

    function createListings(
        uint256[] calldata listingIds,
        ListingDetails[] calldata listings
    ) public onlyOwner {
        if(listingIds.length != listings.length)
            revert ArraysSizesDoNotMatch();
        for(uint256 i; i < listingIds.length; ) {
            _createListing(
                listingIds[i],
                listings[i]
            );
            unchecked {
                ++i;
            }
        }
    }

    function _createListing(
        uint256 listingId,
        ListingDetails memory listing
    ) internal {
        AuctionStorage storage _as = _getAucStorage();
        _checkOwnedByThis(listing.contractAddres, listing.tokenId);
        if(listing.startPrice == 0)
            revert NoZeroStartPrice();
        if(_as.listings[listingId].contractAddres != address(0))
            revert ListingAlreadyExists(listingId);
        _as.listings[listingId] = listing;
        emit ListingAdded(
            listingId,
            listing.contractAddres,
            listing.tokenId,
            listing.tokenProvider,
            listing.startPrice,
            listing.reservePrice
        );
    }

    function replaceListing(
        uint256 listingId,
        ListingDetails memory listing
    ) public onlyOwner {
        AuctionStorage storage _as = _getAucStorage();
        if(_as.listings[listingId].contractAddres == address(0))
            revert NonExistentListing(listingId);
        if(isActive(listingId))
            revert ActiveListingNotReplaceable(listingId);
        _checkOwnedByThis(listing.contractAddres, listing.tokenId);
        _as.listings[listingId] = listing;
        emit ListingAdded(
            listingId,
            listing.contractAddres,
            listing.tokenId,
            listing.tokenProvider,
            listing.startPrice,
            listing.reservePrice
        );
    }

    function removeListing(uint256 listingId) public onlyOwner {
        AuctionStorage storage _as = _getAucStorage();
        if(_as.listings[listingId].contractAddres == address(0))
            revert NonExistentListing(listingId);
        if(isActive(listingId))
            revert ActiveListingNotRemoveable(listingId);
        delete _as.listings[listingId];
        delete _as.listingBids[listingId];
        delete _as.isListingClaimed[listingId];
    }

    function getListing(uint256 listingId) public view returns (ListingDetails memory) {
        AuctionStorage storage _as = _getAucStorage();
        return _as.listings[listingId];
    }

    function isActive(uint256 listingId) public view returns(bool) {
        AuctionStorage storage _as = _getAucStorage();
        return (block.timestamp >= _as.startTimestamp) && 
            (block.timestamp <= 
                _as.startTimestamp +
                _as.auctionDuration +
                _as.listings[listingId].extraTime
            ) /*&&  !_as.isListingClaimed[listingId] */; //last condition removed for gas efficiency
    }

    function listingEndTime(uint256 listingId) public view returns(uint256) {
        AuctionStorage storage _as = _getAucStorage();
        return _as.startTimestamp + _as.auctionDuration + _as.listings[listingId].extraTime;
    }

    /////// Bids ///////
    /**
     * @dev Place a bid on a listing
     * @param listingId id of the listing
     * @notice re entrancy guard was removed from here as re entrancy attempts are not possible
     * because of ETH_SEND_GAS_STIPEND size (see the appropriate test case)
     */
    function placeBid(uint256 listingId) public payable {
        AuctionStorage storage _as = _getAucStorage();
        if(!isActive(listingId))
            revert ListingNotActive(listingId);
        if (msg.value < getMinNextBidAmount(listingId)) 
            revert BidTooLow(listingId, msg.value);
        if (_as.listingBids[listingId].length != 0) {
            Bid storage prevBid = _as.listingBids[listingId][_as.listingBids[listingId].length-1];
            // send eth back to the prev bidder
            // do not revert if not successful
            if(prevBid.bidder.trySafeTransferETH(prevBid.bidAmount, ETH_SEND_GAS_STIPEND)) {
                prevBid.refunded = true;
            } else {
                emit EthTransferFailed(prevBid.bidder, prevBid.bidAmount);
            }
        }

        // record new bid
        _as.listingBids[listingId].push(Bid(msg.sender, false, msg.value));

        _checkAndAddExtraTime(listingId);
    }

    function getMinNextBidAmount(uint256 listingId) public view returns (uint256) {
        AuctionStorage storage _as = _getAucStorage();
        if(_as.listingBids[listingId].length == 0)
            return _as.listings[listingId].startPrice;
        
        uint256 curBidAmount = _as.listingBids[listingId][_as.listingBids[listingId].length-1].bidAmount;    
        uint256 step;
        step = _as.bidSteps[_as.bidSteps.length-1].stepSize;
        for (uint256 i; i < _as.bidSteps.length; ) {
            if (curBidAmount < _as.bidSteps[i].nextBidThreshold) {
                step = _as.bidSteps[i].stepSize;
                break;
            }
            unchecked {
                ++i;
            }
        }
        return curBidAmount + step;   
    }

    function getLatestBid(uint256 listingId) public view returns (Bid memory) {
        AuctionStorage storage _as = _getAucStorage();
        if (_as.listingBids[listingId].length == 0)
            revert NoBids(listingId);
        return _as.listingBids[listingId][_as.listingBids[listingId].length-1];
    }

    function getBids(uint256 listingId) public view returns (Bid[] memory) {
        AuctionStorage storage _as = _getAucStorage();
        return _as.listingBids[listingId];
    }

    function setBidSteps(BidStep[] memory _bidSteps) public onlyOwner {
        AuctionStorage storage _as = _getAucStorage();
        // because copying struct[] from memory to storage not supported yet by codegen
        for(uint256 i; i < _bidSteps.length; ) {
            _as.bidSteps.push(_bidSteps[i]);
            unchecked {
                ++i;
            }
        }
    }

    /////// Send art to the winner ///////
    function completeListing(uint256 listingId) public {
        AuctionStorage storage _as = _getAucStorage();

        address tokenAddress = _as.listings[listingId].contractAddres;
        uint256 tokenId = _as.listings[listingId].tokenId;

        _checkOwnedByThis(tokenAddress, tokenId);
        
        if (!isListingClaimable(listingId))
            revert ListingNotClaimable(listingId);

        // length-1 is safe as we checked that there is at least one bid in isListingClaimable
        Bid memory winnerBid = _as.listingBids[listingId][_as.listingBids[listingId].length-1];

        // mark listing as claimed
        _as.isListingClaimed[listingId] = true;
        
        // distribute funds including royalties
        _distributeFundsIncludingRoyalties(listingId, winnerBid.bidAmount);

        // send token to claimer
        _detectErcAndTransferToken(
            tokenAddress,
            tokenId,
            winnerBid.bidder
        );
    }
    
    function isListingClaimable(uint256 listingId) public view returns (bool) {
        AuctionStorage storage _as = _getAucStorage();
        //active listings are not claimable
        if(isActive(listingId))
            return false;
        //if there's no bids, it is not claimable
        if (_as.listingBids[listingId].length == 0)
            return false;
        //check if listing is already claimed
        if (_as.isListingClaimed[listingId])
            return false;
        // check if reserve price is met
        if (_as.listingBids[listingId][_as.listingBids[listingId].length-1].bidAmount < _as.listings[listingId].reservePrice)
            return false;
        return true;
    }

    /////// Time Management ///////

    function getAuctionStartTime() public view returns (uint256) {
        AuctionStorage storage _as = _getAucStorage();
        return _as.startTimestamp;
    }

    function changeAuctionStartTimestamp(uint256 _newTimestamp) public onlyOwner {
        AuctionStorage storage _as = _getAucStorage();
        if(_newTimestamp < block.timestamp)
            revert("Wrong Timestamp");
        if(block.timestamp > _as.startTimestamp)
            revert("AuctionHasStarted");
        _as.startTimestamp = _newTimestamp;
    }

    function startAuctionImmediately() public onlyOwner {
        AuctionStorage storage _as = _getAucStorage();
        if(block.timestamp > _as.startTimestamp)
            revert("AuctionHasStarted");
        _as.startTimestamp = block.timestamp;
    }

    function getAuctionDuration() public view returns (uint256) {
        AuctionStorage storage _as = _getAucStorage();
        return _as.auctionDuration;
    }

    function changeAuctionDuration(uint256 _newDuration) public onlyOwner {
        AuctionStorage storage _as = _getAucStorage();
        if(block.timestamp > _as.startTimestamp + _newDuration)
            revert("UseEndAuctionImmediatelyInstead");
        _as.auctionDuration = _newDuration;
    }

    function endAuctionImmediately() public onlyOwner {
        AuctionStorage storage _as = _getAucStorage();
        if(block.timestamp > _as.startTimestamp + _as.auctionDuration)
            revert("AuctionHasEnded");
        _as.auctionDuration = block.timestamp - _as.startTimestamp - 1;
    }

    function getExtraTimePeriod() public view returns (uint256) {
        AuctionStorage storage _as = _getAucStorage();
        return _as.extraTimePeriod;
    }

    function changeExtraTimePeriod(uint256 _newPeriod) public onlyOwner {
        AuctionStorage storage _as = _getAucStorage();
        _as.extraTimePeriod = _newPeriod;
    }

    function _checkAndAddExtraTime(uint256 listingId) internal {
        AuctionStorage storage _as = _getAucStorage();
        uint256 timeLeft = _as.startTimestamp + _as.auctionDuration + _as.listings[listingId].extraTime - block.timestamp;
        if (timeLeft < _as.extraTimePeriod) {
            _as.listings[listingId].extraTime += _as.extraTimePeriod - timeLeft;
        }
    }

    /////// Ownership ///////

    function transferOwnership(address _newOwner) public onlyOwner {
        AuctionStorage storage _as = _getAucStorage();
        address oldOwner = _as.owner;
        _as.owner = _newOwner;
        emit OwnershipTransferred(oldOwner, _newOwner);
    }

    modifier onlyOwner() {
        AuctionStorage storage _as = _getAucStorage();
        if (_as.owner != msg.sender)
            revert NotOwner(msg.sender);
        _;
    }

    function owner() public view returns (address) {
        AuctionStorage storage _as = _getAucStorage();
        return _as.owner;
    }

    /////// Administrative methods ///////

    function getRoyaltyEngineAddress() public view returns (address) {
        AuctionStorage storage _as = _getAucStorage();
        return _as.royaltyEngineAddress;
    }

    function changeRoyaltyEngineAddress(address _newRoyaltyEngineAddress) public onlyOwner {
        AuctionStorage storage _as = _getAucStorage();
        _as.royaltyEngineAddress = _newRoyaltyEngineAddress;
    }

    // emergency withdraw ETH
    function emergencyWithdrawETH(address to, uint256 amount) public onlyOwner {
        (bool success, ) = to.call{value: amount}("");
        if (!success) {
            emit EthTransferFailed(to, amount);
        }
    }

    // emergency withdraw ERC20
    function emergencyWithdrawERC20(address tokenContract, address to, uint256 amount) public onlyOwner {
        tokenContract.safeTransfer(to, amount);
    }

    // auction owner can return token to the tokenProvider 
    // if there were no bids or if the winner was not able to accept it
    function returnTokenToProvider(uint256 listingId) public onlyOwner {
        AuctionStorage storage _as = _getAucStorage();        
        // if listing is claimable, it cannot be returned
        if (isListingClaimable(listingId)) 
            revert ListingNotReturnable(listingId);  

        // if listing has been claimed already, it is not claimable
        // thus we need to check if token is still owned by this contract
        // it can be that listing has been marked as claimed, but the token is 
        // still owned by this contract because the winner was not able to accept it
        // so we can transfer it back to the provider
        _checkOwnedByThis(
            _as.listings[listingId].contractAddres,
            _as.listings[listingId].tokenId
        );

        // send token back to the tokenProvider
        _detectErcAndTransferToken(
            _as.listings[listingId].contractAddres,
            _as.listings[listingId].tokenId,
            _as.listings[listingId].tokenProvider
        );

        // clear listing
        delete _as.listings[listingId];
    }

    /**
    * @dev Transfer NFT from auction to the specified address
    * @dev !!! Permissioned method. Introducing it for the first auctions
    * just for the case somethig goes wrong and we need to return NFTs 
    * even without creating a listing etc
    * SHOULD be removed in the future to make auction permissionless
    * so the token can only be returned to the tokenProvider
    * @param tokenContract address of the token contract
    * @param tokenId id of the token
    * @param to address to transfer token to
    */
    function transferNFTFromAuction(
        address tokenContract,
        uint256 tokenId,
        address to
    ) public onlyOwner {
        _detectErcAndTransferToken(tokenContract, tokenId, to);
    }

    /////// Metadata ///////

    function name() public view returns (string memory) {
        AuctionStorage storage _as = _getAucStorage();
        return _as.name;
    }

    function getImplementation() public view returns (address _implementation)
    {
        assembly {
            _implementation := sload(address())
        }
    }

    /////// Internal Helpers ///////

    function _distributeFundsIncludingRoyalties(
        uint256 listingId,
        uint256 bidAmount
    ) internal {
        AuctionStorage storage _as = _getAucStorage();
        // send eth to the auction owner
        (bool success, ) = _as.owner.call{value: bidAmount * ACCA_SHARE / 100}("");
        if (!success) {
            emit EthTransferFailed(_as.owner, bidAmount * ACCA_SHARE / 100);
        }
    
        uint256 providerEarnings = bidAmount * (100 - ACCA_SHARE) / 100;
        
        try IRoyaltyEngineV1(_as.royaltyEngineAddress).getRoyaltyView(
            _as.listings[listingId].contractAddres,
            _as.listings[listingId].tokenId,
            providerEarnings
        ) returns (address payable[] memory recipients, uint256[] memory amounts) {
            //successfully received royalties info even empty
            uint256 totalRoyalties;
            for(uint256 i; i < recipients.length; ) {
                if(!address(recipients[i]).trySafeTransferETH(amounts[i], ETH_SEND_GAS_STIPEND)) {
                    emit EthTransferFailed(recipients[i], amounts[i]);
                }
                totalRoyalties = totalRoyalties + amounts[i];
                unchecked {
                    ++i;
                }
            }
            //send rest of eth to provider
            if(!_as.listings[listingId].tokenProvider.trySafeTransferETH(providerEarnings - totalRoyalties, ETH_SEND_GAS_STIPEND)) {
                emit EthTransferFailed(_as.listings[listingId].tokenProvider, providerEarnings - totalRoyalties);
            }
        } catch (bytes memory reason) {
            // Error getting royalties
            emit RoyaltiesRetrieveError(
                _as.listings[listingId].contractAddres, 
                _as.listings[listingId].tokenId, 
                providerEarnings, 
                reason
            );
            // Then transfer everything to provider
            if(!_as.listings[listingId].tokenProvider.trySafeTransferETH(providerEarnings, ETH_SEND_GAS_STIPEND)) {
                    emit EthTransferFailed(_as.listings[listingId].tokenProvider, providerEarnings);
            }
        } 
    }
    
    function _detectErcAndTransferToken(address tokenContract, uint256 tokenId, address to) internal {
        if (IERC165(tokenContract).supportsInterface(0x80ac58cd)) { 
            // ERC721
            try IERC721(tokenContract).safeTransferFrom(address(this), to, tokenId)
            {} catch(bytes memory reason) {
                emit NFTTransferError(tokenContract, tokenId, to, reason);
            }
        } else if (IERC165(tokenContract).supportsInterface(0x0e89341c)) {
            // ERC-1155
            try IERC1155(tokenContract).safeTransferFrom(address(this), to, tokenId, 1, "") 
            {} catch(bytes memory reason) {
                emit NFTTransferError(tokenContract, tokenId, to, reason);
            }
        } else
            revert NotSupportedToken(tokenContract);
    }

    function _checkOwnedByThis(address tokenContract, uint256 tokenId) internal view {
        if (IERC165(tokenContract).supportsInterface(0x80ac58cd)) // ERC721
            if (IERC721(tokenContract).ownerOf(tokenId) != address(this))
                revert NotOwnedByThis(tokenContract, tokenId);
            // otherwise proceed
        else if (IERC165(tokenContract).supportsInterface(0x0e89341c)) // ERC1155
            if (IERC1155(tokenContract).balanceOf(address(this), tokenId) == 0)
                revert NotOwnedByThis(tokenContract, tokenId);
            // otherwise proceed
        else
            revert NotSupportedToken(tokenContract);
    }

    /////// Token Receiver hooks /////// 

    //function onERC721Received
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4) {
        if(data.length > 0) {
            (
                uint256 listingId, 
                uint256 startPrice, 
                uint256 reservePrice
            ) = abi.decode(data, (uint256, uint256, uint256));
            _createListing(listingId, ListingDetails(
                msg.sender,
                tokenId,
                from,
                startPrice,
                reservePrice,
                0
            ));
        }    
        return this.onERC721Received.selector;
    }

    //function onERC1155Received
    function onERC1155Received(
        address operator,
        address from,
        uint256 id,
        uint256 value,
        bytes calldata data
    ) external returns(bytes4) {
        if(data.length > 0) {
            (
                uint256 listingId, 
                uint256 startPrice, 
                uint256 reservePrice
            ) = abi.decode(data, (uint256, uint256, uint256));
            _createListing(listingId, ListingDetails(
                msg.sender,
                id,
                from,
                startPrice,
                reservePrice,
                0
            ));
        }
        return this.onERC1155Received.selector;
    }

    function onERC1155BatchReceived(
        address operator,
        address from,
        uint256[] calldata ids,
        uint256[] calldata values,
        bytes calldata data
    ) external returns(bytes4) {
        return 0xffffffff; // do not support batch receiving
    }

    function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) {
        return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId);
    }

}
// File: lib/solady/src/utils/SafeTransferLib.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
///
/// @dev Note:
/// - For ETH transfers, please use `forceSafeTransferETH` for DoS protection.
/// - For ERC20s, this implementation won't check that a token has code,
///   responsibility is delegated to the caller.
library SafeTransferLib {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The ETH transfer has failed.
    error ETHTransferFailed();

    /// @dev The ERC20 `transferFrom` has failed.
    error TransferFromFailed();

    /// @dev The ERC20 `transfer` has failed.
    error TransferFailed();

    /// @dev The ERC20 `approve` has failed.
    error ApproveFailed();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         CONSTANTS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Suggested gas stipend for contract receiving ETH that disallows any storage writes.
    uint256 internal constant GAS_STIPEND_NO_STORAGE_WRITES = 2300;

    /// @dev Suggested gas stipend for contract receiving ETH to perform a few
    /// storage reads and writes, but low enough to prevent griefing.
    uint256 internal constant GAS_STIPEND_NO_GRIEF = 100000;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       ETH OPERATIONS                       */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    // If the ETH transfer MUST succeed with a reasonable gas budget, use the force variants.
    //
    // The regular variants:
    // - Forwards all remaining gas to the target.
    // - Reverts if the target reverts.
    // - Reverts if the current contract has insufficient balance.
    //
    // The force variants:
    // - Forwards with an optional gas stipend
    //   (defaults to `GAS_STIPEND_NO_GRIEF`, which is sufficient for most cases).
    // - If the target reverts, or if the gas stipend is exhausted,
    //   creates a temporary contract to force send the ETH via `SELFDESTRUCT`.
    //   Future compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758.
    // - Reverts if the current contract has insufficient balance.
    //
    // The try variants:
    // - Forwards with a mandatory gas stipend.
    // - Instead of reverting, returns whether the transfer succeeded.

    /// @dev Sends `amount` (in wei) ETH to `to`.
    function safeTransferETH(address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            if iszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                revert(0x1c, 0x04)
            }
        }
    }

    /// @dev Sends all the ETH in the current contract to `to`.
    function safeTransferAllETH(address to) internal {
        /// @solidity memory-safe-assembly
        assembly {
            // Transfer all the ETH and check if it succeeded or not.
            if iszero(call(gas(), to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                revert(0x1c, 0x04)
            }
        }
    }

    /// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
    function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal {
        /// @solidity memory-safe-assembly
        assembly {
            if lt(selfbalance(), amount) {
                mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                revert(0x1c, 0x04)
            }
            if iszero(call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
            }
        }
    }

    /// @dev Force sends all the ETH in the current contract to `to`, with a `gasStipend`.
    function forceSafeTransferAllETH(address to, uint256 gasStipend) internal {
        /// @solidity memory-safe-assembly
        assembly {
            if iszero(call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
            }
        }
    }

    /// @dev Force sends `amount` (in wei) ETH to `to`, with `GAS_STIPEND_NO_GRIEF`.
    function forceSafeTransferETH(address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            if lt(selfbalance(), amount) {
                mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                revert(0x1c, 0x04)
            }
            if iszero(call(GAS_STIPEND_NO_GRIEF, to, amount, codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
            }
        }
    }

    /// @dev Force sends all the ETH in the current contract to `to`, with `GAS_STIPEND_NO_GRIEF`.
    function forceSafeTransferAllETH(address to) internal {
        /// @solidity memory-safe-assembly
        assembly {
            // forgefmt: disable-next-item
            if iszero(call(GAS_STIPEND_NO_GRIEF, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
            }
        }
    }

    /// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
    function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend)
        internal
        returns (bool success)
    {
        /// @solidity memory-safe-assembly
        assembly {
            success := call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)
        }
    }

    /// @dev Sends all the ETH in the current contract to `to`, with a `gasStipend`.
    function trySafeTransferAllETH(address to, uint256 gasStipend)
        internal
        returns (bool success)
    {
        /// @solidity memory-safe-assembly
        assembly {
            success := call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                      ERC20 OPERATIONS                      */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
    /// Reverts upon failure.
    ///
    /// The `from` account must have at least `amount` approved for
    /// the current contract to manage.
    function safeTransferFrom(address token, address from, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.
            mstore(0x60, amount) // Store the `amount` argument.
            mstore(0x40, to) // Store the `to` argument.
            mstore(0x2c, shl(96, from)) // Store the `from` argument.
            mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
            // Perform the transfer, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
                    call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
                )
            ) {
                mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x60, 0) // Restore the zero slot to zero.
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /// @dev Sends all of ERC20 `token` from `from` to `to`.
    /// Reverts upon failure.
    ///
    /// The `from` account must have their entire balance approved for
    /// the current contract to manage.
    function safeTransferAllFrom(address token, address from, address to)
        internal
        returns (uint256 amount)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.
            mstore(0x40, to) // Store the `to` argument.
            mstore(0x2c, shl(96, from)) // Store the `from` argument.
            mstore(0x0c, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
            // Read the balance, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                    staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20)
                )
            ) {
                mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x00, 0x23b872dd) // `transferFrom(address,address,uint256)`.
            amount := mload(0x60) // The `amount` is already at 0x60. We'll need to return it.
            // Perform the transfer, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
                    call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
                )
            ) {
                mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x60, 0) // Restore the zero slot to zero.
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /// @dev Sends `amount` of ERC20 `token` from the current contract to `to`.
    /// Reverts upon failure.
    function safeTransfer(address token, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, to) // Store the `to` argument.
            mstore(0x34, amount) // Store the `amount` argument.
            mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
            // Perform the transfer, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
                    call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                )
            ) {
                mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
        }
    }

    /// @dev Sends all of ERC20 `token` from the current contract to `to`.
    /// Reverts upon failure.
    function safeTransferAll(address token, address to) internal returns (uint256 amount) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`.
            mstore(0x20, address()) // Store the address of the current contract.
            // Read the balance, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                    staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20)
                )
            ) {
                mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x14, to) // Store the `to` argument.
            amount := mload(0x34) // The `amount` is already at 0x34. We'll need to return it.
            mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
            // Perform the transfer, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
                    call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                )
            ) {
                mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
        }
    }

    /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
    /// Reverts upon failure.
    function safeApprove(address token, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, to) // Store the `to` argument.
            mstore(0x34, amount) // Store the `amount` argument.
            mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
            // Perform the approval, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
                    call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                )
            ) {
                mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
        }
    }

    /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
    /// If the initial attempt to approve fails, attempts to reset the approved amount to zero,
    /// then retries the approval again (some tokens, e.g. USDT, requires this).
    /// Reverts upon failure.
    function safeApproveWithRetry(address token, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, to) // Store the `to` argument.
            mstore(0x34, amount) // Store the `amount` argument.
            mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
            // Perform the approval, retrying upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
                    call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                )
            ) {
                mstore(0x34, 0) // Store 0 for the `amount`.
                mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
                pop(call(gas(), token, 0, 0x10, 0x44, codesize(), 0x00)) // Reset the approval.
                mstore(0x34, amount) // Store back the original `amount`.
                // Retry the approval, reverting upon failure.
                if iszero(
                    and(
                        or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
                        call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                    )
                ) {
                    mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
                    revert(0x1c, 0x04)
                }
            }
            mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
        }
    }

    /// @dev Returns the amount of ERC20 `token` owned by `account`.
    /// Returns zero if the `token` does not exist.
    function balanceOf(address token, address account) internal view returns (uint256 amount) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, account) // Store the `account` argument.
            mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
            amount :=
                mul(
                    mload(0x20),
                    and( // The arguments of `and` are evaluated from right to left.
                        gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                        staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)
                    )
                )
        }
    }
}

// File: lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165.sol)

pragma solidity ^0.8.20;

import {IERC165} from "./IERC165.sol";

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 */
abstract contract ERC165 is IERC165 {
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
        return interfaceId == type(IERC165).interfaceId;
    }
}

// File: lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721.sol)

pragma solidity ^0.8.20;

import {IERC165} from "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC721 compliant contract.
 */
interface IERC721 is IERC165 {
    /**
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
     */
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
     *   a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC721 protocol to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or
     *   {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
     *   a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
     * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
     * understand this adds an external call which potentially creates a reentrancy vulnerability.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) external;

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the address zero.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}

// File: lib/openzeppelin-contracts/contracts/token/ERC721/IERC721Receiver.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721Receiver.sol)

pragma solidity ^0.8.20;

/**
 * @title ERC721 token receiver interface
 * @dev Interface for any contract that wants to support safeTransfers
 * from ERC721 asset contracts.
 */
interface IERC721Receiver {
    /**
     * @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 `IERC721Receiver.onERC721Received.selector`.
     */
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4);
}

// File: lib/openzeppelin-contracts/contracts/token/ERC1155/IERC1155.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/IERC1155.sol)

pragma solidity ^0.8.20;

import {IERC165} from "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC1155 compliant contract, as defined in the
 * https://eips.ethereum.org/EIPS/eip-1155[EIP].
 */
interface IERC1155 is IERC165 {
    /**
     * @dev Emitted when `value` amount of tokens of type `id` are transferred from `from` to `to` by `operator`.
     */
    event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);

    /**
     * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all
     * transfers.
     */
    event TransferBatch(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256[] ids,
        uint256[] values
    );

    /**
     * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to
     * `approved`.
     */
    event ApprovalForAll(address indexed account, address indexed operator, bool approved);

    /**
     * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI.
     *
     * If an {URI} event was emitted for `id`, the standard
     * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value
     * returned by {IERC1155MetadataURI-uri}.
     */
    event URI(string value, uint256 indexed id);

    /**
     * @dev Returns the value of tokens of token type `id` owned by `account`.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function balanceOf(address account, uint256 id) external view returns (uint256);

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}.
     *
     * Requirements:
     *
     * - `accounts` and `ids` must have the same length.
     */
    function balanceOfBatch(
        address[] calldata accounts,
        uint256[] calldata ids
    ) external view returns (uint256[] memory);

    /**
     * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`,
     *
     * Emits an {ApprovalForAll} event.
     *
     * Requirements:
     *
     * - `operator` cannot be the caller.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns true if `operator` is approved to transfer ``account``'s tokens.
     *
     * See {setApprovalForAll}.
     */
    function isApprovedForAll(address account, address operator) external view returns (bool);

    /**
     * @dev Transfers a `value` amount of tokens of type `id` from `from` to `to`.
     *
     * WARNING: This function can potentially allow a reentrancy attack when transferring tokens
     * to an untrusted contract, when invoking {onERC1155Received} on the receiver.
     * Ensure to follow the checks-effects-interactions pattern and consider employing
     * reentrancy guards when interacting with untrusted contracts.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - If the caller is not `from`, it must have been approved to spend ``from``'s tokens via {setApprovalForAll}.
     * - `from` must have a balance of tokens of type `id` of at least `value` amount.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes calldata data) external;

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}.
     *
     *
     * WARNING: This function can potentially allow a reentrancy attack when transferring tokens
     * to an untrusted contract, when invoking {onERC1155BatchReceived} on the receiver.
     * Ensure to follow the checks-effects-interactions pattern and consider employing
     * reentrancy guards when interacting with untrusted contracts.
     *
     * Emits a {TransferBatch} event.
     *
     * Requirements:
     *
     * - `ids` and `values` must have the same length.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] calldata ids,
        uint256[] calldata values,
        bytes calldata data
    ) external;
}

// File: lib/openzeppelin-contracts/contracts/token/ERC1155/IERC1155Receiver.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/IERC1155Receiver.sol)

pragma solidity ^0.8.20;

import {IERC165} from "../../utils/introspection/IERC165.sol";

/**
 * @dev Interface that must be implemented by smart contracts in order to receive
 * ERC-1155 token transfers.
 */
interface IERC1155Receiver is IERC165 {
    /**
     * @dev Handles the receipt of a single ERC1155 token type. This function is
     * called at the end of a `safeTransferFrom` after the balance has been updated.
     *
     * NOTE: To accept the transfer, this must return
     * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
     * (i.e. 0xf23a6e61, or its own function selector).
     *
     * @param operator The address which initiated the transfer (i.e. msg.sender)
     * @param from The address which previously owned the token
     * @param id The ID of the token being transferred
     * @param value The amount of tokens being transferred
     * @param data Additional data with no specified format
     * @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed
     */
    function onERC1155Received(
        address operator,
        address from,
        uint256 id,
        uint256 value,
        bytes calldata data
    ) external returns (bytes4);

    /**
     * @dev Handles the receipt of a multiple ERC1155 token types. This function
     * is called at the end of a `safeBatchTransferFrom` after the balances have
     * been updated.
     *
     * NOTE: To accept the transfer(s), this must return
     * `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
     * (i.e. 0xbc197c81, or its own function selector).
     *
     * @param operator The address which initiated the batch transfer (i.e. msg.sender)
     * @param from The address which previously owned the token
     * @param ids An array containing ids of each token being transferred (order and length must match values array)
     * @param values An array containing amounts of each token being transferred (order and length must match ids array)
     * @param data Additional data with no specified format
     * @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed
     */
    function onERC1155BatchReceived(
        address operator,
        address from,
        uint256[] calldata ids,
        uint256[] calldata values,
        bytes calldata data
    ) external returns (bytes4);
}

// File: lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the value of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 value) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}

// File: lib/openzeppelin-contracts/contracts/interfaces/IERC2981.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC2981.sol)

pragma solidity ^0.8.20;

import {IERC165} from "../utils/introspection/IERC165.sol";

/**
 * @dev Interface for the NFT Royalty Standard.
 *
 * A standardized way to retrieve royalty payment information for non-fungible tokens (NFTs) to enable universal
 * support for royalty payments across all NFT marketplaces and ecosystem participants.
 */
interface IERC2981 is IERC165 {
    /**
     * @dev Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of
     * exchange. The royalty amount is denominated and should be paid in that same unit of exchange.
     */
    function royaltyInfo(
        uint256 tokenId,
        uint256 salePrice
    ) external view returns (address receiver, uint256 royaltyAmount);
}

// File: lib/royalty-registry-solidity/contracts/IRoyaltyEngineV1.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/// @author: manifold.xyz

import "@openzeppelin/contracts/utils/introspection/IERC165.sol";

/**
 * @dev Lookup engine interface
 */
interface IRoyaltyEngineV1 is IERC165 {
    /**
     * Get the royalty for a given token (address, id) and value amount.  Does not cache the bps/amounts.  Caches the spec for a given token address
     *
     * @param tokenAddress - The address of the token
     * @param tokenId      - The id of the token
     * @param value        - The value you wish to get the royalty of
     *
     * returns Two arrays of equal length, royalty recipients and the corresponding amount each recipient should get
     */
    function getRoyalty(address tokenAddress, uint256 tokenId, uint256 value)
        external
        returns (address payable[] memory recipients, uint256[] memory amounts);

    /**
     * View only version of getRoyalty
     *
     * @param tokenAddress - The address of the token
     * @param tokenId      - The id of the token
     * @param value        - The value you wish to get the royalty of
     *
     * returns Two arrays of equal length, royalty recipients and the corresponding amount each recipient should get
     */
    function getRoyaltyView(address tokenAddress, uint256 tokenId, uint256 value)
        external
        view
        returns (address payable[] memory recipients, uint256[] memory amounts);
}

// File: lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)

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);
}