Skip to content

Commit 2224284

Browse files
committed
all the changes
2 parents 0d665ae + b353632 commit 2224284

20 files changed

+11213
-5
lines changed

.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
NEXT_PUBLIC_WORKSPACE_URL=$CLIENT_URL

.eslintrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": ["next", "next/core-web-vitals"]
3+
}

.example.infuraid

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
""

.gitpod.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
tasks:
2+
- name: Install, start node, and deploy
3+
init: yarn
4+
command: npx hardhat node
5+
- name: Deploy smart contract and start server
6+
command: |
7+
export CLIENT_URL="$(gp url 8545)"
8+
gp await-port 8545
9+
npx hardhat run scripts/deploy.js --network localhost
10+
npm run dev
11+
openMode: tab-after
12+
ports:
13+
- port: 3000-8545
14+
visibility: public

LICENSE.txt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Copyright (c) 2022 Nader Dabit
2+
3+
Permission is hereby granted, free of charge, to any person obtaining
4+
a copy of this software and associated documentation files (the
5+
"Software"), to deal in the Software without restriction, including
6+
without limitation the rights to use, copy, modify, merge, publish,
7+
distribute, sublicense, and/or sell copies of the Software, and to
8+
permit persons to whom the Software is furnished to do so, subject to
9+
the following conditions:
10+
11+
The above copyright notice and this permission notice shall be
12+
included in all copies or substantial portions of the Software.
13+
14+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

config.example.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const nftmarketaddress = ""
2+
export const nftaddress = ""

