├── .eslintrc.json ├── .gitignore ├── .husky └── pre-commit ├── .prettierignore ├── .prettierrc ├── README.md ├── components ├── Footer.js ├── FundWallet.js ├── Header.js ├── MyNFTContainer.js └── index.js ├── constants ├── mock-artist.json └── mock-nft.json ├── context └── bundlrContext.js ├── contracts └── NFTMarketplace.sol ├── hardhat.config.js ├── next.config.js ├── package.json ├── pages ├── _app.js ├── api │ └── hello.js ├── createnft.js ├── dashboard.js ├── index.js ├── nft-details.js ├── profile.js └── sellnft.js ├── pnpm-lock.yaml ├── postcss.config.js ├── public ├── favicon.ico ├── images │ ├── main1.png │ ├── main2.png │ ├── main3.png │ ├── main4.png │ ├── mock.png │ ├── mockcreator.jpg │ ├── nft2.png │ ├── nft3.png │ ├── placeholder1.png │ ├── placeholder2.png │ ├── placeholder3.png │ ├── placeholder4.png │ └── wallet.png ├── logo.png └── vercel.svg ├── scripts └── deploy.js ├── styles ├── Home.module.css └── globals.css ├── tailwind.config.js ├── test └── nftmarketplace-test.js └── utils └── truncAddress.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.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 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | node_modules 35 | .env 36 | coverage 37 | coverage.json 38 | typechain 39 | typechain-types 40 | 41 | #Hardhat files 42 | cache 43 | artifacts 44 | 45 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Directories 2 | node_modules 3 | .next 4 | build 5 | dist 6 | out 7 | coverage 8 | 9 | # Files 10 | *.min.js 11 | *.lock 12 | package-lock.json 13 | yarn.lock 14 | pnpm-lock.yaml 15 | 16 | # Config files 17 | .env* -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "tabWidth": 2, 4 | "printWidth": 100, 5 | "singleQuote": true, 6 | "trailingComma": "es5", 7 | "bracketSpacing": true, 8 | "jsxBracketSameLine": false 9 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DesiNFT - Create & Sell Your Own NFT! 2 | 3 |

4 | 5 |

6 | 7 | ### Tech Stack 8 | 9 | ![HTML5](https://img.shields.io/badge/html5-%23E34F26.svg?style=for-the-badge&logo=html5&logoColor=white) ![CSS3](https://img.shields.io/badge/css3-%231572B6.svg?style=for-the-badge&logo=css3&logoColor=white) ![Visual Studio Code](https://img.shields.io/badge/Visual%20Studio%20Code-0078d7.svg?style=for-the-badge&logo=visual-studio-code&logoColor=white) 10 | 11 | ### Screenshots 12 | 13 | 14 | 15 | 18 | 21 | 22 |
16 | 17 | 19 | 20 |
23 | 24 | ### Installing 25 | 26 | First, we need to clone the repository and change the working directory. Then, we must set up environment variables for the project. Make sure to add `.env` to `.gitignore`. 27 | 28 | ```bash 29 | git clone https://github.com/ayush-that/HackOn-Blocks-2024.git 30 | cd HackOn-Blocks-2024 31 | ``` 32 | 33 | Use the node version manager [(nvm)](https://www.freecodecamp.org/news/node-version-manager-nvm-install-guide/) and set up the project to use Node v16 for seamless installation. Now install the dependencies. 34 | 35 | ```node 36 | nvm install 16 37 | nvm use 16 38 | npm i 39 | ``` 40 | 41 | In the `.env`, add the following environment variables. Get the `PRIVATE_KEY` from your MetaMask account. For Polygon zkEVM (ETH): 42 | 43 | ```env 44 | PRIVATE_KEY="METAMASK_PRIVATE_KEY" 45 | NEXT_PUBLIC_CONTRACT_ADDRESS="CONTRACT_ADDRESS" 46 | URL=https://rpc.cardona.zkevm-rpc.com 47 | NEXT_PUBLIC_RPC_URL=https://rpc.cardona.zkevm-rpc.com 48 | ``` 49 | 50 | To get the value of `NEXT_PUBLIC_CONTRACT_ADDRESS`, In the root directory of the project run the following commands. This will compile and deploy the smart contract. 51 | 52 | ```node 53 | npx hardhat compile 54 | npx hardhat run scripts/deploy.js --network polygonZkEvmTestnet 55 | ``` 56 | 57 | Now, run `npm run dev` or `yarn dev` to start the live deployment server. You can use Vercel to deploy it too. See the [Live](https://desinft.vercel.app/) site here. 58 | -------------------------------------------------------------------------------- /components/Footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Footer = () => { 4 | return ( 5 | 18 | ); 19 | }; 20 | 21 | export default Footer; 22 | -------------------------------------------------------------------------------- /components/FundWallet.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useBundler } from '../context/bundlrContext'; 3 | 4 | const FundWallet = () => { 5 | const { fundWallet, balance } = useBundler(); 6 | const [value, setValue] = React.useState('0.005'); 7 | 8 | return ( 9 |
10 |

