Documents

Main Marketplace Beta Contract

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

import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract ERC721Marketplace is ReentrancyGuard {
    IERC721 public nftContract;

    struct Listing {
        address payable seller;
        uint256 price;
        bool listed;
        string pictureURL;
        string assetType;
        bool priceVerified;
    }

    mapping(uint256 => Listing) public listings;
    mapping(address => uint256) public earnings;

    event TokenListed(uint256 indexed tokenId, address seller, uint256 price, string pictureURL, string assetType);
    event TokenBought(uint256 indexed tokenId, address buyer, address seller, uint256 price);
    event TokenUnlisted(uint256 indexed tokenId, address seller);

    constructor(address _nftContract) {
        nftContract = IERC721(_nftContract);
    }

    function listToken(
        uint256 tokenId,
        uint256 price,
        string calldata pictureURL,
        string calldata assetType
    ) external {
        require(nftContract.ownerOf(tokenId) == msg.sender, "Not the token owner");
        require(nftContract.isApprovedForAll(msg.sender, address(this)), "Marketplace not approved to transfer token");
        require(price > 0, "Price must be greater than zero");

        listings[tokenId] = Listing({
            seller: payable(msg.sender),
            price: price,
            listed: true,
            pictureURL: pictureURL,
            assetType: assetType,
            priceVerified: false
        });
        emit TokenListed(tokenId, msg.sender, price, pictureURL, assetType);
    }

    function buyToken(uint256 tokenId) external payable nonReentrant {
        Listing storage listing = listings[tokenId];
        require(listing.listed, "Token not listed");
        require(msg.value >= listing.price, "Insufficient Ether sent");

        listing.seller.transfer(msg.value);
        earnings[listing.seller] += msg.value;
        nftContract.transferFrom(listing.seller, msg.sender, tokenId);

        listing.listed = false;
        emit TokenBought(tokenId, msg.sender, listing.seller, msg.value);
    }

    function unlistToken(uint256 tokenId) external {
        require(listings[tokenId].seller == msg.sender, "Not the seller");
        require(listings[tokenId].listed, "Token not listed");

        listings[tokenId].listed = false;
        emit TokenUnlisted(tokenId, msg.sender);
    }

    function withdraw() external nonReentrant {
        uint256 balance = earnings[msg.sender];
        require(balance > 0, "No earnings to withdraw");

        earnings[msg.sender] = 0;
        payable(msg.sender).transfer(balance);
    }
}

Components:

Web3 Provider and Contract Instance

// src/utils/getBlockchain.js

import { ethers } from 'ethers';
import marketplaceAbi from '../abis/ERC721Marketplace.json';

const getBlockchain = () =>
  new Promise(async (resolve, reject) => {
    if (typeof window.ethereum === 'undefined') {
      return reject('MetaMask is required.');
    }

    try {
      await window.ethereum.request({ method: 'eth_requestAccounts' });
      const provider = new ethers.providers.Web3Provider(window.ethereum);
      const signer = provider.getSigner();
      const network = await provider.getNetwork();
      
      if(network.chainId !== 1) { // 1 for Ethereum Mainnet, adjust as needed
        return reject('Please connect to the Ethereum Mainnet.');
      }

      const marketplaceContract = new ethers.Contract(
        'YOUR_CONTRACT_ADDRESS',
        marketplaceAbi.abi,
        signer
      );
      resolve({ marketplaceContract });
    } catch (error) {
      reject('Failed to load MetaMask accounts.');
    }
  });

export default getBlockchain;

Marketplace Component

// src/components/Marketplace.js

import React, { useEffect, useState } from 'react';
import getBlockchain from '../utils/getBlockchain';
import { Card, CardContent, Typography, Grid } from '@material-ui/core';

