├── .secret
├── .eslintrc.json
├── next.config.js
├── .config.js
├── styles
├── globals.css
└── Home.module.css
├── public
├── favicon.ico
└── vercel.svg
├── postcss.config.js
├── pages
├── api
│ └── hello.js
├── _app.js
├── my-assets.js
├── creator-dashboard.js
├── index.js
└── create-item.js
├── tailwind.config.js
├── hardhat.config.js
├── .gitignore
├── scripts
└── deploy.js
├── contracts
├── NFT.sol
└── NFTMarket.sol
├── package.json
├── README.md
└── test
└── sample-test.js
/.secret:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | reactStrictMode: true,
3 | }
4 |
--------------------------------------------------------------------------------
/.config.js:
--------------------------------------------------------------------------------
1 | export const nftaddress = ""
2 | export const nftmarketaddress = ""
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwing utilities;
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EmanuelCampos/nft-marketplace/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/pages/api/hello.js:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 |
3 | export default function handler(req, res) {
4 | res.status(200).json({ name: 'John Doe' })
5 | }
6 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | purge: [],
3 | darkMode: false, // or 'media' or 'class'
4 | theme: {
5 | extend: {},
6 | },
7 | variants: {
8 | extend: {},
9 | },
10 | plugins: [],
11 | }
12 |
--------------------------------------------------------------------------------
/hardhat.config.js:
--------------------------------------------------------------------------------
1 | require("@nomiclabs/hardhat-waffle");
2 | const fs = require("fs")
3 |
4 | const privateKey = fs.readFileSync(".secret").toString()
5 | const projectId =
6 |
7 | module.exports = {
8 | networks: {
9 | hardhat: {
10 | chainId: 1337
11 | },
12 | mumbai: {
13 | url: `https://polygon-mumbai.infura.io/v3/${projectId}`,
14 | accounts: [privateKey]
15 | },
16 | mainnet: {
17 | url: `https://mainnet.infura.io/v3/${projectId}`,
18 | accounts: [privateKey]
19 | }
20 | },
21 | solidity: "0.8.4",
22 | };
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
36 | node_modules
37 |
38 | #Hardhat files
39 | cache
40 | artifacts
41 |
--------------------------------------------------------------------------------
/scripts/deploy.js:
--------------------------------------------------------------------------------
1 | const hre = require("hardhat");
2 |
3 | async function main() {
4 | const NFTMarket = await hre.ethers.getContractFactory("NFTMarket");
5 | const nftMarket = await NFTMarket.deploy();
6 | await nftMarket.deployed();
7 | console.log("nftMarket deployed to:", nftMarket.address);
8 |
9 | const NFT = await hre.ethers.getContractFactory("NFT");
10 | const nft = await NFT.deploy(nftMarket.address)
11 | await nft.deployed()
12 | console.log("nft deployed to:", nft.address);
13 |
14 | }
15 |
16 | // We recommend this pattern to be able to use async/await everywhere
17 | // and properly handle errors.
18 | main()
19 | .then(() => process.exit(0))
20 | .catch((error) => {
21 | console.error(error);
22 | process.exit(1);
23 | });
24 |
--------------------------------------------------------------------------------
/contracts/NFT.sol:
--------------------------------------------------------------------------------
1 | //SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.4;
3 |
4 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
5 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
6 | import "@openzeppelin/contracts/utils/Counters.sol";
7 |
8 | contract NFT is ERC721URIStorage {
9 | using Counters for Counters.Counter;
10 | Counters.Counter private _tokenIds;
11 | address contractAddress;
12 |
13 | constructor(address marketplaceAddress) ERC721("Metaverse Tokens", "METT") {
14 | contractAddress = marketplaceAddress;
15 | }
16 |
17 | function createToken(string memory tokenURI) public returns(uint) {
18 | _tokenIds.increment();
19 | uint256 newItemId = _tokenIds.current();
20 |
21 | _mint(msg.sender, newItemId);
22 | _setTokenURI(newItemId, tokenURI);
23 | setApprovalForAll(contractAddress, true);
24 | return newItemId;
25 | }
26 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "polygon-next",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@nomiclabs/hardhat-ethers": "^2.0.2",
13 | "@nomiclabs/hardhat-waffle": "^2.0.1",
14 | "@openzeppelin/contracts": "^4.2.0",
15 | "axios": "^0.21.1",
16 | "chai": "^4.3.4",
17 | "ethereum-waffle": "^3.4.0",
18 | "ethers": "^5.4.1",
19 | "hardhat": "^2.4.1",
20 | "ipfs-http-client": "^50.1.2",
21 | "next": "11.0.1",
22 | "react": "17.0.2",
23 | "react-dom": "17.0.2",
24 | "web3modal": "^1.9.3"
25 | },
26 | "devDependencies": {
27 | "autoprefixer": "^10.2.6",
28 | "eslint": "7.29.0",
29 | "eslint-config-next": "11.0.1",
30 | "postcss": "^8.3.5",
31 | "tailwindcss": "^2.2.4"
32 | }
33 | }
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import 'tailwindcss/tailwind.css'
2 | import Link from 'next/link'
3 |
4 | function MyApp({ Component, pageProps }) {
5 | return (
6 |
7 |
32 |
33 |
34 | )
35 | }
36 |
37 | export default MyApp
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | ```
12 |
13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
14 |
15 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
16 |
17 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
18 |
19 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
20 |
21 | ## Learn More
22 |
23 | To learn more about Next.js, take a look at the following resources:
24 |
25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
27 |
28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
29 |
30 | ## Deploy on Vercel
31 |
32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
33 |
34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
35 |
--------------------------------------------------------------------------------
/test/sample-test.js:
--------------------------------------------------------------------------------
1 | const { expect } = require("chai");
2 |
3 | describe("NFTMarket", function () {
4 |
5 | let owner
6 |
7 | beforeEach(async () => {
8 | const accs = await ethers.getSigners();
9 | console.log(accs[0].address)
10 |
11 | owner = accs[0].address
12 |
13 | });
14 |
15 |
16 | it("should create and execute market sales", async function () {
17 |
18 | const Market = await ethers.getContractFactory("NFTMarket")
19 | const market = await Market.deploy()
20 | await market.deployed()
21 | const marketAddress = market.address
22 |
23 | const NFT = await ethers.getContractFactory("NFT")
24 | const nft = await NFT.deploy(marketAddress)
25 | await nft.deployed()
26 | const nftContractAddress = nft.address
27 |
28 | let listingPrice = await market.getListingPrice()
29 | listingPrice = listingPrice.toString()
30 |
31 | const auctionPrice = ethers.utils.parseUnits("100", "ether")
32 |
33 | await nft.createToken("https://www.mytokenlocation.com")
34 | await nft.createToken("https://www.mytokenlocation2.com")
35 |
36 | await market.createMarketItem(nftContractAddress, 1, auctionPrice, { value: listingPrice })
37 | await market.createMarketItem(nftContractAddress, 2, auctionPrice, { value: listingPrice })
38 |
39 | const [_, buyerAddress] = await ethers.getSigners()
40 |
41 | await market.connect(buyerAddress).createMarketSale(nftContractAddress, 1, { value: auctionPrice })
42 |
43 | let items = await market.fetchMarketItems()
44 |
45 | items = await Promise.all(items.map(async i => {
46 | const tokenUri = await nft.tokenURI(i.tokenId)
47 | let item = {
48 | price: i.price.toString(),
49 | tokenId: i.tokenId.toString(),
50 | seller: i.seller,
51 | owner: i.owner,
52 | tokenUri
53 | }
54 | return item
55 |
56 |
57 | }))
58 |
59 | console.log("items:", items)
60 |
61 | });
62 | });
63 |
--------------------------------------------------------------------------------
/styles/Home.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | min-height: 100vh;
3 | padding: 0 0.5rem;
4 | display: flex;
5 | flex-direction: column;
6 | justify-content: center;
7 | align-items: center;
8 | height: 100vh;
9 | }
10 |
11 | .main {
12 | padding: 5rem 0;
13 | flex: 1;
14 | display: flex;
15 | flex-direction: column;
16 | justify-content: center;
17 | align-items: center;
18 | }
19 |
20 | .footer {
21 | width: 100%;
22 | height: 100px;
23 | border-top: 1px solid #eaeaea;
24 | display: flex;
25 | justify-content: center;
26 | align-items: center;
27 | }
28 |
29 | .footer a {
30 | display: flex;
31 | justify-content: center;
32 | align-items: center;
33 | flex-grow: 1;
34 | }
35 |
36 | .title a {
37 | color: #0070f3;
38 | text-decoration: none;
39 | }
40 |
41 | .title a:hover,
42 | .title a:focus,
43 | .title a:active {
44 | text-decoration: underline;
45 | }
46 |
47 | .title {
48 | margin: 0;
49 | line-height: 1.15;
50 | font-size: 4rem;
51 | }
52 |
53 | .title,
54 | .description {
55 | text-align: center;
56 | }
57 |
58 | .description {
59 | line-height: 1.5;
60 | font-size: 1.5rem;
61 | }
62 |
63 | .code {
64 | background: #fafafa;
65 | border-radius: 5px;
66 | padding: 0.75rem;
67 | font-size: 1.1rem;
68 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
69 | Bitstream Vera Sans Mono, Courier New, monospace;
70 | }
71 |
72 | .grid {
73 | display: flex;
74 | align-items: center;
75 | justify-content: center;
76 | flex-wrap: wrap;
77 | max-width: 800px;
78 | margin-top: 3rem;
79 | }
80 |
81 | .card {
82 | margin: 1rem;
83 | padding: 1.5rem;
84 | text-align: left;
85 | color: inherit;
86 | text-decoration: none;
87 | border: 1px solid #eaeaea;
88 | border-radius: 10px;
89 | transition: color 0.15s ease, border-color 0.15s ease;
90 | width: 45%;
91 | }
92 |
93 | .card:hover,
94 | .card:focus,
95 | .card:active {
96 | color: #0070f3;
97 | border-color: #0070f3;
98 | }
99 |
100 | .card h2 {
101 | margin: 0 0 1rem 0;
102 | font-size: 1.5rem;
103 | }
104 |
105 | .card p {
106 | margin: 0;
107 | font-size: 1.25rem;
108 | line-height: 1.5;
109 | }
110 |
111 | .logo {
112 | height: 1em;
113 | margin-left: 0.5rem;
114 | }
115 |
116 | @media (max-width: 600px) {
117 | .grid {
118 | width: 100%;
119 | flex-direction: column;
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/pages/my-assets.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @next/next/no-img-element */
2 | import { ethers } from 'ethers'
3 | import { useEffect, useState } from 'react'
4 | import axios from 'axios'
5 | import Web3Modal from 'web3modal'
6 |
7 | import {
8 | nftaddress, nftmarketaddress
9 | } from '../.config'
10 |
11 | import NFT from '../artifacts/contracts/NFT.sol/NFT.json'
12 | import Market from '../artifacts/contracts/NFTMarket.sol/NFTMarket.json'
13 |
14 | export default function MyAssets() {
15 | const [nfts, setNfts] = useState([])
16 | const [loadingState, setLoadingState] = useState('not-loaded')
17 |
18 | useEffect(() => {
19 | loadNFTs()
20 | }, [])
21 |
22 | async function loadNFTs() {
23 | const web3Modal = new Web3Modal()
24 | const connection = await web3Modal.connect()
25 | const provider = new ethers.providers.Web3Provider(connection)
26 | const signer = provider.getSigner()
27 |
28 | const marketContract = new ethers.Contract(nftmarketaddress, Market.abi, signer)
29 | const tokenContract = new ethers.Contract(nftaddress, NFT.abi, provider)
30 | const data = await marketContract.fetchMyNFTs()
31 |
32 | const items = await Promise.all(data.map(async i => {
33 | const tokenUri = await tokenContract.tokenURI(i.tokenId)
34 | const meta = await axios.get(tokenUri)
35 | let price = ethers.utils.formatUnits(i.price.toString(), 'ether')
36 | let item = {
37 | price,
38 | tokenId: i.tokenId.toNumber(),
39 | seller: i.seller,
40 | owner: i.owner,
41 | image: meta.data.image,
42 | }
43 | return item
44 | }))
45 | setNfts(items)
46 | setLoadingState('loaded')
47 | }
48 |
49 | if(loadingState === 'loaded' && !nfts.length) return (
50 | No assets owned
51 | )
52 |
53 | return (
54 |
55 |
56 |
57 | {
58 | nfts.map((nft, i) => (
59 |
60 |

61 |
62 |
Price - {nft.price} Eth
63 |
64 |
65 | ))
66 | }
67 |
68 |
69 |
70 | )
71 |
72 |
73 | }
--------------------------------------------------------------------------------
/pages/creator-dashboard.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @next/next/no-img-element */
2 | import { ethers } from 'ethers'
3 | import { useEffect, useState } from 'react'
4 | import axios from 'axios'
5 | import Web3Modal from 'web3modal'
6 |
7 | import {
8 | nftaddress, nftmarketaddress
9 | } from '../.config'
10 |
11 | import NFT from '../artifacts/contracts/NFT.sol/NFT.json'
12 | import Market from '../artifacts/contracts/NFTMarket.sol/NFTMarket.json'
13 |
14 | export default function CreatorDashboard() {
15 | const [nfts, setNfts] = useState([])
16 | const [sold, setSold] = useState([])
17 | const [loadingState, setLoadingState] = useState('not-loaded')
18 |
19 | useEffect(() => {
20 | loadNFTs()
21 | }, [])
22 |
23 | async function loadNFTs() {
24 | const web3Modal = new Web3Modal()
25 | const connection = await web3Modal.connect()
26 | const provider = new ethers.providers.Web3Provider(connection)
27 | const signer = provider.getSigner()
28 |
29 | const marketContract = new ethers.Contract(nftmarketaddress, Market.abi, signer)
30 | const tokenContract = new ethers.Contract(nftaddress, NFT.abi, provider)
31 | const data = await marketContract.fetchItemsCreated()
32 |
33 | const items = await Promise.all(data.map(async i => {
34 | const tokenUri = await tokenContract.tokenURI(i.tokenId)
35 | const meta = await axios.get(tokenUri)
36 | let price = ethers.utils.formatUnits(i.price.toString(), 'ether')
37 | let item = {
38 | price,
39 | tokenId: i.tokenId.toNumber(),
40 | seller: i.seller,
41 | owner: i.owner,
42 | sold: i.sold,
43 | image: meta.data.image,
44 | }
45 | return item
46 | }))
47 |
48 | const soldItems = items.filter(i => i.sold)
49 | setSold(soldItems)
50 | setNfts(items)
51 | setLoadingState('loaded')
52 | }
53 | return(
54 |
55 |
56 |
Items Created
57 |
58 | {
59 | nfts.map((nft, i) => (
60 |
61 |

62 |
63 |
Price - {nft.price} Eth
64 |
65 |
66 | ))
67 | }
68 |
69 |
70 |
71 | {
72 | Boolean(sold.length) && (
73 |
74 |
Items Sold
75 |
76 | {
77 | sold.map((nft, i) => (
78 |
79 |

80 |
81 |
Price - {nft.price} Eth
82 |
83 |
84 | ))
85 | }
86 |
87 |
88 | )
89 | }
90 |
91 |
92 | )
93 | }
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @next/next/no-img-element */
2 | import { ethers} from 'ethers'
3 | import {useEffect, useState } from 'react'
4 | import axios from 'axios'
5 | import Web3Modal from 'web3modal'
6 |
7 | import {
8 | nftaddress, nftmarketaddress
9 | } from '../.config'
10 |
11 | import NFT from '../artifacts/contracts/NFT.sol/NFT.json'
12 | import Market from '../artifacts/contracts/NFTMarket.sol/NFTMarket.json'
13 |
14 | export default function Home() {
15 | const [nfts, setNfts] = useState([])
16 | const [loadingState, setLoadingState] = useState('not-loaded')
17 |
18 | useEffect(() => {
19 | loadNFTs()
20 | }, [])
21 |
22 | async function loadNFTs() {
23 | const provider = new ethers.providers.JsonRpcProvider()
24 | const tokenContract = new ethers.Contract(nftaddress, NFT.abi, provider)
25 | const marketContract = new ethers.Contract(nftmarketaddress, Market.abi, provider)
26 |
27 | const data = await marketContract.fetchMarketItems()
28 |
29 | const items = await Promise.all(data.map(async i => {
30 | const tokenUri = await tokenContract.tokenURI(i.tokenId)
31 | const meta = await axios.get(tokenUri)
32 | let price = ethers.utils.formatUnits(i.price.toString(), 'ether')
33 | let item = {
34 | price,
35 | tokenId: i.tokenId.toNumber(),
36 | seller: i.seller,
37 | owner: i.owner,
38 | image: meta.data.image,
39 | name: meta.data.name,
40 | description: meta.data.description,
41 | }
42 | return item
43 | }))
44 | setNfts(items)
45 | setLoadingState('loaded')
46 | }
47 |
48 | async function buyNft(nft) {
49 | const web3Modal = new Web3Modal()
50 | const connection = await web3Modal.connect()
51 | const provider = new ethers.providers.Web3Provider(connection)
52 |
53 | const signer = provider.getSigner()
54 | const contract = new ethers.Contract(nftmarketaddress, Market.abi, signer)
55 |
56 | const price = ethers.utils.parseUnits(nft.price.toString(), 'ether')
57 |
58 | const transaction = await contract.createMarketSale(nftaddress, nft.tokenId, { value: price })
59 |
60 | await transaction.wait()
61 | loadNFTs()
62 |
63 | }
64 |
65 | if(loadingState === 'loaded' && !nfts.length) return (
66 | No items in marketplace
67 | )
68 |
69 | return (
70 |
71 |
72 |
73 | {
74 | nfts.map((nft, i) => (
75 |
76 |

77 |
78 |
{nft.name}
79 |
80 |
{nft.description}
81 |
82 |
83 |
84 |
{nft.price} Matic
85 |
91 |
92 |
93 |
94 |
95 |
96 | ))
97 | }
98 |
99 |
100 |
101 | )
102 | }
103 |
--------------------------------------------------------------------------------
/pages/create-item.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @next/next/no-img-element */
2 |
3 |
4 | import { useState } from 'react'
5 | import { ethers } from 'ethers'
6 | import { create as ipfsHttpClient} from 'ipfs-http-client'
7 | import { useRouter } from 'next/router'
8 | import Web3Modal from 'web3modal'
9 |
10 | const client = ipfsHttpClient('https://ipfs.infura.io:5001/api/v0')
11 |
12 | import {
13 | nftaddress, nftmarketaddress
14 | } from '../.config'
15 |
16 | import NFT from '../artifacts/contracts/NFT.sol/NFT.json'
17 | import Market from '../artifacts/contracts/NFTMarket.sol/NFTMarket.json'
18 |
19 | export default function CreateItem() {
20 | const [fileUrl, setFileUrl] = useState(null)
21 | const [formInput, updateFormInput] = useState({ price: '', name: '', description: ''})
22 | const router = useRouter()
23 |
24 | async function onChange(e) {
25 | const file = e.target.files[0]
26 | try {
27 | const added = await client.add(
28 | file,
29 | {
30 | progress: (prog) => console.log('received:' + prog)
31 | }
32 | )
33 | const url = `https://ipfs.infura.io/ipfs/${added.path}`
34 | setFileUrl(url)
35 | } catch(e) {
36 | console.log(e)
37 | }
38 | }
39 |
40 | async function createItem() {
41 | const { name, description, price } = formInput
42 |
43 | if(!name || !description || !price || !fileUrl) return
44 |
45 | const data = JSON.stringify({
46 | name, description, image: fileUrl
47 | })
48 |
49 | try {
50 | const added = await client.add(data)
51 | const url = `https://ipfs.infura.io/ipfs/${added.path}`
52 |
53 | createSale(url)
54 |
55 |
56 | } catch(error) {
57 | console.log("Error uploading file:", error)
58 | }
59 | }
60 |
61 | async function createSale(url) {
62 | const web3Modal = new Web3Modal()
63 | const connection = await web3Modal.connect()
64 | const provider = new ethers.providers.Web3Provider(connection)
65 | const signer = provider.getSigner()
66 |
67 | let contract = new ethers.Contract(nftaddress, NFT.abi, signer)
68 | let transaction = await contract.createToken(url)
69 | let tx = await transaction.wait()
70 |
71 | let event = tx.events[0]
72 | let value = event.args[2]
73 | let tokenId = value.toNumber()
74 |
75 | const price = ethers.utils.parseUnits(formInput.price, 'ether')
76 |
77 | contract = new ethers.Contract(nftmarketaddress, Market.abi, signer)
78 | let listingPrice = await contract.getListingPrice()
79 | listingPrice = listingPrice.toString()
80 |
81 | transaction = await contract.createMarketItem(nftaddress, tokenId, price, { value: listingPrice})
82 |
83 | await transaction.wait()
84 | router.push('/')
85 | }
86 |
87 | return (
88 |
124 |
125 |
126 |
127 |
128 |
129 |
130 | )
131 |
132 |
133 | }
--------------------------------------------------------------------------------
/contracts/NFTMarket.sol:
--------------------------------------------------------------------------------
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/ERC721.sol";
6 | import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
7 |
8 | contract NFTMarket is ReentrancyGuard {
9 | using Counters for Counters.Counter;
10 | Counters.Counter private _itemIds;
11 | Counters.Counter private _itemsSold;
12 |
13 | address payable owner;
14 | uint256 listingPrice = 0.025 ether;
15 |
16 | constructor() {
17 | owner = payable(msg.sender);
18 | }
19 |
20 | struct MarketItem {
21 | uint itemId;
22 | address nftContract;
23 | uint256 tokenId;
24 | address payable seller;
25 | address payable owner;
26 | uint256 price;
27 | bool sold;
28 | }
29 |
30 | mapping(uint256 => MarketItem) private idToMarketItem;
31 |
32 | event MarketItemCreated(
33 | uint indexed itemId,
34 | address indexed nftContract,
35 | uint256 indexed tokenId,
36 | address seller,
37 | address owner,
38 | uint256 price,
39 | bool sold
40 | );
41 |
42 | function getListingPrice() public view returns(uint256) {
43 | return listingPrice;
44 | }
45 |
46 | function createMarketItem(
47 | address nftContract,
48 | uint256 tokenId,
49 | uint256 price
50 | ) public payable nonReentrant {
51 | require(price > 0, "Price must be at least 1 wei");
52 | require(msg.value == listingPrice, "Price mus be equal to listing price");
53 |
54 | _itemIds.increment();
55 | uint256 itemId = _itemIds.current();
56 |
57 | idToMarketItem[itemId] = MarketItem(
58 | itemId,
59 | nftContract,
60 | tokenId,
61 | payable(msg.sender),
62 | payable(address(0)),
63 | price,
64 | false
65 | );
66 |
67 | IERC721(nftContract).transferFrom(msg.sender, address(this), tokenId);
68 |
69 | emit MarketItemCreated(
70 | itemId,
71 | nftContract,
72 | tokenId,
73 | msg.sender,
74 | address(0),
75 | price,
76 | false
77 | );
78 |
79 | }
80 |
81 | function createMarketSale(
82 | address nftContract,
83 | uint256 itemId
84 | ) public payable nonReentrant {
85 | uint price = idToMarketItem[itemId].price;
86 | uint tokenId = idToMarketItem[itemId].tokenId;
87 |
88 | require(msg.value == price, "Please submit the asking price in order to complete the purchase");
89 |
90 | idToMarketItem[itemId].seller.transfer(msg.value);
91 | IERC721(nftContract).transferFrom(address(this), msg.sender, tokenId);
92 | idToMarketItem[itemId].owner = payable(msg.sender);
93 | idToMarketItem[itemId].sold = true;
94 | _itemsSold.increment();
95 | payable(owner).transfer(listingPrice);
96 | }
97 |
98 | function fetchMarketItems() public view returns(MarketItem[] memory) {
99 | uint itemCount = _itemIds.current();
100 | uint unsoldItemCount = _itemIds.current() - _itemsSold.current();
101 | uint currentIndex = 0;
102 |
103 | MarketItem[] memory items = new MarketItem[](unsoldItemCount);
104 |
105 | for(uint i = 0; i < itemCount; i++) {
106 | if(idToMarketItem[i + 1].owner == address(0)) {
107 | uint currentId = idToMarketItem[i + 1].itemId;
108 | MarketItem storage currentItem = idToMarketItem[currentId];
109 | items[currentIndex] = currentItem;
110 | currentIndex += 1;
111 | }
112 | }
113 | return items;
114 | }
115 |
116 | function fetchMyNFTs() public view returns(MarketItem[] memory) {
117 | uint totalItemCount = _itemIds.current();
118 | uint itemCount = 0;
119 | uint currentIndex = 0;
120 |
121 | for(uint i = 0; i < totalItemCount; i++) {
122 | if(idToMarketItem[i + 1].owner == msg.sender) {
123 | itemCount += 1;
124 | }
125 | }
126 |
127 | MarketItem[] memory items = new MarketItem[](itemCount);
128 |
129 | for(uint i = 0; i < totalItemCount; i++) {
130 | if(idToMarketItem[i + 1].owner == msg.sender) {
131 | uint currentId = idToMarketItem[i + 1].itemId;
132 | MarketItem storage currentItem = idToMarketItem[currentId];
133 | items[currentIndex] = currentItem;
134 | currentIndex += 1;
135 | }
136 | }
137 | return items;
138 | }
139 |
140 | function fetchItemsCreated() public view returns(MarketItem[] memory) {
141 | uint totalItemCount = _itemIds.current();
142 | uint itemCount = 0;
143 | uint currentIndex = 0;
144 |
145 | for(uint i = 0; i < totalItemCount; i++) {
146 | if(idToMarketItem[i + 1].seller == msg.sender) {
147 | itemCount += 1;
148 | }
149 | }
150 |
151 | MarketItem[] memory items = new MarketItem[](itemCount);
152 |
153 | for(uint i = 0; i < totalItemCount; i++) {
154 | if(idToMarketItem[i + 1].seller == msg.sender) {
155 | uint currentId = idToMarketItem[i + 1].itemId;
156 | MarketItem storage currentItem = idToMarketItem[currentId];
157 | items[currentIndex] = currentItem;
158 | currentIndex += 1;
159 | }
160 | }
161 | return items;
162 | }
163 | }
164 |
--------------------------------------------------------------------------------