├── .example.infuraid ├── .env ├── .eslintrc ├── next.config.js ├── config.example.js ├── wallet.png ├── public ├── favicon.ico └── vercel.svg ├── styles ├── globals.css └── Home.module.css ├── postcss.config.js ├── tailwind.config.js ├── .gitpod.yml ├── scripts └── deploy.js ├── .gitignore ├── package.json ├── hardhat.config.js ├── pages ├── _app.js ├── resell-nft.js ├── dashboard.js ├── my-nfts.js ├── index.js └── create-nft.js ├── LICENSE.txt ├── test └── sample-test.js ├── README.md └── contracts └── NFTMarketplace.sol /.example.infuraid: -------------------------------------------------------------------------------- 1 | "" -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_WORKSPACE_URL=$CLIENT_URL -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next", "next/core-web-vitals"] 3 | } 4 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | reactStrictMode: true, 3 | } 4 | -------------------------------------------------------------------------------- /config.example.js: -------------------------------------------------------------------------------- 1 | export const nftmarketaddress = "" 2 | export const nftaddress = "" -------------------------------------------------------------------------------- /wallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/polygon-ethereum-nextjs-marketplace/main/wallet.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/polygon-ethereum-nextjs-marketplace/main/public/favicon.ico -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | /* ./styles/globals.css */ 2 | @tailwind base; 3 | @tailwind components; 4 | @tailwind utilities; -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 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 -------------------------------------------------------------------------------- /scripts/deploy.js: -------------------------------------------------------------------------------- 1 | const hre = require("hardhat"); 2 | const fs = require('fs'); 3 | 4 | async function main() { 5 | const NFTMarketplace = await hre.ethers.getContractFactory("NFTMarketplace"); 6 | const nftMarketplace = await NFTMarketplace.deploy(); 7 | await nftMarketplace.deployed(); 8 | console.log("nftMarketplace deployed to:", nftMarketplace.address); 9 | 10 | fs.writeFileSync('./config.js', ` 11 | export const marketplaceAddress = "${nftMarketplace.address}" 12 | `) 13 | } 14 | 15 | main() 16 | .then(() => process.exit(0)) 17 | .catch((error) => { 18 | console.error(error); 19 | process.exit(1); 20 | }); 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | .secret 4 | .infuraid 5 | config.js 6 | 7 | # dependencies 8 | /node_modules 9 | /.pnp 10 | .pnp.js 11 | 12 | # testing 13 | /coverage 14 | 15 | # next.js 16 | /.next/ 17 | /out/ 18 | 19 | # production 20 | /build 21 | 22 | # misc 23 | .DS_Store 24 | *.pem 25 | 26 | # debug 27 | npm-debug.log* 28 | yarn-debug.log* 29 | yarn-error.log* 30 | 31 | # local env files 32 | .env.local 33 | .env.development.local 34 | .env.test.local 35 | .env.production.local 36 | 37 | # vercel 38 | .vercel 39 | 40 | node_modules 41 | 42 | #Hardhat files 43 | cache 44 | artifacts 45 | -------------------------------------------------------------------------------- /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.5", 13 | "@nomiclabs/hardhat-waffle": "^2.0.2", 14 | "@openzeppelin/contracts": "^4.5.0", 15 | "axios": "^0.26.0", 16 | "chai": "^4.3.6", 17 | "ethereum-waffle": "^3.4.0", 18 | "ethers": "^5.5.4", 19 | "hardhat": "^2.8.4", 20 | "ipfs-http-client": "^56.0.1", 21 | "next": "11.0.1", 22 | "react": "17.0.2", 23 | "react-dom": "17.0.2", 24 | "web3modal": "^1.9.5" 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 | } 34 | -------------------------------------------------------------------------------- /hardhat.config.js: -------------------------------------------------------------------------------- 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 | 37 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | /* pages/_app.js */ 2 | import '../styles/globals.css' 3 | import Link from 'next/link' 4 | 5 | function MyApp({ Component, pageProps }) { 6 | return ( 7 |
8 | 33 | 34 |
35 | ) 36 | } 37 | 38 | export default MyApp -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 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. -------------------------------------------------------------------------------- /test/sample-test.js: -------------------------------------------------------------------------------- 1 | /* test/sample-test.js */ 2 | describe("NFTMarket", function() { 3 | it("Should create and execute market sales", async function() { 4 | /* deploy the marketplace */ 5 | const NFTMarketplace = await ethers.getContractFactory("NFTMarketplace") 6 | const nftMarketplace = await NFTMarketplace.deploy() 7 | await nftMarketplace.deployed() 8 | 9 | let listingPrice = await nftMarketplace.getListingPrice() 10 | listingPrice = listingPrice.toString() 11 | 12 | const auctionPrice = ethers.utils.parseUnits('1', 'ether') 13 | 14 | /* create two tokens */ 15 | await nftMarketplace.createToken("https://www.mytokenlocation.com", auctionPrice, { value: listingPrice }) 16 | await nftMarketplace.createToken("https://www.mytokenlocation2.com", auctionPrice, { value: listingPrice }) 17 | 18 | const [_, buyerAddress] = await ethers.getSigners() 19 | 20 | /* execute sale of token to another user */ 21 | await nftMarketplace.connect(buyerAddress).createMarketSale(1, { value: auctionPrice }) 22 | 23 | /* resell a token */ 24 | await nftMarketplace.connect(buyerAddress).resellToken(1, auctionPrice, { value: listingPrice }) 25 | 26 | /* query for and return the unsold items */ 27 | items = await nftMarketplace.fetchMarketItems() 28 | items = await Promise.all(items.map(async i => { 29 | const tokenUri = await nftMarketplace.tokenURI(i.tokenId) 30 | let item = { 31 | price: i.price.toString(), 32 | tokenId: i.tokenId.toString(), 33 | seller: i.seller, 34 | owner: i.owner, 35 | tokenUri 36 | } 37 | return item 38 | })) 39 | console.log('items: ', items) 40 | }) 41 | }) -------------------------------------------------------------------------------- /pages/resell-nft.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | import { ethers } from 'ethers' 3 | import { useRouter } from 'next/router' 4 | import axios from 'axios' 5 | import Web3Modal from 'web3modal' 6 | 7 | import { 8 | marketplaceAddress 9 | } from '../config' 10 | 11 | import NFTMarketplace from '../artifacts/contracts/NFTMarketplace.sol/NFTMarketplace.json' 12 | 13 | export default function ResellNFT() { 14 | const [formInput, updateFormInput] = useState({ price: '', image: '' }) 15 | const router = useRouter() 16 | const { id, tokenURI } = router.query 17 | const { image, price } = formInput 18 | 19 | useEffect(() => { 20 | fetchNFT() 21 | }, [id]) 22 | 23 | async function fetchNFT() { 24 | if (!tokenURI) return 25 | const meta = await axios.get(tokenURI) 26 | updateFormInput(state => ({ ...state, image: meta.data.image })) 27 | } 28 | 29 | async function listNFTForSale() { 30 | if (!price) return 31 | const web3Modal = new Web3Modal() 32 | const connection = await web3Modal.connect() 33 | const provider = new ethers.providers.Web3Provider(connection) 34 | const signer = provider.getSigner() 35 | 36 | const priceFormatted = ethers.utils.parseUnits(formInput.price, 'ether') 37 | let contract = new ethers.Contract(marketplaceAddress, NFTMarketplace.abi, signer) 38 | let listingPrice = await contract.getListingPrice() 39 | 40 | listingPrice = listingPrice.toString() 41 | let transaction = await contract.resellToken(id, priceFormatted, { value: listingPrice }) 42 | await transaction.wait() 43 | 44 | router.push('/') 45 | } 46 | 47 | return ( 48 |
49 |
50 | updateFormInput({ ...formInput, price: e.target.value })} 54 | /> 55 | { 56 | image && ( 57 | 58 | ) 59 | } 60 | 63 |
64 |
65 | ) 66 | } -------------------------------------------------------------------------------- /pages/dashboard.js: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers' 2 | import { useEffect, useState } from 'react' 3 | import axios from 'axios' 4 | import Web3Modal from 'web3modal' 5 | 6 | import { 7 | marketplaceAddress 8 | } from '../config' 9 | 10 | import NFTMarketplace from '../artifacts/contracts/NFTMarketplace.sol/NFTMarketplace.json' 11 | 12 | export default function CreatorDashboard() { 13 | const [nfts, setNfts] = useState([]) 14 | const [loadingState, setLoadingState] = useState('not-loaded') 15 | useEffect(() => { 16 | loadNFTs() 17 | }, []) 18 | async function loadNFTs() { 19 | const web3Modal = new Web3Modal({ 20 | network: 'mainnet', 21 | cacheProvider: true, 22 | }) 23 | const connection = await web3Modal.connect() 24 | const provider = new ethers.providers.Web3Provider(connection) 25 | const signer = provider.getSigner() 26 | 27 | const contract = new ethers.Contract(marketplaceAddress, NFTMarketplace.abi, signer) 28 | const data = await contract.fetchItemsListed() 29 | 30 | const items = await Promise.all(data.map(async i => { 31 | const tokenUri = await contract.tokenURI(i.tokenId) 32 | const meta = await axios.get(tokenUri) 33 | let price = ethers.utils.formatUnits(i.price.toString(), 'ether') 34 | let item = { 35 | price, 36 | tokenId: i.tokenId.toNumber(), 37 | seller: i.seller, 38 | owner: i.owner, 39 | image: meta.data.image, 40 | } 41 | return item 42 | })) 43 | 44 | setNfts(items) 45 | setLoadingState('loaded') 46 | } 47 | if (loadingState === 'loaded' && !nfts.length) return (

