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