function Marketplace() {
    const [marketplaceContract, setMarketplaceContract] = useState(undefined);
    const [listings, setListings] = useState([]);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState('');

    useEffect(() => {
        const init = async () => {
            try {
                const { marketplaceContract } = await getBlockchain();
                setMarketplaceContract(marketplaceContract);
            } catch (err) {
                setError('Failed to load blockchain.');
            }
        };
        init();
    }, []);

    useEffect(() => {
        if (!marketplaceContract) return;
        const loadListings = async () => {
            try {
                setLoading(true);
                const listings = await marketplaceContract.getListings();
                setListings(listings);
            } catch (err) {
                setError('Failed to load listings.');
            } finally {
                setLoading(false);
            }
        };
        loadListings();
    }, [marketplaceContract]);

    if (error) {
        return <div>Error: {error}</div>;
    }

    if (loading) {
        return <div>Loading listings...</div>;
    }

    return (
        <Grid container spacing={2}>
            {listings.map((listing, idx) => (
                <Grid item key={idx} xs={12} sm={6} md={4}>
                    <Card>
                        <CardContent>
                            <Typography variant="h5">Token ID: {listing.tokenId}</Typography>
                            {/* Display other listing details here */}
                        </CardContent>
                    </Card>
                </Grid>
            ))}
        </Grid>
    );
}

export default Marketplace;

Wallet Connection

// src/components/WalletConnect.js

import React, { useState, useEffect } from 'react';
import { ethers } from 'ethers';

function WalletConnect({ setMarketplaceContract }) {
    const [account, setAccount] = useState('');

    const connectWalletHandler = async () => {
        if (window.ethereum && window.ethereum.isMetaMask) {
            try {
                const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
                setAccount(accounts[0]);
                const provider = new ethers.providers.Web3Provider(window.ethereum);
                const signer = provider.getSigner();
                const marketplaceContract = new ethers.Contract(
                    'YOUR_CONTRACT_ADDRESS',
                    yourContractABI,
                    signer
                );
                setMarketplaceContract(marketplaceContract);
            } catch (error) {
                console.error(error);
            }
        } else {
            console.log('Please install MetaMask.');
        }
    };

    useEffect(() => {
        if (window.ethereum) {
            window.ethereum.on('accountsChanged', (accounts) => {
                if (accounts.length > 0) {
                    setAccount(accounts[0]);
                } else {
                    setAccount('');
                    setMarketplaceContract(null);
                }
            });
            window.ethereum.on('chainChanged', () => {
                window.location.reload();
            });
        }
    }, []);

    return (
        <div>
            {account ? (
                <p>Connected with <strong>{account}</strong></p>
            ) : (
                <button onClick={connectWalletHandler}>Connect Wallet</button>
            )}
        </div>
    );
}

export default WalletConnect;

Form for Listing Tokens

// src/components/ListTokenForm.js

import React, { useState } from 'react';
import { TextField, Button } from '@material-ui/core';
import { ethers } from 'ethers';

function ListTokenForm({ marketplaceContract }) {
    const [tokenId, setTokenId] = useState('');
    const [price, setPrice] = useState('');
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState('');

    const listToken = async () => {
        try {
            setLoading(true);
            await marketplaceContract.listToken(tokenId, ethers.utils.parseEther(price));
        } catch (err) {
            setError(err.message || 'Failed to list token');
        } finally {
            setLoading(false);
        }
    };

    return (
        <div>
            <TextField
                label="Token ID"
                type="number"
                value={tokenId}
                onChange={e => setTokenId(e.target.value)}
                fullWidth
                margin="normal"
            />
            <TextField
                label="Price in ETH"
                type="number"
                value={price}
                onChange={e => setPrice(e.target.value)}
                fullWidth
                margin="normal"
            />
            <Button 
                onClick={listToken} 
                variant="contained" 
                color="primary" 
                disabled={loading}
            >
                {loading ? 'Listing...' : 'List Token'}
            </Button>
            {error && <p style={{ color: 'red' }}>{error}</p>}
        </div>
    );
}

export default ListTokenForm;

Listing form integration

// src/components/ListTokenForm.js

// ... other imports ...
import uploadToIPFS from '../utils/uploadToIPFS';

function ListTokenForm({ marketplaceContract }) {
    // ... existing states ...
    const [file, setFile] = useState(null);

    const listToken = async () => {
        if (file) {
            try {
                const ipfsHash = await uploadToIPFS(file);
                await marketplaceContract.listToken(tokenId, ethers.utils.parseEther(price), ipfsHash);
                alert('Token listed successfully with IPFS data.');
            } catch (error) {
                console.error('Listing failed', error);
                alert('Failed to list the token.');
            }
        } else {
            alert('Please upload an image.');
        }
    };

    return (
        <div>
            {/* ... existing inputs ... */}
            <input
                type="file"
                onChange={(e) => setFile(e.target.files[0])}
            />
            <button onClick={listToken}>List Token</button>
        </div>
    );
}

