├── .gitignore ├── README.md ├── index.html ├── package.json ├── public └── favicon.png ├── src ├── App.jsx ├── index.css └── main.jsx └── vite.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple NFT Indexer 2 | 3 | This is an skeleton app that uses the Alchemy SDK rigged to Alchemy's Enhanced APIs in order to display all of an address's ERC-721 tokens, including a call to any `image` attached to their metadata. 4 | 5 | ## Set Up 6 | 7 | 1. Install dependencies by running `npm install` 8 | 2. Start application by running `npm run dev` 9 | 10 | ## Challenge 11 | 12 | Fork this repo and build out more features! This is minimalistic on purpose. 13 | 14 | We purposefully built this out to be a skeleton version of what can be the next big thing so that you can practice some software development! Here are a few challenge suggestions: 15 | 16 | 1. Add Wallet integration so that any user that connects their wallet can check see their NFTs in a flash! 17 | 2. There is no indication of a request in progress... that's bad UX! Do you think you can add some sort of indication of loading? 18 | 3. Add some styling! 🎨 19 | 4. The NFT images can sometimes appear and sometimes not... can you think of ways to fix that? 20 | 5. There is no error-checking for wrongly formed requests, or really any error checking of any kind... can you add some in? 21 | 6. The images and grid display could look better... anything you can do about that? 22 | 7. There are ways to make this app faster... can you implement some of them? How can the query be made _even_ quicker? 23 | 8. Can you add ENS support for inputs? 24 | 9. The code has no commenting... bruh! Clear documentation is a clear path for other developers to understand and build on your code... think you can add clear commenting? 25 | 10. Completely open-ended!! Use this as the base for your next hackathon project, dream company or personal expedition :) 26 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nft-indexer", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "@chakra-ui/react": "^2.4.6", 13 | "alchemy-sdk": "^2.2.5", 14 | "axios": "^1.2.2", 15 | "ethers": "^5.7.2", 16 | "react": "^18.2.0", 17 | "react-dom": "^18.2.0" 18 | }, 19 | "devDependencies": { 20 | "@types/react": "^18.0.26", 21 | "@types/react-dom": "^18.0.9", 22 | "@vitejs/plugin-react": "^3.0.0", 23 | "vite": "^4.0.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alchemyplatform/nft-indexer/4d9600800878efeaa27954ae1c397ad299a86629/public/favicon.png -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | Box, 3 | Button, 4 | Center, 5 | Flex, 6 | Heading, 7 | Image, 8 | Input, 9 | SimpleGrid, 10 | Text, 11 | } from '@chakra-ui/react'; 12 | import { Alchemy, Network } from 'alchemy-sdk'; 13 | import { useState } from 'react'; 14 | 15 | function App() { 16 | const [userAddress, setUserAddress] = useState(''); 17 | const [results, setResults] = useState([]); 18 | const [hasQueried, setHasQueried] = useState(false); 19 | const [tokenDataObjects, setTokenDataObjects] = useState([]); 20 | 21 | async function getNFTsForOwner() { 22 | const config = { 23 | apiKey: '<-- COPY-PASTE YOUR ALCHEMY API KEY HERE -->', 24 | network: Network.ETH_MAINNET, 25 | }; 26 | 27 | const alchemy = new Alchemy(config); 28 | const data = await alchemy.nft.getNftsForOwner(userAddress); 29 | setResults(data); 30 | 31 | const tokenDataPromises = []; 32 | 33 | for (let i = 0; i < data.ownedNfts.length; i++) { 34 | const tokenData = alchemy.nft.getNftMetadata( 35 | data.ownedNfts[i].contract.address, 36 | data.ownedNfts[i].tokenId 37 | ); 38 | tokenDataPromises.push(tokenData); 39 | } 40 | 41 | setTokenDataObjects(await Promise.all(tokenDataPromises)); 42 | setHasQueried(true); 43 | } 44 | return ( 45 | 46 |
47 | 52 | 53 | NFT Indexer 🖼 54 | 55 | 56 | Plug in an address and this website will return all of its NFTs! 57 | 58 | 59 |
60 | 66 | Get all the ERC-721 tokens of this address: 67 | setUserAddress(e.target.value)} 69 | color="black" 70 | w="600px" 71 | textAlign="center" 72 | p={4} 73 | bgColor="white" 74 | fontSize={24} 75 | /> 76 | 79 | 80 | Here are your NFTs: 81 | 82 | {hasQueried ? ( 83 | 84 | {results.ownedNfts.map((e, i) => { 85 | return ( 86 | 93 | 94 | Name:{' '} 95 | {tokenDataObjects[i].title?.length === 0 96 | ? 'No Name' 97 | : tokenDataObjects[i].title} 98 | 99 | {'Image'} 106 | 107 | ); 108 | })} 109 | 110 | ) : ( 111 | 'Please make a query! The query may take a few seconds...' 112 | )} 113 | 114 |
115 | ); 116 | } 117 | 118 | export default App; 119 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif; 3 | font-size: 16px; 4 | line-height: 24px; 5 | font-weight: 400; 6 | 7 | color-scheme: light dark; 8 | color: rgba(255, 255, 255, 0.87); 9 | background-color: #242424; 10 | 11 | font-synthesis: none; 12 | text-rendering: optimizeLegibility; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | -webkit-text-size-adjust: 100%; 16 | } 17 | 18 | a { 19 | font-weight: 500; 20 | color: #646cff; 21 | text-decoration: inherit; 22 | } 23 | a:hover { 24 | color: #535bf2; 25 | } 26 | 27 | body { 28 | margin: 0; 29 | display: flex; 30 | place-items: center; 31 | min-width: 320px; 32 | min-height: 100vh; 33 | } 34 | 35 | h1 { 36 | font-size: 3.2em; 37 | line-height: 1.1; 38 | } 39 | 40 | button { 41 | border-radius: 8px; 42 | border: 1px solid transparent; 43 | padding: 0.6em 1.2em; 44 | font-size: 1em; 45 | font-weight: 500; 46 | font-family: inherit; 47 | background-color: #1a1a1a; 48 | cursor: pointer; 49 | transition: border-color 0.25s; 50 | } 51 | button:hover { 52 | border-color: #646cff; 53 | } 54 | button:focus, 55 | button:focus-visible { 56 | outline: 4px auto -webkit-focus-ring-color; 57 | } 58 | 59 | @media (prefers-color-scheme: light) { 60 | :root { 61 | color: #213547; 62 | background-color: #ffffff; 63 | } 64 | a:hover { 65 | color: #747bff; 66 | } 67 | button { 68 | background-color: #f9f9f9; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | --------------------------------------------------------------------------------