MultiMarketplaceAdapter

Description:

Non-Fungible Token (NFT) contract following ERC721 standard.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "@openzeppelin/contracts/token/ERC721/IERC721.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC721/IERC721.sol)

pragma solidity >=0.6.2;

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

/**
 * @dev Required interface of an ERC-721 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 ERC-721 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 ERC-721
     * 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);
}
"
    },
    "@openzeppelin/contracts/utils/introspection/IERC165.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/introspection/IERC165.sol)

pragma solidity >=0.4.16;

/**
 * @dev Interface of the ERC-165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[ERC].
 *
 * 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[ERC 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);
}
"
    },
    "contracts/adapters/INftBuyAdapter.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

interface INftBuyAdapter {
    /**
     * Attempt to buy one NFT from the target collection using provided ETH balance.
     * Returns the tokenId and price paid in wei. Must revert on no purchase.
     */
    function buyOne(address collection, uint256 maxSpend) external payable returns (uint256 tokenId, uint256 pricePaid);
}


"
    },
    "contracts/adapters/MultiMarketplaceAdapter.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {INftBuyAdapter} from "./INftBuyAdapter.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";

/**
 * Multi-Marketplace Adapter for buying NFTs from multiple sources.
 * 
 * This adapter can purchase NFTs from:
 * - Blur (cheapest prices, lower fees)
 * - OpenSea (largest selection)
 * - X2Y2 (alternative marketplace)
 * - LooksRare (community-driven)
 */