contracts/NFTMarketplace.sol

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.4;
3+
4+
import "@openzeppelin/contracts/utils/Counters.sol";
5+
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
6+
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
7+
8+
import "hardhat/console.sol";
9+
10+
contract NFTMarketplace is ERC721URIStorage {
11+
using Counters for Counters.Counter;
12+
Counters.Counter private _tokenIds;
13+
Counters.Counter private _itemsSold;
14+
15+
uint256 listingPrice = 0.025 ether;
16+
address payable owner;
17+
18+
mapping(uint256 => MarketItem) private idToMarketItem;
19+
20+
struct MarketItem {
21+
uint256 tokenId;
22+
address payable seller;
23+
address payable owner;
24+
uint256 price;
25+
bool sold;
26+
}
27+
28+
event MarketItemCreated (
29+
uint256 indexed tokenId,
30+
address seller,
31+
address owner,
32+
uint256 price,
33+
bool sold
34+
);
35+
36+
constructor() ERC721("Metaverse Tokens", "METT") {
37+
owner = payable(msg.sender);
38+
}
39+
40+
/* Updates the listing price of the contract */
41+
function updateListingPrice(uint _listingPrice) public payable {
42+
require(owner == msg.sender, "Only marketplace owner can update listing price.");
43+
listingPrice = _listingPrice;
44+
}
45+
46+
/* Returns the listing price of the contract */
47+
function getListingPrice() public view returns (uint256) {
48+
return listingPrice;
49+
}
50+
51+
/* Mints a token and lists it in the marketplace */
52+
function createToken(string memory tokenURI, uint256 price) public payable returns (uint) {
53+
_tokenIds.increment();
54+
uint256 newTokenId = _tokenIds.current();
55+
56+
_mint(msg.sender, newTokenId);
57+
_setTokenURI(newTokenId, tokenURI);
58+
createMarketItem(newTokenId, price);
59+
return newTokenId;
60+
}
61+
62+
function createMarketItem(
63+
uint256 tokenId,
64+
uint256 price
65+
) private {
66+
require(price > 0, "Price must be at least 1 wei");
67+
require(msg.value == listingPrice, "Price must be equal to listing price");
68+
69+
idToMarketItem[tokenId] = MarketItem(
70+
tokenId,
71+
payable(msg.sender),
72+
payable(address(this)),
73+
price,
74+
false
75+
);
76+
77+
_transfer(msg.sender, address(this), tokenId);
78+
emit MarketItemCreated(
79+
tokenId,
80+
msg.sender,
81+
address(this),
82+
price,
83+
false
84+
);
85+
}
86+
87+
/* allows someone to resell a token they have purchased */
88+
function resellToken(uint256 tokenId, uint256 price) public payable {
89+
require(idToMarketItem[tokenId].owner == msg.sender, "Only item owner can perform this operation");
90+
require(msg.value == listingPrice, "Price must be equal to listing price");
91+
idToMarketItem[tokenId].sold = false;
92+
idToMarketItem[tokenId].price = price;
93+
idToMarketItem[tokenId].seller = payable(msg.sender);
94+
idToMarketItem[tokenId].owner = payable(address(this));
95+
_itemsSold.decrement();
96+
97+
_transfer(msg.sender, address(this), tokenId);
98+
}
99+
100+
/* Creates the sale of a marketplace item */
101+
/* Transfers ownership of the item, as well as funds between parties */
102+
function createMarketSale(
103+
uint256 tokenId
104+
) public payable {
105+
uint price = idToMarketItem[tokenId].price;
106+
address seller = idToMarketItem[tokenId].seller;
107+
require(msg.value == price, "Please submit the asking price in order to complete the purchase");
108+
idToMarketItem[tokenId].owner = payable(msg.sender);
109+
idToMarketItem[tokenId].sold = true;
110+
idToMarketItem[tokenId].seller = payable(address(0));
111+
_itemsSold.increment();
112+
_transfer(address(this), msg.sender, tokenId);
113+
payable(owner).transfer(listingPrice);
114+
payable(seller).transfer(msg.value);
115+
}
116+
117+
/* Returns all unsold market items */
118+
function fetchMarketItems() public view returns (MarketItem[] memory) {
119+
uint itemCount = _tokenIds.current();
120+
uint unsoldItemCount = _tokenIds.current() - _itemsSold.current();
121+
uint currentIndex = 0;
122+
123+
MarketItem[] memory items = new MarketItem[](unsoldItemCount);
124+
for (uint i = 0; i < itemCount; i++) {
125+
if (idToMarketItem[i + 1].owner == address(this)) {
126+
uint currentId = i + 1;
127+
MarketItem storage currentItem = idToMarketItem[currentId];
128+
items[currentIndex] = currentItem;
129+
currentIndex += 1;
130+
}
131+
}
132+
return items;
133+
}
134+
135+
/* Returns only items that a user has purchased */
136+
function fetchMyNFTs() public view returns (MarketItem[] memory) {
137+
uint totalItemCount = _tokenIds.current();
138+
uint itemCount = 0;
139+
uint currentIndex = 0;
140+
141+
for (uint i = 0; i < totalItemCount; i++) {
142+
if (idToMarketItem[i + 1].owner == msg.sender) {
143+
itemCount += 1;
144+
}
145+
}
146+
147+
MarketItem[] memory items = new MarketItem[](itemCount);
148+
for (uint i = 0; i < totalItemCount; i++) {
149+
if (idToMarketItem[i + 1].owner == msg.sender) {
150+
uint currentId = i + 1;
151+
MarketItem storage currentItem = idToMarketItem[currentId];
152+
items[currentIndex] = currentItem;
153+
currentIndex += 1;
154+
}
155+
}
156+
return items;
157+
}
158+
159+
/* Returns only items a user has listed */
160+
function fetchItemsListed() public view returns (MarketItem[] memory) {
161+
uint totalItemCount = _tokenIds.current();
162+
uint itemCount = 0;
163+
uint currentIndex = 0;
164+
165+
for (uint i = 0; i < totalItemCount; i++) {
166+
if (idToMarketItem[i + 1].seller == msg.sender) {
167+
itemCount += 1;
168+
}
169+
}
170+
171+
MarketItem[] memory items = new MarketItem[](itemCount);
172+
for (uint i = 0; i < totalItemCount; i++) {
173+
if (idToMarketItem[i + 1].seller == msg.sender) {
174+
uint currentId = i + 1;
175+
MarketItem storage currentItem = idToMarketItem[currentId];
176+
items[currentIndex] = currentItem;
177+
currentIndex += 1;
178+
}
179+
}
180+
return items;
181+
}
182+
}

hardhat.config.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
require("@nomiclabs/hardhat-waffle");
2+
const fs = require('fs');
3+
// const infuraId = fs.readFileSync(".infuraid").toString().trim() || "";
4+
5+
module.exports = {
6+
defaultNetwork: "hardhat",
7+
networks: {
8+
hardhat: {
9+
chainId: 1337
10+
},
11+
/*
12+
mumbai: {
13+
// Infura
14+
// url: `https://polygon-mumbai.infura.io/v3/${infuraId}`
15+
url: "https://rpc-mumbai.matic.today",
16+
accounts: [process.env.privateKey]
17+
},
18+
matic: {
19+
// Infura
20+
// url: `https://polygon-mainnet.infura.io/v3/${infuraId}`,
21+
url: "https://rpc-mainnet.maticvigil.com",
22+
accounts: [process.env.privateKey]
23+
}
24+
*/
25+
},
26+
solidity: {
27+
version: "0.8.4",
28+
settings: {
29+
optimizer: {
30+
enabled: true,
31+
runs: 200
32+
}
33+
}
34+
}
35+
};
36+