No NFTs listed

) 48 | return ( 49 |
50 |
51 |

Items Listed

52 |
53 | { 54 | nfts.map((nft, i) => ( 55 |
56 | 57 |
58 |

Price - {nft.price} Eth

59 |
60 |
61 | )) 62 | } 63 |
64 |
65 |
66 | ) 67 | } -------------------------------------------------------------------------------- /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-nfts.js: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers' 2 | import { useEffect, useState } from 'react' 3 | import axios from 'axios' 4 | import Web3Modal from 'web3modal' 5 | import { useRouter } from 'next/router' 6 | 7 | import { 8 | marketplaceAddress 9 | } from '../config' 10 | 11 | import NFTMarketplace from '../artifacts/contracts/NFTMarketplace.sol/NFTMarketplace.json' 12 | 13 | export default function MyAssets() { 14 | const [nfts, setNfts] = useState([]) 15 | const [loadingState, setLoadingState] = useState('not-loaded') 16 | const router = useRouter() 17 | useEffect(() => { 18 | loadNFTs() 19 | }, []) 20 | async function loadNFTs() { 21 | const web3Modal = new Web3Modal({ 22 | network: "mainnet", 23 | cacheProvider: true, 24 | }) 25 | const connection = await web3Modal.connect() 26 | const provider = new ethers.providers.Web3Provider(connection) 27 | const signer = provider.getSigner() 28 | 29 | const marketplaceContract = new ethers.Contract(marketplaceAddress, NFTMarketplace.abi, signer) 30 | const data = await marketplaceContract.fetchMyNFTs() 31 | 32 | const items = await Promise.all(data.map(async i => { 33 | const tokenURI = await marketplaceContract.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 | tokenURI 43 | } 44 | return item 45 | })) 46 | setNfts(items) 47 | setLoadingState('loaded') 48 | } 49 | function listNFT(nft) { 50 | console.log('nft:', nft) 51 | router.push(`/resell-nft?id=${nft.tokenId}&tokenURI=${nft.tokenURI}`) 52 | } 53 | if (loadingState === 'loaded' && !nfts.length) return (