contract MultiMarketplaceAdapter is INftBuyAdapter {
    enum Marketplace {
        BLUR,
        OPENSEA,
        X2Y2,
        LOOKSRARE
    }
    
    // Marketplace contract addresses
    address public constant BLUR_MARKETPLACE = 0x000000000000Ad05ccC4F463456d198bfF2c3D0B;
    address public constant OPENSEA_SEAPORT = 0x00000000000000ADc04C56Bf30aC9d3c0aAF14dC;
    address public constant X2Y2_MARKETPLACE = 0x74312363E45DcaBa76c59Ec49a7aa8A65a67e3d9;
    address public constant LOOKSRARE_MARKETPLACE = 0x59728544B08AB483533076417FbBB2fD0B17CE3a;
    
    event NftPurchased(
        address indexed collection, 
        uint256 indexed tokenId, 
        uint256 price,
        Marketplace marketplace
    );
    
    // Mapping to track purchased NFTs by marketplace
    mapping(address => mapping(Marketplace => uint256[])) public purchasedTokens;
    
    // Marketplace preferences (set by owner)
    Marketplace[] public preferredMarketplaces;
    address public owner;
    
    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }
    
    constructor() {
        owner = msg.sender;
        // Default preference: Blur first (cheapest), then OpenSea
        preferredMarketplaces = [Marketplace.BLUR, Marketplace.OPENSEA];
    }
    
    function buyOne(address collection, uint256 maxSpend)
        external
        payable
        override
        returns (uint256 tokenId, uint256 pricePaid)
    {
        require(msg.value > 0, "No ETH sent");
        require(msg.value <= maxSpend, "Exceeds max spend");
        require(collection != address(0), "Invalid collection");
        
        // Try to buy from preferred marketplaces in order
        for (uint256 i = 0; i < preferredMarketplaces.length; i++) {
            Marketplace marketplace = preferredMarketplaces[i];
            
            (bool success, uint256 foundTokenId, uint256 foundPrice) = _tryBuyFromMarketplace(
                collection, 
                maxSpend, 
                marketplace
            );
            
            if (success) {
                // Track the purchase
                purchasedTokens[collection][marketplace].push(foundTokenId);
                
                // Return excess ETH
                if (msg.value > foundPrice) {
                    (bool refundSuccess, ) = msg.sender.call{value: msg.value - foundPrice}("");
                    require(refundSuccess, "ETH refund failed");
                }
                
                emit NftPurchased(collection, foundTokenId, foundPrice, marketplace);
                
                return (foundTokenId, foundPrice);
            }
        }
        
        revert("No NFTs available on any marketplace");
    }
    
    function _tryBuyFromMarketplace(
        address collection,
        uint256 maxSpend,
        Marketplace marketplace
    ) internal returns (bool success, uint256 tokenId, uint256 price) {
        if (marketplace == Marketplace.BLUR) {
            return _buyFromBlur(collection, maxSpend);
        } else if (marketplace == Marketplace.OPENSEA) {
            return _buyFromOpenSea(collection, maxSpend);
        } else if (marketplace == Marketplace.X2Y2) {
            return _buyFromX2Y2(collection, maxSpend);
        } else if (marketplace == Marketplace.LOOKSRARE) {
            return _buyFromLooksRare(collection, maxSpend);
        }
        
        return (false, 0, 0);
    }
    
    function _buyFromBlur(address collection, uint256 maxSpend) 
        internal 
        returns (bool success, uint256 tokenId, uint256 price) 
    {
        // Mock implementation for Blur
        // In production, implement actual Blur integration
        uint256 mockPrice = 0.03 ether; // Blur typically has better prices
        
        if (mockPrice <= maxSpend) {
            return (true, 1, mockPrice);
        }
        
        return (false, 0, 0);
    }
    
    function _buyFromOpenSea(address collection, uint256 maxSpend) 
        internal 
        returns (bool success, uint256 tokenId, uint256 price) 
    {
        // Mock implementation for OpenSea
        // In production, implement actual Seaport integration
        uint256 mockPrice = 0.05 ether; // OpenSea typically has higher prices
        
        if (mockPrice <= maxSpend) {
            return (true, 2, mockPrice);
        }
        
        return (false, 0, 0);
    }
    
    function _buyFromX2Y2(address collection, uint256 maxSpend) 
        internal 
        returns (bool success, uint256 tokenId, uint256 price) 
    {
        // Mock implementation for X2Y2
        uint256 mockPrice = 0.04 ether;
        
        if (mockPrice <= maxSpend) {
            return (true, 3, mockPrice);
        }
        
        return (false, 0, 0);
    }
    
    function _buyFromLooksRare(address collection, uint256 maxSpend) 
        internal 
        returns (bool success, uint256 tokenId, uint256 price) 
    {
        // Mock implementation for LooksRare
        uint256 mockPrice = 0.045 ether;
        
        if (mockPrice <= maxSpend) {
            return (true, 4, mockPrice);
        }
        
        return (false, 0, 0);
    }
    
    // Owner functions
    function setPreferredMarketplaces(Marketplace[] calldata marketplaces) external onlyOwner {
        require(marketplaces.length > 0, "Empty marketplaces");
        preferredMarketplaces = marketplaces;
    }
    
    function addMarketplace(Marketplace marketplace) external onlyOwner {
        // Check if already exists
        for (uint256 i = 0; i < preferredMarketplaces.length; i++) {
            if (preferredMarketplaces[i] == marketplace) {
                return; // Already exists
            }
        }
        preferredMarketplaces.push(marketplace);
    }
    
    function removeMarketplace(Marketplace marketplace) external onlyOwner {
        for (uint256 i = 0; i < preferredMarketplaces.length; i++) {
            if (preferredMarketplaces[i] == marketplace) {
                // Remove by swapping with last element
                preferredMarketplaces[i] = preferredMarketplaces[preferredMarketplaces.length - 1];
                preferredMarketplaces.pop();
                break;
            }
        }
    }
    
    // View functions
    function getPreferredMarketplaces() external view returns (Marketplace[] memory) {
        return preferredMarketplaces;
    }
    
    function getPurchasedTokens(address collection, Marketplace marketplace) 
        external 
        view 
        returns (uint256[] memory) 
    {
        return purchasedTokens[collection][marketplace];
    }
    
    function getTotalPurchased(address collection) external view returns (uint256 total) {
        for (uint256 i = 0; i < uint256(Marketplace.LOOKSRARE) + 1; i++) {
            total += purchasedTokens[collection][Marketplace(i)].length;
        }
    }
    
    // Emergency functions
    function withdrawETH(address to) external onlyOwner {
        require(to != address(0), "Invalid address");
        uint256 amount = address(this).balance;
        require(amount > 0, "No ETH to withdraw");
        
        (bool success, ) = to.call{value: amount}("");
        require(success, "ETH transfer failed");
    }
    
    function withdrawNFT(address collection, uint256 tokenId, address to) external onlyOwner {
        require(to != address(0), "Invalid address");
        require(collection != address(0), "Invalid collection");
        
        IERC721(collection).transferFrom(address(this), to, tokenId);
    }
    
    function transferOwnership(address newOwner) external onlyOwner {
        require(newOwner != address(0), "Invalid new owner");
        owner = newOwner;
    }
}
"
    }
  },
  "settings": {
    "optimizer": {
      "enabled": true,
      "runs": 200
    },
    "viaIR": false,
    "evmVersion": "paris",
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    }
  }
}}

Tags:
ERC721, ERC165, NFT, Non-Fungible, Factory|addr:0xcd01991e72a0c5cc3263eb3fd7bdbaf9cc6e3a3f|verified:true|block:23379144|tx:0x05065a9c79cd1e2c35eecb932b6b91d64ed5c9edb7a30665c3e74a53284e4d0c|first_check:1758105117

Submitted on: 2025-09-17 12:31:58

Comments

Log in to comment.

No comments yet.