pages/create-nft.js

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { useState } from 'react'
2+
import { ethers } from 'ethers'
3+
import { create as ipfsHttpClient } from 'ipfs-http-client'
4+
import { useRouter } from 'next/router'
5+
import Web3Modal from 'web3modal'
6+
7+
const client = ipfsHttpClient('https://ipfs.infura.io:5001/api/v0')
8+
9+
import {
10+
marketplaceAddress
11+
} from '../config'
12+
13+
import NFTMarketplace from '../artifacts/contracts/NFTMarketplace.sol/NFTMarketplace.json'
14+
15+
export default function CreateItem() {
16+
const [fileUrl, setFileUrl] = useState(null)
17+
const [formInput, updateFormInput] = useState({ price: '', name: '', description: '' })
18+
const router = useRouter()
19+
20+
async function onChange(e) {
21+
const file = e.target.files[0]
22+
try {
23+
const added = await client.add(
24+
file,
25+
{
26+
progress: (prog) => console.log(`received: ${prog}`)
27+
}
28+
)
29+
const url = `https://ipfs.infura.io/ipfs/${added.path}`
30+
setFileUrl(url)
31+
} catch (error) {
32+
console.log('Error uploading file: ', error)
33+
}
34+
}
35+
async function uploadToIPFS() {
36+
const { name, description, price } = formInput
37+
if (!name || !description || !price || !fileUrl) return
38+
/* first, upload to IPFS */
39+
const data = JSON.stringify({
40+
name, description, image: fileUrl
41+
})
42+
try {
43+
const added = await client.add(data)
44+
const url = `https://ipfs.infura.io/ipfs/${added.path}`
45+
/* after file is uploaded to IPFS, return the URL to use it in the transaction */
46+
return url
47+
} catch (error) {
48+
console.log('Error uploading file: ', error)
49+
}
50+
}
51+
52+
async function listNFTForSale() {
53+
const url = await uploadToIPFS()
54+
const web3Modal = new Web3Modal()
55+
const connection = await web3Modal.connect()
56+
const provider = new ethers.providers.Web3Provider(connection)
57+
const signer = provider.getSigner()
58+
59+
/* next, create the item */
60+
const price = ethers.utils.parseUnits(formInput.price, 'ether')
61+
let contract = new ethers.Contract(marketplaceAddress, NFTMarketplace.abi, signer)
62+
let listingPrice = await contract.getListingPrice()
63+
listingPrice = listingPrice.toString()
64+
let transaction = await contract.createToken(url, price, { value: listingPrice })
65+
await transaction.wait()
66+
67+
router.push('/')
68+
}
69+
70+
return (
71+
<div className="flex justify-center">
72+
<div className="w-1/2 flex flex-col pb-12">
73+
<input
74+
placeholder="Asset Name"
75+
className="mt-8 border rounded p-4"
76+
onChange={e => updateFormInput({ ...formInput, name: e.target.value })}
77+
/>
78+
<textarea
79+
placeholder="Asset Description"
80+
className="mt-2 border rounded p-4"
81+
onChange={e => updateFormInput({ ...formInput, description: e.target.value })}
82+
/>
83+
<input
84+
placeholder="Asset Price in Eth"
85+
className="mt-2 border rounded p-4"
86+
onChange={e => updateFormInput({ ...formInput, price: e.target.value })}
87+
/>
88+
<input
89+
type="file"
90+
name="Asset"
91+
className="my-4"
92+
onChange={onChange}
93+
/>
94+
{
95+
fileUrl && (
96+
<img className="rounded mt-4" width="350" src={fileUrl} />
97+
)
98+
}
99+
<button onClick={listNFTForSale} className="font-bold mt-4 bg-pink-500 text-white rounded p-4 shadow-lg">
100+
Create NFT
101+
</button>
102+
</div>
103+
</div>
104+
)
105+
}

0 commit comments

Comments
 (0)