export default ListTokenForm;

BuyTokenButton for direct purchases

// src/components/BuyTokenButton.js

import React from 'react';
import { Button } from '@material-ui/core';
import { ethers } from 'ethers';

function BuyTokenButton({ marketplaceContract, tokenId, price }) {
    const buyToken = async () => {
        try {
            const transaction = await marketplaceContract.buyToken(tokenId, { value: ethers.utils.parseEther(price) });
            await transaction.wait();
            alert('Token purchased successfully!');
        } catch (error) {
            console.error('Purchase failed', error);
            alert('Failed to purchase the token.');
        }
    };

    return (
        <Button variant="contained" color="primary" onClick={buyToken}>
            Buy Token
        </Button>
    );
}

export default BuyTokenButton;
// ... Inside the Marketplace component ...

{listings.map((listing, idx) => (
    <div key={idx}>
        {/* Display other listing details */}
        <BuyTokenButton 
            marketplaceContract={marketplaceContract} 
            tokenId={listing.tokenId} 
            price={listing.price}
        />
    </div>
)))
Loan / Financing Beta Contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract ERC721Marketplace is ReentrancyGuard {
    IERC721 public nftContract;

    struct Listing {
        address payable seller;
        uint256 price;
        bool listed;
        bool isOnLoan;
        address payable borrower;
        uint256 loanStartTime;
    }

    mapping(uint256 => Listing) public listings;
    mapping(address => uint256) public earnings;

    uint256 public constant loanDuration = 180 days;
    uint256 public constant initialPaymentPercentage = 10;

    event TokenListed(uint256 indexed tokenId, address seller, uint256 price);
    event TokenBought(uint256 indexed tokenId, address buyer, address seller, uint256 price);
    event TokenUnlisted(uint256 indexed tokenId, address seller);
    event LoanInitiated(uint256 indexed tokenId, address borrower);
    event LoanRepaid(uint256 indexed tokenId, address borrower);

    constructor(address _nftContract) {
        nftContract = IERC721(_nftContract);
    }

    function listToken(uint256 tokenId, uint256 price) external {
        require(nftContract.ownerOf(tokenId) == msg.sender, "Not the token owner");
        require(price > 0, "Price must be greater than zero");

        listings[tokenId] = Listing({
            seller: payable(msg.sender),
            price: price,
            listed: true,
            isOnLoan: false,
            borrower: payable(address(0)),
            loanStartTime: 0
        });

        emit TokenListed(tokenId, msg.sender, price);
    }

    function initiateLoan(uint256 tokenId) external payable {
        Listing storage listing = listings[tokenId];
        require(listing.listed, "Token not listed");
        require(!listing.isOnLoan, "Token is already on loan");
        
        uint256 initialPayment = listing.price * initialPaymentPercentage / 100;
        require(msg.value == initialPayment, "Incorrect initial payment");

        // Transfer the initial payment to the seller
        listing.seller.transfer(msg.value);
        // Transfer the token to this contract as collateral
        nftContract.transferFrom(listing.seller, address(this), tokenId);

        listing.borrower = payable(msg.sender);
        listing.loanStartTime = block.timestamp;
        listing.isOnLoan = true;
        listing.listed = false;

        emit LoanInitiated(tokenId, msg.sender);
    }

    function repayLoan(uint256 tokenId) external payable {
        Listing storage listing = listings[tokenId];
        require(listing.isOnLoan, "Token is not on loan");
        require(listing.borrower == msg.sender, "Not the borrower");
        
        uint256 remainingAmount = listing.price - (listing.price * initialPaymentPercentage / 100);
        require(msg.value == remainingAmount, "Incorrect repayment amount");
        require(block.timestamp <= listing.loanStartTime + loanDuration, "Loan period expired");

        // Transfer the remaining amount to the seller
        listing.seller.transfer(msg.value);
        // Transfer the token from collateral to the borrower
        nftContract.transferFrom(address(this), msg.sender, tokenId);

        listing.isOnLoan = false;
        listing.borrower = payable(address(0));
        listing.loanStartTime = 0;

        emit LoanRepaid(tokenId, msg.sender);
    }

    // ... rest of the functions like unlistToken, withdraw, etc. ...
}

Last updated

Logo