├── .eslintrc.json ├── app ├── favicon.ico ├── layout.tsx ├── globals.css ├── utils │ ├── pinata.tsx │ └── connectWallet.tsx ├── page.tsx └── contract-abi.json ├── next.config.mjs ├── postcss.config.mjs ├── .gitignore ├── tailwind.config.ts ├── public ├── vercel.svg └── next.svg ├── tsconfig.json ├── package.json └── README.md /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imcrazysteven/NFT-minting-dApp/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /.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 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env 30 | .env*.local 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Inter } from "next/font/google"; 3 | import "./globals.css"; 4 | 5 | const inter = Inter({ subsets: ["latin"] }); 6 | 7 | export const metadata: Metadata = { 8 | title: "NFT marketplace", 9 | description: "NFT minter tutorial using Alchemy and pinata", 10 | }; 11 | 12 | export default function RootLayout({ 13 | children, 14 | }: Readonly<{ 15 | children: React.ReactNode; 16 | }>) { 17 | return ( 18 | 19 | {children} 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | const config: Config = { 4 | content: [ 5 | "./pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./components/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./app/**/*.{js,ts,jsx,tsx,mdx}", 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", 13 | "gradient-conic": 14 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", 15 | }, 16 | }, 17 | }, 18 | plugins: [], 19 | }; 20 | export default config; 21 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | :root { 13 | --foreground-rgb: 255, 255, 255; 14 | --background-start-rgb: 0, 0, 0; 15 | --background-end-rgb: 0, 0, 0; 16 | } 17 | } 18 | 19 | body { 20 | color: rgb(var(--foreground-rgb)); 21 | background: linear-gradient( 22 | to bottom, 23 | transparent, 24 | rgb(var(--background-end-rgb)) 25 | ) 26 | rgb(var(--background-start-rgb)); 27 | } 28 | 29 | @layer utilities { 30 | .text-balance { 31 | text-wrap: balance; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nft", 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 | "@alch/alchemy-web3": "^1.4.7", 13 | "@testing-library/jest-dom": "^6.4.8", 14 | "@testing-library/react": "^16.0.0", 15 | "@testing-library/user-event": "^14.5.2", 16 | "axios": "^1.7.4", 17 | "ethers": "^6.13.2", 18 | "next": "14.2.6", 19 | "react": "^18", 20 | "react-dom": "^18", 21 | "web-vitals": "^4.2.3" 22 | }, 23 | "devDependencies": { 24 | "@types/node": "^20", 25 | "@types/react": "^18", 26 | "@types/react-dom": "^18", 27 | "dotenv": "^16.4.5", 28 | "eslint": "^8", 29 | "eslint-config-next": "14.2.6", 30 | "postcss": "^8", 31 | "tailwindcss": "^3.4.1", 32 | "typescript": "^5" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/utils/pinata.tsx: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | export type JSONBody = { 4 | url: string, 5 | name: string, 6 | description: string, 7 | } 8 | 9 | require('dotenv').config(); 10 | 11 | const key = process.env.NEXT_PUBLIC_PINATA_KEY; 12 | const secret = process.env.NEXT_PUBLIC_PINATA_SECRET; 13 | 14 | export const pinJSONToIPFS = async (JSONBody: JSONBody) => { 15 | const to = `https://api.pinata.cloud/pinning/pinJSONToIPFS`; 16 | 17 | //making axios POST request to Pinata ⬇️ 18 | return axios 19 | .post(to, JSONBody, { 20 | headers: { 21 | pinata_api_key: key, 22 | pinata_secret_api_key: secret, 23 | } 24 | }) 25 | .then((response) => { 26 | return { 27 | success: true, 28 | pinataUrl: "https://gateway.pinata.cloud/ipfs/" + response.data.IpfsHash 29 | }; 30 | }) 31 | .catch((error) => { 32 | console.log(error) 33 | return { 34 | success: false, 35 | message: error.message, 36 | } 37 | }); 38 | }; -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Welcome to The NFT 💎 3 | 4 | This repo provides a nice and easy way for linking an existing NFT smart contract to this minting dapp. There are two ways of using this repo, you can go the simple route or the more complex one. 5 | 6 | The simple route is so simple, all you need to do is download the build folder on the release page and change the configuration to fit your needs. (Follow the video for a walk through). 7 | 8 | The more complex route allows you to add additional functionality if you are comfortable with coding in react.js. (Follow the below instructions for a walk through). 9 | 10 | ## Installation 🛠️ 11 | 12 | If you are cloning the project then run this first, otherwise you can download the source code on the release page and skip this step. 13 | 14 | ```sh 15 | git clone https://github.com/imcrazysteven/NFT-minting-dApp.git 16 | ``` 17 | 18 | Make sure you have node.js installed so you can use npm, then run: 19 | 20 | ```sh 21 | npm install 22 | ``` 23 | 24 | ## Usage ℹ️ 25 | 26 | In order to make use of this dapp, all you need to do is change the configurations to point to your smart contract as well as update the images and theme file. 27 | 28 | For the most part all the changes will be in the `public` folder. 29 | 30 | To link up your existing smart contract, go to the `public/config/config.json` file and update the following fields to fit your smart contract, network and marketplace details. The cost field should be in wei. 31 | 32 | Note: this dapp is designed to work with the intended NFT smart contract, that only takes one parameter in the mint function "mintAmount". But you can change that in the App.js file if you need to use a smart contract that takes 2 params. 33 | 34 | ```json 35 | { 36 | "CONTRACT_ADDRESS": "0x827acb09a2dc20e39c9aad7f7190d9bc53534192", 37 | "SCAN_LINK": "https://polygonscan.com/token/0x827acb09a2dc20e39c9aad7f7190d9bc53534192", 38 | "NETWORK": { 39 | "NAME": "Polygon", 40 | "SYMBOL": "Matic", 41 | "ID": 137 42 | }, 43 | "NFT_NAME": "The TS NFT", 44 | "SYMBOL": "TSNFT", 45 | "MAX_SUPPLY": 992, 46 | "WEI_COST": 75000000000000000, 47 | "DISPLAY_COST": 0.075, 48 | "GAS_LIMIT": 285000, 49 | "MARKETPLACE": "Opeansea", 50 | "MARKETPLACE_LINK": "https://opensea.io/collection/the-stripes-nft", 51 | "SHOW_BACKGROUND": true 52 | } 53 | ``` 54 | 55 | Make sure you copy the contract ABI from remix and paste it in the `public/config/abi.json` file. 56 | (follow the youtube video if you struggle with this part). 57 | 58 | Now you will need to create and change 2 images and a gif in the `public/config/images` folder, `bg.png`, `example.gif` and `logo.png`. 59 | 60 | Next change the theme colors to your liking in the `public/config/theme.css` file. 61 | 62 | ```css 63 | :root { 64 | --primary: #ebc908; 65 | --primary-text: #1a1a1a; 66 | --secondary: #ff1dec; 67 | --secondary-text: #ffffff; 68 | --accent: #ffffff; 69 | --accent-text: #000000; 70 | } 71 | ``` 72 | 73 | Now you will need to create and change the `public/favicon.ico`, `public/logo192.png`, and 74 | `public/logo512.png` to your brand images. 75 | 76 | Remember to update the title and description the `public/index.html` file 77 | 78 | ```html 79 | The Stripes NFT 80 | 81 | ``` 82 | 83 | Also remember to update the short_name and name fields in the `public/manifest.json` file 84 | 85 | ```json 86 | { 87 | "short_name": "TSNFT", 88 | "name": "The TS NFT" 89 | } 90 | ``` 91 | 92 | After all the changes you can run. 93 | 94 | ```sh 95 | npm run start 96 | ``` 97 | 98 | Or create the build if you are ready to deploy. 99 | 100 | ```sh 101 | npm run build 102 | ``` 103 | 104 | Now you can host the contents of the build folder on a server. 105 | 106 | That's it! you're done. -------------------------------------------------------------------------------- /app/utils/connectWallet.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { createAlchemyWeb3 } from '@alch/alchemy-web3'; 3 | 4 | import type { JSONBody } from "./pinata"; 5 | import { pinJSONToIPFS } from "./pinata"; 6 | import contractABI from '@/app/contract-abi.json' 7 | const contractAddress = "0x4C4a07F737Bf57F6632B6CAB089B78f62385aCaE" 8 | 9 | const alchemyKey = process.env.NEXT_PUBLIC_ALCHEMY_KEY; 10 | if (!alchemyKey) { 11 | throw new Error("Alchemy key is not defined in environment variables."); 12 | } 13 | 14 | const web3 = createAlchemyWeb3(alchemyKey) 15 | 16 | export const connectWallet = async () => { 17 | if (window.ethereum) { 18 | try { 19 | const addressArray = await window.ethereum.request({ method: 'eth_requestAccounts' }); 20 | const obj = { 21 | status: "👆🏽 Write a message in the text-field above.", 22 | address: addressArray[0] 23 | } 24 | return obj 25 | } catch (err) { 26 | const errorMessage = (err as Error).message || 'An unknown error occurred'; 27 | return { 28 | status: `😥 ${errorMessage}`, 29 | address: '' 30 | } 31 | } 32 | } else { 33 | return { 34 | address: "", 35 | status: ( 36 | 37 |

38 | { " "} 39 | 🦊{ " " } 40 | 41 | "You must install Metamask, a virtual Ethereum wallet, in your browser." 42 | 43 |

44 |
45 | ), 46 | }; 47 | } 48 | } 49 | 50 | export const getCurrentWalletConnected = async () => { 51 | if (window.ethereum) { 52 | try { 53 | const addressArray = await window.ethereum.request({ method: 'eth_accounts' }); 54 | if (addressArray.length > 0) { 55 | return { 56 | address: addressArray[0], 57 | status: "👆🏽 Write a message in the text-field above." 58 | } 59 | } else { 60 | return { 61 | address: "", 62 | status: "🦊 Connect to Metamask using the top right button." 63 | } 64 | } 65 | } catch (err) { 66 | const errorMessage = (err as Error).message || 'An unknown error occured'; 67 | return { 68 | address: "", 69 | status: `😥 ${errorMessage}` 70 | } 71 | } 72 | } else { 73 | return { 74 | address: "", 75 | status: ( 76 | 77 |

78 | { " "} 79 | 🦊{ " " } 80 | 81 | You must install Metamask, a virtual Ethereum wallet, in your browser. 82 | 83 |

84 |
85 | ) 86 | } 87 | } 88 | } 89 | 90 | export const mintNFT = async (JSONBody: JSONBody) => { 91 | 92 | const { url, name, description } = JSONBody; 93 | 94 | if (url.trim() == "" || name.trim() == "" || description.trim() == "") { 95 | return { 96 | success: false, 97 | status: "❗Please make sure all fields are completed before minting." 98 | } 99 | } 100 | 101 | //make pinata call 102 | const pinataResponse = await pinJSONToIPFS({ url, name, description }); 103 | if (!pinataResponse.success) { 104 | return { 105 | sucess: false, 106 | status: "😢 Something went wrong while uploading your tokenURI." 107 | } 108 | } 109 | 110 | const tokenURI = pinataResponse.pinataUrl; 111 | 112 | //load smart contract 113 | window.contract = await new web3.eth.Contract(contractABI, contractAddress); 114 | const transactionParameters = { 115 | to: contractAddress, 116 | from: window.ethereum.selectedAddress, 117 | 'data': window.contract.methods.mintNFT(window.ethereum.selectedAddress, tokenURI).encodeABI() 118 | 119 | } 120 | 121 | try { 122 | const txHash = await window.ethereum 123 | .request({ 124 | method: 'eth_sendTransaction', 125 | params: [transactionParameters], 126 | }); 127 | return { 128 | success: true, 129 | status: "✅ Check out your transaction on Etherscan: https://sepolia.etherscan.io/tx/" + txHash 130 | } 131 | } catch (error) { 132 | const errorMessage = (error as Error).message || 'Something Went wrong!' 133 | return { 134 | success: false, 135 | status: `😥 ${errorMessage}` 136 | } 137 | } 138 | } -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import { useState, useEffect } from 'react' 3 | import { connectWallet, getCurrentWalletConnected, mintNFT } from '@/app/utils/connectWallet' 4 | 5 | const Home = () => { 6 | 7 | //State variables 8 | const [walletAddress, setWalletAddress] = useState('') 9 | const [status, setStatus] = useState('') 10 | 11 | //NFT metadata state 12 | const [name, setName] = useState('') 13 | const [description, setDescription] = useState('') 14 | const [url, setUrl] = useState('') 15 | 16 | useEffect(() => { 17 | //Check if wallet is already connected 18 | const fetchData = async () => { 19 | const { address, status } = await getCurrentWalletConnected(); 20 | setWalletAddress(address); 21 | setStatus(status) 22 | } 23 | 24 | fetchData(); 25 | addWalletListener(); 26 | }, []) 27 | 28 | const connectWalletPressed = async () => { 29 | //If Metamask is installed, it returns the wallet address and current status 30 | const walletResponse = await connectWallet(); 31 | setStatus(walletResponse.status); 32 | setWalletAddress(walletResponse.address); 33 | }; 34 | 35 | const onMintPressed = async () => { 36 | //TODO: implement 37 | const { success,status } = await mintNFT({ url, name, description }); 38 | setStatus(status); 39 | if (success) { 40 | setName(""); 41 | setDescription(""); 42 | setUrl(""); 43 | } 44 | }; 45 | 46 | const addWalletListener = () => { 47 | if (window.ethereum) { 48 | window.ethereum.on("accountsChanged", (accounts: string[]) => { 49 | if (accounts.length > 0) { 50 | setWalletAddress(accounts[0]); 51 | setStatus("👆🏽 Write a message in the text-field above."); 52 | } else { 53 | setWalletAddress(""); 54 | setStatus("🦊 Connect to Metamask using the top right button. haha"); 55 | } 56 | }); 57 | } else { 58 | setStatus( 59 |

60 | {" "} 61 | 🦊{" "} 62 | 63 | You must install Metamask, a virtual Ethereum wallet, in your 64 | browser. 65 | 66 |

67 | ); 68 | } 69 | } 70 | 71 | return ( 72 |
73 |
74 | 84 | 85 |

86 |

🧙‍♂️ Alchemy NFT Minter

87 |

88 | Simply add your asset's link, name, and description, then press "Mint." 89 |

90 |
91 |

🖼 Link to asset:

92 | setUrl(e.target.value)} 97 | /> 98 |