No NFTs owned

) 54 | return ( 55 |
56 |
57 |
58 | { 59 | nfts.map((nft, i) => ( 60 |
61 | 62 |
63 |

Price - {nft.price} Eth

64 | 65 |
66 |
67 | )) 68 | } 69 |
70 |
71 |
72 | ) 73 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Full stack NFT marketplace built with Polygon, Solidity, IPFS, & Next.js 2 | 3 | ![Header](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pfofv47dooojerkmfgr4.png) 4 | 5 | This is the codebase to go along with tbe blog post [Building a Full Stack NFT Marketplace on Ethereum with Polygon](https://dev.to/dabit3/building-scalable-full-stack-apps-on-ethereum-with-polygon-2cfb) 6 | 7 | ### Running this project 8 | 9 | #### Gitpod 10 | 11 | To deploy this project to Gitpod, follow these steps: 12 | 13 | 1. Click this link to deploy 14 | 15 | [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#github.com/dabit3/polygon-ethereum-nextjs-marketplace) 16 | 17 | 2. Import the RPC address given to you by GitPod into your MetaMask wallet 18 | 19 | This endpoint will look something like this: 20 | 21 | ``` 22 | https://8545-copper-swordtail-j1mvhxv3.ws-eu18.gitpod.io/ 23 | ``` 24 | 25 | The chain ID should be 1337. If you have a localhost rpc set up, you may need to overwrite it. 26 | 27 | ![MetaMask RPC Import](wallet.png) 28 | 29 | #### Local setup 30 | 31 | To run this project locally, follow these steps. 32 | 33 | 1. Clone the project locally, change into the directory, and install the dependencies: 34 | 35 | ```sh 36 | git clone https://github.com/dabit3/polygon-ethereum-nextjs-marketplace.git 37 | 38 | cd polygon-ethereum-nextjs-marketplace 39 | 40 | # install using NPM or Yarn 41 | npm install 42 | 43 | # or 44 | 45 | yarn 46 | ``` 47 | 48 | 2. Start the local Hardhat node 49 | 50 | ```sh 51 | npx hardhat node 52 | ``` 53 | 54 | 3. With the network running, deploy the contracts to the local network in a separate terminal window 55 | 56 | ```sh 57 | npx hardhat run scripts/deploy.js --network localhost 58 | ``` 59 | 60 | 4. Start the app 61 | 62 | ``` 63 | npm run dev 64 | ``` 65 | 66 | ### Configuration 67 | 68 | To deploy to Polygon test or main networks, update the configurations located in __hardhat.config.js__ to use a private key and, optionally, deploy to a private RPC like Infura. 69 | 70 | ```javascript 71 | require("@nomiclabs/hardhat-waffle"); 72 | const fs = require('fs'); 73 | const privateKey = fs.readFileSync(".secret").toString().trim() || "01234567890123456789"; 74 | 75 | // infuraId is optional if you are using Infura RPC 76 | const infuraId = fs.readFileSync(".infuraid").toString().trim() || ""; 77 | 78 | module.exports = { 79 | defaultNetwork: "hardhat", 80 | networks: { 81 | hardhat: { 82 | chainId: 1337 83 | }, 84 | mumbai: { 85 | // Infura 86 | // url: `https://polygon-mumbai.infura.io/v3/${infuraId}` 87 | url: "https://rpc-mumbai.matic.today", 88 | accounts: [privateKey] 89 | }, 90 | matic: { 91 | // Infura 92 | // url: `https://polygon-mainnet.infura.io/v3/${infuraId}`, 93 | url: "https://rpc-mainnet.maticvigil.com", 94 | accounts: [privateKey] 95 | } 96 | }, 97 | solidity: { 98 | version: "0.8.4", 99 | settings: { 100 | optimizer: { 101 | enabled: true, 102 | runs: 200 103 | } 104 | } 105 | } 106 | }; 107 | ``` 108 | 109 | If using Infura, update __.infuraid__ with your [Infura](https://infura.io/) project ID. 110 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers' 2 | import { useEffect, useState } from 'react' 3 | import axios from 'axios' 4 | import Web3Modal from 'web3modal' 5 | 6 | import { 7 | marketplaceAddress 8 | } from '../config' 9 | 10 | import NFTMarketplace from '../artifacts/contracts/NFTMarketplace.sol/NFTMarketplace.json' 11 | 12 | export default function Home() { 13 | const [nfts, setNfts] = useState([]) 14 | const [loadingState, setLoadingState] = useState('not-loaded') 15 | useEffect(() => { 16 | loadNFTs() 17 | }, []) 18 | async function loadNFTs() { 19 | /* create a generic provider and query for unsold market items */ 20 | const provider = new ethers.providers.JsonRpcProvider() 21 | const contract = new ethers.Contract(marketplaceAddress, NFTMarketplace.abi, provider) 22 | const data = await contract.fetchMarketItems() 23 | 24 | /* 25 | * map over items returned from smart contract and format 26 | * them as well as fetch their token metadata 27 | */ 28 | const items = await Promise.all(data.map(async i => { 29 | const tokenUri = await contract.tokenURI(i.tokenId) 30 | const meta = await axios.get(tokenUri) 31 | let price = ethers.utils.formatUnits(i.price.toString(), 'ether') 32 | let item = { 33 | price, 34 | tokenId: i.tokenId.toNumber(), 35 | seller: i.seller, 36 | owner: i.owner, 37 | image: meta.data.image, 38 | name: meta.data.name, 39 | description: meta.data.description, 40 | } 41 | return item 42 | })) 43 | setNfts(items) 44 | setLoadingState('loaded') 45 | } 46 | async function buyNft(nft) { 47 | /* needs the user to sign the transaction, so will use Web3Provider and sign it */ 48 | const web3Modal = new Web3Modal() 49 | const connection = await web3Modal.connect() 50 | const provider = new ethers.providers.Web3Provider(connection) 51 | const signer = provider.getSigner() 52 | const contract = new ethers.Contract(marketplaceAddress, NFTMarketplace.abi, signer) 53 | 54 | /* user will be prompted to pay the asking proces to complete the transaction */ 55 | const price = ethers.utils.parseUnits(nft.price.toString(), 'ether') 56 | const transaction = await contract.createMarketSale(nft.tokenId, { 57 | value: price 58 | }) 59 | await transaction.wait() 60 | loadNFTs() 61 | } 62 | if (loadingState === 'loaded' && !nfts.length) return (

No items in marketplace

) 63 | return ( 64 |
65 |
66 |
67 | { 68 | nfts.map((nft, i) => ( 69 |
70 | 71 |
72 |

{nft.name}

73 |
74 |

{nft.description}

75 |
76 |
77 |
78 |

{nft.price} ETH

79 | 80 |
81 |
82 | )) 83 | } 84 |
85 |
86 |
87 | ) 88 | } -------------------------------------------------------------------------------- /pages/create-nft.js: -------------------------------------------------------------------------------- 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 |
72 |
73 | updateFormInput({ ...formInput, name: e.target.value })} 77 | /> 78 |