├── src ├── components │ ├── variables.css │ ├── connectButton │ │ ├── connectBotton.module.css │ │ └── connectBotton.js │ ├── mintBox │ │ ├── index.module.css │ │ ├── box.module.css │ │ ├── box.js │ │ └── index.js │ └── shared-components │ │ └── component.js ├── utils │ ├── const.js │ ├── configuration.js │ ├── util.js │ ├── wallet.js │ ├── status.js │ ├── mint.js │ └── abi.js ├── setupTests.js ├── App.test.js ├── index.css ├── reportWebVitals.js ├── index.js ├── App.css └── App.js ├── public ├── favicon.ico ├── logo192.png ├── logo512.png ├── robots.txt ├── manifest.json └── index.html ├── .gitignore ├── config-overrides.js ├── README.md ├── webpack.config.js ├── package.json └── contract └── example.sol /src/components/variables.css: -------------------------------------------------------------------------------- 1 | :root { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaozaa/MintTem/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaozaa/MintTem/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaozaa/MintTem/HEAD/public/logo512.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/components/connectButton/connectBotton.module.css: -------------------------------------------------------------------------------- 1 | .buttonWrapper{ 2 | margin: 20px; 3 | } -------------------------------------------------------------------------------- /src/utils/const.js: -------------------------------------------------------------------------------- 1 | export const MINTED_CHECK_CAP = 5000; 2 | 3 | export const GAS_INCREASE = 1.06; 4 | 5 | export const DISABLE_CHECK = false; 6 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /src/components/mintBox/index.module.css: -------------------------------------------------------------------------------- 1 | @import "../variables.css"; 2 | 3 | .homeWrapper { 4 | position: relative; 5 | display: flex; 6 | flex-direction: column; 7 | align-items: center; 8 | -webkit-box-pack: justify; 9 | -ms-flex-pack: justify; 10 | height: 100vh; 11 | background-position: center; 12 | } 13 | -------------------------------------------------------------------------------- /.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 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /src/utils/configuration.js: -------------------------------------------------------------------------------- 1 | // Set smart contract based on chain id 2 | export const CONTRACTADDRESS = { 3 | 1: "", 4 | 4: "", 5 | }; 6 | 7 | // Set the price of your NFT 8 | export const PRICE = 0; 9 | 10 | // Set the max amount of your NFT collection 11 | export const MAX_AMOUNT = 1000; 12 | 13 | // Set the max batch size per wallet 14 | export const MAX_BATCH_SIZE = 9; 15 | 16 | // Set the opesnea url 17 | export const OPENSEA_URL = "https://opensea.io/collection/" -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | import reportWebVitals from "./reportWebVitals"; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById("root") 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Catch On Lab", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/util.js: -------------------------------------------------------------------------------- 1 | export const formatWalletAddress = (address) => { 2 | return address && `${address.substring(0, 4)}...${address.substring(40)}`; 3 | }; 4 | 5 | export const formatTxn = (txn) => { 6 | return txn && `${txn.substring(0, 4)}...${txn.substring(62)}`; 7 | }; 8 | 9 | export const transactionHostURL = (chainId) => { 10 | let host = "https://etherscan.io/tx"; 11 | switch (chainId) { 12 | case 4: 13 | host = "https://rinkeby.etherscan.io/tx"; 14 | break; 15 | case 5: 16 | host = "https://goerli.etherscan.io/tx"; 17 | break; 18 | default: 19 | break; 20 | 21 | } 22 | return host; 23 | }; 24 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | 2 | .App { 3 | text-align: center; 4 | } 5 | 6 | .App-logo { 7 | height: 40vmin; 8 | pointer-events: none; 9 | } 10 | 11 | @media (prefers-reduced-motion: no-preference) { 12 | .App-logo { 13 | animation: App-logo-spin infinite 20s linear; 14 | } 15 | } 16 | 17 | .App-header { 18 | background-color: #282c34; 19 | min-height: 100vh; 20 | display: flex; 21 | flex-direction: column; 22 | align-items: center; 23 | justify-content: center; 24 | font-size: calc(10px + 2vmin); 25 | color: white; 26 | } 27 | 28 | .App-link { 29 | color: #61dafb; 30 | } 31 | 32 | @keyframes App-logo-spin { 33 | from { 34 | transform: rotate(0deg); 35 | } 36 | to { 37 | transform: rotate(360deg); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/components/connectButton/connectBotton.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Button from '@mui/material/Button'; 3 | import { onConnect } from "../../utils/wallet"; 4 | import { formatWalletAddress } from "../../utils/util"; 5 | 6 | import styles from "./connectBotton.module.css"; 7 | 8 | export const ConnectButton = ({ data }) => { 9 | return ( 10 |
11 | {data.state.address ? ( 12 | Connected: {formatWalletAddress(data.state.address)} 13 | ) : ( 14 | 19 | )} 20 |
21 | ) 22 | } -------------------------------------------------------------------------------- /config-overrides.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | 3 | module.exports = function override(config) { 4 | const fallback = config.resolve.fallback || {}; 5 | Object.assign(fallback, { 6 | "crypto": require.resolve("crypto-browserify"), 7 | "stream": require.resolve("stream-browserify"), 8 | "assert": require.resolve("assert"), 9 | "http": require.resolve("stream-http"), 10 | "https": require.resolve("https-browserify"), 11 | "os": require.resolve("os-browserify"), 12 | "url": require.resolve("url") 13 | }) 14 | config.ignoreWarnings = [/Failed to parse source map/]; 15 | config.resolve.fallback = fallback; 16 | config.plugins = (config.plugins || []).concat([ 17 | new webpack.ProvidePlugin({ 18 | process: 'process/browser', 19 | Buffer: ['buffer', 'Buffer'] 20 | }) 21 | ]) 22 | return config; 23 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Installment 6 | 7 | git clone this project 8 | 9 | ### `npm i` 10 | 11 | ## Smart contract Configuration 12 | 13 | copy contract/example.sol to REMIX 14 | 15 | deploy the example contract 16 | 17 | Copy the contract address 18 | 19 | npx hardhat verify --constructor-args argument.js 20 | 21 | ## Website configuration 22 | 23 | In the project directory ./src/utils/configuration.js, you need to change assign the correct contract address based on chain id: 24 | 25 | configuare all options based on comments 26 | 27 | Then you can run to launch the website server. 28 | 29 | ### `npm start` 30 | 31 | Runs the app in the development mode.\ 32 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser. 33 | 34 | The page will reload when you make changes.\ 35 | You may also see any lint errors in the console. 36 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; 2 | import { Home } from "./components/mintBox"; 3 | import React from "react"; 4 | 5 | class App extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | provider: {}, 10 | web3: {}, 11 | account: {}, 12 | count: 1, 13 | mintType: "", 14 | mintTransaction: "", 15 | pendingRequest: false, 16 | connected: false, 17 | txn: "", 18 | finish: false, 19 | mintedNum: 0, 20 | mintableNum: -1, 21 | tier: 0, 22 | address: "", 23 | containedModalShow: "", 24 | modalDialogTitle: "", 25 | modalDialogBodyText: "", 26 | modalDialogBodyHref: "", 27 | mintErrorMsg: "", 28 | }; 29 | this.web3Modal = {}; 30 | } 31 | render() { 32 | return ( 33 | 34 | 35 | } /> 36 | 37 | 38 | ); 39 | } 40 | } 41 | 42 | export default App; 43 | -------------------------------------------------------------------------------- /src/components/mintBox/box.module.css: -------------------------------------------------------------------------------- 1 | .boxWrapper { 2 | width: 50%; 3 | letter-spacing: 1px; 4 | display: -webkit-box; 5 | display: -webkit-flex; 6 | display: -ms-flexbox; 7 | display: flex; 8 | -webkit-box-orient: vertical; 9 | -webkit-box-direction: normal; 10 | -webkit-flex-direction: column; 11 | -ms-flex-direction: column; 12 | flex-direction: column; 13 | -webkit-justify-content: space-around; 14 | -ms-flex-pack: distribute; 15 | justify-content: space-around; 16 | -webkit-box-align: center; 17 | -webkit-align-items: center; 18 | -ms-flex-align: center; 19 | align-items: center; 20 | padding: 5%; 21 | margin: 5%; 22 | background-color: gainsboro; 23 | } 24 | 25 | .boxWrapperTop, 26 | .boxWrapperBottom { 27 | height: 20%; 28 | display: flex; 29 | flex-direction: column; 30 | justify-content: center; 31 | align-items: center; 32 | font-size: large; 33 | gap: 10px; 34 | } 35 | 36 | .boxWrapperTopContent { 37 | display: flex; 38 | flex-direction: row; 39 | justify-content: center; 40 | align-items: center; 41 | gap: 20px; 42 | } 43 | 44 | .boxWrapperTop span { 45 | color: black; 46 | font-size: x-large; 47 | } 48 | 49 | -------------------------------------------------------------------------------- /src/components/shared-components/component.js: -------------------------------------------------------------------------------- 1 | import CircularProgress from '@mui/material/CircularProgress'; 2 | import DialogTitle from "@mui/material/DialogTitle"; 3 | import Dialog from "@mui/material/Dialog"; 4 | import Card from '@mui/material/Card'; 5 | import Typography from '@mui/material/Typography'; 6 | import CardContent from '@mui/material/CardContent'; 7 | 8 | import { formatTxn } from "../../utils/util"; 9 | 10 | export const ModalDialog = (props) => { 11 | const { onHide } = props; 12 | const handleClose = () => { 13 | onHide(); 14 | }; 15 | return ( 16 |
17 | 21 | Claiming NFT 22 | 23 | 24 | 25 | {props.showPendingIcons && ( 26 | 27 | )} 28 |

{props.bodyText}

29 | {props.bodyHref ? ( 30 | 31 | Txn Details:{" "} 32 | 37 | {formatTxn(props.bodyTxn)} 38 | 39 | 40 | ) : ( 41 | "" 42 | )} 43 |
44 |
45 |
46 |
47 |
48 | ); 49 | }; 50 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const HtmlWebPackPlugin = require("html-webpack-plugin"); 3 | const webpack = require("webpack"); 4 | 5 | module.exports = { 6 | entry: "/src/index.js", 7 | output: { 8 | path: path.resolve(__dirname, "dist"), 9 | libraryTarget: "umd", 10 | umdNamedDefine: true, 11 | publicPath: "/", 12 | }, 13 | devServer: { 14 | historyApiFallback: true, 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.css$/, 20 | use: ["style-loader", "css-loader"], 21 | }, 22 | { 23 | test: /node_modules\/@0x/, 24 | use: { loader: "umd-compat-loader" }, 25 | }, 26 | { 27 | test: /\.(js|jsx)$/, 28 | exclude: /node_modules/, 29 | use: { 30 | loader: "babel-loader", 31 | options: { 32 | presets: ["@babel/preset-env", "@babel/preset-react"], 33 | }, 34 | }, 35 | }, 36 | ], 37 | }, 38 | plugins: [ 39 | new HtmlWebPackPlugin({ 40 | template: "./src/index.html", 41 | favicon: "./src/images/logo.png", 42 | }), 43 | new webpack.ProvidePlugin({ 44 | Buffer: ["buffer", "Buffer"], 45 | process: "process/browser", 46 | }), 47 | new webpack.DefinePlugin({}), 48 | ], 49 | resolve: { 50 | fallback: { 51 | "https": false, 52 | os: require.resolve("os-browserify/browser"), 53 | "http": false, 54 | stream: require.resolve("stream-browserify"), 55 | buffer: require.resolve("buffer/"), 56 | "crypto": false, 57 | }, 58 | }, 59 | }; 60 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 25 | Catchon Labs 26 | 27 | 28 | 29 | 30 |
31 |
32 |
33 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mint_helper", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@emotion/react": "^11.7.1", 7 | "@emotion/styled": "^11.6.0", 8 | "@ethersproject/providers": "^5.5.3", 9 | "@mui/material": "^5.4.1", 10 | "@mui/styles": "^5.5.3", 11 | "@testing-library/jest-dom": "^5.16.2", 12 | "@testing-library/react": "^12.1.2", 13 | "@testing-library/user-event": "^13.5.0", 14 | "@walletconnect/web3-provider": "^1.7.1", 15 | "@web3-react/core": "^6.1.9", 16 | "@web3-react/injected-connector": "^6.0.7", 17 | "bootstrap": "^5.1.3", 18 | "ethers": "^5.5.4", 19 | "html-webpack-plugin": "^5.5.0", 20 | "path": "^0.12.7", 21 | "react": "^17.0.2", 22 | "react-bootstrap": "^2.1.2", 23 | "react-dom": "^17.0.2", 24 | "react-router-dom": "^6.2.1", 25 | "react-router-hash-link": "^2.4.3", 26 | "react-scripts": "5.0.0", 27 | "walletlink": "^2.4.7", 28 | "web-vitals": "^2.1.4", 29 | "web3": "^1.7.0", 30 | "web3modal": "^1.9.5", 31 | "webpack": "^5.68.0" 32 | }, 33 | "scripts": { 34 | "start": "react-app-rewired start", 35 | "build": "react-app-rewired build", 36 | "test": "react-app-rewired test", 37 | "eject": "react-scripts eject" 38 | }, 39 | "eslintConfig": { 40 | "extends": [ 41 | "react-app", 42 | "react-app/jest" 43 | ] 44 | }, 45 | "browserslist": { 46 | "production": [ 47 | ">0.2%", 48 | "not dead", 49 | "not op_mini all" 50 | ], 51 | "development": [ 52 | "last 1 chrome version", 53 | "last 1 firefox version", 54 | "last 1 safari version" 55 | ] 56 | }, 57 | "devDependencies": { 58 | "assert": "^2.0.0", 59 | "buffer": "^6.0.3", 60 | "crypto-browserify": "^3.12.0", 61 | "https-browserify": "^1.0.0", 62 | "os-browserify": "^0.3.0", 63 | "process": "^0.11.10", 64 | "react-app-rewired": "^2.1.11", 65 | "react-icons": "^4.3.1", 66 | "stream-browserify": "^3.0.0", 67 | "stream-http": "^3.2.0", 68 | "url": "^0.11.0" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/utils/wallet.js: -------------------------------------------------------------------------------- 1 | import Web3 from "web3"; 2 | import { getMintStatus, getMintedRecur } from "./status"; 3 | 4 | /** 5 | * Kick in the UI action after Web3modal dialog has chosen a provider 6 | */ 7 | 8 | export const onConnect = async (data) => { 9 | 10 | const provider = await data.web3Modal.connect(); 11 | 12 | await subscribeProvider(provider, data); 13 | 14 | await provider.enable(); 15 | 16 | const web3 = new Web3(provider); 17 | 18 | const accounts = await web3.eth.getAccounts(); 19 | 20 | const address = accounts[0]; 21 | 22 | const networkId = await web3.eth.net.getId(); 23 | 24 | const chainId = await web3.eth.getChainId(); 25 | 26 | await data.setState({ 27 | web3, 28 | provider, 29 | connected: true, 30 | address, 31 | chainId, 32 | networkId, 33 | count: 1, 34 | mintTransaction: "", 35 | pendingRequest: false, 36 | txn: "", 37 | finish: false, 38 | mintableNum: -1, 39 | tier: 0, 40 | modalDialogTitle: "", 41 | modalDialogBodyText: "", 42 | containedModalShow: "", 43 | mintErrorMsg: "", 44 | }); 45 | var mintType = await getMintStatus(data); 46 | if (mintType) { 47 | data.setState({ 48 | mintType: mintType, 49 | }); 50 | } 51 | getMintedRecur(data); 52 | }; 53 | 54 | const subscribeProvider = async (provider, data) => { 55 | if (!provider.on) { 56 | return; 57 | } 58 | provider.on("close", () => data.state.resetApp()); 59 | provider.on("accountsChanged", async (accounts) => { 60 | await data.setState({ 61 | address: accounts[0], 62 | mintableNum: -1, 63 | tier: 0 64 | }); 65 | }); 66 | provider.on("chainChanged", async (chainId) => { 67 | const { web3 } = data.state; 68 | const networkId = await web3.eth.net.getId(); 69 | await data.setState({ 70 | chainId, networkId, 71 | mintType: "" 72 | }); 73 | }); 74 | 75 | provider.on("networkChanged", async (networkId) => { 76 | const { web3 } = data.state; 77 | const chainId = await web3.eth.chainId(); 78 | await data.setState({ 79 | chainId, networkId, 80 | mintType: "" 81 | }); 82 | }); 83 | }; 84 | 85 | -------------------------------------------------------------------------------- /src/utils/status.js: -------------------------------------------------------------------------------- 1 | import { abi } from "./abi"; 2 | import { 3 | MINTED_CHECK_CAP, 4 | } from "./const"; 5 | import { 6 | CONTRACTADDRESS, 7 | } from "./configuration"; 8 | 9 | var interId; 10 | 11 | export const getMintedRecur = async (data) => { 12 | try { 13 | var mintType = await getMintStatus(data); 14 | if (mintType) { 15 | getMinted(data); 16 | } 17 | if (interId) { 18 | clearInterval(interId); 19 | } else { 20 | interId = setInterval(async function () { 21 | var mintType = await getMintStatus(data); 22 | if (mintType) { 23 | getMinted(data); 24 | data.setState({ 25 | mintType 26 | }) 27 | } 28 | }, MINTED_CHECK_CAP); 29 | } 30 | } catch (err) { 31 | console.error(err.message); 32 | } 33 | }; 34 | 35 | export const getMintStatus = async (data) => { 36 | if (!data.state.web3) { 37 | throw new Error("Error: Please connect correct wallet."); 38 | } else { 39 | const web3 = data.state.web3; 40 | const chainId = Number(data.state.chainId); 41 | const targetContract = CONTRACTADDRESS[chainId]; 42 | var contract = new web3.eth.Contract(abi, targetContract); 43 | 44 | var isPublic = false; 45 | try { 46 | isPublic = await contract.methods.isPublicSaleOn().call((err, result) => { 47 | if (err) { 48 | return false; 49 | } 50 | return result; 51 | }); 52 | } catch (err) { 53 | isPublic = false; 54 | } 55 | if (isPublic) { 56 | return "public"; 57 | } else { 58 | return ""; 59 | } 60 | } 61 | }; 62 | 63 | export const getMinted = async (data) => { 64 | if (!data.state.web3) { 65 | throw new Error("Error: Please connect correct wallet."); 66 | } else { 67 | const web3 = data.state.web3; 68 | const chainId = data.state.chainId; 69 | const targetContract = CONTRACTADDRESS[chainId]; 70 | var contract = new web3.eth.Contract(abi, targetContract); 71 | contract.methods.totalSupply().call((err, result) => { 72 | if (err) { 73 | console.error("Error: ", err); 74 | } 75 | data.setState({ 76 | mintedNum: result, 77 | }); 78 | }); 79 | } 80 | }; 81 | -------------------------------------------------------------------------------- /src/components/mintBox/box.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { MintTransaction } from "../../utils/mint"; 3 | import styles from "./box.module.css"; 4 | import { MAX_AMOUNT, OPENSEA_URL, MAX_BATCH_SIZE } from "../../utils/configuration"; 5 | import { Button } from "@mui/material"; 6 | import Select, { SelectChangeEvent } from '@mui/material/Select'; 7 | import InputLabel from '@mui/material/InputLabel'; 8 | import MenuItem from '@mui/material/MenuItem'; 9 | import FormControl from '@mui/material/FormControl'; 10 | import Box from '@mui/material/Box'; 11 | 12 | 13 | export const MintBox = ({ data }) => { 14 | const [amount, setAmount] = React.useState(''); 15 | const handleChange = (event: SelectChangeEvent) => { 16 | setAmount(event.target.value); 17 | data.setState({ 18 | count: parseInt(event.target.value) 19 | }) 20 | }; 21 | const menuList = Array.from({ length: MAX_BATCH_SIZE }, (_, i) => i + 1); 22 | return ( 23 |
24 | {(data.state.mintedNum >= MAX_AMOUNT) ? ( 25 |
26 |

27 | Sold out! 28 | 29 | Opensea{" "} 30 | {" "} 31 |

32 |
) 33 | : 34 | (
35 |
36 | 37 | 38 | 39 | 49 | 50 | 51 | 59 |
60 |

{data.state.mintedNum}/{MAX_AMOUNT} Minted

61 |
)} 62 |
63 | ); 64 | }; 65 | -------------------------------------------------------------------------------- /src/components/mintBox/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | 3 | import { onConnect } from "../../utils/wallet"; 4 | import { ConnectButton } from "../connectButton/connectBotton"; 5 | import { MintBox } from "./box"; 6 | 7 | import Web3Modal from "web3modal"; 8 | import WalletConnectProvider from "@walletconnect/web3-provider"; 9 | import WalletLink from "walletlink"; 10 | import { ModalDialog } from "../shared-components/component"; 11 | 12 | import styles from "./index.module.css"; 13 | 14 | const providerOptions = { 15 | walletlink: { 16 | package: WalletLink, // Required 17 | options: { 18 | appName: "My Awesome App", // Required 19 | infuraId: "INFURA_ID", // Required unless you provide a JSON RPC url; see `rpc` below 20 | rpc: "", // Optional if `infuraId` is provided; otherwise it's required 21 | chainId: 1, // Optional. It defaults to 1 if not provided 22 | appLogoUrl: null, // Optional. Application logo image URL. favicon is used if unspecified 23 | darkMode: false, // Optional. Use dark theme, defaults to false 24 | }, 25 | }, 26 | walletconnect: { 27 | display: { 28 | name: "Mobile", 29 | }, 30 | package: WalletConnectProvider, 31 | options: { 32 | infuraId: "INFURA_ID", // required 33 | }, 34 | }, 35 | }; 36 | 37 | export const Home = ({ data }) => { 38 | data.web3Modal = new Web3Modal({ 39 | network: "mainnet", 40 | cacheProvider: true, 41 | providerOptions: providerOptions, 42 | }); 43 | 44 | useEffect(() => { 45 | (async () => { 46 | if (localStorage.getItem("WEB3_CONNECT_CACHED_PROVIDER")) 47 | await onConnect(data); 48 | })(); 49 | }, [data]); 50 | 51 | return ( 52 |
53 | 54 | 55 | {data.state.containedModalShow && ( 56 | { 68 | data.setState({ 69 | containedModalShow: false, 70 | modalDialogTitle: "", 71 | modalDialogBodyText: "", 72 | pendingRequest: false, 73 | mintErrorMsg: "", 74 | modalDialogBodyHref: "", 75 | }); 76 | }} 77 | state={data.state} 78 | /> 79 | )} 80 |
81 | ); 82 | }; 83 | -------------------------------------------------------------------------------- /src/utils/mint.js: -------------------------------------------------------------------------------- 1 | import { abi } from "./abi"; 2 | import { 3 | GAS_INCREASE, 4 | } from "./const"; 5 | import { 6 | CONTRACTADDRESS, 7 | PRICE, 8 | } from "./configuration"; 9 | import { getMintStatus } from "./status"; 10 | 11 | import { transactionHostURL } from "./util"; 12 | 13 | const formatMintTransaction = async (data) => { 14 | const web3 = data.state.web3; 15 | const address = data.state.address; 16 | const chainId = data.state.chainId; 17 | const count = data.state.count; 18 | const targetContract = CONTRACTADDRESS[chainId]; 19 | var contract = new web3.eth.Contract(abi, targetContract); 20 | 21 | let extraData; 22 | var mintType = await getMintStatus(data); 23 | if (!mintType) { 24 | mintType = data.state.mintType; 25 | } 26 | if (mintType === "public") { 27 | extraData = await contract.methods.mint(count); 28 | } else { 29 | throw new Error("Sales is not start yet."); 30 | } 31 | let input = extraData.encodeABI(); 32 | const finalPrice = count * PRICE; 33 | const estimatedGas = await web3.eth.estimateGas({ 34 | from: address, 35 | data: input, 36 | to: targetContract, 37 | value: web3.utils.toWei(finalPrice.toString(), "ether"), 38 | }); 39 | const nonce = await web3.eth.getTransactionCount(address, "latest"); 40 | return { 41 | gas: parseInt(estimatedGas * GAS_INCREASE), 42 | to: targetContract, 43 | from: address, 44 | value: web3.utils.toWei(finalPrice.toString(), "ether"), 45 | data: web3.utils.toHex(input), 46 | nonce, 47 | }; 48 | }; 49 | 50 | export const MintTransaction = async (data) => { 51 | if (!data.state.web3) { 52 | return; 53 | } 54 | 55 | if (data.state.mintErrorMsg) { 56 | data.setState({ 57 | mintErrorMsg: "", 58 | }); 59 | } 60 | 61 | try { 62 | const tx = await formatMintTransaction(data); 63 | function sendTransaction(_tx) { 64 | return new Promise((resolve, reject) => { 65 | data.state.web3.eth 66 | .sendTransaction(_tx) 67 | .once("transactionHash", (txHash) => resolve(txHash)) 68 | .catch((err) => reject(err)); 69 | }); 70 | } 71 | const result = await sendTransaction(tx); 72 | data.setState({ 73 | txn: result, 74 | pendingRequest: true, 75 | modalDialogBodyHref: `${transactionHostURL( 76 | data.state.chainId 77 | )}/${result}`, 78 | modalDialogBodyText: 79 | "You have approved the transaction, please don't close the dialog until your transaction completes successfully.", 80 | }); 81 | const interval = setInterval(function () { 82 | data.state.web3.eth.getTransactionReceipt(result, function (err, rec) { 83 | if (rec) { 84 | clearInterval(interval); 85 | data.setState({ 86 | finish: true, 87 | pendingRequest: false, 88 | modalDialogBodyHref: `${transactionHostURL( 89 | data.state.chainId 90 | )}/${result}`, 91 | modalDialogBodyText: `Successfully minted NFT!`, 92 | }); 93 | } 94 | }); 95 | }, 1000); 96 | } catch (error) { 97 | console.error("CATCHERROR", error.message); 98 | var myRe = /{.*}/g; 99 | var str = error.message.replace(/(\r\n|\n|\r)/gm, ""); 100 | var errArray = myRe.exec(str); 101 | if (errArray && errArray.length > 0) { 102 | if (JSON.parse(errArray[0]).originalError.message) { 103 | const errorDetails = JSON.parse(errArray[0]).originalError.message; 104 | data.setState({ 105 | mintErrorMsg: errorDetails, 106 | }); 107 | } else { 108 | data.setState({ 109 | mintErrorMsg: error.message, 110 | }); 111 | } 112 | } else { 113 | data.setState({ 114 | mintErrorMsg: error.message, 115 | }); 116 | } 117 | } 118 | }; 119 | 120 | -------------------------------------------------------------------------------- /contract/example.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >= 0.8.9 < 0.9.0; 4 | 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | import "erc721a/contracts/ERC721A.sol"; 7 | import "@openzeppelin/contracts/utils/Strings.sol"; 8 | 9 | error AddressNotAllowlistVerified(); 10 | 11 | contract Example is Ownable, ERC721A { 12 | uint256 public immutable maxPerAddressDuringMint; 13 | uint256 public immutable collectionSize; 14 | uint256 public immutable amountForDevs; 15 | 16 | 17 | struct SaleConfig { 18 | uint32 publicSaleStartTime; 19 | uint64 publicPriceWei; 20 | } 21 | 22 | 23 | SaleConfig public saleConfig; 24 | 25 | // metadata URI 26 | string private _baseTokenURI; 27 | 28 | constructor( 29 | uint256 maxBatchSize_, 30 | uint256 collectionSize_, 31 | uint256 amountForDevs_ 32 | ) ERC721A("Catchon", "CATCHON") { 33 | require( 34 | maxBatchSize_ < collectionSize_, 35 | "MaxBarchSize should be smaller than collectionSize" 36 | ); 37 | maxPerAddressDuringMint = maxBatchSize_; 38 | collectionSize = collectionSize_; 39 | amountForDevs = amountForDevs_; 40 | } 41 | 42 | modifier callerIsUser() { 43 | require(tx.origin == msg.sender, "The caller is another contract"); 44 | _; 45 | } 46 | 47 | function devMint(uint256 quantity) external onlyOwner { 48 | require( 49 | quantity <= amountForDevs, 50 | "Too many already minted before dev mint" 51 | ); 52 | require( 53 | totalSupply() + quantity <= collectionSize, 54 | "Reached max supply" 55 | ); 56 | _safeMint(msg.sender, quantity); 57 | } 58 | // Public Mint 59 | // ***************************************************************************** 60 | // Public Functions 61 | function mint(uint256 quantity) 62 | external 63 | payable 64 | callerIsUser 65 | { 66 | require(isPublicSaleOn(), "Public sale has not begun yet"); 67 | require( 68 | totalSupply() + quantity <= collectionSize, 69 | "Reached max supply" 70 | ); 71 | require( 72 | numberMinted(msg.sender) + quantity <= maxPerAddressDuringMint, 73 | "Reached max quantity that one wallet can mint" 74 | ); 75 | uint256 priceWei = quantity * saleConfig.publicPriceWei; 76 | 77 | _safeMint(msg.sender, quantity); 78 | refundIfOver(priceWei); 79 | } 80 | 81 | function isPublicSaleOn() public view returns(bool) { 82 | require( 83 | saleConfig.publicSaleStartTime != 0, 84 | "Public Sale Time is TBD." 85 | ); 86 | 87 | return block.timestamp >= saleConfig.publicSaleStartTime; 88 | } 89 | 90 | // Owner Controls 91 | 92 | // Public Views 93 | // ***************************************************************************** 94 | function numberMinted(address minter) public view returns(uint256) { 95 | return _numberMinted(minter); 96 | } 97 | 98 | // Contract Controls (onlyOwner) 99 | // ***************************************************************************** 100 | function setBaseURI(string calldata baseURI) external onlyOwner { 101 | _baseTokenURI = baseURI; 102 | } 103 | 104 | function withdrawMoney() external onlyOwner { 105 | (bool success, ) = msg.sender.call{ value: address(this).balance } (""); 106 | require(success, "Transfer failed."); 107 | } 108 | 109 | function setupNonAuctionSaleInfo( 110 | uint64 publicPriceWei, 111 | uint32 publicSaleStartTime 112 | ) public onlyOwner { 113 | saleConfig = SaleConfig( 114 | publicSaleStartTime, 115 | publicPriceWei 116 | ); 117 | } 118 | 119 | // Internal Functions 120 | // ***************************************************************************** 121 | 122 | function refundIfOver(uint256 price) internal { 123 | require(msg.value >= price, "Need to send more ETH."); 124 | if (msg.value > price) { 125 | payable(msg.sender).transfer(msg.value - price); 126 | } 127 | } 128 | 129 | function _baseURI() internal view virtual override returns(string memory) { 130 | return _baseTokenURI; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/utils/abi.js: -------------------------------------------------------------------------------- 1 | export const abi = [ 2 | { 3 | "inputs": [ 4 | { 5 | "internalType": "uint256", 6 | "name": "maxBatchSize_", 7 | "type": "uint256" 8 | }, 9 | { 10 | "internalType": "uint256", 11 | "name": "collectionSize_", 12 | "type": "uint256" 13 | } 14 | ], 15 | "stateMutability": "nonpayable", 16 | "type": "constructor" 17 | }, 18 | { 19 | "inputs": [], 20 | "name": "ApprovalCallerNotOwnerNorApproved", 21 | "type": "error" 22 | }, 23 | { 24 | "inputs": [], 25 | "name": "ApprovalQueryForNonexistentToken", 26 | "type": "error" 27 | }, 28 | { 29 | "inputs": [], 30 | "name": "ApprovalToCurrentOwner", 31 | "type": "error" 32 | }, 33 | { 34 | "inputs": [ 35 | { 36 | "internalType": "address", 37 | "name": "to", 38 | "type": "address" 39 | }, 40 | { 41 | "internalType": "uint256", 42 | "name": "tokenId", 43 | "type": "uint256" 44 | } 45 | ], 46 | "name": "approve", 47 | "outputs": [], 48 | "stateMutability": "nonpayable", 49 | "type": "function" 50 | }, 51 | { 52 | "inputs": [], 53 | "name": "ApproveToCaller", 54 | "type": "error" 55 | }, 56 | { 57 | "inputs": [], 58 | "name": "BalanceQueryForZeroAddress", 59 | "type": "error" 60 | }, 61 | { 62 | "inputs": [ 63 | { 64 | "internalType": "uint256", 65 | "name": "quantity", 66 | "type": "uint256" 67 | } 68 | ], 69 | "name": "mint", 70 | "outputs": [], 71 | "stateMutability": "payable", 72 | "type": "function" 73 | }, 74 | { 75 | "inputs": [], 76 | "name": "MintToZeroAddress", 77 | "type": "error" 78 | }, 79 | { 80 | "inputs": [], 81 | "name": "MintZeroQuantity", 82 | "type": "error" 83 | }, 84 | { 85 | "inputs": [], 86 | "name": "OwnerQueryForNonexistentToken", 87 | "type": "error" 88 | }, 89 | { 90 | "inputs": [], 91 | "name": "renounceOwnership", 92 | "outputs": [], 93 | "stateMutability": "nonpayable", 94 | "type": "function" 95 | }, 96 | { 97 | "inputs": [ 98 | { 99 | "internalType": "address", 100 | "name": "from", 101 | "type": "address" 102 | }, 103 | { 104 | "internalType": "address", 105 | "name": "to", 106 | "type": "address" 107 | }, 108 | { 109 | "internalType": "uint256", 110 | "name": "tokenId", 111 | "type": "uint256" 112 | } 113 | ], 114 | "name": "safeTransferFrom", 115 | "outputs": [], 116 | "stateMutability": "nonpayable", 117 | "type": "function" 118 | }, 119 | { 120 | "inputs": [ 121 | { 122 | "internalType": "address", 123 | "name": "from", 124 | "type": "address" 125 | }, 126 | { 127 | "internalType": "address", 128 | "name": "to", 129 | "type": "address" 130 | }, 131 | { 132 | "internalType": "uint256", 133 | "name": "tokenId", 134 | "type": "uint256" 135 | }, 136 | { 137 | "internalType": "bytes", 138 | "name": "_data", 139 | "type": "bytes" 140 | } 141 | ], 142 | "name": "safeTransferFrom", 143 | "outputs": [], 144 | "stateMutability": "nonpayable", 145 | "type": "function" 146 | }, 147 | { 148 | "inputs": [ 149 | { 150 | "internalType": "address", 151 | "name": "operator", 152 | "type": "address" 153 | }, 154 | { 155 | "internalType": "bool", 156 | "name": "approved", 157 | "type": "bool" 158 | } 159 | ], 160 | "name": "setApprovalForAll", 161 | "outputs": [], 162 | "stateMutability": "nonpayable", 163 | "type": "function" 164 | }, 165 | { 166 | "inputs": [ 167 | { 168 | "internalType": "string", 169 | "name": "baseURI", 170 | "type": "string" 171 | } 172 | ], 173 | "name": "setBaseURI", 174 | "outputs": [], 175 | "stateMutability": "nonpayable", 176 | "type": "function" 177 | }, 178 | { 179 | "inputs": [ 180 | { 181 | "internalType": "uint32", 182 | "name": "publicSaleStartTime", 183 | "type": "uint32" 184 | } 185 | ], 186 | "name": "setupNonAuctionSaleInfo", 187 | "outputs": [], 188 | "stateMutability": "nonpayable", 189 | "type": "function" 190 | }, 191 | { 192 | "inputs": [], 193 | "name": "TransferCallerNotOwnerNorApproved", 194 | "type": "error" 195 | }, 196 | { 197 | "inputs": [ 198 | { 199 | "internalType": "address", 200 | "name": "from", 201 | "type": "address" 202 | }, 203 | { 204 | "internalType": "address", 205 | "name": "to", 206 | "type": "address" 207 | }, 208 | { 209 | "internalType": "uint256", 210 | "name": "tokenId", 211 | "type": "uint256" 212 | } 213 | ], 214 | "name": "transferFrom", 215 | "outputs": [], 216 | "stateMutability": "nonpayable", 217 | "type": "function" 218 | }, 219 | { 220 | "inputs": [], 221 | "name": "TransferFromIncorrectOwner", 222 | "type": "error" 223 | }, 224 | { 225 | "inputs": [ 226 | { 227 | "internalType": "address", 228 | "name": "newOwner", 229 | "type": "address" 230 | } 231 | ], 232 | "name": "transferOwnership", 233 | "outputs": [], 234 | "stateMutability": "nonpayable", 235 | "type": "function" 236 | }, 237 | { 238 | "inputs": [], 239 | "name": "TransferToNonERC721ReceiverImplementer", 240 | "type": "error" 241 | }, 242 | { 243 | "inputs": [], 244 | "name": "TransferToZeroAddress", 245 | "type": "error" 246 | }, 247 | { 248 | "inputs": [], 249 | "name": "URIQueryForNonexistentToken", 250 | "type": "error" 251 | }, 252 | { 253 | "anonymous": false, 254 | "inputs": [ 255 | { 256 | "indexed": true, 257 | "internalType": "address", 258 | "name": "owner", 259 | "type": "address" 260 | }, 261 | { 262 | "indexed": true, 263 | "internalType": "address", 264 | "name": "approved", 265 | "type": "address" 266 | }, 267 | { 268 | "indexed": true, 269 | "internalType": "uint256", 270 | "name": "tokenId", 271 | "type": "uint256" 272 | } 273 | ], 274 | "name": "Approval", 275 | "type": "event" 276 | }, 277 | { 278 | "anonymous": false, 279 | "inputs": [ 280 | { 281 | "indexed": true, 282 | "internalType": "address", 283 | "name": "owner", 284 | "type": "address" 285 | }, 286 | { 287 | "indexed": true, 288 | "internalType": "address", 289 | "name": "operator", 290 | "type": "address" 291 | }, 292 | { 293 | "indexed": false, 294 | "internalType": "bool", 295 | "name": "approved", 296 | "type": "bool" 297 | } 298 | ], 299 | "name": "ApprovalForAll", 300 | "type": "event" 301 | }, 302 | { 303 | "anonymous": false, 304 | "inputs": [ 305 | { 306 | "indexed": true, 307 | "internalType": "address", 308 | "name": "previousOwner", 309 | "type": "address" 310 | }, 311 | { 312 | "indexed": true, 313 | "internalType": "address", 314 | "name": "newOwner", 315 | "type": "address" 316 | } 317 | ], 318 | "name": "OwnershipTransferred", 319 | "type": "event" 320 | }, 321 | { 322 | "anonymous": false, 323 | "inputs": [ 324 | { 325 | "indexed": true, 326 | "internalType": "address", 327 | "name": "from", 328 | "type": "address" 329 | }, 330 | { 331 | "indexed": true, 332 | "internalType": "address", 333 | "name": "to", 334 | "type": "address" 335 | }, 336 | { 337 | "indexed": true, 338 | "internalType": "uint256", 339 | "name": "tokenId", 340 | "type": "uint256" 341 | } 342 | ], 343 | "name": "Transfer", 344 | "type": "event" 345 | }, 346 | { 347 | "inputs": [], 348 | "name": "withdrawMoney", 349 | "outputs": [], 350 | "stateMutability": "nonpayable", 351 | "type": "function" 352 | }, 353 | { 354 | "inputs": [ 355 | { 356 | "internalType": "address", 357 | "name": "owner", 358 | "type": "address" 359 | } 360 | ], 361 | "name": "balanceOf", 362 | "outputs": [ 363 | { 364 | "internalType": "uint256", 365 | "name": "", 366 | "type": "uint256" 367 | } 368 | ], 369 | "stateMutability": "view", 370 | "type": "function" 371 | }, 372 | { 373 | "inputs": [], 374 | "name": "collectionSize", 375 | "outputs": [ 376 | { 377 | "internalType": "uint256", 378 | "name": "", 379 | "type": "uint256" 380 | } 381 | ], 382 | "stateMutability": "view", 383 | "type": "function" 384 | }, 385 | { 386 | "inputs": [ 387 | { 388 | "internalType": "uint256", 389 | "name": "tokenId", 390 | "type": "uint256" 391 | } 392 | ], 393 | "name": "getApproved", 394 | "outputs": [ 395 | { 396 | "internalType": "address", 397 | "name": "", 398 | "type": "address" 399 | } 400 | ], 401 | "stateMutability": "view", 402 | "type": "function" 403 | }, 404 | { 405 | "inputs": [ 406 | { 407 | "internalType": "address", 408 | "name": "owner", 409 | "type": "address" 410 | }, 411 | { 412 | "internalType": "address", 413 | "name": "operator", 414 | "type": "address" 415 | } 416 | ], 417 | "name": "isApprovedForAll", 418 | "outputs": [ 419 | { 420 | "internalType": "bool", 421 | "name": "", 422 | "type": "bool" 423 | } 424 | ], 425 | "stateMutability": "view", 426 | "type": "function" 427 | }, 428 | { 429 | "inputs": [], 430 | "name": "isPublicSaleOn", 431 | "outputs": [ 432 | { 433 | "internalType": "bool", 434 | "name": "", 435 | "type": "bool" 436 | } 437 | ], 438 | "stateMutability": "view", 439 | "type": "function" 440 | }, 441 | { 442 | "inputs": [], 443 | "name": "maxPerAddressDuringMint", 444 | "outputs": [ 445 | { 446 | "internalType": "uint256", 447 | "name": "", 448 | "type": "uint256" 449 | } 450 | ], 451 | "stateMutability": "view", 452 | "type": "function" 453 | }, 454 | { 455 | "inputs": [], 456 | "name": "name", 457 | "outputs": [ 458 | { 459 | "internalType": "string", 460 | "name": "", 461 | "type": "string" 462 | } 463 | ], 464 | "stateMutability": "view", 465 | "type": "function" 466 | }, 467 | { 468 | "inputs": [ 469 | { 470 | "internalType": "address", 471 | "name": "minter", 472 | "type": "address" 473 | } 474 | ], 475 | "name": "numberMinted", 476 | "outputs": [ 477 | { 478 | "internalType": "uint256", 479 | "name": "", 480 | "type": "uint256" 481 | } 482 | ], 483 | "stateMutability": "view", 484 | "type": "function" 485 | }, 486 | { 487 | "inputs": [], 488 | "name": "owner", 489 | "outputs": [ 490 | { 491 | "internalType": "address", 492 | "name": "", 493 | "type": "address" 494 | } 495 | ], 496 | "stateMutability": "view", 497 | "type": "function" 498 | }, 499 | { 500 | "inputs": [ 501 | { 502 | "internalType": "uint256", 503 | "name": "tokenId", 504 | "type": "uint256" 505 | } 506 | ], 507 | "name": "ownerOf", 508 | "outputs": [ 509 | { 510 | "internalType": "address", 511 | "name": "", 512 | "type": "address" 513 | } 514 | ], 515 | "stateMutability": "view", 516 | "type": "function" 517 | }, 518 | { 519 | "inputs": [], 520 | "name": "saleConfig", 521 | "outputs": [ 522 | { 523 | "internalType": "uint32", 524 | "name": "publicSaleStartTime", 525 | "type": "uint32" 526 | } 527 | ], 528 | "stateMutability": "view", 529 | "type": "function" 530 | }, 531 | { 532 | "inputs": [ 533 | { 534 | "internalType": "bytes4", 535 | "name": "interfaceId", 536 | "type": "bytes4" 537 | } 538 | ], 539 | "name": "supportsInterface", 540 | "outputs": [ 541 | { 542 | "internalType": "bool", 543 | "name": "", 544 | "type": "bool" 545 | } 546 | ], 547 | "stateMutability": "view", 548 | "type": "function" 549 | }, 550 | { 551 | "inputs": [], 552 | "name": "symbol", 553 | "outputs": [ 554 | { 555 | "internalType": "string", 556 | "name": "", 557 | "type": "string" 558 | } 559 | ], 560 | "stateMutability": "view", 561 | "type": "function" 562 | }, 563 | { 564 | "inputs": [ 565 | { 566 | "internalType": "uint256", 567 | "name": "tokenId", 568 | "type": "uint256" 569 | } 570 | ], 571 | "name": "tokenURI", 572 | "outputs": [ 573 | { 574 | "internalType": "string", 575 | "name": "", 576 | "type": "string" 577 | } 578 | ], 579 | "stateMutability": "view", 580 | "type": "function" 581 | }, 582 | { 583 | "inputs": [], 584 | "name": "totalSupply", 585 | "outputs": [ 586 | { 587 | "internalType": "uint256", 588 | "name": "", 589 | "type": "uint256" 590 | } 591 | ], 592 | "stateMutability": "view", 593 | "type": "function" 594 | } 595 | ] --------------------------------------------------------------------------------