You Current Balance is : {balance || 0} $BNDLR

11 | setValue(e.target.value)} 15 | /> 16 | 24 |
25 | ); 26 | }; 27 | 28 | export default FundWallet; 29 | -------------------------------------------------------------------------------- /components/Header.js: -------------------------------------------------------------------------------- 1 | import { CloseSquare, HambergerMenu } from 'iconsax-react'; 2 | import Link from 'next/link'; 3 | import { useRouter } from 'next/router'; 4 | import React, { useEffect, useState } from 'react'; 5 | import { truncateEthAddress } from '../utils/truncAddress'; 6 | 7 | const Header = () => { 8 | const [isOpen, setIsOpen] = useState(false); 9 | const router = useRouter(); 10 | const currentRoute = router.pathname; 11 | 12 | const [hasScrolled, setHasScrolled] = useState(false); 13 | 14 | const [addr, setAddr] = useState(''); 15 | 16 | const changeNavbar = () => { 17 | if (window.scrollY >= 20) { 18 | setHasScrolled(true); 19 | } else { 20 | setHasScrolled(false); 21 | } 22 | }; 23 | 24 | useEffect(() => { 25 | document.addEventListener('scroll', changeNavbar); 26 | }); 27 | 28 | useEffect(() => { 29 | const addr = localStorage.getItem('walletAddress'); 30 | setAddr(addr); 31 | }, []); 32 | 33 | const toggle = () => setIsOpen(!isOpen); 34 | 35 | return ( 36 | <> 37 |
44 | 106 |
107 | 108 |
115 |
116 | 122 | 123 |

124 | 125 | DesiNFT 126 | 127 |

128 | 169 | 170 |

171 | {truncateEthAddress(addr)} 172 |

173 |
174 |
175 | 176 | ); 177 | }; 178 | 179 | export default Header; 180 | -------------------------------------------------------------------------------- /components/MyNFTContainer.js: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router'; 2 | import React from 'react'; 3 | import { truncateEthAddress } from '../utils/truncAddress'; 4 | 5 | // Test mode settings 6 | const TEST_MODE = true; 7 | const DEFAULT_TEST_IMAGE = '/logo.png'; // Use your logo as placeholder for test images 8 | 9 | const mainURL = `https://arweave.net/`; 10 | 11 | const MyNFTContainer = ({ nft }) => { 12 | const router = useRouter(); 13 | 14 | return ( 15 |
16 |
{ 19 | router.push({ 20 | pathname: '/nft-details', 21 | query: nft, 22 | }); 23 | }} 24 | > 25 |
26 | mock 31 |
32 |
33 | 44 |
45 |
46 |
47 |
48 |

{nft.name}

49 |
50 |
51 |

Owner

52 |

53 | {truncateEthAddress(nft.owner)} 54 |

55 |
56 |
57 |

Price

58 |

{nft.price} ETH

