├── 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 |
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 |
)
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 | ]
--------------------------------------------------------------------------------