-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b904c2a
commit 90ee1db
Showing
12 changed files
with
574 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export const nftmarketaddress = "0x28E6D76B75C9A8855Fc5a9842236f2aE62cE8d84" | ||
export const nftaddress = "0xdfF2a93F367caB2D66CED0f1fA5193264d11F834" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// contracts/NFT.sol | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.4; | ||
|
||
import "@openzeppelin/contracts/utils/Counters.sol"; | ||
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; | ||
import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; | ||
|
||
contract NFT is ERC721URIStorage { | ||
using Counters for Counters.Counter; | ||
Counters.Counter private _tokenIds; | ||
address contractAddress; | ||
|
||
constructor(address marketplaceAddress) ERC721("Metaverse Tokens", "METT") { | ||
contractAddress = marketplaceAddress; | ||
} | ||
|
||
function createToken(string memory tokenURI) public returns (uint) { | ||
_tokenIds.increment(); | ||
uint256 newItemId = _tokenIds.current(); | ||
|
||
_mint(msg.sender, newItemId); | ||
_setTokenURI(newItemId, tokenURI); | ||
setApprovalForAll(contractAddress, true); | ||
return newItemId; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.4; | ||
|
||
import "@openzeppelin/contracts/utils/Counters.sol"; | ||
import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; | ||
import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; | ||
|
||
import "hardhat/console.sol"; | ||
|
||
contract NFTMarket is ReentrancyGuard { | ||
using Counters for Counters.Counter; | ||
Counters.Counter private _itemIds; | ||
Counters.Counter private _itemsSold; | ||
|
||
address payable owner; | ||
uint256 listingPrice = 0.025 ether; | ||
|
||
constructor() { | ||
owner = payable(msg.sender); | ||
} | ||
struct MarketItem { | ||
uint itemId; | ||
address nftContract; | ||
uint256 tokenId; | ||
address payable seller; | ||
address payable owner; | ||
uint256 price; | ||
bool sold; | ||
} | ||
|
||
mapping(uint256 => MarketItem) private idToMarketItem; | ||
|
||
event MarketItemCreated ( | ||
uint indexed itemId, | ||
address indexed nftContract, | ||
uint256 indexed tokenId, | ||
address seller, | ||
address owner, | ||
uint256 price, | ||
bool sold | ||
); | ||
|
||
/* Returns the listing price of the contract */ | ||
function getListingPrice() public view returns (uint256) { | ||
return listingPrice; | ||
} | ||
|
||
/* Places an item for sale on the marketplace */ | ||
function createMarketItem( | ||
address nftContract, | ||
uint256 tokenId, | ||
uint256 price | ||
) public payable nonReentrant { | ||
require(price > 0, "Price must be at least 1 wei"); | ||
require(msg.value == listingPrice, "Price must be equal to listing price"); | ||
|
||
_itemIds.increment(); | ||
uint256 itemId = _itemIds.current(); | ||
|
||
idToMarketItem[itemId] = MarketItem( | ||
itemId, | ||
nftContract, | ||
tokenId, | ||
payable(msg.sender), | ||
payable(address(0)), | ||
price, | ||
false | ||
); | ||
|
||
IERC721(nftContract).transferFrom(msg.sender, address(this), tokenId); | ||
|
||
emit MarketItemCreated( | ||
itemId, | ||
nftContract, | ||
tokenId, | ||
msg.sender, | ||
address(0), | ||
price, | ||
false | ||
); | ||
} | ||
|
||
/* Creates the sale of a marketplace item */ | ||
/* Transfers ownership of the item, as well as funds between parties */ | ||
function createMarketSale( | ||
address nftContract, | ||
uint256 itemId | ||
) public payable nonReentrant { | ||
uint price = idToMarketItem[itemId].price; | ||
uint tokenId = idToMarketItem[itemId].tokenId; | ||
require(msg.value == price, "Please submit the asking price in order to complete the purchase"); | ||
|
||
idToMarketItem[itemId].seller.transfer(msg.value); | ||
IERC721(nftContract).transferFrom(address(this), msg.sender, tokenId); | ||
idToMarketItem[itemId].owner = payable(msg.sender); | ||
idToMarketItem[itemId].sold = true; | ||
_itemsSold.increment(); | ||
payable(owner).transfer(listingPrice); | ||
} | ||
|
||
/* Returns all unsold market items */ | ||
function fetchMarketItems() public view returns (MarketItem[] memory) { | ||
uint itemCount = _itemIds.current(); | ||
uint unsoldItemCount = _itemIds.current() - _itemsSold.current(); | ||
uint currentIndex = 0; | ||
|
||
MarketItem[] memory items = new MarketItem[](unsoldItemCount); | ||
for (uint i = 0; i < itemCount; i++) { | ||
if (idToMarketItem[i + 1].owner == address(0)) { | ||
uint currentId = i + 1; | ||
MarketItem storage currentItem = idToMarketItem[currentId]; | ||
items[currentIndex] = currentItem; | ||
currentIndex += 1; | ||
} | ||
} | ||
return items; | ||
} | ||
|
||
/* Returns only items that a user has purchased */ | ||
function fetchMyNFTs() public view returns (MarketItem[] memory) { | ||
uint totalItemCount = _itemIds.current(); | ||
uint itemCount = 0; | ||
uint currentIndex = 0; | ||
|
||
for (uint i = 0; i < totalItemCount; i++) { | ||
if (idToMarketItem[i + 1].owner == msg.sender) { | ||
itemCount += 1; | ||
} | ||
} | ||
|
||
MarketItem[] memory items = new MarketItem[](itemCount); | ||
for (uint i = 0; i < totalItemCount; i++) { | ||
if (idToMarketItem[i + 1].owner == msg.sender) { | ||
uint currentId = i + 1; | ||
MarketItem storage currentItem = idToMarketItem[currentId]; | ||
items[currentIndex] = currentItem; | ||
currentIndex += 1; | ||
} | ||
} | ||
return items; | ||
} | ||
|
||
/* Returns only items a user has created */ | ||
function fetchItemsCreated() public view returns (MarketItem[] memory) { | ||
uint totalItemCount = _itemIds.current(); | ||
uint itemCount = 0; | ||
uint currentIndex = 0; | ||
|
||
for (uint i = 0; i < totalItemCount; i++) { | ||
if (idToMarketItem[i + 1].seller == msg.sender) { | ||
itemCount += 1; | ||
} | ||
} | ||
|
||
MarketItem[] memory items = new MarketItem[](itemCount); | ||
for (uint i = 0; i < totalItemCount; i++) { | ||
if (idToMarketItem[i + 1].seller == msg.sender) { | ||
uint currentId = i + 1; | ||
MarketItem storage currentItem = idToMarketItem[currentId]; | ||
items[currentIndex] = currentItem; | ||
currentIndex += 1; | ||
} | ||
} | ||
return items; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
require("@nomiclabs/hardhat-waffle"); | ||
require('dotenv').config(); | ||
const projectId = process.env.ALCHEMY_API_KEY | ||
const privateKey = process.env.KEY | ||
|
||
|
||
module.exports = { | ||
networks: { | ||
hardhat: { | ||
chainId: 1337, | ||
}, | ||
mumbai: { | ||
url: `https://polygon-mumbai.g.alchemy.com/v2/${projectId}`, | ||
accounts: [privateKey] | ||
} | ||
}, | ||
solidity: "0.8.4", | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import { useState } from 'react' | ||
import { ethers } from 'ethers' | ||
import { create as ipfsHttpClient } from 'ipfs-http-client' | ||
import { useRouter } from 'next/router' | ||
import Web3Modal from 'web3modal' | ||
|
||
const client = ipfsHttpClient('https://ipfs.infura.io:5001/api/v0') | ||
|
||
import { | ||
nftaddress, nftmarketaddress | ||
} from '../config' | ||
|
||
import NFT from '../artifacts/contracts/NFT.sol/NFT.json' | ||
import Market from '../artifacts/contracts/NFTMarket.sol/NFTMarket.json' | ||
|
||
export default function CreateItem() { | ||
const [fileUrl, setFileUrl] = useState(null) | ||
const [formInput, updateFormInput] = useState({ price: '', name: '', description: '' }) | ||
const router = useRouter() | ||
|
||
async function onChange(e) { | ||
const file = e.target.files[0] | ||
try { | ||
const added = await client.add( | ||
file, | ||
{ | ||
progress: (prog) => console.log(`received: ${prog}`) | ||
} | ||
) | ||
const url = `https://ipfs.infura.io/ipfs/${added.path}` | ||
setFileUrl(url) | ||
} catch (error) { | ||
console.log('Error uploading file: ', error) | ||
} | ||
} | ||
async function createMarket() { | ||
const { name, description, price } = formInput | ||
if (!name || !description || !price || !fileUrl) return | ||
/* first, upload to IPFS */ | ||
const data = JSON.stringify({ | ||
name, description, image: fileUrl | ||
}) | ||
try { | ||
const added = await client.add(data) | ||
const url = `https://ipfs.infura.io/ipfs/${added.path}` | ||
/* after file is uploaded to IPFS, pass the URL to save it on Polygon */ | ||
createSale(url) | ||
} catch (error) { | ||
console.log('Error uploading file: ', error) | ||
} | ||
} | ||
|
||
async function createSale(url) { | ||
const web3Modal = new Web3Modal() | ||
const connection = await web3Modal.connect() | ||
const provider = new ethers.providers.Web3Provider(connection) | ||
const signer = provider.getSigner() | ||
|
||
/* next, create the item */ | ||
let contract = new ethers.Contract(nftaddress, NFT.abi, signer) | ||
let transaction = await contract.createToken(url) | ||
let tx = await transaction.wait() | ||
let event = tx.events[0] | ||
let value = event.args[2] | ||
let tokenId = value.toNumber() | ||
const price = ethers.utils.parseUnits(formInput.price, 'ether') | ||
|
||
/* then list the item for sale on the marketplace */ | ||
contract = new ethers.Contract(nftmarketaddress, Market.abi, signer) | ||
let listingPrice = await contract.getListingPrice() | ||
listingPrice = listingPrice.toString() | ||
|
||
transaction = await contract.createMarketItem(nftaddress, tokenId, price, { value: listingPrice }) | ||
await transaction.wait() | ||
router.push('/') | ||
} | ||
|
||
return ( | ||
<div className="flex justify-center"> | ||
<div className="w-1/2 flex flex-col pb-12"> | ||
<input | ||
placeholder="Asset Name" | ||
className="mt-8 border rounded p-4" | ||
onChange={e => updateFormInput({ ...formInput, name: e.target.value })} | ||
/> | ||
<textarea | ||
placeholder="Asset Description" | ||
className="mt-2 border rounded p-4" | ||
onChange={e => updateFormInput({ ...formInput, description: e.target.value })} | ||
/> | ||
<input | ||
placeholder="Asset Price in Eth" | ||
className="mt-2 border rounded p-4" | ||
onChange={e => updateFormInput({ ...formInput, price: e.target.value })} | ||
/> | ||
<input | ||
type="file" | ||
name="Asset" | ||
className="my-4" | ||
onChange={onChange} | ||
/> | ||
{ | ||
fileUrl && ( | ||
<img className="rounded mt-4" width="350" src={fileUrl} /> | ||
) | ||
} | ||
<button onClick={createMarket} className="font-bold mt-4 bg-pink-500 text-white rounded p-4 shadow-lg"> | ||
Create Digital Asset | ||
</button> | ||
</div> | ||
</div> | ||
) | ||
} |
Oops, something went wrong.