🤔 Name:

99 | setName(e.target.value)} 104 | /> 105 |

✍️ Description:

106 | setDescription(e.target.value)} 111 | /> 112 |
113 | 116 | 117 |

118 | {status} 119 |

120 | 121 |
122 |
123 | ); 124 | } 125 | 126 | export default Home -------------------------------------------------------------------------------- /app/contract-abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [], 4 | "stateMutability": "nonpayable", 5 | "type": "constructor" 6 | }, 7 | { 8 | "anonymous": false, 9 | "inputs": [ 10 | { 11 | "indexed": true, 12 | "internalType": "address", 13 | "name": "owner", 14 | "type": "address" 15 | }, 16 | { 17 | "indexed": true, 18 | "internalType": "address", 19 | "name": "approved", 20 | "type": "address" 21 | }, 22 | { 23 | "indexed": true, 24 | "internalType": "uint256", 25 | "name": "tokenId", 26 | "type": "uint256" 27 | } 28 | ], 29 | "name": "Approval", 30 | "type": "event" 31 | }, 32 | { 33 | "anonymous": false, 34 | "inputs": [ 35 | { 36 | "indexed": true, 37 | "internalType": "address", 38 | "name": "owner", 39 | "type": "address" 40 | }, 41 | { 42 | "indexed": true, 43 | "internalType": "address", 44 | "name": "operator", 45 | "type": "address" 46 | }, 47 | { 48 | "indexed": false, 49 | "internalType": "bool", 50 | "name": "approved", 51 | "type": "bool" 52 | } 53 | ], 54 | "name": "ApprovalForAll", 55 | "type": "event" 56 | }, 57 | { 58 | "anonymous": false, 59 | "inputs": [ 60 | { 61 | "indexed": true, 62 | "internalType": "address", 63 | "name": "from", 64 | "type": "address" 65 | }, 66 | { 67 | "indexed": true, 68 | "internalType": "address", 69 | "name": "to", 70 | "type": "address" 71 | }, 72 | { 73 | "indexed": true, 74 | "internalType": "uint256", 75 | "name": "tokenId", 76 | "type": "uint256" 77 | } 78 | ], 79 | "name": "Transfer", 80 | "type": "event" 81 | }, 82 | { 83 | "inputs": [ 84 | { 85 | "internalType": "address", 86 | "name": "to", 87 | "type": "address" 88 | }, 89 | { 90 | "internalType": "uint256", 91 | "name": "tokenId", 92 | "type": "uint256" 93 | } 94 | ], 95 | "name": "approve", 96 | "outputs": [], 97 | "stateMutability": "nonpayable", 98 | "type": "function" 99 | }, 100 | { 101 | "inputs": [ 102 | { 103 | "internalType": "address", 104 | "name": "owner", 105 | "type": "address" 106 | } 107 | ], 108 | "name": "balanceOf", 109 | "outputs": [ 110 | { 111 | "internalType": "uint256", 112 | "name": "", 113 | "type": "uint256" 114 | } 115 | ], 116 | "stateMutability": "view", 117 | "type": "function" 118 | }, 119 | { 120 | "inputs": [], 121 | "name": "baseURI", 122 | "outputs": [ 123 | { 124 | "internalType": "string", 125 | "name": "", 126 | "type": "string" 127 | } 128 | ], 129 | "stateMutability": "view", 130 | "type": "function" 131 | }, 132 | { 133 | "inputs": [ 134 | { 135 | "internalType": "uint256", 136 | "name": "tokenId", 137 | "type": "uint256" 138 | } 139 | ], 140 | "name": "getApproved", 141 | "outputs": [ 142 | { 143 | "internalType": "address", 144 | "name": "", 145 | "type": "address" 146 | } 147 | ], 148 | "stateMutability": "view", 149 | "type": "function" 150 | }, 151 | { 152 | "inputs": [ 153 | { 154 | "internalType": "address", 155 | "name": "owner", 156 | "type": "address" 157 | }, 158 | { 159 | "internalType": "address", 160 | "name": "operator", 161 | "type": "address" 162 | } 163 | ], 164 | "name": "isApprovedForAll", 165 | "outputs": [ 166 | { 167 | "internalType": "bool", 168 | "name": "", 169 | "type": "bool" 170 | } 171 | ], 172 | "stateMutability": "view", 173 | "type": "function" 174 | }, 175 | { 176 | "inputs": [ 177 | { 178 | "internalType": "address", 179 | "name": "recipient", 180 | "type": "address" 181 | }, 182 | { 183 | "internalType": "string", 184 | "name": "tokenURI", 185 | "type": "string" 186 | } 187 | ], 188 | "name": "mintNFT", 189 | "outputs": [ 190 | { 191 | "internalType": "uint256", 192 | "name": "", 193 | "type": "uint256" 194 | } 195 | ], 196 | "stateMutability": "nonpayable", 197 | "type": "function" 198 | }, 199 | { 200 | "inputs": [], 201 | "name": "name", 202 | "outputs": [ 203 | { 204 | "internalType": "string", 205 | "name": "", 206 | "type": "string" 207 | } 208 | ], 209 | "stateMutability": "view", 210 | "type": "function" 211 | }, 212 | { 213 | "inputs": [ 214 | { 215 | "internalType": "uint256", 216 | "name": "tokenId", 217 | "type": "uint256" 218 | } 219 | ], 220 | "name": "ownerOf", 221 | "outputs": [ 222 | { 223 | "internalType": "address", 224 | "name": "", 225 | "type": "address" 226 | } 227 | ], 228 | "stateMutability": "view", 229 | "type": "function" 230 | }, 231 | { 232 | "inputs": [ 233 | { 234 | "internalType": "address", 235 | "name": "from", 236 | "type": "address" 237 | }, 238 | { 239 | "internalType": "address", 240 | "name": "to", 241 | "type": "address" 242 | }, 243 | { 244 | "internalType": "uint256", 245 | "name": "tokenId", 246 | "type": "uint256" 247 | } 248 | ], 249 | "name": "safeTransferFrom", 250 | "outputs": [], 251 | "stateMutability": "nonpayable", 252 | "type": "function" 253 | }, 254 | { 255 | "inputs": [ 256 | { 257 | "internalType": "address", 258 | "name": "from", 259 | "type": "address" 260 | }, 261 | { 262 | "internalType": "address", 263 | "name": "to", 264 | "type": "address" 265 | }, 266 | { 267 | "internalType": "uint256", 268 | "name": "tokenId", 269 | "type": "uint256" 270 | }, 271 | { 272 | "internalType": "bytes", 273 | "name": "_data", 274 | "type": "bytes" 275 | } 276 | ], 277 | "name": "safeTransferFrom", 278 | "outputs": [], 279 | "stateMutability": "nonpayable", 280 | "type": "function" 281 | }, 282 | { 283 | "inputs": [ 284 | { 285 | "internalType": "address", 286 | "name": "operator", 287 | "type": "address" 288 | }, 289 | { 290 | "internalType": "bool", 291 | "name": "approved", 292 | "type": "bool" 293 | } 294 | ], 295 | "name": "setApprovalForAll", 296 | "outputs": [], 297 | "stateMutability": "nonpayable", 298 | "type": "function" 299 | }, 300 | { 301 | "inputs": [ 302 | { 303 | "internalType": "bytes4", 304 | "name": "interfaceId", 305 | "type": "bytes4" 306 | } 307 | ], 308 | "name": "supportsInterface", 309 | "outputs": [ 310 | { 311 | "internalType": "bool", 312 | "name": "", 313 | "type": "bool" 314 | } 315 | ], 316 | "stateMutability": "view", 317 | "type": "function" 318 | }, 319 | { 320 | "inputs": [], 321 | "name": "symbol", 322 | "outputs": [ 323 | { 324 | "internalType": "string", 325 | "name": "", 326 | "type": "string" 327 | } 328 | ], 329 | "stateMutability": "view", 330 | "type": "function" 331 | }, 332 | { 333 | "inputs": [ 334 | { 335 | "internalType": "uint256", 336 | "name": "index", 337 | "type": "uint256" 338 | } 339 | ], 340 | "name": "tokenByIndex", 341 | "outputs": [ 342 | { 343 | "internalType": "uint256", 344 | "name": "", 345 | "type": "uint256" 346 | } 347 | ], 348 | "stateMutability": "view", 349 | "type": "function" 350 | }, 351 | { 352 | "inputs": [ 353 | { 354 | "internalType": "address", 355 | "name": "owner", 356 | "type": "address" 357 | }, 358 | { 359 | "internalType": "uint256", 360 | "name": "index", 361 | "type": "uint256" 362 | } 363 | ], 364 | "name": "tokenOfOwnerByIndex", 365 | "outputs": [ 366 | { 367 | "internalType": "uint256", 368 | "name": "", 369 | "type": "uint256" 370 | } 371 | ], 372 | "stateMutability": "view", 373 | "type": "function" 374 | }, 375 | { 376 | "inputs": [ 377 | { 378 | "internalType": "uint256", 379 | "name": "tokenId", 380 | "type": "uint256" 381 | } 382 | ], 383 | "name": "tokenURI", 384 | "outputs": [ 385 | { 386 | "internalType": "string", 387 | "name": "", 388 | "type": "string" 389 | } 390 | ], 391 | "stateMutability": "view", 392 | "type": "function" 393 | }, 394 | { 395 | "inputs": [], 396 | "name": "totalSupply", 397 | "outputs": [ 398 | { 399 | "internalType": "uint256", 400 | "name": "", 401 | "type": "uint256" 402 | } 403 | ], 404 | "stateMutability": "view", 405 | "type": "function" 406 | }, 407 | { 408 | "inputs": [ 409 | { 410 | "internalType": "address", 411 | "name": "from", 412 | "type": "address" 413 | }, 414 | { 415 | "internalType": "address", 416 | "name": "to", 417 | "type": "address" 418 | }, 419 | { 420 | "internalType": "uint256", 421 | "name": "tokenId", 422 | "type": "uint256" 423 | } 424 | ], 425 | "name": "transferFrom", 426 | "outputs": [], 427 | "stateMutability": "nonpayable", 428 | "type": "function" 429 | } 430 | ] --------------------------------------------------------------------------------