59 |
60 |
61 |
62 |
63 |
64 | ); 65 | }; 66 | 67 | export default MyNFTContainer; 68 | -------------------------------------------------------------------------------- /components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Header } from './Header'; 2 | export { default as Footer } from './Footer'; 3 | export { default as FundWallet } from './FundWallet'; 4 | export { default as MyNFTContainer } from './MyNFTContainer'; 5 | -------------------------------------------------------------------------------- /constants/mock-artist.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "name": "Khamaj - The night Raga", 5 | "bgImage": "/images/placeholder1.png", 6 | "image": "/images/main1.png", 7 | "price": "37.7K" 8 | }, 9 | { 10 | "id": 2, 11 | "name": "Ecotherapy", 12 | "bgImage": "/images/placeholder2.png", 13 | "image": "/images/main2.png", 14 | "price": "87.1K" 15 | }, 16 | { 17 | "id": 3, 18 | "name": "Yuzen Sarees", 19 | "bgImage": "/images/placeholder3.png", 20 | "image": "/images/main3.png", 21 | "price": "79.4K " 22 | }, 23 | { 24 | "id": 4, 25 | "name": "Toy Faces", 26 | "bgImage": "/images/placeholder4.png", 27 | "image": "/images/main4.png", 28 | "price": "13.9K" 29 | } 30 | ] 31 | -------------------------------------------------------------------------------- /constants/mock-nft.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "image": "images/wallet.png", 5 | "title": "Set up your wallet", 6 | "description": "Choose a wallet of your choice. We recommend Metamask Wallet." 7 | }, 8 | { 9 | "id": 2, 10 | "image": "images/nft2.png", 11 | "title": "Add your NFTs", 12 | "description": "Once you’ve set up your wallet of choice,Start Creating your Your NFTs" 13 | }, 14 | { 15 | "id": 3, 16 | "image": "images/nft3.png", 17 | "title": "List them for sale", 18 | "description": "List Out all the Top hot notch NFTs available for sale" 19 | } 20 | ] 21 | -------------------------------------------------------------------------------- /context/bundlrContext.js: -------------------------------------------------------------------------------- 1 | import { WebBundlr } from '@bundlr-network/client'; 2 | import BigNumber from 'bignumber.js'; 3 | import { providers, utils } from 'ethers'; 4 | import React, { createContext, useContext, useEffect, useState } from 'react'; 5 | import { toast } from 'react-toastify'; 6 | 7 | const TEST_MODE = true; 8 | const BundlrContext = createContext({ 9 | initialiseBundlr: async () => {}, 10 | fundWallet: (_) => {}, 11 | balance: '', 12 | uploadFile: async (_file) => {}, 13 | uploadURI: async (_file) => {}, 14 | bundlrInstance: null, 15 | }); 16 | 17 | const BundlrContextProvider = ({ children }) => { 18 | const [bundlrInstance, setBundlrInstance] = useState(); 19 | const [balance, setBalance] = useState(TEST_MODE ? '1.0' : ''); 20 | 21 | useEffect(() => { 22 | if (bundlrInstance) { 23 | fetchBalance(); 24 | } 25 | }, [bundlrInstance]); 26 | 27 | const initialiseBundlr = async () => { 28 | if (TEST_MODE) { 29 | setBundlrInstance({ test: true }); 30 | setBalance('1.0'); 31 | return; 32 | } 33 | 34 | const { ethereum } = window; 35 | const provider = new providers.Web3Provider(ethereum); 36 | await provider._ready(); 37 | const bundlr = new WebBundlr('https://devnet.bundlr.network', 'matic', provider, { 38 | providerUrl: process.env.NEXT_PUBLIC_RPC_URL, 39 | }); 40 | await bundlr.ready(); 41 | setBundlrInstance(bundlr); 42 | }; 43 | 44 | async function fundWallet(amount) { 45 | if (TEST_MODE) { 46 | toast.success('Test mode: Funds added successfully'); 47 | setBalance('1.0'); 48 | return; 49 | } 50 | 51 | console.log(amount); 52 | try { 53 | if (bundlrInstance) { 54 | console.log(bundlrInstance); 55 | if (!amount) return; 56 | const amountParsed = parseInput(amount); 57 | console.log(amountParsed); 58 | if (amountParsed) { 59 | toast.info('Adding funds please wait', { progress: 1 }); 60 | console.log('Adding...'); 61 | let response = await bundlrInstance.fund(amountParsed); 62 | console.log('Wallet funded: ', response); 63 | toast.success('Funds added', { progress: 1 }); 64 | } 65 | fetchBalance(); 66 | } 67 | } catch (error) { 68 | console.log('error', error); 69 | toast.error(error.message || 'Something went wrong!'); 70 | } 71 | } 72 | 73 | function parseInput(input) { 74 | if (TEST_MODE) return new BigNumber(input); 75 | 76 | const conv = new BigNumber(input).multipliedBy(bundlrInstance?.currencyConfig.base[1]); 77 | if (conv.isLessThan(1)) { 78 | console.log('error: value too small'); 79 | toast.error('Error: value too small'); 80 | return; 81 | } else { 82 | return conv; 83 | } 84 | } 85 | 86 | async function fetchBalance() { 87 | if (TEST_MODE) { 88 | setBalance('1.0'); 89 | return; 90 | } 91 | 92 | if (bundlrInstance) { 93 | const bal = await bundlrInstance.getLoadedBalance(); 94 | console.log('bal: ', utils.formatEther(bal.toString())); 95 | setBalance(utils.formatEther(bal.toString())); 96 | } 97 | } 98 | 99 | async function uploadFile(file) { 100 | if (TEST_MODE) { 101 | const mockTxId = `test-image-${Date.now()}`; 102 | return { data: { id: mockTxId } }; 103 | } 104 | 105 | try { 106 | let tx = await bundlrInstance.uploader.upload(file, [ 107 | { name: 'Content-Type', value: 'image/png' }, 108 | ]); 109 | return tx; 110 | } catch (error) { 111 | toast.error(error.message || 'Something went wrong!'); 112 | return null; 113 | } 114 | } 115 | 116 | async function uploadURI(file) { 117 | if (TEST_MODE) { 118 | const mockTxId = `test-uri-${Date.now()}`; 119 | return { data: { id: mockTxId } }; 120 | } 121 | 122 | try { 123 | console.log(file); 124 | let tx = await bundlrInstance.uploader.upload(file, [ 125 | { name: 'Content-Type', value: 'application/json' }, 126 | ]); 127 | return tx; 128 | } catch (error) { 129 | toast.error(error.message || 'Something went wrong!'); 130 | return null; 131 | } 132 | } 133 | 134 | return ( 135 | 145 | {children} 146 | 147 | ); 148 | }; 149 | 150 | export default BundlrContextProvider; 151 | 152 | export const useBundler = () => { 153 | return useContext(BundlrContext); 154 | }; 155 | -------------------------------------------------------------------------------- /contracts/NFTMarketplace.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.9; 4 | 5 | import "@openzeppelin/contracts/utils/Counters.sol"; 6 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; 7 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 8 | 9 | import "hardhat/console.sol"; 10 | 11 | contract NFTMarketplace is ERC721URIStorage { 12 | using Counters for Counters.Counter; 13 | Counters.Counter private _tokenIds; 14 | Counters.Counter private _itemsSold; 15 | 16 | uint256 listingPrice = 0.025 ether; 17 | address payable owner; 18 | 19 | mapping(uint256 => MarketItem) private idToMarketItem; 20 | 21 | struct MarketItem { 22 | uint256 tokenId; 23 | address payable seller; 24 | address payable owner; 25 | uint256 price; 26 | bool sold; 27 | } 28 | 29 | event MarketItemCreated( 30 | uint256 indexed tokenId, 31 | address seller, 32 | address owner, 33 | uint256 price, 34 | bool sold 35 | ); 36 | 37 | constructor() ERC721("Crypto Tokens", "CT") { 38 | owner = payable(msg.sender); 39 | } 40 | 41 | // Update the listing price of the Contract 42 | function updateListingPrice(uint _listingPrice) public payable { 43 | require( 44 | owner == msg.sender, 45 | "Only marketplace owner can update listing price." 46 | ); 47 | listingPrice = _listingPrice; 48 | } 49 | 50 | // Returns the listing price of the contract 51 | function getListingPrice() public view returns (uint256) { 52 | return listingPrice; 53 | } 54 | 55 | // Mint the Token and list it in the marketplace 56 | function createToken(string memory tokenURI, uint256 price) 57 | public 58 | payable 59 | returns (uint) 60 | { 61 | _tokenIds.increment(); 62 | uint256 newTokenId = _tokenIds.current(); 63 | 64 | _mint(msg.sender, newTokenId); 65 | _setTokenURI(newTokenId, tokenURI); 66 | createMarketItem(newTokenId, price); 67 | return newTokenId; 68 | } 69 | 70 | // create market item 71 | function createMarketItem(uint256 tokenId, uint256 price) private { 72 | require(price > 0, "Price must be at least 1 wei"); 73 | require( 74 | msg.value == listingPrice, 75 | "Price must be equal to listing price" 76 | ); 77 | 78 | idToMarketItem[tokenId] = MarketItem( 79 | tokenId, 80 | payable(msg.sender), 81 | payable(address(this)), 82 | price, 83 | false 84 | ); 85 | 86 | _transfer(msg.sender, address(this), tokenId); 87 | emit MarketItemCreated( 88 | tokenId, 89 | msg.sender, 90 | address(this), 91 | price, 92 | false 93 | ); 94 | } 95 | 96 | // allow someone to resell 97 | function resellToken(uint256 tokenId, uint256 price) public payable { 98 | require( 99 | idToMarketItem[tokenId].owner == msg.sender, 100 | "Only item owner can perform this operation" 101 | ); 102 | require( 103 | msg.value == listingPrice, 104 | "Price must be equal to listing price" 105 | ); 106 | idToMarketItem[tokenId].sold = false; 107 | idToMarketItem[tokenId].price = price; 108 | idToMarketItem[tokenId].seller = payable(msg.sender); 109 | idToMarketItem[tokenId].owner = payable(address(this)); 110 | _itemsSold.decrement(); 111 | 112 | _transfer(msg.sender, address(this), tokenId); 113 | } 114 | 115 | // creating market sale 116 | function createMarketSale(uint256 tokenId) public payable { 117 | uint price = idToMarketItem[tokenId].price; 118 | address seller = idToMarketItem[tokenId].seller; 119 | require( 120 | msg.value == price, 121 | "Please submit the asking price in order to complete the purchase" 122 | ); 123 | idToMarketItem[tokenId].owner = payable(msg.sender); 124 | idToMarketItem[tokenId].sold = true; 125 | idToMarketItem[tokenId].seller = payable(address(0)); 126 | _itemsSold.increment(); 127 | _transfer(address(this), msg.sender, tokenId); 128 | (bool ownersent, ) = payable(owner).call{value: listingPrice}(""); 129 | require(ownersent, "Failed to send"); 130 | (bool sellersent, ) = payable(seller).call{value: msg.value}(""); 131 | require(sellersent, "Failed to send"); 132 | } 133 | 134 | // Returns all unsold market items 135 | function fetchMarketItems() public view returns (MarketItem[] memory) { 136 | uint itemCount = _tokenIds.current(); 137 | uint unsoldItemCount = _tokenIds.current() - _itemsSold.current(); 138 | uint currentIndex = 0; 139 | // creating an empty array and provinding the size of the array that is unsold items as unsoldItemCount 140 | MarketItem[] memory items = new MarketItem[](unsoldItemCount); 141 | for (uint i = 0; i < itemCount; i++) { 142 | if (idToMarketItem[i + 1].owner == address(this)) { 143 | uint currentId = i + 1; 144 | MarketItem storage currentItem = idToMarketItem[currentId]; 145 | items[currentIndex] = currentItem; 146 | currentIndex += 1; 147 | } 148 | } 149 | return items; 150 | } 151 | 152 | // Returns only items that a user has purchased 153 | function fetchMyNFTs() public view returns (MarketItem[] memory) { 154 | uint totalItemCount = _tokenIds.current(); 155 | uint itemCount = 0; 156 | uint currentIndex = 0; 157 | 158 | for (uint i = 0; i < totalItemCount; i++) { 159 | if (idToMarketItem[i + 1].owner == msg.sender) { 160 | itemCount += 1; 161 | } 162 | } 163 | // creating an empty array and provinding the size of the array that is my item count as itemCount 164 | MarketItem[] memory items = new MarketItem[](itemCount); 165 | for (uint i = 0; i < totalItemCount; i++) { 166 | if (idToMarketItem[i + 1].owner == msg.sender) { 167 | uint currentId = i + 1; 168 | MarketItem storage currentItem = idToMarketItem[currentId]; 169 | items[currentIndex] = currentItem; 170 | currentIndex += 1; 171 | } 172 | } 173 | return items; 174 | } 175 | 176 | // Returns only items a user has listed 177 | function fetchItemsListed() public view returns (MarketItem[] memory) { 178 | uint totalItemCount = _tokenIds.current(); 179 | uint itemCount = 0; 180 | uint currentIndex = 0; 181 | 182 | for (uint i = 0; i < totalItemCount; i++) { 183 | if (idToMarketItem[i + 1].seller == msg.sender) { 184 | itemCount += 1; 185 | } 186 | } 187 | // creating an empty array and provinding the size of the array that is total items as itemCount 188 | MarketItem[] memory items = new MarketItem[](itemCount); 189 | for (uint i = 0; i < totalItemCount; i++) { 190 | if (idToMarketItem[i + 1].seller == msg.sender) { 191 | uint currentId = i + 1; 192 | MarketItem storage currentItem = idToMarketItem[currentId]; 193 | items[currentIndex] = currentItem; 194 | currentIndex += 1; 195 | } 196 | } 197 | return items; 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /hardhat.config.js: -------------------------------------------------------------------------------- 1 | require('@nomicfoundation/hardhat-toolbox'); 2 | require('dotenv').config(); 3 | 4 | /** @type import('hardhat/config').HardhatUserConfig */ 5 | module.exports = { 6 | solidity: '0.8.16', 7 | networks: { 8 | zkEVM: { 9 | url: 'https://rpc.cardona.zkevm-rpc.com', 10 | accounts: [process.env.PRIVATE_KEY], 11 | }, 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | swcMinify: true, 5 | }; 6 | 7 | module.exports = nextConfig; 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "08-nftmarketplace-app", 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 | "format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,css,md}\"", 11 | "format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,json,css,md}\"", 12 | "prepare": "husky" 13 | }, 14 | "dependencies": { 15 | "@bundlr-network/client": "^0.7.8", 16 | "@openzeppelin/contracts": "^4.7.3", 17 | "axios": "^1.0.0", 18 | "bignumber.js": "^9.1.0", 19 | "dotenv": "^16.0.3", 20 | "ethers": "^5.7.1", 21 | "iconsax-react": "^0.0.8", 22 | "next": "12.3.1", 23 | "react": "18.2.0", 24 | "react-dom": "18.2.0", 25 | "react-toastify": "^9.0.8" 26 | }, 27 | "devDependencies": { 28 | "@nomicfoundation/hardhat-toolbox": "2.0.0", 29 | "autoprefixer": "^10.4.12", 30 | "eslint": "8.24.0", 31 | "eslint-config-next": "12.3.1", 32 | "hardhat": "2.11.1", 33 | "husky": "^9.1.7", 34 | "lint-staged": "^15.5.2", 35 | "postcss": "^8.4.17", 36 | "prettier": "^3.5.3", 37 | "pretty-quick": "^4.1.1", 38 | "tailwindcss": "^3.1.8" 39 | }, 40 | "lint-staged": { 41 | "**/*.{js,jsx,ts,tsx,json,css,md}": [ 42 | "prettier --write" 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css'; 2 | import { ToastContainer } from 'react-toastify'; 3 | import 'react-toastify/dist/ReactToastify.css'; 4 | import BundlrContextProvider from '../context/bundlrContext'; 5 | 6 | function MyApp({ Component, pageProps }) { 7 | return ( 8 |
9 | 10 | 11 | 23 | 24 |
25 | ); 26 | } 27 | 28 | export default MyApp; 29 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /pages/createnft.js: -------------------------------------------------------------------------------- 1 | import Head from 'next/head'; 2 | import { useRouter } from 'next/router'; 3 | import React, { useRef, useState } from 'react'; 4 | import { Footer, FundWallet, Header } from '../components'; 5 | import { useBundler } from '../context/bundlrContext'; 6 | import ContractABI from '../artifacts/contracts/NFTMarketplace.sol/NFTMarketplace.json'; 7 | import { toast } from 'react-toastify'; 8 | import { ethers } from 'ethers'; 9 | 10 | const TEST_MODE = true; 11 | const mainURL = `https://arweave.net/`; 12 | 13 | const Create = () => { 14 | const { initialiseBundlr, bundlrInstance, balance, uploadFile, uploadURI } = useBundler(); 15 | 16 | const [nftDetails, setNftDetails] = useState({ 17 | name: '', 18 | description: '', 19 | price: '', 20 | image: '', 21 | }); 22 | 23 | const [file, setFile] = useState(''); 24 | 25 | const [loading, setLoading] = useState(false); 26 | 27 | const router = useRouter(); 28 | 29 | const dataRef = useRef(); 30 | 31 | function triggerOnChange() { 32 | dataRef.current.click(); 33 | } 34 | 35 | async function handleFileChange(e) { 36 | const uploadedFile = e.target.files[0]; 37 | if (!uploadedFile) return; 38 | setNftDetails({ ...nftDetails, image: uploadedFile }); 39 | let reader = new FileReader(); 40 | reader.onload = function () { 41 | if (reader.result) { 42 | setFile(Buffer.from(reader.result)); 43 | } 44 | }; 45 | reader.readAsArrayBuffer(uploadedFile); 46 | } 47 | 48 | const getContract = async () => { 49 | const provider = new ethers.providers.Web3Provider(window.ethereum); 50 | 51 | const signer = provider.getSigner(); 52 | 53 | let contract = new ethers.Contract( 54 | process.env.NEXT_PUBLIC_CONTRACT_ADDRESS, 55 | ContractABI.abi, 56 | signer 57 | ); 58 | return contract; 59 | }; 60 | 61 | const handleUpload = async () => { 62 | const { name, description, price, image } = nftDetails; 63 | if (name === '') { 64 | toast.error('Please provide name for NFT'); 65 | } else if (description === '') { 66 | toast.error('Please provide description for NFT'); 67 | } else if (price === '') { 68 | toast.error('Please provide Price'); 69 | } else if (image === '') { 70 | toast.error('Please Select Image'); 71 | } else { 72 | setLoading(true); 73 | const url = await uploadFile(file); 74 | uploadToArweave(url.data.id); 75 | } 76 | }; 77 | 78 | const uploadToArweave = async (url) => { 79 | const { name, description } = nftDetails; 80 | 81 | const data = JSON.stringify({ 82 | name, 83 | description, 84 | image: url, 85 | }); 86 | 87 | const tokenURI = await uploadURI(data); 88 | 89 | mintNFT(tokenURI.data.id); 90 | }; 91 | 92 | const mintNFT = async (tokenURI) => { 93 | try { 94 | if (TEST_MODE) { 95 | // In test mode, skip actual contract interaction 96 | setLoading(false); 97 | setNftDetails({ 98 | name: '', 99 | description: '', 100 | price: '', 101 | image: '', 102 | }); 103 | setFile(''); 104 | toast.success('Test Mode: NFT Minted Successfully'); 105 | router.push('/dashboard'); 106 | return; 107 | } 108 | 109 | const contract = await getContract(); 110 | 111 | const price = ethers.utils.parseUnits(nftDetails.price, 'ether'); 112 | 113 | let listingPrice = await contract.getListingPrice(); 114 | listingPrice = listingPrice.toString(); 115 | 116 | let transaction = await contract.createToken(tokenURI, price, { 117 | value: listingPrice, 118 | }); 119 | await transaction.wait(); 120 | 121 | setLoading(false); 122 | 123 | setNftDetails({ 124 | name: '', 125 | description: '', 126 | price: '', 127 | image: '', 128 | }); 129 | 130 | setFile(''); 131 | 132 | toast.success('Minted Successfully'); 133 | 134 | router.push('/dashboard'); 135 | } catch (error) { 136 | console.error(error); 137 | if (TEST_MODE) { 138 | setLoading(false); 139 | setNftDetails({ 140 | name: '', 141 | description: '', 142 | price: '', 143 | image: '', 144 | }); 145 | setFile(''); 146 | toast.success('Test Mode: NFT Minted Successfully'); 147 | router.push('/dashboard'); 148 | } else { 149 | toast.error('Something went wrong', error); 150 | setLoading(false); 151 | } 152 | } 153 | }; 154 | 155 | if (!bundlrInstance) { 156 | return ( 157 |
158 |

Let's initialise Bundlr now 💱

159 | 167 |
168 | ); 169 | } 170 | 171 | if (!balance || (Number(balance) <= 0 && !balance) || Number(balance) <= 0.0005) { 172 | return ( 173 |
174 |

175 | Oops! Before Publishing NFT Please Add Some Funds.🪙 176 |

177 | 178 |
179 | ); 180 | } 181 | 182 | return ( 183 |
184 | 185 | Create NFT || DesiNFT 186 | 187 | 188 | 189 |
190 | 191 |

Create NFT

192 | 193 |
194 |
195 |
199 | 206 | {nftDetails.image ? ( 207 |
208 | image 214 |
215 | ) : ( 216 |
217 |

Please Select Here to See Your File Preview

218 |
219 | )} 220 |
221 | 222 |
223 |
224 | 225 | setNftDetails({ ...nftDetails, name: e.target.value })} 231 | /> 232 |
233 | 234 |
235 | 236 |