├── .gitignore
├── CODEOWNERS
├── LICENSE
├── README.md
├── package.json
├── public
├── favicon.ico
├── index.html
├── media
│ └── logo.svg
└── robots.txt
├── sample.env
├── src
├── App.tsx
├── abi
│ ├── ERC20.json
│ ├── V2
│ │ ├── GenArt721CoreV2.json
│ │ └── GenArt721MintV2.json
│ └── V3
│ │ ├── GenArt721CoreV3_Engine.json
│ │ ├── MinterDAExpSettlementV1.json
│ │ ├── MinterDAExpV4.json
│ │ ├── MinterFilterV1.json
│ │ ├── MinterHolderV4.json
│ │ ├── MinterMerkleV5.json
│ │ ├── MinterSetPriceERC20V4.json
│ │ └── MinterSetPriceV4.json
├── components
│ ├── Address.tsx
│ ├── Collapsible.tsx
│ ├── Connect.tsx
│ ├── EditProjectButton.tsx
│ ├── Header.tsx
│ ├── Loading.tsx
│ ├── MinterButtons
│ │ ├── GenArt721MinterButton.tsx
│ │ ├── MinterDAExpSettlementV1Button.tsx
│ │ ├── MinterDAExpV4Button.tsx
│ │ ├── MinterHolderV4Button.tsx
│ │ ├── MinterMerkleV5Button.tsx
│ │ ├── MinterSetPriceERC20V4Button.tsx
│ │ └── MinterSetPriceV4Button.tsx
│ ├── MinterInterfaces
│ │ ├── GenArt721MinterInterface.tsx
│ │ ├── MinterDAExpSettlementV1Interface.tsx
│ │ ├── MinterDAExpV4Interface.tsx
│ │ ├── MinterHolderV4Interface.tsx
│ │ ├── MinterMerkleV5Interface.tsx
│ │ ├── MinterSetPriceERC20V4Interface.tsx
│ │ └── MinterSetPriceV4Interface.tsx
│ ├── MintingButton.tsx
│ ├── MintingCountdown.tsx
│ ├── MintingInterfaceFilter.tsx
│ ├── MintingPrice.tsx
│ ├── MintingProgress.tsx
│ ├── OwnedProjects.tsx
│ ├── OwnedTokens.tsx
│ ├── Page.tsx
│ ├── ProjectDate.tsx
│ ├── ProjectDetails.tsx
│ ├── ProjectExplore.tsx
│ ├── ProjectPreview.tsx
│ ├── ProjectStatusBadge.tsx
│ ├── Projects.tsx
│ ├── Providers.tsx
│ ├── TokenDetails.tsx
│ ├── TokenImage.tsx
│ ├── TokenLive.tsx
│ ├── TokenTraits.tsx
│ ├── TokenView.tsx
│ └── Tokens.tsx
├── config.ts
├── contractConfig.ts
├── hooks
│ ├── useCountOwnedProjects.tsx
│ ├── useCountOwnedTokens.tsx
│ ├── useCountProjects.tsx
│ ├── useGeneratorPreview.tsx
│ ├── useInterval.tsx
│ ├── useOwnedProjects.tsx
│ ├── useOwnedTokens.tsx
│ ├── useProject.tsx
│ ├── useProjects.tsx
│ ├── useToken.tsx
│ ├── useTokenTraits.tsx
│ ├── useTokens.tsx
│ └── useWindowSize.tsx
├── index.css
├── index.tsx
├── pages
│ ├── LandingPage.tsx
│ ├── ProjectPage.tsx
│ ├── ProjectsPage.tsx
│ ├── TokenPage.tsx
│ └── UserPage.tsx
├── theme.tsx
└── utils
│ ├── contractInfoHelper.ts
│ ├── getMintingInterface.ts
│ ├── numbers.ts
│ ├── scriptJSON.ts
│ └── types.ts
├── tsconfig.json
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # dependencies
2 | /node_modules
3 | /.pnp
4 | .pnp.js
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env
15 | *.env
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 | !sample.env
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
--------------------------------------------------------------------------------
/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # Request review from the best fit approvers group for this repo.
2 | * @ArtBlocks/Eng-Approvers-Backend
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2023 Art Blocks
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Art Blocks Engine (React Template)
2 | [](https://www.gitpoap.io/gh/ArtBlocks/artblocks-engine-react)
3 |
4 | This project is meant to be used as a template to build Art Blocks Engine web apps. It contains all pages and views
5 | necessary for users to browse projects, tokens and be able to purchase mints. This repository serves as a prototype or
6 | template.
7 |
8 | It assumes your core contracts are either `GenArt721CoreV2` or `GenArt721CoreV3`, and supports the following minters:
9 | `GenArt721Minter`, `MinterSetPriceV4`, `MinterSetPriceERC20V4`, `MinterMerkleV5`, and `MinterHolderV4`.
10 |
11 | It is NOT intended for production use as-is. Please modify for your needs and test extensively before using.
12 |
13 | Absolutely no warranty of any kind is provided. Please review from The MIT License:
14 |
15 | ## Warning
16 |
17 | **THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | SOFTWARE.**
24 |
25 | # Quick Start
26 |
27 | Run `npm install` or `yarn` to install dependencies.
28 |
29 | To run the project locally you will need to create a `.env` configuration file. You can get started by copying
30 | `sample.env` and renaming it as `.env`.
31 |
32 | Run `npm start` or `yarn start` to run the project locally.
33 |
34 | After making any changes to the `.env` file, you will need to restart the app.
35 |
36 | The default values specified in the provided `sample.env` file are reflected in the demo hosting found at:
37 | https://artblocks-engine-react.vercel.app/
38 |
39 | **Important note:** if you are planning to run/host this template via Vercel, you will populate these environment
40 | variables in the Vercel "Environment Variables" settings rather than defining them in your local `.env` file.
41 |
42 | [](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FArtBlocks%2Fartblocks-engine-react&env=REACT_APP_EXPECTED_CHAIN_ID,REACT_APP_GRAPHQL_URL,REACT_APP_INFURA_KEY&envDescription=Required%20environment%20variables%20for%20deployment&envLink=https%3A%2F%2Fgithub.com%2FArtBlocks%2Fartblocks-engine-react%2Fblob%2Fmain%2Fsample.env)
43 |
44 | ## Customizing your configuration
45 |
46 | In order to customize your specific implementation, you will need to edit the default configuration provided on the
47 | `sample.env` file.
48 |
49 | You must specify an API key from [Infura](https://www.infura.io/) as well as a chain id in your environment file. Use
50 | `1` for mainnet or `5` for goerli. Alternative providers can be used by modifying the `src/components/Providers.tsx`
51 | file. Use multiple `.env` fields to set up `development` or `staging` environments on `testnet` if you wish to do so.
52 |
53 | You must also obtain and supply a projectId from [WalletConnect Cloud](https://cloud.walletconnect.com/). This is free
54 | and only takes a few minutes.
55 |
56 | Additionally, you will need to edit the default configuration in the `src/contractConfig.ts` file -
57 | here you will find arrays for your mainnet and testnet contracts. Further configuration values like the number of
58 | projects per page, tokens per page, etc. can be found in `src/config.ts`.
59 |
60 | **Important note:** if you intend to support either of the `MinterMerkleV5` or `MinterHolderV4` minters there are a few
61 | extra necessary requirements and configurations.
62 |
63 | Support for the `MinterMerkleV5` minter requires a custom API endpoint that is responsible for calculating the merkle
64 | root for a given wallet address - the url for this endpoint must be configured in the `.env` file with the
65 | `REACT_APP_MERKLE_PROOF_API_URL` key. It is assumed that this endpoint takes the following url parameters:
66 | `?contractAddress={}&projectId={}&walletAddress={}` - this can be customized in the
67 | `src/components/MinterInterfaces/MinterMerkleV5Interface.tsx` file. For an example of this endpoint please see
68 | [here](https://github.com/plottables/media/blob/main/pages/api/getMerkleProof.tsx).
69 |
70 | Support for the `MinterHolderV4` minter requires a custom API endpoint that is responsible for determining the holder
71 | proof for a given wallet address - the url for this endpoint must be configured in the `.env` file with the
72 | `REACT_APP_HOLDER_PROOF_API_URL` key. It is assumed that this endpoint takes the following url parameters:
73 | `?contractAddress={}&projectId={}&walletAddress={}&isMainnet={}` - this can be customized in the
74 | `src/components/MinterInterfaces/MinterHolderV4Interface.tsx` file. For an example of this endpoint please see
75 | [here](https://github.com/plottables/media/blob/main/pages/api/getHolderProof.tsx).
76 |
77 | # Sections and Features
78 |
79 | This project includes wallet connection with [RainbowKit](https://www.rainbowkit.com/) and
80 | [wagmi.js](https://wagmi.sh/).
81 |
82 | ## Lander
83 | - An empty landing page
84 |
85 | ## Projects
86 | - Header/subheader
87 | - Title/artist name/description blurb
88 | - Grid of recent projects
89 |
90 | ## Project
91 | - Breadcrumb nav
92 | - Status
93 | - Cover image
94 | - Link to token shown as cover
95 | - Title/artist
96 | - Number of invocations
97 | - Mint button
98 | - Description
99 | - License/library
100 | - Token grid
101 | - Sort (by date)
102 | - Pagination
103 |
104 | ## Token
105 | - Breadcrumb nav
106 | - Date minted
107 | - Token cover img with links to live/static views
108 | - Owned by address or ens
109 | - Title/artist name
110 | - Features table
111 | - Etherscan and OpenSea links
112 |
113 | ## Project list
114 | - Grid of projects
115 | - Cover images
116 | - Link to token shown as cover
117 | - Title/artist name
118 | - Description blurb
119 | - Pagination
120 |
121 | ## Owned Tokens
122 | - List of projects with tokens owned by wallet
123 | - Title/artist name with link to project page
124 | - Images/Links to owned tokens with pagination
125 | - Pagination
126 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "artblocks-engine",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@apollo/client": "^3.6.6",
7 | "@artblocks/contracts": "1.0.2",
8 | "@emotion/react": "^11.9.0",
9 | "@emotion/styled": "^11.8.1",
10 | "@mui/icons-material": "^5.8.4",
11 | "@mui/lab": "^5.0.0-alpha.86",
12 | "@mui/material": "^5.8.2",
13 | "@rainbow-me/rainbowkit": "^0.12.4",
14 | "@testing-library/jest-dom": "^5.16.4",
15 | "@testing-library/react": "^13.3.0",
16 | "@testing-library/user-event": "^13.5.0",
17 | "@types/jest": "^27.5.2",
18 | "@types/node": "^16.11.38",
19 | "@types/react": "^18.0.12",
20 | "@types/react-dom": "^18.0.5",
21 | "buffer": "^5.7.1",
22 | "ethers": "^5.7.2",
23 | "graphql": "^16.5.0",
24 | "moment": "^2.29.4",
25 | "moment-timezone": "^0.5.39",
26 | "react": "^18.1.0",
27 | "react-dom": "^18.1.0",
28 | "react-markdown": "^8.0.7",
29 | "react-query": "^3.39.1",
30 | "react-router-dom": "^6.3.0",
31 | "react-scripts": "5.0.1",
32 | "react-toastify": "^9.0.5",
33 | "typescript": "^4.7.3",
34 | "usehooks-ts": "^2.9.1",
35 | "util": "^0.12.4",
36 | "wagmi": "^0.12.6"
37 | },
38 | "scripts": {
39 | "start": "GENERATE_SOURCEMAP=false react-scripts start",
40 | "build": "react-scripts build",
41 | "test": "react-scripts test",
42 | "eject": "react-scripts eject"
43 | },
44 | "eslintConfig": {
45 | "extends": [
46 | "react-app",
47 | "react-app/jest"
48 | ]
49 | },
50 | "browserslist": {
51 | "production": [
52 | ">0.2%",
53 | "not dead",
54 | "not op_mini all"
55 | ],
56 | "development": [
57 | "last 1 chrome version",
58 | "last 1 firefox version",
59 | "last 1 safari version"
60 | ]
61 | },
62 | "devDependencies": {
63 | "@typechain/ethers-v5": "^10.1.0",
64 | "typechain": "^8.1.0"
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArtBlocks/artblocks-engine-react/a787769020411d0958c63c749dec6caae23a7a95/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 | Engine
12 |
13 |
14 | You need to enable JavaScript to run this app.
15 |
16 |
17 |
--------------------------------------------------------------------------------
/public/media/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/sample.env:
--------------------------------------------------------------------------------
1 | # MAINNET
2 | #REACT_APP_EXPECTED_CHAIN_ID=1
3 | #REACT_APP_GRAPHQL_URL=https://api.thegraph.com/subgraphs/name/artblocks/art-blocks
4 |
5 | # TESTNET
6 | REACT_APP_EXPECTED_CHAIN_ID=3
7 | REACT_APP_GRAPHQL_URL=https://api.thegraph.com/subgraphs/name/artblocks/art-blocks-artist-staging-goerli
8 |
9 | REACT_APP_WALLET_CONNECT_PROJECT_ID=xyz789
10 | REACT_APP_INFURA_KEY=abc123
11 | REACT_APP_MERKLE_PROOF_API_URL=https://media.plottables.io/api/getMerkleProof
12 | REACT_APP_HOLDER_PROOF_API_URL=https://media.plottables.io/api/getHolderProof
13 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { BrowserRouter as Router, Routes, Route } from "react-router-dom"
2 | import { ToastContainer } from "react-toastify"
3 | import "react-toastify/dist/ReactToastify.css"
4 | import LandingPage from "pages/LandingPage"
5 | import ProjectsPage from "pages/ProjectsPage"
6 | import ProjectPage from "pages/ProjectPage"
7 | import TokenPage from "pages/TokenPage"
8 | import UserPage from "pages/UserPage"
9 | import Providers from "components/Providers"
10 |
11 | function App() {
12 | return (
13 |
14 |
15 |
16 | }/>
17 | }/>
18 | }/>
19 | }/>
20 | }/>
21 |
22 |
23 |
31 |
32 | )
33 | }
34 |
35 | export default App
36 |
--------------------------------------------------------------------------------
/src/abi/ERC20.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "constant": true,
4 | "inputs": [],
5 | "name": "name",
6 | "outputs": [
7 | {
8 | "name": "",
9 | "type": "string"
10 | }
11 | ],
12 | "payable": false,
13 | "stateMutability": "view",
14 | "type": "function"
15 | },
16 | {
17 | "constant": false,
18 | "inputs": [
19 | {
20 | "name": "_spender",
21 | "type": "address"
22 | },
23 | {
24 | "name": "_value",
25 | "type": "uint256"
26 | }
27 | ],
28 | "name": "approve",
29 | "outputs": [
30 | {
31 | "name": "",
32 | "type": "bool"
33 | }
34 | ],
35 | "payable": false,
36 | "stateMutability": "nonpayable",
37 | "type": "function"
38 | },
39 | {
40 | "constant": true,
41 | "inputs": [],
42 | "name": "totalSupply",
43 | "outputs": [
44 | {
45 | "name": "",
46 | "type": "uint256"
47 | }
48 | ],
49 | "payable": false,
50 | "stateMutability": "view",
51 | "type": "function"
52 | },
53 | {
54 | "constant": false,
55 | "inputs": [
56 | {
57 | "name": "_from",
58 | "type": "address"
59 | },
60 | {
61 | "name": "_to",
62 | "type": "address"
63 | },
64 | {
65 | "name": "_value",
66 | "type": "uint256"
67 | }
68 | ],
69 | "name": "transferFrom",
70 | "outputs": [
71 | {
72 | "name": "",
73 | "type": "bool"
74 | }
75 | ],
76 | "payable": false,
77 | "stateMutability": "nonpayable",
78 | "type": "function"
79 | },
80 | {
81 | "constant": true,
82 | "inputs": [],
83 | "name": "decimals",
84 | "outputs": [
85 | {
86 | "name": "",
87 | "type": "uint8"
88 | }
89 | ],
90 | "payable": false,
91 | "stateMutability": "view",
92 | "type": "function"
93 | },
94 | {
95 | "constant": true,
96 | "inputs": [
97 | {
98 | "name": "_owner",
99 | "type": "address"
100 | }
101 | ],
102 | "name": "balanceOf",
103 | "outputs": [
104 | {
105 | "name": "balance",
106 | "type": "uint256"
107 | }
108 | ],
109 | "payable": false,
110 | "stateMutability": "view",
111 | "type": "function"
112 | },
113 | {
114 | "constant": true,
115 | "inputs": [],
116 | "name": "symbol",
117 | "outputs": [
118 | {
119 | "name": "",
120 | "type": "string"
121 | }
122 | ],
123 | "payable": false,
124 | "stateMutability": "view",
125 | "type": "function"
126 | },
127 | {
128 | "constant": false,
129 | "inputs": [
130 | {
131 | "name": "_to",
132 | "type": "address"
133 | },
134 | {
135 | "name": "_value",
136 | "type": "uint256"
137 | }
138 | ],
139 | "name": "transfer",
140 | "outputs": [
141 | {
142 | "name": "",
143 | "type": "bool"
144 | }
145 | ],
146 | "payable": false,
147 | "stateMutability": "nonpayable",
148 | "type": "function"
149 | },
150 | {
151 | "constant": true,
152 | "inputs": [
153 | {
154 | "name": "_owner",
155 | "type": "address"
156 | },
157 | {
158 | "name": "_spender",
159 | "type": "address"
160 | }
161 | ],
162 | "name": "allowance",
163 | "outputs": [
164 | {
165 | "name": "",
166 | "type": "uint256"
167 | }
168 | ],
169 | "payable": false,
170 | "stateMutability": "view",
171 | "type": "function"
172 | },
173 | {
174 | "payable": true,
175 | "stateMutability": "payable",
176 | "type": "fallback"
177 | },
178 | {
179 | "anonymous": false,
180 | "inputs": [
181 | {
182 | "indexed": true,
183 | "name": "owner",
184 | "type": "address"
185 | },
186 | {
187 | "indexed": true,
188 | "name": "spender",
189 | "type": "address"
190 | },
191 | {
192 | "indexed": false,
193 | "name": "value",
194 | "type": "uint256"
195 | }
196 | ],
197 | "name": "Approval",
198 | "type": "event"
199 | },
200 | {
201 | "anonymous": false,
202 | "inputs": [
203 | {
204 | "indexed": true,
205 | "name": "from",
206 | "type": "address"
207 | },
208 | {
209 | "indexed": true,
210 | "name": "to",
211 | "type": "address"
212 | },
213 | {
214 | "indexed": false,
215 | "name": "value",
216 | "type": "uint256"
217 | }
218 | ],
219 | "name": "Transfer",
220 | "type": "event"
221 | }
222 | ]
223 |
--------------------------------------------------------------------------------
/src/abi/V2/GenArt721MintV2.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "inputs":[
4 | {
5 | "internalType":"address",
6 | "name":"_genArt721Address",
7 | "type":"address"
8 | }
9 | ],
10 | "payable":false,
11 | "stateMutability":"nonpayable",
12 | "type":"constructor"
13 | },
14 | {
15 | "constant":false,
16 | "inputs":[
17 | {
18 | "internalType":"uint256",
19 | "name":"_projectId",
20 | "type":"uint256"
21 | },
22 | {
23 | "internalType":"address",
24 | "name":"_bonusContractAddress",
25 | "type":"address"
26 | }
27 | ],
28 | "name":"artistSetBonusContractAddress",
29 | "outputs":[
30 |
31 | ],
32 | "payable":false,
33 | "stateMutability":"nonpayable",
34 | "type":"function"
35 | },
36 | {
37 | "constant":false,
38 | "inputs":[
39 | {
40 | "internalType":"uint256",
41 | "name":"_projectId",
42 | "type":"uint256"
43 | }
44 | ],
45 | "name":"artistToggleBonus",
46 | "outputs":[
47 |
48 | ],
49 | "payable":false,
50 | "stateMutability":"nonpayable",
51 | "type":"function"
52 | },
53 | {
54 | "constant":true,
55 | "inputs":[
56 | {
57 | "internalType":"uint256",
58 | "name":"_projectId",
59 | "type":"uint256"
60 | }
61 | ],
62 | "name":"checkYourAllowanceOfProjectERC20",
63 | "outputs":[
64 | {
65 | "internalType":"uint256",
66 | "name":"",
67 | "type":"uint256"
68 | }
69 | ],
70 | "payable":false,
71 | "stateMutability":"view",
72 | "type":"function"
73 | },
74 | {
75 | "constant":true,
76 | "inputs":[
77 | {
78 | "internalType":"uint256",
79 | "name":"",
80 | "type":"uint256"
81 | }
82 | ],
83 | "name":"contractFilterProject",
84 | "outputs":[
85 | {
86 | "internalType":"bool",
87 | "name":"",
88 | "type":"bool"
89 | }
90 | ],
91 | "payable":false,
92 | "stateMutability":"view",
93 | "type":"function"
94 | },
95 | {
96 | "constant":true,
97 | "inputs":[
98 |
99 | ],
100 | "name":"genArtCoreContract",
101 | "outputs":[
102 | {
103 | "internalType":"contract IGenArt721CoreV2",
104 | "name":"",
105 | "type":"address"
106 | }
107 | ],
108 | "payable":false,
109 | "stateMutability":"view",
110 | "type":"function"
111 | },
112 | {
113 | "constant":true,
114 | "inputs":[
115 | {
116 | "internalType":"uint256",
117 | "name":"_projectId",
118 | "type":"uint256"
119 | }
120 | ],
121 | "name":"getYourBalanceOfProjectERC20",
122 | "outputs":[
123 | {
124 | "internalType":"uint256",
125 | "name":"",
126 | "type":"uint256"
127 | }
128 | ],
129 | "payable":false,
130 | "stateMutability":"view",
131 | "type":"function"
132 | },
133 | {
134 | "constant":true,
135 | "inputs":[
136 |
137 | ],
138 | "name":"ownerAddress",
139 | "outputs":[
140 | {
141 | "internalType":"address payable",
142 | "name":"",
143 | "type":"address"
144 | }
145 | ],
146 | "payable":false,
147 | "stateMutability":"view",
148 | "type":"function"
149 | },
150 | {
151 | "constant":true,
152 | "inputs":[
153 |
154 | ],
155 | "name":"ownerPercentage",
156 | "outputs":[
157 | {
158 | "internalType":"uint256",
159 | "name":"",
160 | "type":"uint256"
161 | }
162 | ],
163 | "payable":false,
164 | "stateMutability":"view",
165 | "type":"function"
166 | },
167 | {
168 | "constant":true,
169 | "inputs":[
170 | {
171 | "internalType":"uint256",
172 | "name":"",
173 | "type":"uint256"
174 | }
175 | ],
176 | "name":"projectIdToBonus",
177 | "outputs":[
178 | {
179 | "internalType":"bool",
180 | "name":"",
181 | "type":"bool"
182 | }
183 | ],
184 | "payable":false,
185 | "stateMutability":"view",
186 | "type":"function"
187 | },
188 | {
189 | "constant":true,
190 | "inputs":[
191 | {
192 | "internalType":"uint256",
193 | "name":"",
194 | "type":"uint256"
195 | }
196 | ],
197 | "name":"projectIdToBonusContractAddress",
198 | "outputs":[
199 | {
200 | "internalType":"address",
201 | "name":"",
202 | "type":"address"
203 | }
204 | ],
205 | "payable":false,
206 | "stateMutability":"view",
207 | "type":"function"
208 | },
209 | {
210 | "constant":true,
211 | "inputs":[
212 | {
213 | "internalType":"uint256",
214 | "name":"",
215 | "type":"uint256"
216 | }
217 | ],
218 | "name":"projectMaxHasBeenInvoked",
219 | "outputs":[
220 | {
221 | "internalType":"bool",
222 | "name":"",
223 | "type":"bool"
224 | }
225 | ],
226 | "payable":false,
227 | "stateMutability":"view",
228 | "type":"function"
229 | },
230 | {
231 | "constant":true,
232 | "inputs":[
233 | {
234 | "internalType":"uint256",
235 | "name":"",
236 | "type":"uint256"
237 | }
238 | ],
239 | "name":"projectMaxInvocations",
240 | "outputs":[
241 | {
242 | "internalType":"uint256",
243 | "name":"",
244 | "type":"uint256"
245 | }
246 | ],
247 | "payable":false,
248 | "stateMutability":"view",
249 | "type":"function"
250 | },
251 | {
252 | "constant":true,
253 | "inputs":[
254 | {
255 | "internalType":"address",
256 | "name":"",
257 | "type":"address"
258 | },
259 | {
260 | "internalType":"uint256",
261 | "name":"",
262 | "type":"uint256"
263 | }
264 | ],
265 | "name":"projectMintCounter",
266 | "outputs":[
267 | {
268 | "internalType":"uint256",
269 | "name":"",
270 | "type":"uint256"
271 | }
272 | ],
273 | "payable":false,
274 | "stateMutability":"view",
275 | "type":"function"
276 | },
277 | {
278 | "constant":true,
279 | "inputs":[
280 | {
281 | "internalType":"uint256",
282 | "name":"",
283 | "type":"uint256"
284 | }
285 | ],
286 | "name":"projectMintLimit",
287 | "outputs":[
288 | {
289 | "internalType":"uint256",
290 | "name":"",
291 | "type":"uint256"
292 | }
293 | ],
294 | "payable":false,
295 | "stateMutability":"view",
296 | "type":"function"
297 | },
298 | {
299 | "constant":false,
300 | "inputs":[
301 | {
302 | "internalType":"uint256",
303 | "name":"_projectId",
304 | "type":"uint256"
305 | }
306 | ],
307 | "name":"purchase",
308 | "outputs":[
309 | {
310 | "internalType":"uint256",
311 | "name":"_tokenId",
312 | "type":"uint256"
313 | }
314 | ],
315 | "payable":true,
316 | "stateMutability":"payable",
317 | "type":"function"
318 | },
319 | {
320 | "constant":false,
321 | "inputs":[
322 | {
323 | "internalType":"address",
324 | "name":"_to",
325 | "type":"address"
326 | },
327 | {
328 | "internalType":"uint256",
329 | "name":"_projectId",
330 | "type":"uint256"
331 | }
332 | ],
333 | "name":"purchaseTo",
334 | "outputs":[
335 | {
336 | "internalType":"uint256",
337 | "name":"_tokenId",
338 | "type":"uint256"
339 | }
340 | ],
341 | "payable":true,
342 | "stateMutability":"payable",
343 | "type":"function"
344 | },
345 | {
346 | "constant":false,
347 | "inputs":[
348 | {
349 | "internalType":"address payable",
350 | "name":"_ownerAddress",
351 | "type":"address"
352 | }
353 | ],
354 | "name":"setOwnerAddress",
355 | "outputs":[
356 |
357 | ],
358 | "payable":false,
359 | "stateMutability":"nonpayable",
360 | "type":"function"
361 | },
362 | {
363 | "constant":false,
364 | "inputs":[
365 | {
366 | "internalType":"uint256",
367 | "name":"_ownerPercentage",
368 | "type":"uint256"
369 | }
370 | ],
371 | "name":"setOwnerPercentage",
372 | "outputs":[
373 |
374 | ],
375 | "payable":false,
376 | "stateMutability":"nonpayable",
377 | "type":"function"
378 | },
379 | {
380 | "constant":false,
381 | "inputs":[
382 | {
383 | "internalType":"uint256",
384 | "name":"_projectId",
385 | "type":"uint256"
386 | }
387 | ],
388 | "name":"setProjectMaxInvocations",
389 | "outputs":[
390 |
391 | ],
392 | "payable":false,
393 | "stateMutability":"nonpayable",
394 | "type":"function"
395 | },
396 | {
397 | "constant":false,
398 | "inputs":[
399 | {
400 | "internalType":"uint256",
401 | "name":"_projectId",
402 | "type":"uint256"
403 | },
404 | {
405 | "internalType":"uint8",
406 | "name":"_limit",
407 | "type":"uint8"
408 | }
409 | ],
410 | "name":"setProjectMintLimit",
411 | "outputs":[
412 |
413 | ],
414 | "payable":false,
415 | "stateMutability":"nonpayable",
416 | "type":"function"
417 | },
418 | {
419 | "constant":false,
420 | "inputs":[
421 | {
422 | "internalType":"uint256",
423 | "name":"_projectId",
424 | "type":"uint256"
425 | }
426 | ],
427 | "name":"toggleContractFilter",
428 | "outputs":[
429 |
430 | ],
431 | "payable":false,
432 | "stateMutability":"nonpayable",
433 | "type":"function"
434 | }
435 | ]
436 |
--------------------------------------------------------------------------------
/src/abi/V3/MinterFilterV1.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "inputs":[
4 | {
5 | "internalType":"address",
6 | "name":"_genArt721Address",
7 | "type":"address"
8 | }
9 | ],
10 | "stateMutability":"nonpayable",
11 | "type":"constructor"
12 | },
13 | {
14 | "anonymous":false,
15 | "inputs":[
16 |
17 | ],
18 | "name":"Deployed",
19 | "type":"event"
20 | },
21 | {
22 | "anonymous":false,
23 | "inputs":[
24 | {
25 | "indexed":true,
26 | "internalType":"address",
27 | "name":"_minterAddress",
28 | "type":"address"
29 | },
30 | {
31 | "indexed":false,
32 | "internalType":"string",
33 | "name":"_minterType",
34 | "type":"string"
35 | }
36 | ],
37 | "name":"MinterApproved",
38 | "type":"event"
39 | },
40 | {
41 | "anonymous":false,
42 | "inputs":[
43 | {
44 | "indexed":true,
45 | "internalType":"address",
46 | "name":"_minterAddress",
47 | "type":"address"
48 | }
49 | ],
50 | "name":"MinterRevoked",
51 | "type":"event"
52 | },
53 | {
54 | "anonymous":false,
55 | "inputs":[
56 | {
57 | "indexed":true,
58 | "internalType":"uint256",
59 | "name":"_projectId",
60 | "type":"uint256"
61 | },
62 | {
63 | "indexed":true,
64 | "internalType":"address",
65 | "name":"_minterAddress",
66 | "type":"address"
67 | },
68 | {
69 | "indexed":false,
70 | "internalType":"string",
71 | "name":"_minterType",
72 | "type":"string"
73 | }
74 | ],
75 | "name":"ProjectMinterRegistered",
76 | "type":"event"
77 | },
78 | {
79 | "anonymous":false,
80 | "inputs":[
81 | {
82 | "indexed":true,
83 | "internalType":"uint256",
84 | "name":"_projectId",
85 | "type":"uint256"
86 | }
87 | ],
88 | "name":"ProjectMinterRemoved",
89 | "type":"event"
90 | },
91 | {
92 | "inputs":[
93 | {
94 | "internalType":"address",
95 | "name":"_minterAddress",
96 | "type":"address"
97 | }
98 | ],
99 | "name":"addApprovedMinter",
100 | "outputs":[
101 |
102 | ],
103 | "stateMutability":"nonpayable",
104 | "type":"function"
105 | },
106 | {
107 | "inputs":[
108 |
109 | ],
110 | "name":"genArt721CoreAddress",
111 | "outputs":[
112 | {
113 | "internalType":"address",
114 | "name":"",
115 | "type":"address"
116 | }
117 | ],
118 | "stateMutability":"view",
119 | "type":"function"
120 | },
121 | {
122 | "inputs":[
123 | {
124 | "internalType":"uint256",
125 | "name":"_projectId",
126 | "type":"uint256"
127 | }
128 | ],
129 | "name":"getMinterForProject",
130 | "outputs":[
131 | {
132 | "internalType":"address",
133 | "name":"",
134 | "type":"address"
135 | }
136 | ],
137 | "stateMutability":"view",
138 | "type":"function"
139 | },
140 | {
141 | "inputs":[
142 |
143 | ],
144 | "name":"getNumProjectsWithMinters",
145 | "outputs":[
146 | {
147 | "internalType":"uint256",
148 | "name":"",
149 | "type":"uint256"
150 | }
151 | ],
152 | "stateMutability":"view",
153 | "type":"function"
154 | },
155 | {
156 | "inputs":[
157 | {
158 | "internalType":"uint256",
159 | "name":"_index",
160 | "type":"uint256"
161 | }
162 | ],
163 | "name":"getProjectAndMinterInfoAt",
164 | "outputs":[
165 | {
166 | "internalType":"uint256",
167 | "name":"projectId",
168 | "type":"uint256"
169 | },
170 | {
171 | "internalType":"address",
172 | "name":"minterAddress",
173 | "type":"address"
174 | },
175 | {
176 | "internalType":"string",
177 | "name":"minterType",
178 | "type":"string"
179 | }
180 | ],
181 | "stateMutability":"view",
182 | "type":"function"
183 | },
184 | {
185 | "inputs":[
186 | {
187 | "internalType":"address",
188 | "name":"",
189 | "type":"address"
190 | }
191 | ],
192 | "name":"isApprovedMinter",
193 | "outputs":[
194 | {
195 | "internalType":"bool",
196 | "name":"",
197 | "type":"bool"
198 | }
199 | ],
200 | "stateMutability":"view",
201 | "type":"function"
202 | },
203 | {
204 | "inputs":[
205 | {
206 | "internalType":"address",
207 | "name":"_to",
208 | "type":"address"
209 | },
210 | {
211 | "internalType":"uint256",
212 | "name":"_projectId",
213 | "type":"uint256"
214 | },
215 | {
216 | "internalType":"address",
217 | "name":"sender",
218 | "type":"address"
219 | }
220 | ],
221 | "name":"mint",
222 | "outputs":[
223 | {
224 | "internalType":"uint256",
225 | "name":"_tokenId",
226 | "type":"uint256"
227 | }
228 | ],
229 | "stateMutability":"nonpayable",
230 | "type":"function"
231 | },
232 | {
233 | "inputs":[
234 |
235 | ],
236 | "name":"minterFilterType",
237 | "outputs":[
238 | {
239 | "internalType":"string",
240 | "name":"",
241 | "type":"string"
242 | }
243 | ],
244 | "stateMutability":"pure",
245 | "type":"function"
246 | },
247 | {
248 | "inputs":[
249 |
250 | ],
251 | "name":"minterFilterVersion",
252 | "outputs":[
253 | {
254 | "internalType":"string",
255 | "name":"",
256 | "type":"string"
257 | }
258 | ],
259 | "stateMutability":"pure",
260 | "type":"function"
261 | },
262 | {
263 | "inputs":[
264 | {
265 | "internalType":"address",
266 | "name":"",
267 | "type":"address"
268 | }
269 | ],
270 | "name":"numProjectsUsingMinter",
271 | "outputs":[
272 | {
273 | "internalType":"uint256",
274 | "name":"",
275 | "type":"uint256"
276 | }
277 | ],
278 | "stateMutability":"view",
279 | "type":"function"
280 | },
281 | {
282 | "inputs":[
283 | {
284 | "internalType":"uint256",
285 | "name":"_projectId",
286 | "type":"uint256"
287 | }
288 | ],
289 | "name":"projectHasMinter",
290 | "outputs":[
291 | {
292 | "internalType":"bool",
293 | "name":"",
294 | "type":"bool"
295 | }
296 | ],
297 | "stateMutability":"view",
298 | "type":"function"
299 | },
300 | {
301 | "inputs":[
302 | {
303 | "internalType":"address",
304 | "name":"_minterAddress",
305 | "type":"address"
306 | }
307 | ],
308 | "name":"removeApprovedMinter",
309 | "outputs":[
310 |
311 | ],
312 | "stateMutability":"nonpayable",
313 | "type":"function"
314 | },
315 | {
316 | "inputs":[
317 | {
318 | "internalType":"uint256",
319 | "name":"_projectId",
320 | "type":"uint256"
321 | }
322 | ],
323 | "name":"removeMinterForProject",
324 | "outputs":[
325 |
326 | ],
327 | "stateMutability":"nonpayable",
328 | "type":"function"
329 | },
330 | {
331 | "inputs":[
332 | {
333 | "internalType":"uint256[]",
334 | "name":"_projectIds",
335 | "type":"uint256[]"
336 | }
337 | ],
338 | "name":"removeMintersForProjects",
339 | "outputs":[
340 |
341 | ],
342 | "stateMutability":"nonpayable",
343 | "type":"function"
344 | },
345 | {
346 | "inputs":[
347 | {
348 | "internalType":"uint256",
349 | "name":"_projectId",
350 | "type":"uint256"
351 | },
352 | {
353 | "internalType":"address",
354 | "name":"_minterAddress",
355 | "type":"address"
356 | }
357 | ],
358 | "name":"setMinterForProject",
359 | "outputs":[
360 |
361 | ],
362 | "stateMutability":"nonpayable",
363 | "type":"function"
364 | }
365 | ]
366 |
--------------------------------------------------------------------------------
/src/components/Address.tsx:
--------------------------------------------------------------------------------
1 | import { useEnsName } from "wagmi"
2 | import Tooltip from "@mui/material/Tooltip"
3 |
4 | interface Props {
5 | address?: any
6 | }
7 |
8 | const Address = ({ address }: Props) => {
9 | const ensName = useEnsName({
10 | address: address,
11 | chainId: 1
12 | })
13 |
14 | const shortAddress = address ? `${address.slice(0, 6)}...${ address.slice(38, 42)}` : null
15 |
16 | return (
17 | address !== null ?
18 |
19 | {ensName.data || shortAddress}
20 |
21 | : null
22 | )
23 | }
24 |
25 | export default Address
--------------------------------------------------------------------------------
/src/components/Collapsible.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import {
3 | Box,
4 | Typography,
5 | ButtonBase
6 | } from "@mui/material"
7 | import ReactMarkdown from "react-markdown"
8 |
9 | interface Props {
10 | content: string
11 | maxWords?: number
12 | useMarkdown?: boolean
13 | }
14 |
15 | const Collapsible = ({ content, maxWords=50, useMarkdown=true }: Props) => {
16 | const [open, setOpen] = useState(false)
17 | const words = content ? content.split(" ") : []
18 | const truncated = words.slice(0, maxWords).join(" ")
19 | const overflows = words.length > maxWords
20 |
21 | return (
22 | <>
23 | {
24 | useMarkdown
25 | ?
26 |
27 | {open ? content : overflows ? `${truncated}...` : truncated}
28 |
29 | :
30 |
31 | {open ? content : truncated} {overflows && !open && "..."}
32 |
33 | }
34 | { overflows && (
35 |
36 | { !open && (
37 | setOpen(true)}
39 | sx={{ textDecoration: "underline", textTransform: "none" }}
40 | >
41 | More
42 |
43 | )}
44 |
45 | )}
46 | >
47 | )
48 | }
49 |
50 | export default Collapsible
51 |
--------------------------------------------------------------------------------
/src/components/Connect.tsx:
--------------------------------------------------------------------------------
1 | import { ConnectButton } from "@rainbow-me/rainbowkit"
2 | import Box from "@mui/material/Box"
3 |
4 | const Connect = () => {
5 | return (
6 |
7 |
15 |
16 | )
17 | }
18 |
19 | export default Connect
20 |
--------------------------------------------------------------------------------
/src/components/EditProjectButton.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Button,
3 | Typography
4 | } from "@mui/material"
5 |
6 | interface Props {
7 | contractAddress: string,
8 | projectId: string,
9 | editProjectUrl: string
10 | }
11 |
12 | const EditProjectButton = ({contractAddress, projectId, editProjectUrl}: Props) => {
13 | return (
14 |
18 | window.open(`${editProjectUrl}/${contractAddress}/${projectId}`, '_blank')
19 | }
20 | sx={{
21 | minWidth: "210px",
22 | paddingTop: 1.5,
23 | paddingRight: 1,
24 | paddingLeft: 1,
25 | paddingBottom: 1.5,
26 | boxShadow: "none",
27 | textTransform: "none"
28 | }}>
29 |
30 | {"Edit Project"}
31 |
32 |
33 | )
34 | }
35 |
36 | export default EditProjectButton
37 |
--------------------------------------------------------------------------------
/src/components/Header.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import {
3 | Box,
4 | Link,
5 | AppBar,
6 | Toolbar,
7 | Drawer,
8 | IconButton,
9 | List,
10 | ListItem,
11 | ListItemButton,
12 | ListItemText
13 | } from "@mui/material"
14 | import MenuIcon from "@mui/icons-material/Menu"
15 | import Connect from "components/Connect"
16 | import { useAccount } from "wagmi"
17 |
18 | let items = [
19 | {
20 | label: "Projects",
21 | url: "/projects",
22 | enabled: true
23 | },
24 | {
25 | label: "Owned",
26 | url: "/user",
27 | enabled: false
28 | },
29 | {
30 | label: "Mint",
31 | url: "/mint",
32 | enabled: false
33 | }
34 | ]
35 |
36 | const Header = () => {
37 | const { address, isConnected } = useAccount()
38 | const [mobileOpen, setMobileOpen] = useState(false)
39 |
40 | let userItem = items.find((item) => {
41 | return item.label === "Owned"
42 | })
43 | if (isConnected) {
44 | if (userItem) {
45 | userItem.enabled = true
46 | userItem.url = `/user/${address}`
47 | }
48 | } else {
49 | if (userItem) {
50 | userItem.enabled = false
51 | userItem.url = `/user`
52 | }
53 | }
54 |
55 | const handleDrawerToggle = () => {
56 | setMobileOpen(!mobileOpen)
57 | }
58 |
59 | const drawer = (
60 |
61 |
62 | {items.map((item) => (
63 |
64 |
65 |
66 |
67 |
68 | ))}
69 |
70 |
71 | )
72 | return (
73 |
74 |
75 |
76 |
77 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | {items.map((item) => (
92 |
97 | {item.label}
98 |
99 | ))}
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
117 | {drawer}
118 |
119 |
120 |
121 | )
122 | }
123 |
124 | export default Header
125 |
--------------------------------------------------------------------------------
/src/components/Loading.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import {
3 | Box,
4 | CircularProgress
5 | } from "@mui/material"
6 | import { Container } from "@mui/system"
7 | import useInterval from "hooks/useInterval"
8 |
9 | const Loading = () => {
10 | const [waitTime, setWaitTime] = useState(0)
11 |
12 | useInterval(() => {
13 | setWaitTime(waitTime+1)
14 | }, 1000)
15 |
16 | return (
17 |
18 | {
19 | waitTime > 0 &&
20 | (
21 |
22 |
23 |
24 | )
25 | }
26 |
27 | )
28 | }
29 |
30 | export default Loading
--------------------------------------------------------------------------------
/src/components/MinterButtons/GenArt721MinterButton.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react"
2 | import {
3 | usePrepareContractWrite,
4 | useContractWrite,
5 | useWaitForTransaction,
6 | useAccount,
7 | useBalance,
8 | useContractRead
9 | } from "wagmi"
10 | import { BigNumber, ethers } from "ethers"
11 | import { Box, Typography, Modal } from "@mui/material"
12 | import { MULTIPLY_GAS_LIMIT } from "config"
13 | import { multiplyBigNumberByFloat, formatEtherFixed } from "utils/numbers"
14 | import TokenView from "components/TokenView"
15 | import useWindowSize from "hooks/useWindowSize"
16 | import MintingButton from "components/MintingButton"
17 | import GenArt721MintV2ABI from "abi/V2/GenArt721MintV2.json"
18 | import GenArt721CoreV2ABI from "abi/V2/GenArt721CoreV2.json"
19 | import ERC20ABI from "abi/ERC20.json"
20 |
21 | interface Props {
22 | coreContractAddress: string,
23 | mintContractAddress: string,
24 | projectId: string,
25 | priceWei: BigNumber,
26 | currencySymbol: string,
27 | currencyAddress: string,
28 | isConnected: boolean,
29 | artistCanMint: boolean,
30 | anyoneCanMint: boolean,
31 | scriptAspectRatio: number,
32 | isPaused: boolean,
33 | isSoldOut: boolean
34 | }
35 |
36 | const GenArt721MinterButton = (
37 | {
38 | coreContractAddress,
39 | mintContractAddress,
40 | projectId,
41 | priceWei,
42 | currencySymbol,
43 | currencyAddress,
44 | isConnected,
45 | artistCanMint,
46 | anyoneCanMint,
47 | scriptAspectRatio,
48 | isPaused,
49 | isSoldOut
50 | }: Props
51 | ) => {
52 |
53 | const windowSize = useWindowSize()
54 | const [dialog, setDialog] = useState("")
55 | const [mintingTokenId, setMintingTokenId] = useState(null)
56 | const [mintingPreview, setMintingPreview] = useState(false)
57 | const handleMintingPreviewOpen = () => setMintingPreview(true)
58 | const handleMintingPreviewClose = () => setMintingPreview(false)
59 |
60 | const projectUsesErc20 = currencyAddress !== "0x0000000000000000000000000000000000000000"
61 |
62 | const account = useAccount()
63 | const ethBalance = useBalance({
64 | address: account.address
65 | })
66 | const erc20Balance = useBalance({
67 | address: account.address,
68 | token: projectUsesErc20 ? currencyAddress as `0x${string}` : undefined
69 | })
70 | const [isAllowanceVerified, setIsAllowanceVerified] = useState(!projectUsesErc20)
71 | const [isBalanceVerified, setIsBalanceVerified] = useState(false)
72 |
73 | useContractRead({
74 | address: currencyAddress as `0x${string}`,
75 | abi: ERC20ABI,
76 | functionName: "allowance",
77 | args: [account.address, mintContractAddress],
78 | watch: true,
79 | enabled: !isPaused && !isSoldOut && projectUsesErc20,
80 | onSuccess(data: BigNumber) {
81 | setIsAllowanceVerified(data >= priceWei)
82 | }
83 | })
84 |
85 | useEffect(() => {
86 | if (projectUsesErc20) {
87 | if (erc20Balance?.data?.value.gt(priceWei)) {
88 | setIsBalanceVerified(true)
89 | }
90 | } else {
91 | if (ethBalance?.data?.value.gt(priceWei)) {
92 | setIsBalanceVerified(true)
93 | }
94 | }
95 | }, [erc20Balance, ethBalance])
96 |
97 | const erc20PrepareApprove = usePrepareContractWrite({
98 | address: currencyAddress as `0x${string}`,
99 | abi: ERC20ABI,
100 | functionName: "approve",
101 | enabled: (!isPaused || artistCanMint) && !isSoldOut && projectUsesErc20 && !isAllowanceVerified && isBalanceVerified,
102 | args: [
103 | mintContractAddress, BigNumber.from(priceWei)
104 | ]
105 | })
106 | const erc20WriteApprove = useContractWrite({
107 | ...erc20PrepareApprove.config,
108 | onSuccess() {
109 | setDialog("Approving ERC20...")
110 | }
111 | })
112 | useWaitForTransaction({
113 | hash: erc20WriteApprove?.data?.hash,
114 | confirmations: 1,
115 | onSuccess() {
116 | setDialog("ERC20 Approved...")
117 | }
118 | })
119 |
120 | const mintPrepare = usePrepareContractWrite({
121 | address: mintContractAddress as `0x${string}`,
122 | abi: GenArt721MintV2ABI,
123 | functionName: "purchase",
124 | overrides: projectUsesErc20 ? undefined : {
125 | value: priceWei
126 | },
127 | enabled: (!isPaused || artistCanMint) && !isSoldOut && isBalanceVerified && isAllowanceVerified,
128 | args: [
129 | BigNumber.from(projectId)
130 | ]
131 | })
132 | let customRequest = mintPrepare.config.request ? {
133 | data: mintPrepare.config.request?.data,
134 | from: mintPrepare.config.request?.from,
135 | gasLimit: multiplyBigNumberByFloat(mintPrepare.config.request?.gasLimit, MULTIPLY_GAS_LIMIT),
136 | to: mintPrepare.config.request?.to,
137 | value: mintPrepare.config.request?.value
138 | } : undefined
139 | const mintWrite = useContractWrite({
140 | ...mintPrepare.config,
141 | request: customRequest,
142 | onSuccess() {
143 | setDialog("Transaction pending...")
144 | }
145 | })
146 | useWaitForTransaction({
147 | hash: mintWrite.data?.hash,
148 | confirmations: 1,
149 | onSuccess(data) {
150 | const mintInterface = new ethers.utils.Interface(GenArt721CoreV2ABI)
151 | const mintEvent = (data.logs || []).find(
152 | (receiptEvent: { topics: string[] }) => {
153 | const event = mintInterface.getEvent(receiptEvent.topics[0]);
154 | return event && event.name === "Mint";
155 | }
156 | )
157 | const mintEventDecoded = mintInterface.decodeEventLog("Mint", mintEvent?.data!, mintEvent?.topics)
158 | const tokenId = mintEventDecoded["_tokenId"].toString()
159 | if (tokenId) {
160 | setMintingTokenId(tokenId)
161 | handleMintingPreviewOpen()
162 | }
163 | setDialog("")
164 | }
165 | })
166 |
167 | const mintingDisabled = isPaused || isSoldOut || !isConnected || !isAllowanceVerified || !isBalanceVerified
168 |
169 | let mintingMessage = `${artistCanMint ? "Artist Mint " : "Purchase "} for ${formatEtherFixed(priceWei.toString(), 3)} ${currencySymbol}`
170 | if (isPaused && !artistCanMint) mintingMessage = "minting paused"
171 | else if (isSoldOut) mintingMessage = "sold out"
172 | else if (!isConnected) mintingMessage = "connect to purchase"
173 | else if (!isBalanceVerified) mintingMessage = "insufficient funds"
174 | else if (projectUsesErc20 && !isAllowanceVerified) mintingMessage = "set ERC20 allowance"
175 |
176 | return (
177 | <>
178 |
183 |
184 |
185 | {dialog}
186 |
187 |
188 |
194 |
205 |
206 |
207 | Minted #{mintingTokenId}
208 |
209 |
210 |
217 |
218 |
219 |
220 |
221 | >
222 | )
223 | }
224 |
225 | export default GenArt721MinterButton
226 |
--------------------------------------------------------------------------------
/src/components/MinterButtons/MinterDAExpSettlementV1Button.tsx:
--------------------------------------------------------------------------------
1 | import {useEffect, useState} from "react"
2 | import { usePrepareContractWrite, useContractWrite, useWaitForTransaction } from "wagmi"
3 | import { BigNumber } from "ethers"
4 | import { Box, Typography, Modal } from "@mui/material"
5 | import { MULTIPLY_GAS_LIMIT } from "config"
6 | import { multiplyBigNumberByFloat, formatEtherFixed } from "utils/numbers"
7 | import MinterDAExpSettlementV1ABI from "abi/V3/MinterDAExpSettlementV1.json"
8 | import TokenView from "components/TokenView"
9 | import useWindowSize from "hooks/useWindowSize"
10 | import MintingButton from "components/MintingButton"
11 |
12 | interface Props {
13 | coreContractAddress: string,
14 | mintContractAddress: string,
15 | projectId: string,
16 | priceWei: BigNumber
17 | currencySymbol: string,
18 | isConnected: boolean,
19 | artistCanMint: boolean,
20 | anyoneCanMint: boolean,
21 | scriptAspectRatio: number,
22 | verifyBalance: boolean,
23 | isPaused: boolean,
24 | isSoldOut: boolean,
25 | excessSettlementFunds: BigNumber,
26 | auctionHasStarted: boolean
27 | }
28 |
29 | const MinterDAExpSettlementV1Button = (
30 | {
31 | coreContractAddress,
32 | mintContractAddress,
33 | projectId,
34 | priceWei,
35 | currencySymbol,
36 | isConnected,
37 | artistCanMint,
38 | anyoneCanMint,
39 | scriptAspectRatio,
40 | verifyBalance,
41 | isPaused,
42 | isSoldOut,
43 | excessSettlementFunds,
44 | auctionHasStarted
45 | }: Props
46 | ) => {
47 | const windowSize = useWindowSize()
48 | const [dialog, setDialog] = useState("")
49 | const [mintingTokenId, setMintingTokenId] = useState(null)
50 | const [mintingPreview, setMintingPreview] = useState(false)
51 | const handleMintingPreviewOpen = () => setMintingPreview(true)
52 | const handleMintingPreviewClose = () => setMintingPreview(false)
53 |
54 | useEffect(() => {
55 | if (excessSettlementFunds.gt(BigNumber.from(0))) {
56 | setDialog(`${formatEtherFixed(excessSettlementFunds.toString(), 3)} ETH available`)
57 | }
58 | }, [excessSettlementFunds])
59 |
60 | const { config } = usePrepareContractWrite({
61 | address: mintContractAddress as `0x${string}`,
62 | abi: MinterDAExpSettlementV1ABI,
63 | functionName: "purchase",
64 | overrides: {
65 | value: priceWei
66 | },
67 | enabled: !isPaused && !isSoldOut && verifyBalance && auctionHasStarted,
68 | args: [
69 | BigNumber.from(projectId)
70 | ]
71 | })
72 |
73 | let customRequest = config.request ? {
74 | data: config.request?.data,
75 | from: config.request?.from,
76 | gasLimit: multiplyBigNumberByFloat(config.request?.gasLimit, MULTIPLY_GAS_LIMIT),
77 | to: config.request?.to,
78 | value: config.request?.value
79 | } : undefined
80 |
81 | const { data, write } = useContractWrite({
82 | ...config,
83 | request: customRequest,
84 | onSuccess() {
85 | setDialog("Transaction pending...")
86 | }
87 | })
88 |
89 | useWaitForTransaction({
90 | hash: data?.hash,
91 | confirmations: 1,
92 | onSuccess(data) {
93 | let tokenId = data?.logs[0]?.topics[3]
94 | if (tokenId) {
95 | setMintingTokenId(parseInt(tokenId, 16).toString())
96 | handleMintingPreviewOpen()
97 | }
98 | setDialog("")
99 | }
100 | })
101 |
102 | const prepareClaimSettlementFunds = usePrepareContractWrite({
103 | address: mintContractAddress as `0x${string}`,
104 | abi: MinterDAExpSettlementV1ABI,
105 | functionName: "reclaimProjectExcessSettlementFunds",
106 | enabled: excessSettlementFunds.gt(BigNumber.from(0)),
107 | args: [
108 | BigNumber.from(projectId)
109 | ]
110 | })
111 | const writeClaimSettlementFunds = useContractWrite({
112 | ...prepareClaimSettlementFunds.config,
113 | onSuccess() {
114 | setDialog("Claiming settlement funds...")
115 | }
116 | })
117 | useWaitForTransaction({
118 | hash: writeClaimSettlementFunds?.data?.hash,
119 | confirmations: 1,
120 | onSuccess() {
121 | setDialog("Settlement funds claimed...")
122 | }
123 | })
124 |
125 | const isSettlementAvailable = excessSettlementFunds.gt(BigNumber.from(0))
126 |
127 | const mintingDisabled = isPaused || isSoldOut || !isConnected || !verifyBalance || !auctionHasStarted
128 | let mintingMessage = `Purchase for ${formatEtherFixed(priceWei.toString(), 3)} ${currencySymbol}`
129 | if (isSettlementAvailable) mintingMessage = "claim settlement funds"
130 | else if (isPaused) mintingMessage = "minting paused"
131 | else if (!auctionHasStarted) mintingMessage = "auction not live"
132 | else if (isSoldOut) mintingMessage = "sold out"
133 | else if (!isConnected) mintingMessage = "connect to purchase"
134 | else if (!verifyBalance) mintingMessage = "insufficient funds"
135 |
136 | return (
137 | <>
138 |
143 |
144 |
145 | {dialog}
146 |
147 |
148 |
154 |
165 |
166 |
167 | Minted #{mintingTokenId}
168 |
169 |
170 |
177 |
178 |
179 |
180 |
181 | >
182 | )
183 | }
184 |
185 | export default MinterDAExpSettlementV1Button
186 |
--------------------------------------------------------------------------------
/src/components/MinterButtons/MinterDAExpV4Button.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import { usePrepareContractWrite, useContractWrite, useWaitForTransaction } from "wagmi"
3 | import { BigNumber } from "ethers"
4 | import { Box, Typography, Modal } from "@mui/material"
5 | import { MULTIPLY_GAS_LIMIT } from "config"
6 | import { multiplyBigNumberByFloat, formatEtherFixed } from "utils/numbers"
7 | import MinterDAExpV4ABI from "abi/V3/MinterDAExpV4.json"
8 | import TokenView from "components/TokenView"
9 | import useWindowSize from "hooks/useWindowSize"
10 | import MintingButton from "components/MintingButton"
11 |
12 | interface Props {
13 | coreContractAddress: string,
14 | mintContractAddress: string,
15 | projectId: string,
16 | priceWei: BigNumber
17 | currencySymbol: string,
18 | isConnected: boolean,
19 | artistCanMint: boolean,
20 | anyoneCanMint: boolean,
21 | scriptAspectRatio: number,
22 | verifyBalance: boolean,
23 | isPaused: boolean,
24 | isSoldOut: boolean,
25 | auctionHasStarted: boolean
26 | }
27 |
28 | const MinterDAExpV4Button = (
29 | {
30 | coreContractAddress,
31 | mintContractAddress,
32 | projectId,
33 | priceWei,
34 | currencySymbol,
35 | isConnected,
36 | artistCanMint,
37 | anyoneCanMint,
38 | scriptAspectRatio,
39 | verifyBalance,
40 | isPaused,
41 | isSoldOut,
42 | auctionHasStarted
43 | }: Props
44 | ) => {
45 | const windowSize = useWindowSize()
46 | const [dialog, setDialog] = useState("")
47 | const [mintingTokenId, setMintingTokenId] = useState(null)
48 | const [mintingPreview, setMintingPreview] = useState(false)
49 | const handleMintingPreviewOpen = () => setMintingPreview(true)
50 | const handleMintingPreviewClose = () => setMintingPreview(false)
51 |
52 | const { config } = usePrepareContractWrite({
53 | address: mintContractAddress as `0x${string}`,
54 | abi: MinterDAExpV4ABI,
55 | functionName: "purchase",
56 | overrides: {
57 | value: priceWei
58 | },
59 | enabled: !isPaused && !isSoldOut && verifyBalance && auctionHasStarted,
60 | args: [
61 | BigNumber.from(projectId)
62 | ]
63 | })
64 |
65 | let customRequest = config.request ? {
66 | data: config.request?.data,
67 | from: config.request?.from,
68 | gasLimit: multiplyBigNumberByFloat(config.request?.gasLimit, MULTIPLY_GAS_LIMIT),
69 | to: config.request?.to,
70 | value: config.request?.value
71 | } : undefined
72 |
73 | const { data, write } = useContractWrite({
74 | ...config,
75 | request: customRequest,
76 | onSuccess() {
77 | setDialog("Transaction pending...")
78 | }
79 | })
80 |
81 | useWaitForTransaction({
82 | hash: data?.hash,
83 | confirmations: 1,
84 | onSuccess(data) {
85 | let tokenId = data?.logs[0]?.topics[3]
86 | if (tokenId) {
87 | setMintingTokenId(parseInt(tokenId, 16).toString())
88 | handleMintingPreviewOpen()
89 | }
90 | setDialog("")
91 | }
92 | })
93 |
94 | const mintingDisabled = isPaused || isSoldOut || !isConnected || !verifyBalance || !auctionHasStarted
95 | let mintingMessage = `Purchase for ${formatEtherFixed(priceWei.toString(), 3)} ${currencySymbol}`
96 | if (isPaused) mintingMessage = "minting paused"
97 | else if (!auctionHasStarted) mintingMessage = "auction not live"
98 | else if (isSoldOut) mintingMessage = "sold out"
99 | else if (!isConnected) mintingMessage = "connect to purchase"
100 | else if (!verifyBalance) mintingMessage = "insufficient funds"
101 |
102 | return (
103 | <>
104 |
109 |
110 |
111 | {dialog}
112 |
113 |
114 |
120 |
131 |
132 |
133 | Minted #{mintingTokenId}
134 |
135 |
136 |
143 |
144 |
145 |
146 |
147 | >
148 | )
149 | }
150 |
151 | export default MinterDAExpV4Button
152 |
--------------------------------------------------------------------------------
/src/components/MinterButtons/MinterHolderV4Button.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import { usePrepareContractWrite, useContractWrite, useWaitForTransaction } from "wagmi"
3 | import { BigNumber } from "ethers"
4 | import { Box, Typography, Modal } from "@mui/material"
5 | import { MULTIPLY_GAS_LIMIT } from "config"
6 | import { multiplyBigNumberByFloat, formatEtherFixed } from "utils/numbers"
7 | import MinterHolderV4ABI from "abi/V3/MinterHolderV4.json"
8 | import TokenView from "components/TokenView"
9 | import useWindowSize from "hooks/useWindowSize"
10 | import MintingButton from "components/MintingButton"
11 |
12 | interface Props {
13 | coreContractAddress: string,
14 | mintContractAddress: string,
15 | projectId: string,
16 | priceWei: BigNumber
17 | currencySymbol: string,
18 | isConnected: boolean,
19 | artistCanMint: boolean,
20 | anyoneCanMint: boolean,
21 | scriptAspectRatio: number,
22 | verifyBalance: boolean,
23 | isPaused: boolean,
24 | isSoldOut: boolean,
25 | holderContractAddress: string,
26 | holderTokenId: string
27 | }
28 |
29 | const MinterHolderV4Button = (
30 | {
31 | coreContractAddress,
32 | mintContractAddress,
33 | projectId,
34 | priceWei,
35 | currencySymbol,
36 | isConnected,
37 | artistCanMint,
38 | anyoneCanMint,
39 | scriptAspectRatio,
40 | verifyBalance,
41 | isPaused,
42 | isSoldOut,
43 | holderContractAddress,
44 | holderTokenId
45 | }: Props
46 | ) => {
47 | const windowSize = useWindowSize()
48 | const [dialog, setDialog] = useState("")
49 | const [mintingTokenId, setMintingTokenId] = useState(null)
50 | const [mintingPreview, setMintingPreview] = useState(false)
51 | const handleMintingPreviewOpen = () => setMintingPreview(true)
52 | const handleMintingPreviewClose = () => setMintingPreview(false)
53 |
54 | const { config } = usePrepareContractWrite({
55 | address: mintContractAddress as `0x${string}`,
56 | abi: MinterHolderV4ABI,
57 | functionName: "purchase",
58 | overrides: {
59 | value: priceWei
60 | },
61 | enabled: (!isPaused || artistCanMint) && !isSoldOut && verifyBalance && holderContractAddress !== undefined && holderTokenId !== undefined,
62 | args: [
63 | BigNumber.from(projectId),
64 | holderContractAddress,
65 | BigNumber.from(holderTokenId || 0)
66 | ]
67 | })
68 |
69 | let customRequest = config.request ? {
70 | data: config.request?.data,
71 | from: config.request?.from,
72 | gasLimit: multiplyBigNumberByFloat(config.request?.gasLimit, MULTIPLY_GAS_LIMIT),
73 | to: config.request?.to,
74 | value: config.request?.value
75 | } : undefined
76 |
77 | const { data, write } = useContractWrite({
78 | ...config,
79 | request: customRequest,
80 | onSuccess() {
81 | setDialog("Transaction pending...")
82 | }
83 | })
84 |
85 | useWaitForTransaction({
86 | hash: data?.hash,
87 | confirmations: 1,
88 | onSuccess(data) {
89 | let tokenId = data?.logs[0]?.topics[3]
90 | if (tokenId) {
91 | setMintingTokenId(parseInt(tokenId, 16).toString())
92 | handleMintingPreviewOpen()
93 | }
94 | setDialog("")
95 | }
96 | })
97 |
98 | const mintingDisabled = isPaused || isSoldOut || !isConnected || !verifyBalance || holderContractAddress === ""
99 | let mintingMessage = `${artistCanMint ? "Artist Mint " : "Purchase "} for ${formatEtherFixed(priceWei.toString(), 3)} ${currencySymbol}`
100 | if (isPaused && !artistCanMint) mintingMessage = "minting paused"
101 | else if (isSoldOut) mintingMessage = "sold out"
102 | else if (!isConnected) mintingMessage = "connect to purchase"
103 | else if (!verifyBalance) mintingMessage = "insufficient funds"
104 | else if (holderContractAddress === "") mintingMessage = "no NFTs held"
105 |
106 | return (
107 | <>
108 |
113 |
114 |
115 | {dialog}
116 |
117 |
118 |
124 |
135 |
136 |
137 | Minted #{mintingTokenId}
138 |
139 |
140 |
147 |
148 |
149 |
150 |
151 | >
152 | )
153 | }
154 |
155 | export default MinterHolderV4Button
156 |
--------------------------------------------------------------------------------
/src/components/MinterButtons/MinterMerkleV5Button.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import { usePrepareContractWrite, useContractWrite, useWaitForTransaction } from "wagmi"
3 | import { BigNumber } from "ethers"
4 | import {
5 | Box,
6 | Typography,
7 | Modal
8 | } from "@mui/material"
9 | import { MULTIPLY_GAS_LIMIT } from "config"
10 | import { multiplyBigNumberByFloat, formatEtherFixed } from "utils/numbers"
11 | import MinterMerkleV5ABI from "abi/V3/MinterMerkleV5.json"
12 | import TokenView from "components/TokenView"
13 | import useWindowSize from "hooks/useWindowSize"
14 | import MintingButton from "components/MintingButton"
15 |
16 | interface Props {
17 | coreContractAddress: string,
18 | mintContractAddress: string,
19 | projectId: string,
20 | priceWei: BigNumber
21 | currencySymbol: string,
22 | isConnected: boolean,
23 | artistCanMint: boolean,
24 | anyoneCanMint: boolean,
25 | scriptAspectRatio: number,
26 | verifyAddress: boolean,
27 | remainingInvocations: number,
28 | merkleProof: string | null,
29 | verifyBalance: boolean,
30 | isPaused: boolean,
31 | isSoldOut: boolean
32 | }
33 |
34 | const MinterMerkleV5Button = (
35 | {
36 | coreContractAddress,
37 | mintContractAddress,
38 | projectId,
39 | priceWei,
40 | currencySymbol,
41 | isConnected,
42 | artistCanMint,
43 | anyoneCanMint,
44 | scriptAspectRatio,
45 | verifyAddress,
46 | remainingInvocations,
47 | merkleProof,
48 | verifyBalance,
49 | isPaused,
50 | isSoldOut
51 | }: Props
52 | ) => {
53 | const windowSize = useWindowSize()
54 | const [dialog, setDialog] = useState("")
55 | const [mintingTokenId, setMintingTokenId] = useState(null)
56 | const [mintingPreview, setMintingPreview] = useState(false)
57 | const handleMintingPreviewOpen = () => setMintingPreview(true)
58 | const handleMintingPreviewClose = () => setMintingPreview(false)
59 |
60 | const { config } = usePrepareContractWrite({
61 | address: mintContractAddress as `0x${string}`,
62 | abi: MinterMerkleV5ABI,
63 | functionName: "purchase",
64 | overrides: {
65 | value: priceWei
66 | },
67 | enabled: (!isPaused || artistCanMint) && !isSoldOut && verifyAddress && remainingInvocations > 0 && verifyBalance,
68 | args: [
69 | BigNumber.from(projectId),
70 | merkleProof
71 | ]
72 | })
73 |
74 | let customRequest = config.request ? {
75 | data: config.request?.data,
76 | from: config.request?.from,
77 | gasLimit: multiplyBigNumberByFloat(config.request?.gasLimit, MULTIPLY_GAS_LIMIT),
78 | to: config.request?.to,
79 | value: config.request?.value
80 | } : undefined
81 |
82 | const { data, write } = useContractWrite({
83 | ...config,
84 | request: customRequest,
85 | onSuccess() {
86 | setDialog("Transaction pending...")
87 | }
88 | })
89 |
90 | useWaitForTransaction({
91 | hash: data?.hash,
92 | confirmations: 1,
93 | onSuccess(data) {
94 | let tokenId = data?.logs[0]?.topics[3]
95 | if (tokenId) {
96 | setMintingTokenId(parseInt(tokenId, 16).toString())
97 | handleMintingPreviewOpen()
98 | }
99 | setDialog("")
100 | }
101 | })
102 |
103 | const mintingDisabled = isPaused || isSoldOut || !isConnected || !verifyAddress || !verifyBalance || remainingInvocations === 0
104 | let mintingMessage = `${artistCanMint ? "Artist Mint " : "Purchase "} for ${formatEtherFixed(priceWei.toString(), 3)} ${currencySymbol}`
105 | if (isPaused && !artistCanMint) mintingMessage = "minting paused"
106 | else if (isSoldOut) mintingMessage = "sold out"
107 | else if (!isConnected) mintingMessage = "connect to purchase"
108 | else if (!verifyAddress) mintingMessage = "address not whitelisted"
109 | else if (!verifyBalance) mintingMessage = "insufficient funds"
110 | else if (remainingInvocations === 0) mintingMessage = "mint limit reached"
111 |
112 | return (
113 | <>
114 |
119 |
120 |
121 | {dialog === "" && remainingInvocations > 0 ? `whitelisted for ${remainingInvocations} ${remainingInvocations === 1 ? "mint" : "mints"}` : dialog}
122 |
123 |
124 |
130 |
141 |
142 |
143 | Minted #{mintingTokenId}
144 |
145 |
146 |
153 |
154 |
155 |
156 |
157 | >
158 | )
159 | }
160 |
161 | export default MinterMerkleV5Button
162 |
--------------------------------------------------------------------------------
/src/components/MinterButtons/MinterSetPriceERC20V4Button.tsx:
--------------------------------------------------------------------------------
1 | import {useEffect, useState} from "react"
2 | import {
3 | usePrepareContractWrite,
4 | useContractWrite,
5 | useWaitForTransaction,
6 | useAccount,
7 | useBalance,
8 | useContractRead
9 | } from "wagmi"
10 | import { BigNumber } from "ethers"
11 | import { Box, Typography, Modal } from "@mui/material"
12 | import { MULTIPLY_GAS_LIMIT } from "config"
13 | import { multiplyBigNumberByFloat, formatEtherFixed } from "utils/numbers"
14 | import MinterSetPriceERC20V4ABI from "abi/V3/MinterSetPriceERC20V4.json"
15 | import TokenView from "components/TokenView"
16 | import useWindowSize from "hooks/useWindowSize"
17 | import MintingButton from "components/MintingButton"
18 | import ERC20ABI from "../../abi/ERC20.json";
19 |
20 | interface Props {
21 | coreContractAddress: string,
22 | mintContractAddress: string,
23 | projectId: string,
24 | priceWei: BigNumber
25 | currencySymbol: string,
26 | currencyAddress: string,
27 | isConnected: boolean,
28 | artistCanMint: boolean,
29 | anyoneCanMint: boolean,
30 | scriptAspectRatio: number,
31 | isPaused: boolean,
32 | isSoldOut: boolean
33 | }
34 |
35 | const MinterSetPriceERC20V4Button = (
36 | {
37 | coreContractAddress,
38 | mintContractAddress,
39 | projectId,
40 | priceWei,
41 | currencySymbol,
42 | currencyAddress,
43 | isConnected,
44 | artistCanMint,
45 | anyoneCanMint,
46 | scriptAspectRatio,
47 | isPaused,
48 | isSoldOut
49 | }: Props
50 | ) => {
51 | const windowSize = useWindowSize()
52 | const [dialog, setDialog] = useState("")
53 | const [mintingTokenId, setMintingTokenId] = useState(null)
54 | const [mintingPreview, setMintingPreview] = useState(false)
55 | const handleMintingPreviewOpen = () => setMintingPreview(true)
56 | const handleMintingPreviewClose = () => setMintingPreview(false)
57 |
58 | const account = useAccount()
59 | const balance = useBalance({
60 | address: account.address,
61 | token: currencyAddress as `0x${string}`
62 | })
63 | const [isBalanceVerified, setIsBalanceVerified] = useState(false)
64 | const [isAllowanceVerified, setIsAllowanceVerified] = useState(false)
65 |
66 | useEffect(() => {
67 | if (balance?.data?.value.gt(priceWei)) {
68 | setIsBalanceVerified(true)
69 | }
70 | }, [balance, priceWei])
71 |
72 | useContractRead({
73 | address: currencyAddress as `0x${string}`,
74 | abi: ERC20ABI,
75 | functionName: "allowance",
76 | args: [account.address, mintContractAddress],
77 | watch: true,
78 | enabled: (!isPaused || artistCanMint) && !isSoldOut,
79 | onSuccess(data: BigNumber) {
80 | setIsAllowanceVerified(data >= priceWei)
81 | }
82 | })
83 |
84 | const erc20PrepareApprove = usePrepareContractWrite({
85 | address: currencyAddress as `0x${string}`,
86 | abi: ERC20ABI,
87 | functionName: "approve",
88 | enabled: (!isPaused || artistCanMint) && !isSoldOut && !isAllowanceVerified && isBalanceVerified,
89 | args: [
90 | mintContractAddress, BigNumber.from(priceWei)
91 | ]
92 | })
93 | const erc20WriteApprove = useContractWrite({
94 | ...erc20PrepareApprove.config,
95 | onSuccess() {
96 | setDialog("Approving ERC20...")
97 | }
98 | })
99 | useWaitForTransaction({
100 | hash: erc20WriteApprove?.data?.hash,
101 | confirmations: 1,
102 | onSuccess() {
103 | setDialog("ERC20 Approved...")
104 | }
105 | })
106 |
107 | const { config } = usePrepareContractWrite({
108 | address: mintContractAddress as `0x${string}`,
109 | abi: MinterSetPriceERC20V4ABI,
110 | functionName: "purchase",
111 | enabled: (!isPaused || artistCanMint) && !isSoldOut && isBalanceVerified && isAllowanceVerified,
112 | args: [
113 | BigNumber.from(projectId)
114 | ]
115 | })
116 |
117 | let customRequest = config.request ? {
118 | data: config.request?.data,
119 | from: config.request?.from,
120 | gasLimit: multiplyBigNumberByFloat(config.request?.gasLimit, MULTIPLY_GAS_LIMIT),
121 | to: config.request?.to,
122 | value: config.request?.value
123 | } : undefined
124 |
125 | const { data, write } = useContractWrite({
126 | ...config,
127 | request: customRequest,
128 | onSuccess() {
129 | setDialog("Transaction pending...")
130 | }
131 | })
132 |
133 | useWaitForTransaction({
134 | hash: data?.hash,
135 | confirmations: 1,
136 | onSuccess(data) {
137 | let tokenId = data?.logs[0]?.topics[3]
138 | if (tokenId) {
139 | setMintingTokenId(parseInt(tokenId, 16).toString())
140 | handleMintingPreviewOpen()
141 | }
142 | setDialog("")
143 | }
144 | })
145 |
146 | const mintingDisabled = isPaused || isSoldOut || !isConnected || !isBalanceVerified
147 | let mintingMessage = `${artistCanMint ? "Artist Mint " : "Purchase "} for ${formatEtherFixed(priceWei.toString(), 3)} ${currencySymbol}`
148 | if (isPaused && !artistCanMint) mintingMessage = "minting paused"
149 | else if (isSoldOut) mintingMessage = "sold out"
150 | else if (!isConnected) mintingMessage = "connect to purchase"
151 | else if (!isBalanceVerified) mintingMessage = "insufficient funds"
152 | else if (!isAllowanceVerified) mintingMessage = "set ERC20 allowance"
153 |
154 | return (
155 | <>
156 |
161 |
162 |
163 | {dialog}
164 |
165 |
166 |
172 |
183 |
184 |
185 | Minted #{mintingTokenId}
186 |
187 |
188 |
195 |
196 |
197 |
198 |
199 | >
200 | )
201 | }
202 |
203 | export default MinterSetPriceERC20V4Button
204 |
--------------------------------------------------------------------------------
/src/components/MinterButtons/MinterSetPriceV4Button.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import { usePrepareContractWrite, useContractWrite, useWaitForTransaction } from "wagmi"
3 | import { BigNumber } from "ethers"
4 | import { Box, Typography, Modal } from "@mui/material"
5 | import { MULTIPLY_GAS_LIMIT } from "config"
6 | import { multiplyBigNumberByFloat, formatEtherFixed } from "utils/numbers"
7 | import MinterMerkleV5ABI from "abi/V3/MinterMerkleV5.json"
8 | import TokenView from "components/TokenView"
9 | import useWindowSize from "hooks/useWindowSize"
10 | import MintingButton from "components/MintingButton"
11 |
12 | interface Props {
13 | coreContractAddress: string,
14 | mintContractAddress: string,
15 | projectId: string,
16 | priceWei: BigNumber
17 | currencySymbol: string,
18 | isConnected: boolean,
19 | artistCanMint: boolean,
20 | anyoneCanMint: boolean,
21 | scriptAspectRatio: number,
22 | verifyBalance: boolean,
23 | isPaused: boolean,
24 | isSoldOut: boolean
25 | }
26 |
27 | const MinterSetPriceV4Button = (
28 | {
29 | coreContractAddress,
30 | mintContractAddress,
31 | projectId,
32 | priceWei,
33 | currencySymbol,
34 | isConnected,
35 | artistCanMint,
36 | anyoneCanMint,
37 | scriptAspectRatio,
38 | verifyBalance,
39 | isPaused,
40 | isSoldOut
41 | }: Props
42 | ) => {
43 | const windowSize = useWindowSize()
44 | const [dialog, setDialog] = useState("")
45 | const [mintingTokenId, setMintingTokenId] = useState(null)
46 | const [mintingPreview, setMintingPreview] = useState(false)
47 | const handleMintingPreviewOpen = () => setMintingPreview(true)
48 | const handleMintingPreviewClose = () => setMintingPreview(false)
49 |
50 | const { config } = usePrepareContractWrite({
51 | address: mintContractAddress as `0x${string}`,
52 | abi: MinterMerkleV5ABI,
53 | functionName: "purchase",
54 | overrides: {
55 | value: priceWei
56 | },
57 | enabled: (!isPaused || artistCanMint) && !isSoldOut && verifyBalance,
58 | args: [
59 | BigNumber.from(projectId)
60 | ]
61 | })
62 |
63 | let customRequest = config.request ? {
64 | data: config.request?.data,
65 | from: config.request?.from,
66 | gasLimit: multiplyBigNumberByFloat(config.request?.gasLimit, MULTIPLY_GAS_LIMIT),
67 | to: config.request?.to,
68 | value: config.request?.value
69 | } : undefined
70 |
71 | const { data, write } = useContractWrite({
72 | ...config,
73 | request: customRequest,
74 | onSuccess() {
75 | setDialog("Transaction pending...")
76 | }
77 | })
78 |
79 | useWaitForTransaction({
80 | hash: data?.hash,
81 | confirmations: 1,
82 | onSuccess(data) {
83 | let tokenId = data?.logs[0]?.topics[3]
84 | if (tokenId) {
85 | setMintingTokenId(parseInt(tokenId, 16).toString())
86 | handleMintingPreviewOpen()
87 | }
88 | setDialog("")
89 | }
90 | })
91 |
92 | const mintingDisabled = isPaused || isSoldOut || !isConnected || !verifyBalance
93 | let mintingMessage = `${artistCanMint ? "Artist Mint " : "Purchase "} for ${formatEtherFixed(priceWei.toString(), 3)} ${currencySymbol}`
94 | if (isPaused && !artistCanMint) mintingMessage = "minting paused"
95 | else if (isSoldOut) mintingMessage = "sold out"
96 | else if (!isConnected) mintingMessage = "connect to purchase"
97 | else if (!verifyBalance) mintingMessage = "insufficient funds"
98 |
99 | return (
100 | <>
101 |
106 |
107 |
108 | {dialog}
109 |
110 |
111 |
117 |
128 |
129 |
130 | Minted #{mintingTokenId}
131 |
132 |
133 |
140 |
141 |
142 |
143 |
144 | >
145 | )
146 | }
147 |
148 | export default MinterSetPriceV4Button
149 |
--------------------------------------------------------------------------------
/src/components/MinterInterfaces/GenArt721MinterInterface.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import { useAccount, useContractReads } from "wagmi"
3 | import { BigNumber } from "ethers"
4 | import { Box } from "@mui/material"
5 | import MintingProgress from "components/MintingProgress"
6 | import MintingPrice from "components/MintingPrice"
7 | import GenArt721CoreV2ABI from "abi/V2/GenArt721CoreV2.json"
8 | import GenArt721MinterButton from "components/MinterButtons/GenArt721MinterButton"
9 |
10 | interface Props {
11 | coreContractAddress: string,
12 | mintContractAddress: string,
13 | projectId: string,
14 | artistAddress: string,
15 | scriptAspectRatio: number
16 | }
17 |
18 | const GenArt721MinterInterface = (
19 | {
20 | coreContractAddress,
21 | mintContractAddress,
22 | projectId,
23 | artistAddress,
24 | scriptAspectRatio
25 | }: Props
26 | ) => {
27 |
28 | const account = useAccount()
29 |
30 | const [projectDetails, setProjectDetails] = useState(null)
31 | const [projectTokenInfo, setProjectTokenInfo] = useState(null)
32 | const [projectScriptInfo, setProjectScriptInfo] = useState(null)
33 |
34 | const { data, isError, isLoading } = useContractReads({
35 | contracts: [
36 | {
37 | address: coreContractAddress as `0x${string}`,
38 | abi: GenArt721CoreV2ABI,
39 | functionName: "projectDetails",
40 | args: [BigNumber.from(projectId)]
41 | },
42 | {
43 | address: coreContractAddress as `0x${string}`,
44 | abi: GenArt721CoreV2ABI,
45 | functionName: "projectTokenInfo",
46 | args: [BigNumber.from(projectId)]
47 | },
48 | {
49 | address: coreContractAddress as `0x${string}`,
50 | abi: GenArt721CoreV2ABI,
51 | functionName: "projectScriptInfo",
52 | args: [BigNumber.from(projectId)]
53 | }
54 | ],
55 | watch: true,
56 | onSuccess(data) {
57 | setProjectDetails(data[0])
58 | setProjectTokenInfo(data[1])
59 | setProjectScriptInfo(data[2])
60 | }
61 | })
62 |
63 | if (!data || !projectDetails || !projectTokenInfo || !projectScriptInfo || isLoading || isError) {
64 | return null
65 | }
66 |
67 | const invocations = projectTokenInfo.invocations.toNumber()
68 | const maxInvocations = projectTokenInfo.maxInvocations.toNumber()
69 | const maxHasBeenInvoked = invocations >= maxInvocations
70 | const currencySymbol = projectTokenInfo.currency
71 | const currencyAddress = projectTokenInfo.currencyAddress
72 | const currentPriceWei = projectTokenInfo.pricePerTokenInWei
73 | const isPaused = projectScriptInfo.paused
74 | const isArtist = account.isConnected && account.address?.toLowerCase() === artistAddress?.toLowerCase()
75 | const isNotArtist = account.isConnected && account.address?.toLowerCase() !== artistAddress?.toLowerCase()
76 | const artistCanMint = isArtist && !maxHasBeenInvoked
77 | const anyoneCanMint = isNotArtist && !maxHasBeenInvoked && !isPaused
78 |
79 | return (
80 |
81 |
86 |
92 |
106 |
107 | )
108 | }
109 |
110 | export default GenArt721MinterInterface
111 |
--------------------------------------------------------------------------------
/src/components/MinterInterfaces/MinterDAExpSettlementV1Interface.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import moment from "moment-timezone"
3 | import {useAccount, useBalance, useContractRead, useContractReads} from "wagmi"
4 | import { BigNumber } from "ethers"
5 | import { Box } from "@mui/material"
6 | import GenArt721CoreV3_EngineABI from "abi/V3/GenArt721CoreV3_Engine.json"
7 | import MinterDAExpSettlementV1ABI from "abi/V3/MinterDAExpSettlementV1.json"
8 | import MintingCountdown from "components/MintingCountdown"
9 | import MintingProgress from "components/MintingProgress"
10 | import MintingPrice from "components/MintingPrice"
11 | import MinterDAExpSettlementV1Button from "components/MinterButtons/MinterDAExpSettlementV1Button"
12 | import useCountOwnedTokens from "../../hooks/useCountOwnedTokens";
13 |
14 | interface Props {
15 | coreContractAddress: string,
16 | mintContractAddress: string,
17 | projectId: string,
18 | artistAddress: string,
19 | scriptAspectRatio: number
20 | }
21 |
22 | const MinterDAExpSettlementV1Interface = (
23 | {
24 | coreContractAddress,
25 | mintContractAddress,
26 | projectId,
27 | artistAddress,
28 | scriptAspectRatio
29 | }: Props
30 | ) => {
31 |
32 | const account = useAccount()
33 | const balance = useBalance({
34 | address: account.address
35 | })
36 |
37 | const [projectStateData, setProjectStateData] = useState(null)
38 | const [projectPriceInfo, setProjectPriceInfo] = useState(null)
39 | const [projectConfig, setProjectConfig] = useState(null)
40 | const [projectExcessSettlementFunds, setProjectExcessSettlementFunds] = useState(BigNumber.from(0))
41 | const countOwnedTokensResponse = useCountOwnedTokens(`${coreContractAddress}-${projectId}`, account?.address?.toLowerCase() || "")
42 |
43 | const { data, isError, isLoading } = useContractReads({
44 | contracts: [
45 | {
46 | address: coreContractAddress as `0x${string}`,
47 | abi: GenArt721CoreV3_EngineABI,
48 | functionName: "projectStateData",
49 | args: [BigNumber.from(projectId)]
50 | },
51 | {
52 | address: mintContractAddress as `0x${string}`,
53 | abi: MinterDAExpSettlementV1ABI,
54 | functionName: "getPriceInfo",
55 | args: [BigNumber.from(projectId)]
56 | },
57 | {
58 | address: mintContractAddress as `0x${string}`,
59 | abi: MinterDAExpSettlementV1ABI,
60 | functionName: "projectConfig",
61 | args: [BigNumber.from(projectId)]
62 | }
63 | ],
64 | watch: true,
65 | onSuccess(data) {
66 | setProjectStateData(data[0])
67 | setProjectPriceInfo(data[1])
68 | setProjectConfig(data[2])
69 | }
70 | })
71 |
72 | useContractRead({
73 | address: mintContractAddress as `0x${string}`,
74 | abi: MinterDAExpSettlementV1ABI,
75 | functionName: "getProjectExcessSettlementFunds",
76 | args: [BigNumber.from(projectId), account.address],
77 | watch: true,
78 | enabled: account.isConnected && countOwnedTokensResponse?.data?.tokens?.length > 0,
79 | onSuccess(data) {
80 | setProjectExcessSettlementFunds(data)
81 | }
82 | })
83 |
84 | if (!data || !projectStateData || !projectPriceInfo || !projectConfig || isLoading || isError) {
85 | return null
86 | }
87 |
88 | const invocations = projectStateData.invocations.toNumber()
89 | const maxInvocations = projectStateData.maxInvocations.toNumber()
90 | const maxHasBeenInvoked = projectConfig.maxHasBeenInvoked
91 | const currencySymbol = projectPriceInfo.currencySymbol
92 | const currentPriceWei = projectPriceInfo.tokenPriceInWei
93 | const priceIsConfigured = projectPriceInfo.isConfigured
94 | const startPriceWei = projectConfig.startPrice
95 | const endPriceWei = projectConfig.basePrice
96 | const auctionStartUnix = projectConfig.timestampStart.toNumber()
97 | const auctionHasStarted = auctionStartUnix <= moment().unix()
98 | const auctionStartFormatted = moment.unix(auctionStartUnix).format("LLL")
99 | const auctionStartCountdown = moment.unix(auctionStartUnix).fromNow()
100 | const isSoldOut = maxHasBeenInvoked || invocations >= maxInvocations
101 | const isPaused = projectStateData.paused
102 | const isArtist = account.isConnected && account.address?.toLowerCase() === artistAddress?.toLowerCase()
103 | const isNotArtist = account.isConnected && account.address?.toLowerCase() !== artistAddress?.toLowerCase()
104 | const artistCanMint = isArtist && priceIsConfigured && !isSoldOut && auctionHasStarted
105 | const anyoneCanMint = isNotArtist && priceIsConfigured && !isSoldOut && auctionHasStarted && !isPaused
106 |
107 | return (
108 |
109 |
114 | {
115 | priceIsConfigured && !auctionHasStarted &&
116 | (
117 |
121 | )
122 | }
123 | {
124 | priceIsConfigured &&
125 | (
126 |
132 | )
133 | }
134 |
150 |
151 | )
152 | }
153 |
154 | export default MinterDAExpSettlementV1Interface
155 |
--------------------------------------------------------------------------------
/src/components/MinterInterfaces/MinterDAExpV4Interface.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import moment from "moment-timezone"
3 | import { useAccount, useBalance, useContractReads } from "wagmi"
4 | import { BigNumber } from "ethers"
5 | import { Box } from "@mui/material"
6 | import GenArt721CoreV3_EngineABI from "abi/V3/GenArt721CoreV3_Engine.json"
7 | import MinterDAExpV4ABI from "abi/V3/MinterDAExpV4.json"
8 | import MintingCountdown from "components/MintingCountdown"
9 | import MintingProgress from "components/MintingProgress"
10 | import MintingPrice from "components/MintingPrice"
11 | import MinterDAExpV4Button from "components/MinterButtons/MinterDAExpV4Button"
12 |
13 | interface Props {
14 | coreContractAddress: string,
15 | mintContractAddress: string,
16 | projectId: string,
17 | artistAddress: string,
18 | scriptAspectRatio: number
19 | }
20 |
21 | const MinterDAExpV4Interface = (
22 | {
23 | coreContractAddress,
24 | mintContractAddress,
25 | projectId,
26 | artistAddress,
27 | scriptAspectRatio
28 | }: Props
29 | ) => {
30 |
31 | const account = useAccount()
32 | const balance = useBalance({
33 | address: account.address
34 | })
35 |
36 | const [projectStateData, setProjectStateData] = useState(null)
37 | const [projectPriceInfo, setProjectPriceInfo] = useState(null)
38 | const [projectConfig, setProjectConfig] = useState(null)
39 |
40 | const { data, isError, isLoading } = useContractReads({
41 | contracts: [
42 | {
43 | address: coreContractAddress as `0x${string}`,
44 | abi: GenArt721CoreV3_EngineABI,
45 | functionName: "projectStateData",
46 | args: [BigNumber.from(projectId)]
47 | },
48 | {
49 | address: mintContractAddress as `0x${string}`,
50 | abi: MinterDAExpV4ABI,
51 | functionName: "getPriceInfo",
52 | args: [BigNumber.from(projectId)]
53 | },
54 | {
55 | address: mintContractAddress as `0x${string}`,
56 | abi: MinterDAExpV4ABI,
57 | functionName: "projectConfig",
58 | args: [BigNumber.from(projectId)]
59 | }
60 | ],
61 | watch: true,
62 | onSuccess(data) {
63 | setProjectStateData(data[0])
64 | setProjectPriceInfo(data[1])
65 | setProjectConfig(data[2])
66 | }
67 | })
68 |
69 | if (!data || !projectStateData || !projectPriceInfo || !projectConfig || isLoading || isError) {
70 | return null
71 | }
72 |
73 | const invocations = projectStateData.invocations.toNumber()
74 | const maxInvocations = projectStateData.maxInvocations.toNumber()
75 | const maxHasBeenInvoked = projectConfig.maxHasBeenInvoked
76 | const currencySymbol = projectPriceInfo.currencySymbol
77 | const currentPriceWei = projectPriceInfo.tokenPriceInWei
78 | const priceIsConfigured = projectPriceInfo.isConfigured
79 | const startPriceWei = projectConfig.startPrice
80 | const endPriceWei = projectConfig.basePrice
81 | const auctionStartUnix = projectConfig.timestampStart.toNumber()
82 | const auctionHasStarted = auctionStartUnix <= moment().unix()
83 | const auctionStartFormatted = moment.unix(auctionStartUnix).format("LLL")
84 | const auctionStartCountdown = moment.unix(auctionStartUnix).fromNow()
85 | const isSoldOut = maxHasBeenInvoked || invocations >= maxInvocations
86 | const isPaused = projectStateData.paused
87 | const isArtist = account.isConnected && account.address?.toLowerCase() === artistAddress?.toLowerCase()
88 | const isNotArtist = account.isConnected && account.address?.toLowerCase() !== artistAddress?.toLowerCase()
89 | const artistCanMint = isArtist && priceIsConfigured && !isSoldOut && auctionHasStarted
90 | const anyoneCanMint = isNotArtist && priceIsConfigured && !isSoldOut && auctionHasStarted && !isPaused
91 |
92 | return (
93 |
94 |
99 | {
100 | priceIsConfigured && !auctionHasStarted &&
101 | (
102 |
106 | )
107 | }
108 | {
109 | priceIsConfigured &&
110 | (
111 |
117 | )
118 | }
119 |
134 |
135 | )
136 | }
137 |
138 | export default MinterDAExpV4Interface
139 |
--------------------------------------------------------------------------------
/src/components/MinterInterfaces/MinterHolderV4Interface.tsx:
--------------------------------------------------------------------------------
1 | import {useEffect, useState} from "react"
2 | import { useAccount, useBalance, useContractReads } from "wagmi"
3 | import { BigNumber, utils } from "ethers"
4 | import { Box } from "@mui/material"
5 | import GenArt721CoreV3_EngineABI from "abi/V3/GenArt721CoreV3_Engine.json"
6 | import MinterHolderV4ABI from "abi/V3/MinterHolderV4.json"
7 | import MintingProgress from "components/MintingProgress"
8 | import MintingPrice from "components/MintingPrice"
9 | import MinterHolderV4Button from "components/MinterButtons/MinterHolderV4Button"
10 | import {EXPECTED_CHAIN_ID, HOLDER_PROOF_API_URL} from "config"
11 |
12 | interface Props {
13 | coreContractAddress: string,
14 | mintContractAddress: string,
15 | projectId: string,
16 | artistAddress: string,
17 | scriptAspectRatio: number
18 | }
19 |
20 | const MinterHolderV4Interface = (
21 | {
22 | coreContractAddress,
23 | mintContractAddress,
24 | projectId,
25 | artistAddress,
26 | scriptAspectRatio
27 | }: Props
28 | ) => {
29 |
30 | const account = useAccount()
31 | const balance = useBalance({
32 | address: account.address
33 | })
34 |
35 | const [projectStateData, setProjectStateData] = useState(null)
36 | const [projectPriceInfo, setProjectPriceInfo] = useState(null)
37 | const [projectConfig, setProjectConfig] = useState(null)
38 | const [holderProof, setHolderProof] = useState(null)
39 |
40 | useEffect(() => {
41 | if (account.isConnected) {
42 | fetch(`${HOLDER_PROOF_API_URL}?contractAddress=${coreContractAddress}&projectId=${projectId}&walletAddress=${account.address}&isMainnet=${EXPECTED_CHAIN_ID === 0 ? 1 : 0}`)
43 | .then(response => response.json())
44 | .then(data => setHolderProof(data))
45 | }
46 | }, [account.isConnected, account.address, coreContractAddress, projectId])
47 |
48 | const { data, isError, isLoading } = useContractReads({
49 | contracts: [
50 | {
51 | address: coreContractAddress as `0x${string}`,
52 | abi: GenArt721CoreV3_EngineABI,
53 | functionName: "projectStateData",
54 | args: [BigNumber.from(projectId)]
55 | },
56 | {
57 | address: mintContractAddress as `0x${string}`,
58 | abi: MinterHolderV4ABI,
59 | functionName: "getPriceInfo",
60 | args: [BigNumber.from(projectId)]
61 | },
62 | {
63 | address: mintContractAddress as `0x${string}`,
64 | abi: MinterHolderV4ABI,
65 | functionName: "projectConfig",
66 | args: [BigNumber.from(projectId)]
67 | }
68 | ],
69 | watch: true,
70 | onSuccess(data) {
71 | setProjectStateData(data[0])
72 | setProjectPriceInfo(data[1])
73 | setProjectConfig(data[2])
74 | }
75 | })
76 |
77 | if (!data || !projectStateData || !projectPriceInfo || !projectConfig || isLoading || isError) {
78 | return null
79 | }
80 |
81 | const invocations = projectStateData.invocations.toNumber()
82 | const maxInvocations = projectStateData.maxInvocations.toNumber()
83 | const maxHasBeenInvoked = projectConfig.maxHasBeenInvoked
84 | const currencySymbol = projectPriceInfo.currencySymbol
85 | const currentPriceWei = projectPriceInfo.tokenPriceInWei
86 | const priceIsConfigured = projectPriceInfo.isConfigured
87 | const isSoldOut = maxHasBeenInvoked || invocations >= maxInvocations
88 | const isPaused = projectStateData.paused
89 | const isArtist = account.isConnected && account.address?.toLowerCase() === artistAddress?.toLowerCase()
90 | const isNotArtist = account.isConnected && account.address?.toLowerCase() !== artistAddress?.toLowerCase()
91 | const artistCanMint = isArtist && priceIsConfigured && !isSoldOut
92 | const anyoneCanMint = isNotArtist && priceIsConfigured && !isSoldOut && !isPaused
93 |
94 | return (
95 |
96 |
101 | {
102 | priceIsConfigured &&
103 | (
104 |
110 | )
111 | }
112 | = utils.formatEther(projectPriceInfo.tokenPriceInWei.toString())}
123 | isPaused={isPaused}
124 | isSoldOut={isSoldOut}
125 | holderContractAddress={holderProof?.contractAddress}
126 | holderTokenId={holderProof?.tokenId}
127 | />
128 |
129 | )
130 | }
131 |
132 | export default MinterHolderV4Interface
133 |
--------------------------------------------------------------------------------
/src/components/MinterInterfaces/MinterMerkleV5Interface.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react"
2 | import { useAccount, useBalance, useContractReads } from "wagmi"
3 | import { BigNumber, utils } from "ethers"
4 | import { Box } from "@mui/material"
5 | import GenArt721CoreV3_EngineABI from "abi/V3/GenArt721CoreV3_Engine.json"
6 | import MinterMerkleV5ABI from "abi/V3/MinterMerkleV5.json"
7 | import MintingProgress from "components/MintingProgress"
8 | import MintingPrice from "components/MintingPrice"
9 | import MinterMerkleV5Button from "components/MinterButtons/MinterMerkleV5Button"
10 | import { MERKLE_PROOF_API_URL } from "config"
11 |
12 | interface Props {
13 | coreContractAddress: string,
14 | mintContractAddress: string,
15 | projectId: string,
16 | artistAddress: string,
17 | scriptAspectRatio: number
18 | }
19 |
20 | const MinterMerkleV5Interface = (
21 | {
22 | coreContractAddress,
23 | mintContractAddress,
24 | projectId,
25 | artistAddress,
26 | scriptAspectRatio
27 | }: Props
28 | ) => {
29 |
30 | const account = useAccount()
31 | const balance = useBalance({
32 | address: account.address
33 | })
34 |
35 | const [projectStateData, setProjectStateData] = useState(null)
36 | const [projectPriceInfo, setProjectPriceInfo] = useState(null)
37 | const [projectConfig, setProjectConfig] = useState(null)
38 | const [verifyAddress, setVerifyAddress] = useState(false)
39 | const [remainingInvocations, setRemainingInvocations] = useState(null)
40 | const [merkleProof, setMerkleProof] = useState(null)
41 |
42 | useContractReads({
43 | contracts: [
44 | {
45 | address: mintContractAddress as `0x${string}`,
46 | abi: MinterMerkleV5ABI,
47 | functionName: "verifyAddress",
48 | args: [BigNumber.from(projectId), merkleProof, account.address],
49 | },
50 | {
51 | address: mintContractAddress as `0x${string}`,
52 | abi: MinterMerkleV5ABI,
53 | functionName: "projectRemainingInvocationsForAddress",
54 | args: [BigNumber.from(projectId), account.address],
55 | }
56 | ],
57 | enabled: merkleProof != null && account.isConnected,
58 | watch: true,
59 | onSuccess(data) {
60 | setVerifyAddress(data[0])
61 | setRemainingInvocations(data[1])
62 | }
63 | })
64 |
65 | useEffect(() => {
66 | if (account.isConnected) {
67 | fetch(`${MERKLE_PROOF_API_URL}?contractAddress=${coreContractAddress}&projectId=${projectId}&walletAddress=${account.address}`)
68 | .then(response => response.json())
69 | .then(data => setMerkleProof(data))
70 | } else {
71 | setMerkleProof(null)
72 | setVerifyAddress(false)
73 | }
74 | }, [account.address, account.isConnected]);
75 |
76 | const { data, isError, isLoading } = useContractReads({
77 | contracts: [
78 | {
79 | address: coreContractAddress as `0x${string}`,
80 | abi: GenArt721CoreV3_EngineABI,
81 | functionName: "projectStateData",
82 | args: [BigNumber.from(projectId)]
83 | },
84 | {
85 | address: mintContractAddress as `0x${string}`,
86 | abi: MinterMerkleV5ABI,
87 | functionName: "getPriceInfo",
88 | args: [BigNumber.from(projectId)]
89 | },
90 | {
91 | address: mintContractAddress as `0x${string}`,
92 | abi: MinterMerkleV5ABI,
93 | functionName: "projectConfig",
94 | args: [BigNumber.from(projectId)]
95 | }
96 | ],
97 | watch: true,
98 | onSuccess(data) {
99 | setProjectStateData(data[0])
100 | setProjectPriceInfo(data[1])
101 | setProjectConfig(data[2])
102 | }
103 | })
104 |
105 | if (!data || !projectStateData || !projectPriceInfo || !projectConfig || isLoading || isError) {
106 | return null
107 | }
108 |
109 | const invocations = projectStateData.invocations.toNumber()
110 | const maxInvocations = projectStateData.maxInvocations.toNumber()
111 | const maxHasBeenInvoked = projectConfig.maxHasBeenInvoked
112 | const currencySymbol = projectPriceInfo.currencySymbol
113 | const currentPriceWei = projectPriceInfo.tokenPriceInWei
114 | const priceIsConfigured = projectPriceInfo.isConfigured
115 | const isSoldOut = maxHasBeenInvoked || invocations >= maxInvocations
116 | const isPaused = projectStateData.paused
117 | const isArtist = account.isConnected && account.address?.toLowerCase() === artistAddress?.toLowerCase()
118 | const isNotArtist = account.isConnected && account.address?.toLowerCase() !== artistAddress?.toLowerCase()
119 | const artistCanMint = isArtist && priceIsConfigured && !isSoldOut
120 | const anyoneCanMint = isNotArtist && priceIsConfigured && !isSoldOut && !isPaused
121 |
122 | return (
123 |
124 |
129 | {
130 | priceIsConfigured &&
131 | (
132 |
138 | )
139 | }
140 | = utils.formatEther(projectPriceInfo.tokenPriceInWei.toString())}
154 | isPaused={isPaused}
155 | isSoldOut={isSoldOut}
156 | />
157 |
158 | )
159 | }
160 |
161 | export default MinterMerkleV5Interface
162 |
--------------------------------------------------------------------------------
/src/components/MinterInterfaces/MinterSetPriceERC20V4Interface.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import { useAccount, useContractReads } from "wagmi"
3 | import { BigNumber } from "ethers"
4 | import { Box } from "@mui/material"
5 | import GenArt721CoreV3_EngineABI from "abi/V3/GenArt721CoreV3_Engine.json"
6 | import MinterSetPriceERC20V4ABI from "abi/V3/MinterSetPriceERC20V4.json"
7 | import MintingProgress from "components/MintingProgress"
8 | import MintingPrice from "components/MintingPrice"
9 | import MinterSetPriceERC20V4Button from "components/MinterButtons/MinterSetPriceERC20V4Button"
10 |
11 | interface Props {
12 | coreContractAddress: string,
13 | mintContractAddress: string,
14 | projectId: string,
15 | artistAddress: string,
16 | scriptAspectRatio: number
17 | }
18 |
19 | const MinterSetPriceERC20V4Interface = (
20 | {
21 | coreContractAddress,
22 | mintContractAddress,
23 | projectId,
24 | artistAddress,
25 | scriptAspectRatio
26 | }: Props
27 | ) => {
28 |
29 | const account = useAccount()
30 |
31 | const [projectStateData, setProjectStateData] = useState(null)
32 | const [projectPriceInfo, setProjectPriceInfo] = useState(null)
33 | const [projectConfig, setProjectConfig] = useState(null)
34 |
35 | const { data, isError, isLoading } = useContractReads({
36 | contracts: [
37 | {
38 | address: coreContractAddress as `0x${string}`,
39 | abi: GenArt721CoreV3_EngineABI,
40 | functionName: "projectStateData",
41 | args: [BigNumber.from(projectId)]
42 | },
43 | {
44 | address: mintContractAddress as `0x${string}`,
45 | abi: MinterSetPriceERC20V4ABI,
46 | functionName: "getPriceInfo",
47 | args: [BigNumber.from(projectId)]
48 | },
49 | {
50 | address: mintContractAddress as `0x${string}`,
51 | abi: MinterSetPriceERC20V4ABI,
52 | functionName: "projectConfig",
53 | args: [BigNumber.from(projectId)]
54 | }
55 | ],
56 | watch: true,
57 | onSuccess(data) {
58 | setProjectStateData(data[0])
59 | setProjectPriceInfo(data[1])
60 | setProjectConfig(data[2])
61 | }
62 | })
63 |
64 | if (!data || !projectStateData || !projectPriceInfo || !projectConfig || isLoading || isError) {
65 | return null
66 | }
67 |
68 | const invocations = projectStateData.invocations.toNumber()
69 | const maxInvocations = projectStateData.maxInvocations.toNumber()
70 | const maxHasBeenInvoked = projectConfig.maxHasBeenInvoked
71 | const currencySymbol = projectPriceInfo.currencySymbol
72 | const currencyAddress = projectPriceInfo.currencyAddress
73 | const currentPriceWei = projectPriceInfo.tokenPriceInWei
74 | const priceIsConfigured = projectPriceInfo.isConfigured
75 | const isSoldOut = maxHasBeenInvoked || invocations >= maxInvocations
76 | const isPaused = projectStateData.paused
77 | const isArtist = account.isConnected && account.address?.toLowerCase() === artistAddress?.toLowerCase()
78 | const isNotArtist = account.isConnected && account.address?.toLowerCase() !== artistAddress?.toLowerCase()
79 | const artistCanMint = isArtist && priceIsConfigured && !isSoldOut
80 | const anyoneCanMint = isNotArtist && priceIsConfigured && !isSoldOut && !isPaused
81 |
82 | return (
83 |
84 |
89 | {
90 | priceIsConfigured &&
91 | (
92 |
98 | )
99 | }
100 |
114 |
115 | )
116 | }
117 |
118 | export default MinterSetPriceERC20V4Interface
119 |
--------------------------------------------------------------------------------
/src/components/MinterInterfaces/MinterSetPriceV4Interface.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import { useAccount, useBalance, useContractReads } from "wagmi"
3 | import { BigNumber, utils } from "ethers"
4 | import { Box } from "@mui/material"
5 | import GenArt721CoreV3_EngineABI from "abi/V3/GenArt721CoreV3_Engine.json"
6 | import MinterSetPriceV4ABI from "abi/V3/MinterSetPriceV4.json"
7 | import MintingProgress from "components/MintingProgress"
8 | import MintingPrice from "components/MintingPrice"
9 | import MinterSetPriceV4Button from "components/MinterButtons/MinterSetPriceV4Button"
10 |
11 | interface Props {
12 | coreContractAddress: string,
13 | mintContractAddress: string,
14 | projectId: string,
15 | artistAddress: string,
16 | scriptAspectRatio: number
17 | }
18 |
19 | const MinterSetPriceV4Interface = (
20 | {
21 | coreContractAddress,
22 | mintContractAddress,
23 | projectId,
24 | artistAddress,
25 | scriptAspectRatio
26 | }: Props
27 | ) => {
28 |
29 | const account = useAccount()
30 | const balance = useBalance({
31 | address: account.address
32 | })
33 |
34 | const [projectStateData, setProjectStateData] = useState(null)
35 | const [projectPriceInfo, setProjectPriceInfo] = useState(null)
36 | const [projectConfig, setProjectConfig] = useState(null)
37 |
38 | const { data, isError, isLoading } = useContractReads({
39 | contracts: [
40 | {
41 | address: coreContractAddress as `0x${string}`,
42 | abi: GenArt721CoreV3_EngineABI,
43 | functionName: "projectStateData",
44 | args: [BigNumber.from(projectId)]
45 | },
46 | {
47 | address: mintContractAddress as `0x${string}`,
48 | abi: MinterSetPriceV4ABI,
49 | functionName: "getPriceInfo",
50 | args: [BigNumber.from(projectId)]
51 | },
52 | {
53 | address: mintContractAddress as `0x${string}`,
54 | abi: MinterSetPriceV4ABI,
55 | functionName: "projectConfig",
56 | args: [BigNumber.from(projectId)]
57 | }
58 | ],
59 | watch: true,
60 | onSuccess(data) {
61 | setProjectStateData(data[0])
62 | setProjectPriceInfo(data[1])
63 | setProjectConfig(data[2])
64 | }
65 | })
66 |
67 | if (!data || !projectStateData || !projectPriceInfo || !projectConfig || isLoading || isError) {
68 | return null
69 | }
70 |
71 | const invocations = projectStateData.invocations.toNumber()
72 | const maxInvocations = projectStateData.maxInvocations.toNumber()
73 | const maxHasBeenInvoked = projectConfig.maxHasBeenInvoked
74 | const currencySymbol = projectPriceInfo.currencySymbol
75 | const currentPriceWei = projectPriceInfo.tokenPriceInWei
76 | const priceIsConfigured = projectPriceInfo.isConfigured
77 | const isSoldOut = maxHasBeenInvoked || invocations >= maxInvocations
78 | const isPaused = projectStateData.paused
79 | const isArtist = account.isConnected && account.address?.toLowerCase() === artistAddress?.toLowerCase()
80 | const isNotArtist = account.isConnected && account.address?.toLowerCase() !== artistAddress?.toLowerCase()
81 | const artistCanMint = isArtist && priceIsConfigured && !isSoldOut
82 | const anyoneCanMint = isNotArtist && priceIsConfigured && !isSoldOut && !isPaused
83 |
84 | return (
85 |
86 |
91 | {
92 | priceIsConfigured &&
93 | (
94 |
100 | )
101 | }
102 | = utils.formatEther(projectPriceInfo.tokenPriceInWei.toString())}
113 | isPaused={isPaused}
114 | isSoldOut={isSoldOut}
115 | />
116 |
117 | )
118 | }
119 |
120 | export default MinterSetPriceV4Interface
121 |
--------------------------------------------------------------------------------
/src/components/MintingButton.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Typography } from "@mui/material"
2 |
3 | interface Props {
4 | disabled: boolean,
5 | message: string,
6 | contractPurchase: any
7 | }
8 |
9 | const MintingButton = ({disabled, message, contractPurchase}: Props) => {
10 | return (
11 | contractPurchase?.()}
16 | sx={{
17 | minWidth: "210px",
18 | paddingTop: 1.5,
19 | paddingRight: 1,
20 | paddingLeft: 1,
21 | paddingBottom: 1.5,
22 | boxShadow: "none",
23 | textTransform: "none"
24 | }}>
25 |
26 | {message}
27 |
28 |
29 | )
30 | }
31 |
32 | export default MintingButton
33 |
--------------------------------------------------------------------------------
/src/components/MintingCountdown.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from "@mui/material"
2 |
3 | interface Props {
4 | auctionStartFormatted: any,
5 | auctionStartCountdown: any
6 | }
7 |
8 | const MintingCountdown = ({auctionStartFormatted, auctionStartCountdown}: Props) => {
9 | return (
10 |
11 | Live: {auctionStartFormatted} ({auctionStartCountdown})
12 |
13 | )
14 | }
15 |
16 | export default MintingCountdown
--------------------------------------------------------------------------------
/src/components/MintingInterfaceFilter.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import { useContractRead } from "wagmi"
3 | import { BigNumber } from "ethers"
4 | import MinterFilterV1ABI from "abi/V3/MinterFilterV1.json"
5 | import { getMintingInterface } from "utils/getMintingInterface"
6 |
7 | interface Props {
8 | contractVersion: string,
9 | coreContractAddress: string,
10 | mintContractAddress: string,
11 | projectId: string,
12 | artistAddress: string,
13 | scriptAspectRatio: number
14 | }
15 |
16 | const MintingInterfaceFilter = (
17 | {
18 | contractVersion,
19 | coreContractAddress,
20 | mintContractAddress,
21 | projectId,
22 | artistAddress,
23 | scriptAspectRatio
24 | }: Props
25 | ) => {
26 |
27 | const [v3ProjectAndMinterInfo, setV3ProjectAndMinterInfo] = useState(null)
28 | const { data, isError, isLoading } = useContractRead({
29 | address: mintContractAddress as `0x${string}`,
30 | abi: MinterFilterV1ABI,
31 | functionName: "getProjectAndMinterInfoAt",
32 | args: [BigNumber.from(projectId)],
33 | enabled: contractVersion === "V3",
34 | watch: true,
35 | onSuccess(data) {
36 | setV3ProjectAndMinterInfo(data)
37 | }
38 | })
39 |
40 | if (contractVersion === "V3" && (!data || !v3ProjectAndMinterInfo || isLoading || isError)) {
41 | return null
42 | }
43 |
44 | let minterType = null
45 | let minterAddress = mintContractAddress
46 | if (contractVersion === "V3") {
47 | if (!data || !v3ProjectAndMinterInfo || isLoading || isError) return null
48 | minterType = v3ProjectAndMinterInfo?.minterType
49 | minterAddress = v3ProjectAndMinterInfo.minterAddress
50 | }
51 |
52 | const MintingInterface = getMintingInterface(contractVersion, minterType)
53 | return MintingInterface && (
54 |
61 | )
62 | }
63 |
64 | export default MintingInterfaceFilter
65 |
--------------------------------------------------------------------------------
/src/components/MintingPrice.tsx:
--------------------------------------------------------------------------------
1 | import { utils, BigNumber } from "ethers"
2 | import {
3 | Box,
4 | LinearProgress,
5 | Typography
6 | } from "@mui/material"
7 |
8 | interface Props {
9 | startPriceWei: BigNumber,
10 | currentPriceWei: BigNumber,
11 | endPriceWei: BigNumber
12 | currencySymbol: string
13 | }
14 |
15 | const MintingPrice = ({startPriceWei, currentPriceWei, endPriceWei, currencySymbol}: Props) => {
16 | const fixedPrice = startPriceWei === endPriceWei
17 | const startToEnd = Number(startPriceWei.toBigInt()-endPriceWei.toBigInt())
18 | const startToCurrent = Number(startPriceWei.toBigInt()-currentPriceWei.toBigInt())
19 | return (
20 |
21 |
22 | {
23 | fixedPrice ?
24 | (
25 | Fixed Price: {utils.formatEther(startPriceWei.toString())} {currencySymbol}
26 | ) :
27 | (
28 | Auction Price ({currencySymbol})
29 | )
30 | }
31 |
32 | {
33 | !fixedPrice &&
34 | (
35 |
36 |
37 |
38 | {utils.formatEther(startPriceWei.toString())}
39 |
40 |
41 | {utils.formatEther(endPriceWei.toString())}
42 |
43 |
44 |
45 |
51 |
52 |
53 | )
54 | }
55 |
56 | )
57 | }
58 |
59 | export default MintingPrice
--------------------------------------------------------------------------------
/src/components/MintingProgress.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Box,
3 | Typography,
4 | LinearProgress
5 | } from "@mui/material"
6 |
7 | interface Props {
8 | invocations: number,
9 | maxInvocations: number,
10 | maxHasBeenInvoked: boolean
11 | }
12 |
13 | const MintingProgress = ({invocations, maxInvocations, maxHasBeenInvoked}: Props) => {
14 | return (
15 |
16 |
17 |
18 | {invocations.toString()} / {maxInvocations.toString()} minted
19 |
20 |
21 |
22 |
28 |
29 |
30 | )
31 | }
32 |
33 | export default MintingProgress
--------------------------------------------------------------------------------
/src/components/OwnedProjects.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react"
2 | import {
3 | Box,
4 | Typography,
5 | Alert,
6 | FormControl,
7 | NativeSelect,
8 | Pagination,
9 | Grid,
10 | Link
11 | } from "@mui/material"
12 | import { PROJECTS_PER_PAGE } from "config"
13 | import { OrderDirection, Project } from "utils/types"
14 | import Loading from "components/Loading"
15 | import OwnedTokens from "components/OwnedTokens"
16 | import useOwnedProjects from "hooks/useOwnedProjects"
17 | import useCountOwnedProjects from "hooks/useCountOwnedProjects"
18 | import { parseAspectRatio } from "utils/scriptJSON"
19 |
20 | interface Props {
21 | walletAddress: string
22 | }
23 |
24 | const OwnedProjects = ({ walletAddress }: Props) => {
25 | const [countOwnedProjects, setCountOwnedProjects] = useState(0)
26 | const [currentPage, setCurrentPage] = useState(0)
27 | const skip = currentPage * PROJECTS_PER_PAGE
28 | const first = PROJECTS_PER_PAGE
29 | const [orderDirection, setOrderDirection] = useState(OrderDirection.DESC)
30 | const { loading, error, data } = useOwnedProjects(walletAddress, {skip, first, orderDirection})
31 | const [filteredProjects, setFilteredProjects] = useState([])
32 | const countOwnedProjectsResponse = useCountOwnedProjects(walletAddress)
33 |
34 | useEffect(() => {
35 | setFilteredProjects(data?.projects?.filter((project: { tokens: string | any[] }) => {
36 | return project.tokens.length > 0
37 | }))
38 | setCountOwnedProjects(countOwnedProjectsResponse.data?.projects?.filter((project: { tokens: string | any[] }) => {
39 | return project.tokens.length > 0
40 | }).length)
41 | }, [data, countOwnedProjectsResponse])
42 |
43 | return (
44 |
45 |
46 |
47 |
48 |
49 | {
50 | !error && !loading && filteredProjects?.length > 0 &&
51 | (
52 |
53 | {
57 | setCurrentPage(0)
58 | setOrderDirection(e.target.value as OrderDirection)
59 | }}
60 | >
61 | Newest
62 | Oldest
63 |
64 |
65 | )
66 | }
67 |
68 |
69 |
70 |
71 | {
72 | loading ?
73 | (
74 |
75 |
76 |
77 | ) :
78 | error ?
79 | (
80 |
81 |
82 | Error loading projects
83 |
84 |
85 | ) :
86 | filteredProjects?.length > 0 ?
87 | (
88 |
89 | {
90 | filteredProjects && (
91 | filteredProjects.map((project: Project) => (
92 |
93 |
94 | {project.name} by {project.artistName}
95 |
96 |
97 |
98 | ))
99 | )
100 | }
101 |
102 | ) :
103 | filteredProjects?.length === 0 ? (
104 |
105 |
106 | No projects found
107 |
108 |
109 | ) :
110 | null
111 | }
112 | {
113 | !error && !loading && filteredProjects?.length > 0 && (
114 |
115 | {
120 | window.scrollTo(0, 0)
121 | setCurrentPage(page - 1)
122 | }}/>
123 |
124 | )
125 | }
126 |
127 |
128 | )
129 | }
130 |
131 | export default OwnedProjects
132 |
--------------------------------------------------------------------------------
/src/components/OwnedTokens.tsx:
--------------------------------------------------------------------------------
1 | import useTheme from "@mui/material/styles/useTheme"
2 | import { OrderDirection, Token } from "utils/types"
3 | import {
4 | Grid,
5 | Link,
6 | Alert,
7 | Typography,
8 | Box,
9 | Pagination
10 | } from "@mui/material"
11 | import Loading from "components/Loading"
12 | import TokenView from "components/TokenView"
13 | import useWindowSize from "hooks/useWindowSize"
14 | import useOwnedTokens from "hooks/useOwnedTokens"
15 | import useCountOwnedTokens from "hooks/useCountOwnedTokens"
16 | import { useEffect, useState } from "react"
17 |
18 | interface Props {
19 | contractAddress: string
20 | projectId: string
21 | walletAddress: string
22 | aspectRatio: number
23 | }
24 |
25 | const OwnedTokens = ({
26 | contractAddress,
27 | projectId,
28 | walletAddress,
29 | aspectRatio
30 | }: Props) => {
31 | const OWNED_TOKENS_PER_PAGE = 3
32 | const theme = useTheme()
33 | const windowSize = useWindowSize()
34 | const [currentPage, setCurrentPage] = useState(0)
35 | const [countOwnedTokens, setCountOwnedTokens] = useState(0)
36 | const skip = currentPage * OWNED_TOKENS_PER_PAGE
37 | const first = OWNED_TOKENS_PER_PAGE
38 | const [orderDirection, setOrderDirection] = useState(OrderDirection.ASC)
39 | const {loading, error, data } = useOwnedTokens(projectId, walletAddress, {
40 | first,
41 | skip,
42 | orderDirection
43 | })
44 | const countOwnedTokensResponse = useCountOwnedTokens(projectId, walletAddress)
45 |
46 | useEffect(() => {
47 | setCountOwnedTokens(countOwnedTokensResponse?.data?.tokens?.length)
48 | }, [countOwnedTokensResponse])
49 |
50 | if (loading) {
51 | return
52 | }
53 |
54 | if (error) {
55 | return (
56 |
57 | Error loading tokens
58 |
59 | )
60 | }
61 |
62 | if (!data || !data.tokens) {
63 | return (
64 |
65 | No tokens found for this project.
66 |
67 | )
68 | }
69 |
70 | // todo: need to fix this to properly size thumbnails for mobile
71 | let width = 280
72 | if (windowSize && !isNaN(windowSize.width)) {
73 | width = windowSize.width > theme.breakpoints.values.md
74 | ? (Math.min(windowSize.width, 1200)-96) / 3
75 | : (windowSize.width-60) / 2
76 | }
77 |
78 | return (
79 |
80 |
81 | {
82 | data.tokens.map(((token: Token) => (
83 |
84 |
85 |
91 |
92 |
93 | #{token.invocation.toString()}
94 |
95 |
96 | )))
97 | }
98 |
99 | {
100 | !countOwnedTokensResponse.error && !countOwnedTokensResponse.loading && countOwnedTokens && (
101 |
102 | {
107 | setCurrentPage(page - 1)
108 | }}/>
109 |
110 | )
111 | }
112 |
113 | )
114 | }
115 |
116 | export default OwnedTokens
117 |
--------------------------------------------------------------------------------
/src/components/Page.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Container,
3 | Box
4 | } from "@mui/material"
5 |
6 | import Header from "components/Header"
7 |
8 | interface Props {
9 | children: React.ReactNode
10 | }
11 |
12 | const Page = ({ children }: Props) => {
13 | return (
14 |
15 |
16 |
17 |
18 | {children}
19 |
20 |
21 |
22 | )
23 | }
24 |
25 | export default Page
26 |
--------------------------------------------------------------------------------
/src/components/ProjectDate.tsx:
--------------------------------------------------------------------------------
1 | import moment from "moment"
2 | import {
3 | Box,
4 | Typography
5 | } from "@mui/material"
6 |
7 | interface Props {
8 | startTime?: BigInt
9 | }
10 |
11 | const ProjectDate = ({ startTime }: Props) => {
12 | const startDate = startTime ? moment.unix(parseInt(startTime.toString())) : null
13 |
14 | return (
15 |
16 | {
17 | startDate ?
18 | (
19 |
20 | {startDate.isBefore() ? "Launched" : ""} {startDate.format("LL")}
21 |
22 | ) :
23 | (
24 |
25 |
26 |
27 | )
28 | }
29 |
30 | )
31 | }
32 |
33 | export default ProjectDate
34 |
--------------------------------------------------------------------------------
/src/components/ProjectDetails.tsx:
--------------------------------------------------------------------------------
1 | import useTheme from "@mui/material/styles/useTheme"
2 | import { useState } from "react"
3 | import {
4 | Box,
5 | Grid,
6 | Breadcrumbs,
7 | Divider,
8 | Typography,
9 | Button,
10 | Stack,
11 | FormControl,
12 | InputLabel,
13 | NativeSelect,
14 | Pagination,
15 | Alert,
16 | Link
17 | } from "@mui/material"
18 | import { TOKENS_PER_PAGE } from "config"
19 | import { OrderDirection } from "utils/types"
20 | import { parseScriptType, parseAspectRatio } from "utils/scriptJSON"
21 | import ProjectDate from "components/ProjectDate"
22 | import ProjectExplore from "components/ProjectExplore"
23 | import TokenView from "components/TokenView"
24 | import Tokens from "components/Tokens"
25 | import Loading from "components/Loading"
26 | import Collapsible from "components/Collapsible"
27 | import useProject from "hooks/useProject"
28 | import useWindowSize from "hooks/useWindowSize"
29 | import { getContractConfigByAddress } from "utils/contractInfoHelper"
30 | import EditProjectButton from "components/EditProjectButton"
31 | import { useAccount } from "wagmi"
32 | import MintingInterfaceFilter from "components/MintingInterfaceFilter"
33 |
34 | interface Props {
35 | contractAddress: string
36 | id: string
37 | }
38 |
39 | const ProjectDetails = ({ contractAddress, id }: Props) => {
40 | const theme = useTheme()
41 | const windowSize = useWindowSize()
42 | const { address } = useAccount()
43 | const { loading, error, data } = useProject(`${contractAddress}-${id}`)
44 | const [currentPage, setCurrentPage] = useState(0)
45 | const [orderDirection, setOrderDirection] = useState(OrderDirection.ASC)
46 | const project = data?.project
47 | const token = project?.tokens[0]
48 | const width = windowSize.width > theme.breakpoints.values.md
49 | ? (Math.min(windowSize.width, 1200)-48)*0.666666
50 | : windowSize.width > theme.breakpoints.values.sm
51 | ? windowSize.width - 48
52 | : windowSize.width - 32
53 | const contractConfig = getContractConfigByAddress(contractAddress)
54 |
55 | if (error) {
56 | return (
57 |
58 |
59 | Error loading project
60 |
61 |
62 | )
63 | }
64 |
65 | if (loading) {
66 | return
67 | }
68 |
69 | return project && contractConfig && (
70 |
71 |
72 |
73 | Home
74 |
75 |
76 | {project.name}
77 |
78 |
79 |
80 | {
81 | token && (
82 |
83 |
91 |
92 | )
93 | }
94 |
95 |
96 |
97 |
98 | {project.name}
99 |
100 |
101 | {project.artistName}
102 |
103 |
104 | {
105 | contractConfig.EDIT_PROJECT_URL && address?.toLowerCase() === project.artistAddress &&
106 | (
107 |
112 | )
113 | }
114 |
122 |
123 |
124 |
125 |
126 |
127 |
128 | About {project.name}
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 | License
138 |
139 |
140 | {project.license}
141 |
142 |
143 |
144 |
145 | Library
146 |
147 |
148 | {parseScriptType(project.scriptJSON) || project.scriptTypeAndVersion}
149 |
150 |
151 |
152 |
153 |
154 |
155 | {
156 | project.website && (
157 | window.open(project.website)}
160 | >
161 | Artist link
162 |
163 | )
164 | }
165 |
166 |
167 |
168 |
169 |
170 |
171 | {project.invocations} Item{Number(project.invocations) === 1 ? "" : "s"}
172 |
173 |
174 |
175 |
176 | Sort
177 |
178 | {
182 | setOrderDirection(e.target.value as OrderDirection)
183 | }}
184 | >
185 | Latest
186 | Earliest
187 |
188 |
189 |
190 |
191 |
192 |
200 |
201 |
202 | {
207 | setCurrentPage(page - 1)
208 | }}
209 | />
210 |
211 |
212 |
213 |
214 | )
215 | }
216 |
217 | export default ProjectDetails
218 |
--------------------------------------------------------------------------------
/src/components/ProjectExplore.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import Box from "@mui/material/Box"
3 | import Button from "@mui/material/Button"
4 | import RestartAltIcon from "@mui/icons-material/RestartAlt"
5 | import RefreshIcon from "@mui/icons-material/Refresh"
6 | import Dialog from "@mui/material/Dialog"
7 | import DialogContent from "@mui/material/DialogContent"
8 | import DialogTitle from "@mui/material/DialogTitle"
9 | import Alert from "@mui/material/Alert"
10 | import ButtonBase from "@mui/material/ButtonBase"
11 | import CloseIcon from "@mui/icons-material/Close"
12 | import Loading from "components/Loading"
13 | import { Project } from "utils/types"
14 | import { parseAspectRatio } from "utils/scriptJSON"
15 | import useGeneratorPreview from "hooks/useGeneratorPreview"
16 |
17 | interface Props {
18 | project: Project
19 | }
20 |
21 | const ProjectExplore = ({project}: Props) => {
22 | const [dialogOpen, setDialogOpen] = useState(false)
23 |
24 | const {
25 | content,
26 | loading,
27 | error,
28 | refreshPreview
29 | } = useGeneratorPreview(project)
30 |
31 | const handleClose = () => {
32 | setDialogOpen(false)
33 | }
34 |
35 | const aspectRatio = project?.aspectRatio || parseAspectRatio(project?.scriptJSON) || 1
36 | const width = 280
37 | const height = width / aspectRatio
38 |
39 | return (
40 |
41 | }
44 | onClick={() => setDialogOpen(true)}
45 | >
46 | Explore possibilities
47 |
48 |
60 |
68 |
69 |
70 |
71 | Exploring {project.name}
72 |
73 |
74 |
82 | {
83 | loading ? (
84 |
85 | ) : error ? (
86 |
87 | Error loading preview
88 |
89 | ) : content ? (
90 |
97 | ) : null
98 | }
99 |
100 |
101 | Refresh to see more possible results
102 |
103 |
104 | }
108 | >
109 | Refresh
110 |
111 |
112 |
113 |
114 |
115 | )
116 | }
117 |
118 | export default ProjectExplore
119 |
--------------------------------------------------------------------------------
/src/components/ProjectPreview.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Box,
3 | Typography,
4 | Link
5 | } from "@mui/material"
6 | import { Project } from "utils/types"
7 | import { parseAspectRatio } from "utils/scriptJSON"
8 | import Collapsible from "components/Collapsible"
9 | import ProjectDate from "components/ProjectDate"
10 | import TokenView from "components/TokenView"
11 | import ProjectStatusBadge from "./ProjectStatusBadge"
12 |
13 | interface Props {
14 | project: Project
15 | width?: number
16 | showDescription?: boolean
17 | }
18 |
19 | const ProjectPreview = ({project, width=280, showDescription=false}: Props) => {
20 | if (!project) {
21 | return null
22 | }
23 |
24 | const token = project?.tokens[0]
25 | return (
26 |
27 |
28 |
29 |
30 | {project.name}
31 |
32 |
33 |
34 | by {project.artistName}
35 |
36 |
37 |
44 |
45 |
46 |
47 |
48 |
51 |
52 | {
53 | showDescription && (
54 |
55 |
56 |
57 | )
58 | }
59 |
60 |
61 | )
62 | }
63 |
64 | export default ProjectPreview
65 |
--------------------------------------------------------------------------------
/src/components/ProjectStatusBadge.tsx:
--------------------------------------------------------------------------------
1 | import moment from 'moment';
2 | import Box from '@mui/material/Box';
3 | import Chip from '@mui/material/Chip';
4 | import Typography from '@mui/material/Typography';
5 |
6 | interface Props {
7 | complete: boolean;
8 | paused: boolean;
9 | startTime?: BigInt;
10 | }
11 |
12 | const ProjectStatusBadge = ({ complete, paused, startTime }: Props) => {
13 | const startDate = startTime ? moment.unix(parseInt(startTime.toString())) : null;
14 |
15 | return (
16 |
20 | {
21 | startDate?.isAfter() ?
22 |
28 | : paused ? (
29 |
35 | ) : !complete ? (
36 |
42 | ) : (
43 |
50 | )
51 | }
52 |
53 | {
54 | startDate && (
55 |
56 |
57 |
58 | )
59 | }
60 |
61 | )
62 | }
63 |
64 | export default ProjectStatusBadge;
65 |
--------------------------------------------------------------------------------
/src/components/Projects.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react"
2 | import {
3 | Box,
4 | Typography,
5 | Alert,
6 | FormControl,
7 | NativeSelect,
8 | Pagination,
9 | Grid
10 | } from "@mui/material"
11 | import useTheme from "@mui/material/styles/useTheme"
12 | import { PROJECTS_PER_PAGE } from "config"
13 | import { OrderDirection, Project } from "utils/types"
14 | import ProjectPreview from "components/ProjectPreview"
15 | import Loading from "components/Loading"
16 | import useProjects from "hooks/useProjects"
17 | import useWindowSize from "hooks/useWindowSize"
18 | import useCountProjects from "hooks/useCountProjects"
19 |
20 | const Projects = () => {
21 | const theme = useTheme()
22 | const windowSize = useWindowSize()
23 | const [countProjects, setCountProjects] = useState(0)
24 | const [currentPage, setCurrentPage] = useState(0)
25 | const skip = currentPage * PROJECTS_PER_PAGE
26 | const first = PROJECTS_PER_PAGE
27 | const [orderDirection, setOrderDirection] = useState(OrderDirection.DESC)
28 | const { loading, error, data } = useProjects({skip, first, orderDirection})
29 | const countProjectsResponse = useCountProjects()
30 |
31 | useEffect(() => {
32 | if (countProjectsResponse.data?.projects?.length) {
33 | setCountProjects(countProjectsResponse.data?.projects?.length)
34 | }
35 | }, [countProjectsResponse.data?.projects?.length])
36 |
37 | let width = 280
38 | const maxColumns = 2
39 | if (windowSize && !isNaN(windowSize.width)) {
40 | width = windowSize.width > theme.breakpoints.values.md
41 | ? (Math.min(windowSize.width, 1200)- 96)/maxColumns
42 | : windowSize.width > theme.breakpoints.values.sm
43 | ? windowSize.width - 64
44 | : windowSize.width - 48
45 | }
46 |
47 | return (
48 |
49 |
50 |
51 |
52 |
53 | {
54 | !error && !loading && data?.projects?.length > 0 &&
55 | (
56 |
57 | {
61 | setCurrentPage(0)
62 | setOrderDirection(e.target.value as OrderDirection)
63 | }}
64 | >
65 | Newest
66 | Oldest
67 |
68 |
69 | )
70 | }
71 |
72 |
73 |
74 |
75 | {
76 | loading ?
77 | (
78 |
79 |
80 |
81 | ) :
82 | error ?
83 | (
84 |
85 |
86 | Error loading projects
87 |
88 |
89 | ) :
90 | data?.projects?.length > 0 ?
91 | (
92 |
93 | {
94 | data?.projects && (
95 | data.projects.map((project: Project) => (
96 |
97 |
102 |
103 | ))
104 | )
105 | }
106 |
107 | ) :
108 | data?.projects?.length === 0 ? (
109 |
110 |
111 | No projects found
112 |
113 |
114 | ) :
115 | null
116 | }
117 | {
118 | !error && !loading && data?.projects?.length > 0 && (
119 |
120 | {
125 | window.scrollTo(0, 0)
126 | setCurrentPage(page - 1)
127 | }}/>
128 |
129 | )
130 | }
131 |
132 |
133 | )
134 | }
135 |
136 | export default Projects
137 |
--------------------------------------------------------------------------------
/src/components/Providers.tsx:
--------------------------------------------------------------------------------
1 | import CssBaseline from "@mui/material/CssBaseline"
2 | import "@rainbow-me/rainbowkit/styles.css"
3 | import { ThemeProvider } from "@mui/material/styles"
4 | import theme from "theme"
5 | import { RainbowKitProvider, getDefaultWallets, midnightTheme } from "@rainbow-me/rainbowkit"
6 | import {configureChains, createClient, WagmiConfig} from "wagmi"
7 | import { mainnet, goerli } from 'wagmi/chains'
8 | import { infuraProvider } from "wagmi/providers/infura"
9 | import { publicProvider } from "wagmi/providers/public"
10 | import { ApolloClient, ApolloProvider, InMemoryCache } from "@apollo/client"
11 | import { GRAPHQL_URL, INFURA_KEY, EXPECTED_CHAIN_ID, WALLET_CONNECT_PROJECT_ID } from "config"
12 |
13 | const client = new ApolloClient({
14 | uri: GRAPHQL_URL,
15 | cache: new InMemoryCache()
16 | })
17 |
18 | // Defaults to goerli testing network if mainnet is not set
19 | const expectedChains = [EXPECTED_CHAIN_ID === 1 ? mainnet : goerli]
20 | const initialChain = EXPECTED_CHAIN_ID === 1 ? mainnet : goerli
21 |
22 | const { chains, provider, webSocketProvider } = configureChains(
23 | expectedChains,
24 | [
25 | infuraProvider({apiKey: INFURA_KEY, priority: 0}),
26 | publicProvider({priority: 1})
27 | ]
28 | )
29 |
30 | const { connectors } = getDefaultWallets({
31 | appName: "Engine",
32 | chains,
33 | projectId: WALLET_CONNECT_PROJECT_ID
34 | })
35 |
36 | const wagmiClient = createClient({
37 | autoConnect: true,
38 | connectors,
39 | provider,
40 | webSocketProvider
41 | })
42 |
43 | interface Props {
44 | children: React.ReactNode
45 | }
46 |
47 | const AppProvider = ({children}:Props) => {
48 | return (
49 |
50 |
58 |
59 |
60 |
61 | {children}
62 |
63 |
64 |
65 |
66 | )
67 | }
68 |
69 | export default AppProvider
70 |
--------------------------------------------------------------------------------
/src/components/TokenDetails.tsx:
--------------------------------------------------------------------------------
1 | import moment from "moment"
2 | import { parseAspectRatio } from "utils/scriptJSON"
3 | import {
4 | Box,
5 | Typography,
6 | Link,
7 | Grid,
8 | Alert,
9 | Button,
10 | Breadcrumbs
11 | } from "@mui/material"
12 | import VisibilityIcon from "@mui/icons-material/Visibility"
13 | import ArrowForwardIcon from "@mui/icons-material/ArrowForward"
14 | import ImageIcon from "@mui/icons-material/Image"
15 | import useTheme from "@mui/material/styles/useTheme"
16 | import TokenTraits from "components/TokenTraits"
17 | import Address from "components/Address"
18 | import Loading from "components/Loading"
19 | import TokenView from "components/TokenView"
20 | import useToken from "hooks/useToken"
21 | import useWindowSize from "hooks/useWindowSize"
22 | import { getContractConfigByAddress } from "utils/contractInfoHelper";
23 |
24 | interface Props {
25 | contractAddress: string
26 | id: string
27 | }
28 |
29 | const TokenDetails = ({ contractAddress, id }: Props) => {
30 | const theme = useTheme()
31 | const windowSize = useWindowSize()
32 | const { loading, error, data } = useToken(`${contractAddress.toLowerCase()}-${id}`)
33 | const token = data?.token
34 | const contractConfig = getContractConfigByAddress(contractAddress)
35 |
36 | if (loading) {
37 | return
38 | }
39 |
40 | if (error) {
41 | return (
42 |
43 |
44 | Error loading token
45 |
46 |
47 | )
48 | }
49 |
50 | const width = windowSize.width > theme.breakpoints.values.md
51 | ? (Math.min(windowSize.width, 1200)- 48)*0.666666
52 | : windowSize.width > theme.breakpoints.values.sm
53 | ? windowSize.width - 48
54 | : windowSize.width - 32
55 |
56 | return token && contractConfig && (
57 |
58 |
59 |
60 | Home
61 |
62 |
63 | {token.project.name}
64 |
65 |
66 | {token.invocation}
67 |
68 |
69 |
70 |
71 |
78 |
79 |
80 |
81 | Owned by
82 |
83 |
84 |
85 | }
87 | sx={{
88 | fontSize: 14,
89 | textTransform: "none",
90 | minWidth: [0, 0, "64px"],
91 | padding: [0, 0, "default"]
92 | }}
93 | onClick={() => {
94 | const generatorUrl = contractConfig?.GENERATOR_URL
95 | window.open(`${generatorUrl}/${contractAddress?.toLowerCase()}/${token.tokenId}`)
96 | }}
97 | >
98 |
99 | Live view
100 |
101 |
102 | }
104 | sx={{
105 | fontSize: 14,
106 | textTransform: "none",
107 | marginLeft: [1, 1, 2],
108 | minWidth: [0, 0, "64px"],
109 | padding: [0, 0, "default"]
110 | }}
111 | onClick={() => {
112 | const mediaUrl = contractConfig?.MEDIA_URL
113 | window.open(`${mediaUrl}/${token.tokenId}.png`)
114 | }}
115 | >
116 |
117 | Image
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 | Minted {moment.unix(token.createdAt).format("LL")}
126 |
127 |
128 | {token.project.name} #{token.invocation}
129 |
130 |
131 | {token.project.artistName}
132 |
133 |
134 |
135 | }
137 | onClick={() => {
138 | window.open(`https://etherscan.io/token/${contractAddress?.toLowerCase()}?a=${token.tokenId}`)
139 | }}
140 | >
141 |
142 | View on Etherscan
143 |
144 |
145 |
146 |
147 | }
149 | onClick={() => {
150 | window.open(`https://opensea.io/assets/ethereum/${contractAddress?.toLowerCase()}/${token.tokenId}`)
151 | }}
152 | >
153 |
154 | View on OpenSea
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 | )
168 | }
169 |
170 | export default TokenDetails
171 |
--------------------------------------------------------------------------------
/src/components/TokenImage.tsx:
--------------------------------------------------------------------------------
1 | import { getContractConfigByAddress } from "utils/contractInfoHelper";
2 |
3 | interface Props {
4 | contractAddress: string
5 | tokenId: string
6 | width: number
7 | height: number
8 | }
9 |
10 | const TokenImage = ({contractAddress, tokenId, width, height}: Props) => {
11 | const contractConfig = getContractConfigByAddress(contractAddress)
12 |
13 | return (
14 |
20 | )
21 | }
22 |
23 | export default TokenImage
24 |
--------------------------------------------------------------------------------
/src/components/TokenLive.tsx:
--------------------------------------------------------------------------------
1 | import axios from "axios"
2 | import { useState } from "react"
3 | import {
4 | Box,
5 | Typography
6 | } from "@mui/material"
7 | import Loading from "components/Loading"
8 | import TokenImage from "components/TokenImage"
9 | import useInterval from "hooks/useInterval"
10 | import { getContractConfigByAddress } from "utils/contractInfoHelper";
11 |
12 | interface Props {
13 | contractAddress: string
14 | tokenId: string
15 | width: number
16 | height: number
17 | }
18 |
19 | const TokenLive = ({contractAddress, tokenId, width, height}: Props) => {
20 | const [status, setStatus] = useState(404)
21 | const [pollingTime, setPollingTime] = useState(0)
22 | const [pollingDelay, setPollingDelay] = useState(0)
23 | const [pollingAttempts, setPollingAttempts] = useState(0)
24 | const contractConfig = getContractConfigByAddress(contractAddress)
25 | const generatorUrl = contractConfig?.GENERATOR_URL
26 | const endpoint = `${generatorUrl}/${contractAddress.toLowerCase()}/${tokenId}`
27 |
28 | useInterval(() => {
29 | setPollingTime(pollingTime+1)
30 | }, 1000)
31 |
32 | useInterval(() => {
33 | setPollingDelay(pollingDelay+3)
34 | if (status === 404) {
35 | axios
36 | .get(endpoint)
37 | .then(function(response) {
38 | setStatus(response.status)
39 | })
40 | .catch((error) => {
41 | setStatus(404)
42 | })
43 | .finally(() => {
44 | setPollingAttempts(pollingAttempts+1)
45 | })
46 | }
47 | }, 1000*pollingDelay)
48 |
49 | if (pollingAttempts === 0) {
50 | return (
51 |
52 | )
53 | }
54 |
55 | if (pollingTime > 500) {
56 | return (
57 |
58 | )
59 | }
60 |
61 | return (
62 |
63 | {
64 | status === 200 ?
65 | (
66 |
73 | ) :
74 | (
75 |
82 |
83 |
84 | Waiting for indexing ({pollingTime})
85 |
86 |
87 | )
88 | }
89 |
90 | )
91 | }
92 |
93 | export default TokenLive
94 |
--------------------------------------------------------------------------------
/src/components/TokenTraits.tsx:
--------------------------------------------------------------------------------
1 | import { Trait } from "utils/types"
2 | import {
3 | Typography,
4 | Alert,
5 | Table,
6 | TableBody,
7 | TableCell,
8 | TableContainer,
9 | TableHead,
10 | TableRow
11 | } from "@mui/material"
12 | import Loading from "components/Loading"
13 | import useTokenTraits from "hooks/useTokenTraits"
14 |
15 | interface Props {
16 | contractAddress: string
17 | tokenId: string
18 | }
19 |
20 | const TokenTraits = ({ contractAddress, tokenId }: Props) => {
21 | const { loading, error, data } = useTokenTraits(contractAddress, tokenId)
22 | const traits = data?.traits?.filter((t:Trait) => t.value.indexOf('All') === -1)
23 |
24 | if (loading) {
25 | return
26 | }
27 |
28 | if (error) {
29 | return (
30 |
31 | Error loading traits
32 |
33 | )
34 | }
35 |
36 | return traits && traits.length > 0 && (
37 |
38 | Features
39 |
40 |
41 |
42 |
43 |
44 | Feature
45 |
46 |
47 |
48 |
49 | Value
50 |
51 |
52 |
53 |
54 |
55 | {traits.map((trait:Trait) => {
56 | const p = trait.value.split(":")
57 | return (
58 |
59 |
60 |
61 | {p[0]}
62 |
63 |
64 |
65 |
66 | {p[1]}
67 |
68 |
69 |
70 | )
71 | })}
72 |
73 |
74 |
75 | )
76 | }
77 |
78 | export default TokenTraits
79 |
--------------------------------------------------------------------------------
/src/components/TokenView.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Box,
3 | Card,
4 | Link
5 | } from "@mui/material"
6 | import TokenImage from "components/TokenImage"
7 | import TokenLive from "components/TokenLive"
8 |
9 | interface Props {
10 | contractAddress: string
11 | tokenId: string
12 | width: number
13 | invocation?: BigInt
14 | aspectRatio?: number
15 | live?: boolean
16 | }
17 |
18 | const TokenView = ({
19 | contractAddress,
20 | tokenId,
21 | width,
22 | invocation,
23 | aspectRatio=1,
24 | live=false
25 | }: Props) => {
26 | const height = width / aspectRatio
27 | return (
28 |
29 |
30 | {
31 | live ?
32 | (
33 |
34 | ) :
35 | (
36 |
37 | )
38 | }
39 |
40 | { invocation !== undefined &&
41 | (
42 |
43 |
44 | No. { invocation?.toString() }
45 |
46 |
47 | )
48 | }
49 |
50 | )
51 | }
52 |
53 | export default TokenView
54 |
--------------------------------------------------------------------------------
/src/components/Tokens.tsx:
--------------------------------------------------------------------------------
1 | import useTheme from "@mui/material/styles/useTheme"
2 | import { TOKENS_PER_PAGE } from "config"
3 | import { OrderDirection, Token } from "utils/types"
4 | import {
5 | Grid,
6 | Link,
7 | Alert,
8 | Typography
9 | } from "@mui/material"
10 | import Loading from "components/Loading"
11 | import TokenView from "components/TokenView"
12 | import useTokens from "hooks/useTokens"
13 | import useWindowSize from "hooks/useWindowSize"
14 |
15 | interface Props {
16 | contractAddress: string
17 | projectId: string
18 | first?: number
19 | skip?: number
20 | orderDirection?: OrderDirection
21 | aspectRatio?: number
22 | }
23 |
24 | const Tokens = ({
25 | contractAddress,
26 | projectId,
27 | first=TOKENS_PER_PAGE,
28 | skip=0,
29 | orderDirection=OrderDirection.ASC,
30 | aspectRatio=1
31 | }: Props) => {
32 | const theme = useTheme()
33 | const windowSize = useWindowSize()
34 | const {loading, error, data } = useTokens(projectId, {
35 | first,
36 | skip,
37 | orderDirection
38 | })
39 |
40 | if (loading) {
41 | return
42 | }
43 |
44 | if (error) {
45 | return (
46 |
47 | Error loading tokens
48 |
49 | )
50 | }
51 |
52 | if (!data || !data.tokens) {
53 | return (
54 |
55 | No tokens found for this project.
56 |
57 | )
58 | }
59 |
60 | let width = 280
61 | if (windowSize && !isNaN(windowSize.width)) {
62 | width = windowSize.width > theme.breakpoints.values.md
63 | ? (Math.min(windowSize.width, 1200)-96) / 3
64 | : (windowSize.width-60) / 2
65 | }
66 |
67 | return (
68 | data.tokens.length > 0 ?
69 |
70 | {
71 | data.tokens.map(((token:Token) => (
72 |
73 |
74 |
80 |
81 |
82 | #{token.invocation.toString()}
83 |
84 |
85 | )))
86 | }
87 |
88 | : null
89 | )
90 | }
91 |
92 | export default Tokens
93 |
--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------
1 | import { mainnetContractConfig, testnetContractConfig } from "./contractConfig";
2 |
3 | export const EXPECTED_CHAIN_ID = Number(process.env.REACT_APP_EXPECTED_CHAIN_ID)
4 | export const GRAPHQL_URL = process.env.REACT_APP_GRAPHQL_URL
5 | export const WALLET_CONNECT_PROJECT_ID = process.env.REACT_APP_WALLET_CONNECT_PROJECT_ID || ""
6 | export const INFURA_KEY = process.env.REACT_APP_INFURA_KEY || ""
7 | export const PROJECTS_PER_PAGE = 8
8 | export const TOKENS_PER_PAGE = 9
9 | export const MULTIPLY_GAS_LIMIT = 1.1
10 | export const CONTRACT_INFO = EXPECTED_CHAIN_ID === 1 ? mainnetContractConfig : testnetContractConfig
11 | export const MERKLE_PROOF_API_URL = process.env.REACT_APP_MERKLE_PROOF_API_URL || ""
12 | export const HOLDER_PROOF_API_URL = process.env.REACT_APP_HOLDER_PROOF_API_URL || ""
13 |
--------------------------------------------------------------------------------
/src/contractConfig.ts:
--------------------------------------------------------------------------------
1 | export const mainnetContractConfig = [
2 | {
3 | "CONTRACT_VERSION": "V2",
4 | "CORE_CONTRACT_ADDRESS": "0xa319c382a702682129fcbf55d514e61a16f97f9c",
5 | "MINT_CONTRACT_ADDRESS": "0x463b8ced7d22a55aa4a5d69ef6a54a08aa0feb93",
6 | "MEDIA_URL": "https://plottables-mainnet.s3.amazonaws.com",
7 | "TOKEN_URL": "https://token.artblocks.io",
8 | "GENERATOR_URL": "https://generator.artblocks.io",
9 | "EDIT_PROJECT_URL": "https://artblocks.io/engine/fullyonchain/projects"
10 | },
11 | {
12 | "CONTRACT_VERSION": "V2",
13 | "CORE_CONTRACT_ADDRESS": "0x18de6097ce5b5b2724c9cae6ac519917f3f178c0",
14 | "MINT_CONTRACT_ADDRESS": "0xe6e728361b7c824cba64cc1e5323efb7a5bb65da",
15 | "MEDIA_URL": "https://plottables-flex-mainnet.s3.amazonaws.com",
16 | "TOKEN_URL": "https://token.artblocks.io",
17 | "GENERATOR_URL": "https://generator.artblocks.io",
18 | "EDIT_PROJECT_URL": "https://artblocks.io/engine/flex/projects"
19 | }
20 | ]
21 |
22 | export const testnetContractConfig = [
23 | {
24 | "CONTRACT_VERSION": "V2",
25 | "CORE_CONTRACT_ADDRESS": "0x9B0c67496Be8c6422fED0372be7a87707e3a6F09",
26 | "MINT_CONTRACT_ADDRESS": "0x068C519D00A60CCD1830fabfe6eC428F2FDb4146",
27 | "MEDIA_URL": "https://plottables-goerli.s3.amazonaws.com",
28 | "TOKEN_URL": "https://token.staging.artblocks.io",
29 | "GENERATOR_URL": "https://generator-staging-goerli.artblocks.io",
30 | "EDIT_PROJECT_URL": "https://artist-staging.artblocks.io/engine/fullyonchain/projects"
31 | },
32 | {
33 | "CONTRACT_VERSION": "V2",
34 | "CORE_CONTRACT_ADDRESS": "0x48742D38a0809135EFd643c1150BfC13768C3907",
35 | "MINT_CONTRACT_ADDRESS": "0x1DEC9E52f1320F7Deb29cBCd7B7d67f3dF785142",
36 | "MEDIA_URL": "https://plottables-flex-goerli.s3.amazonaws.com",
37 | "TOKEN_URL": "https://token.staging.artblocks.io",
38 | "GENERATOR_URL": "https://generator-staging-goerli.artblocks.io",
39 | "EDIT_PROJECT_URL": "https://artist-staging.artblocks.io/engine/flex/projects"
40 | },
41 | {
42 | "CONTRACT_VERSION": "V3",
43 | "CORE_CONTRACT_ADDRESS": "0xCEd5350f5a2Ba24946F92C08260931CFf65dc954",
44 | "MINT_CONTRACT_ADDRESS": "0x0AB754254d7243315FFFDC363a6A0997aD9c3118",
45 | "MEDIA_URL": "https://plottablesv3-goerli.s3.amazonaws.com",
46 | "TOKEN_URL": "https://token.staging.artblocks.io",
47 | "GENERATOR_URL": "https://generator-staging-goerli.artblocks.io",
48 | "EDIT_PROJECT_URL": "https://artist-staging.artblocks.io/engine/fullyonchain/projects"
49 | }
50 | ]
51 |
--------------------------------------------------------------------------------
/src/hooks/useCountOwnedProjects.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery, gql } from "@apollo/client"
2 | import { getConfiguredContractAddresses } from "utils/contractInfoHelper"
3 |
4 | const countOwnedProjectsQuery = (walletAddress: string) => `
5 | query GetProjects {
6 | projects(
7 | where: {
8 | contract_in: ["${getConfiguredContractAddresses().join("\",\"").toLowerCase()}"]
9 | active: true
10 | }
11 | ) {
12 | id
13 | tokens (
14 | where: {
15 | owner: "${walletAddress}"
16 | }
17 | first: 1
18 | ) {
19 | id
20 | tokenId
21 | invocation
22 | }
23 | }
24 | }`
25 |
26 | const useCountOwnedProjects = (walletAddress: string) => {
27 | const { loading, error, data } = useQuery(gql(countOwnedProjectsQuery(walletAddress)))
28 |
29 | return {
30 | loading,
31 | error,
32 | data
33 | }
34 | }
35 |
36 | export default useCountOwnedProjects
37 |
--------------------------------------------------------------------------------
/src/hooks/useCountOwnedTokens.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery, gql } from "@apollo/client"
2 |
3 | const countOwnedTokensQuery = (projectId: string, walletAddress: string) => `
4 | query GetTokens {
5 | tokens(
6 | where: {
7 | project: "${projectId}"
8 | owner: "${walletAddress}"
9 | }
10 | ) {
11 | id
12 | }
13 | }`
14 |
15 | const useCountOwnedTokens = (projectId: string, walletAddress: string) => {
16 | const { loading, error, data } = useQuery(gql(countOwnedTokensQuery(projectId, walletAddress)))
17 |
18 | return {
19 | loading,
20 | error,
21 | data
22 | }
23 | }
24 |
25 | export default useCountOwnedTokens
26 |
--------------------------------------------------------------------------------
/src/hooks/useCountProjects.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery, gql } from "@apollo/client"
2 | import { getConfiguredContractAddresses } from "utils/contractInfoHelper"
3 |
4 | const countProjectsQuery = () => `
5 | query GetProjects {
6 | projects(
7 | where: {
8 | contract_in: ["${getConfiguredContractAddresses().join("\",\"").toLowerCase()}"]
9 | active: true
10 | }
11 | ) {
12 | id
13 | }
14 | }`
15 |
16 | const useCountProjects = () => {
17 | const { loading, error, data } = useQuery(gql(countProjectsQuery()))
18 |
19 | return {
20 | loading,
21 | error,
22 | data
23 | }
24 | }
25 |
26 | export default useCountProjects
27 |
--------------------------------------------------------------------------------
/src/hooks/useGeneratorPreview.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useCallback } from 'react'
2 | import axios from 'axios'
3 | import { Project } from 'utils/types'
4 | import { getContractConfigByAddress } from "utils/contractInfoHelper";
5 |
6 | interface TokenData {
7 | tokenId: number
8 | hash: string
9 | }
10 |
11 | const generateToken = (p: number): TokenData => {
12 | let hash = '0x'
13 | for (var i = 0; i < 64; i++) {
14 | hash += Math.floor(Math.random()*16).toString(16)
15 | }
16 | return {
17 | hash,
18 | tokenId: p * 1000000 + Math.floor(Math.random()*1000)
19 | }
20 | }
21 |
22 | const useGeneratorPreview = (project: Project) => {
23 | const [content, setContent] = useState('')
24 | const [loading, setLoading] = useState(false)
25 | const [error, setError] = useState(false)
26 | const contractConfig = getContractConfigByAddress(project.contract.id)
27 |
28 | const refreshPreview = useCallback(async () => {
29 | setLoading(true)
30 | try {
31 | const token = generateToken(Number(project.projectId))
32 | const generatorUrl = contractConfig?.GENERATOR_URL
33 | const { data } = await axios.get(`${generatorUrl}/${project.id}/${token.tokenId}/${token.hash}`)
34 | setContent(data)
35 | setError(false)
36 | } catch(error) {
37 | setError(true)
38 | } finally {
39 | setLoading(false)
40 | }
41 | }, [project, contractConfig?.GENERATOR_URL])
42 |
43 | useEffect(() => {
44 | refreshPreview()
45 | }, [refreshPreview])
46 |
47 | return {
48 | content,
49 | loading,
50 | error,
51 | refreshPreview
52 | }
53 | }
54 |
55 | export default useGeneratorPreview
56 |
--------------------------------------------------------------------------------
/src/hooks/useInterval.tsx:
--------------------------------------------------------------------------------
1 | // Source: https://usehooks-ts.com/react-hook/use-interval
2 | import { useEffect, useRef } from "react"
3 | import { useIsomorphicLayoutEffect } from "usehooks-ts"
4 |
5 | function useInterval(callback: () => void, delay: number | null) {
6 | const savedCallback = useRef(callback)
7 |
8 | // Remember the latest callback if it changes.
9 | useIsomorphicLayoutEffect(() => {
10 | savedCallback.current = callback
11 | }, [callback])
12 |
13 | // Set up the interval.
14 | useEffect(() => {
15 | // Don"t schedule if no delay is specified.
16 | // Note: 0 is a valid value for delay.
17 | if (!delay && delay !== 0) {
18 | return
19 | }
20 |
21 | const id = setInterval(() => savedCallback.current(), delay)
22 |
23 | return () => clearInterval(id)
24 | }, [delay])
25 | }
26 |
27 | export default useInterval
--------------------------------------------------------------------------------
/src/hooks/useOwnedProjects.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery, gql } from "@apollo/client"
2 | import { PROJECTS_PER_PAGE } from "config"
3 | import { OrderDirection } from "utils/types"
4 | import { getConfiguredContractAddresses } from "utils/contractInfoHelper"
5 |
6 | interface Params {
7 | first?: number
8 | skip?: number
9 | orderDirection?: OrderDirection
10 | }
11 |
12 | const ownedProjectsQuery = (walletAddress: string, { first, skip, orderDirection }: Params) => `
13 | query GetProjects {
14 | projects(
15 | where: {
16 | contract_in: ["${getConfiguredContractAddresses().join("\",\"").toLowerCase()}"]
17 | active: true
18 | }
19 | first: ${first}
20 | skip: ${skip}
21 | orderBy: createdAt
22 | orderDirection: ${orderDirection}
23 | ) {
24 | id
25 | contract {
26 | id
27 | }
28 | projectId
29 | name
30 | description
31 | license
32 | locked
33 | pricePerTokenInWei
34 | active
35 | paused
36 | complete
37 | artistName
38 | artistAddress
39 | invocations
40 | maxInvocations
41 | scriptJSON
42 | aspectRatio
43 | currencyAddress
44 | currencySymbol
45 | createdAt
46 | activatedAt
47 | tokens (
48 | where: {
49 | owner: "${walletAddress}"
50 | }
51 | first: 2
52 | ) {
53 | id
54 | tokenId
55 | invocation
56 | }
57 | minterConfiguration {
58 | basePrice
59 | startPrice
60 | priceIsConfigured
61 | currencySymbol
62 | currencyAddress
63 | startTime
64 | endTime
65 | }
66 | }
67 | }`
68 |
69 | const useOwnedProjects = (walletAddress: string, params?: Params) => {
70 | const first = params?.first || PROJECTS_PER_PAGE
71 | const skip = params?.skip || 0
72 | const orderDirection = params?.orderDirection || OrderDirection.DESC
73 | const { loading, error, data } = useQuery(gql(ownedProjectsQuery(walletAddress, { first, skip, orderDirection })))
74 |
75 | return {
76 | loading,
77 | error,
78 | data
79 | }
80 | }
81 |
82 | export default useOwnedProjects
83 |
--------------------------------------------------------------------------------
/src/hooks/useOwnedTokens.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery, gql } from "@apollo/client"
2 | import { TOKENS_PER_PAGE } from "config"
3 | import { OrderDirection } from "utils/types"
4 |
5 | interface Params {
6 | first?: number
7 | skip?: number
8 | orderDirection?: OrderDirection
9 | }
10 |
11 | const ownedTokensQuery = (projectId: string, walletAddress: string, {
12 | first,
13 | skip,
14 | orderDirection
15 | }: Params) => `
16 | query GetTokens {
17 | tokens(
18 | first: ${first},
19 | skip: ${skip},
20 | orderBy: createdAt orderDirection: ${orderDirection},
21 | where: {
22 | project: "${projectId}"
23 | owner: "${walletAddress}"
24 | }
25 | ) {
26 | id
27 | tokenId
28 | invocation
29 | }
30 | }`
31 |
32 | const useOwnedTokens = (projectId: string, walletAddress: string, params: Params) => {
33 | const first = params?.first || TOKENS_PER_PAGE
34 | const skip = params?.skip || 0
35 | const orderDirection = params?.orderDirection || OrderDirection.ASC
36 |
37 | const { loading, error, data } = useQuery(gql(ownedTokensQuery(projectId, walletAddress,{
38 | first,
39 | skip,
40 | orderDirection
41 | })))
42 |
43 | return {
44 | loading,
45 | error,
46 | data
47 | }
48 | }
49 |
50 | export default useOwnedTokens
51 |
--------------------------------------------------------------------------------
/src/hooks/useProject.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery, gql } from "@apollo/client"
2 |
3 | const projectQuery = (id: string) => `
4 | query GetProject {
5 | project(
6 | id: "${id.toLowerCase()}"
7 | ) {
8 | id
9 | contract {
10 | id
11 | }
12 | projectId
13 | name
14 | description
15 | license
16 | locked
17 | pricePerTokenInWei
18 | active
19 | paused
20 | complete
21 | artistName
22 | artistAddress
23 | invocations
24 | maxInvocations
25 | scriptJSON
26 | scriptTypeAndVersion
27 | aspectRatio
28 | currencyAddress
29 | currencySymbol
30 | createdAt
31 | activatedAt
32 | tokens (first:1 orderBy: createdAt orderDirection: desc) {
33 | id
34 | tokenId
35 | invocation
36 | }
37 | minterConfiguration {
38 | basePrice
39 | startPrice
40 | priceIsConfigured
41 | currencySymbol
42 | currencyAddress
43 | startTime
44 | endTime
45 | }
46 | }
47 | }`
48 |
49 | const useProject = (id: string) => {
50 | const { loading, error, data } = useQuery(gql(projectQuery(id)))
51 |
52 | return {
53 | loading,
54 | error,
55 | data
56 | }
57 | }
58 |
59 | export default useProject
60 |
--------------------------------------------------------------------------------
/src/hooks/useProjects.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery, gql } from "@apollo/client"
2 | import { PROJECTS_PER_PAGE } from "config"
3 | import { OrderDirection } from "utils/types"
4 | import { getConfiguredContractAddresses } from "utils/contractInfoHelper";
5 |
6 | interface ProjectsQueryParams {
7 | first?: number
8 | skip?: number
9 | orderDirection?: OrderDirection
10 | }
11 |
12 | const projectsQuery = ({ first, skip, orderDirection }: ProjectsQueryParams) => `
13 | query GetProjects {
14 | projects(
15 | where: {
16 | contract_in: ["${getConfiguredContractAddresses().join("\",\"").toLowerCase()}"]
17 | active: true
18 | }
19 | first: ${first}
20 | skip: ${skip}
21 | orderBy: createdAt
22 | orderDirection: ${orderDirection}
23 | ) {
24 | id
25 | contract {
26 | id
27 | }
28 | projectId
29 | name
30 | description
31 | license
32 | locked
33 | pricePerTokenInWei
34 | active
35 | paused
36 | complete
37 | artistName
38 | artistAddress
39 | invocations
40 | maxInvocations
41 | scriptJSON
42 | aspectRatio
43 | currencyAddress
44 | currencySymbol
45 | createdAt
46 | activatedAt
47 | tokens (first:10 orderBy: createdAt orderDirection: desc) {
48 | id
49 | tokenId
50 | invocation
51 | }
52 | minterConfiguration {
53 | basePrice
54 | startPrice
55 | priceIsConfigured
56 | currencySymbol
57 | currencyAddress
58 | startTime
59 | endTime
60 | }
61 | }
62 | }`
63 |
64 | const useProjects = (params?: ProjectsQueryParams) => {
65 | const first = params?.first || PROJECTS_PER_PAGE
66 | const skip = params?.skip || 0
67 | const orderDirection = params?.orderDirection || OrderDirection.DESC
68 | const { loading, error, data } = useQuery(gql(projectsQuery({ first, skip, orderDirection })))
69 |
70 | return {
71 | loading,
72 | error,
73 | data
74 | }
75 | }
76 |
77 | export default useProjects
78 |
--------------------------------------------------------------------------------
/src/hooks/useToken.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery, gql } from "@apollo/client"
2 |
3 | const tokenQuery = (id: string) => `
4 | query GetToken {
5 | token(
6 | id: "${id}"
7 | ) {
8 | id
9 | tokenId
10 | invocation
11 | createdAt
12 | uri
13 | owner {
14 | id
15 | }
16 | project {
17 | id
18 | projectId
19 | name
20 | artistName
21 | artistAddress
22 | scriptJSON
23 | }
24 | }
25 | }`
26 |
27 | const useToken = (id: string) => {
28 | const { loading, error, data } = useQuery(gql(tokenQuery(id)))
29 |
30 | return {
31 | loading,
32 | error,
33 | data
34 | }
35 | }
36 |
37 | export default useToken
38 |
--------------------------------------------------------------------------------
/src/hooks/useTokenTraits.tsx:
--------------------------------------------------------------------------------
1 | import axios from "axios"
2 | import { useState, useEffect } from "react"
3 | import { getContractConfigByAddress } from "utils/contractInfoHelper";
4 |
5 | const useTokenTraits = (contractAddress: string, tokenId: string) => {
6 | const [data, setData] = useState(null)
7 | const [error, setError] = useState(false)
8 | const [loading, setLoading] = useState(false)
9 | const contractConfig = getContractConfigByAddress(contractAddress)
10 |
11 | useEffect(() => {
12 | setLoading(true)
13 |
14 | const fetchData = async () => {
15 | try {
16 | const tokenUrl = contractConfig?.TOKEN_URL
17 | const r = await axios.get(`${tokenUrl}/${contractAddress}/${tokenId}`)
18 | setData(r.data)
19 | } catch (error) {
20 | setError(true)
21 | } finally {
22 | setLoading(false)
23 | }
24 | }
25 |
26 | fetchData()
27 | }, [tokenId, contractAddress])
28 |
29 | return {
30 | loading,
31 | error,
32 | data
33 | }
34 | }
35 |
36 | export default useTokenTraits
37 |
--------------------------------------------------------------------------------
/src/hooks/useTokens.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery, gql } from "@apollo/client"
2 | import { TOKENS_PER_PAGE } from "config"
3 | import { OrderDirection } from "utils/types"
4 |
5 | interface TokensQueryParams {
6 | first?: number
7 | skip?: number
8 | orderDirection?: OrderDirection
9 | }
10 |
11 | const tokensQuery = (projectId: string, {
12 | first,
13 | skip,
14 | orderDirection
15 | }: TokensQueryParams) => `
16 | query GetTokens {
17 | tokens(
18 | first: ${first},
19 | skip: ${skip},
20 | orderBy: createdAt orderDirection: ${orderDirection},
21 | where: {
22 | project: "${projectId}"
23 | }
24 | ) {
25 | id
26 | tokenId
27 | invocation
28 | }
29 | }`
30 |
31 | const useTokens = (projectId: string, params: TokensQueryParams) => {
32 | const first = params?.first || TOKENS_PER_PAGE
33 | const skip = params?.skip || 0
34 | const orderDirection = params?.orderDirection || OrderDirection.ASC
35 |
36 | const { loading, error, data } = useQuery(gql(tokensQuery(projectId, {
37 | first,
38 | skip,
39 | orderDirection
40 | })))
41 |
42 | return {
43 | loading,
44 | error,
45 | data
46 | }
47 | }
48 |
49 | export default useTokens
50 |
--------------------------------------------------------------------------------
/src/hooks/useWindowSize.tsx:
--------------------------------------------------------------------------------
1 | // Source: https://usehooks-ts.com/react-hook/use-window-size
2 | import { useState } from "react"
3 | import { useEventListener, useIsomorphicLayoutEffect } from "usehooks-ts"
4 |
5 | interface WindowSize {
6 | width: number
7 | height: number
8 | }
9 |
10 | function useWindowSize(): WindowSize {
11 | const [windowSize, setWindowSize] = useState({
12 | width: 0,
13 | height: 0
14 | })
15 |
16 | const handleSize = () => {
17 | setWindowSize({
18 | width: window.innerWidth,
19 | height: window.innerHeight,
20 | })
21 | }
22 |
23 | useEventListener("resize", handleSize)
24 |
25 | // Set size at the first client-side load
26 | useIsomorphicLayoutEffect(() => {
27 | handleSize()
28 | // eslint-disable-next-line react-hooks/exhaustive-deps
29 | }, [])
30 |
31 | return windowSize
32 | }
33 |
34 | export default useWindowSize
35 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Archivo+Black&display=swap");
2 | @import url("https://fonts.googleapis.com/css2?family=Raleway:wght@400;500;600&display=swap");
3 |
4 | body {
5 | margin: 0;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | font-family: "Geometric";
9 | }
10 |
11 | code {
12 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
13 | }
14 |
15 | a, a:visited {
16 | color: "black";
17 | text-decoration: none;
18 | }
19 |
20 | a:hover {
21 | text-decoration: underline;
22 | }
23 |
24 | .mobile-link:hover {
25 | text-decoration: none;
26 | }
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import ReactDOM from "react-dom/client"
3 | import "./index.css"
4 | import App from "./App"
5 |
6 | window.Buffer = window.Buffer || require("buffer").Buffer
7 |
8 | const root = ReactDOM.createRoot(
9 | document.getElementById("root") as HTMLElement
10 | )
11 |
12 | root.render(
13 |
14 |
15 |
16 | )
17 |
--------------------------------------------------------------------------------
/src/pages/LandingPage.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Box,
3 | Typography
4 | } from "@mui/material"
5 | import Page from "components/Page"
6 |
7 | const LandingPage = () => {
8 | return (
9 |
10 |
11 | ArtBlocks Engine
12 | Template Project
13 |
14 |
15 | )
16 | }
17 |
18 | export default LandingPage
19 |
--------------------------------------------------------------------------------
/src/pages/ProjectPage.tsx:
--------------------------------------------------------------------------------
1 | import { useParams } from "react-router-dom"
2 | import Page from "components/Page"
3 | import ProjectDetails from "components/ProjectDetails"
4 |
5 | const ProjectPage = () => {
6 | const { contractAddress, projectId } = useParams()
7 | return (
8 |
9 | {
10 | contractAddress && projectId &&
11 | }
12 |
13 | )
14 | }
15 |
16 | export default ProjectPage
17 |
--------------------------------------------------------------------------------
/src/pages/ProjectsPage.tsx:
--------------------------------------------------------------------------------
1 | import Page from "components/Page"
2 | import Projects from "components/Projects"
3 |
4 | const ProjectsPage = () => {
5 | return (
6 |
7 |
8 |
9 | )
10 | }
11 |
12 | export default ProjectsPage
--------------------------------------------------------------------------------
/src/pages/TokenPage.tsx:
--------------------------------------------------------------------------------
1 | import { useParams } from "react-router-dom"
2 | import Page from "components/Page"
3 | import TokenDetails from "components/TokenDetails"
4 |
5 | const TokenPage = () => {
6 | const { contractAddress, id } = useParams()
7 | return (
8 |
9 | {
10 | contractAddress && id &&
11 | }
12 |
13 | )
14 | }
15 |
16 | export default TokenPage
17 |
--------------------------------------------------------------------------------
/src/pages/UserPage.tsx:
--------------------------------------------------------------------------------
1 | import { useParams } from "react-router-dom"
2 | import Page from "components/Page"
3 | import OwnedProjects from "components/OwnedProjects"
4 |
5 | const UserPage = () => {
6 | const { walletAddress } = useParams()
7 | return (
8 |
9 | {
10 | walletAddress &&
11 | }
12 |
13 | )
14 | }
15 |
16 | export default UserPage
17 |
--------------------------------------------------------------------------------
/src/theme.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import {
3 | Link as RouterLink,
4 | LinkProps as RouterLinkProps
5 | } from "react-router-dom"
6 | import { createTheme, PaletteColor } from "@mui/material/styles"
7 | import { LinkProps } from "@mui/material/Link"
8 |
9 | declare module "@mui/material/styles" {
10 | interface Palette {
11 | upcoming: PaletteColor
12 | }
13 | interface PaletteOptions {
14 | upcoming: PaletteColor
15 | }
16 | }
17 |
18 | declare module "@mui/material/Chip" {
19 | interface ChipPropsColorOverrides {
20 | upcoming: true
21 | }
22 | }
23 |
24 | const LinkBehavior = React.forwardRef<
25 | any,
26 | Omit & { href: RouterLinkProps["to"] }
27 | >((props, ref) => {
28 | const { href, ...other } = props
29 | // Map href (MUI) -> to (react-router)
30 | return
31 | })
32 |
33 | const { palette } = createTheme()
34 |
35 | const theme = createTheme({
36 | palette: {
37 | primary: {
38 | main: "#212121",
39 | contrastText: "#ECF0F1"
40 | },
41 | secondary: {
42 | main: "#2C3E50",
43 | contrastText: "#ECF0F1"
44 | },
45 | success: {
46 | main: "#27AE60"
47 | },
48 | error: {
49 | main: "#E74C3C"
50 | },
51 | upcoming: palette.augmentColor({
52 | color: {
53 | main: "#CE7A18"
54 | }
55 | })
56 | },
57 | typography: {
58 | fontFamily: [
59 | "Raleway",
60 | "Geometric",
61 | "Segoe UI",
62 | "Helvetica Neue",
63 | "Arial",
64 | "sans-serif"
65 | ].join(",")
66 | },
67 | components: {
68 | MuiLink: {
69 | defaultProps: {
70 | component: LinkBehavior
71 | } as LinkProps
72 | },
73 | MuiButtonBase: {
74 | defaultProps: {
75 | LinkComponent: LinkBehavior
76 | }
77 | }
78 | }
79 | })
80 |
81 | theme.typography.h1 = {
82 | fontFamily: "Archivo Black",
83 | fontWeight: "400"
84 | }
85 |
86 | export default theme
--------------------------------------------------------------------------------
/src/utils/contractInfoHelper.ts:
--------------------------------------------------------------------------------
1 | import { CONTRACT_INFO } from "config"
2 |
3 | export const getContractConfigByAddress = (contractAddress: string) => {
4 | return CONTRACT_INFO.find(
5 | x => x.CORE_CONTRACT_ADDRESS.toLowerCase() === contractAddress.toLowerCase()
6 | )
7 | }
8 |
9 | export const getConfiguredContractAddresses = () => {
10 | return CONTRACT_INFO.map(x => x.CORE_CONTRACT_ADDRESS)
11 | }
12 |
--------------------------------------------------------------------------------
/src/utils/getMintingInterface.ts:
--------------------------------------------------------------------------------
1 | import GenArt721MinterInterface from "components/MinterInterfaces/GenArt721MinterInterface"
2 | import MinterSetPriceV4Interface from "components/MinterInterfaces/MinterSetPriceV4Interface"
3 | import MinterDAExpV4Interface from "components/MinterInterfaces/MinterDAExpV4Interface"
4 | import MinterMerkleV5Interface from "components/MinterInterfaces/MinterMerkleV5Interface"
5 | import MinterHolderV4Interface from "components/MinterInterfaces/MinterHolderV4Interface"
6 | import MinterSetPriceERC20V4Interface from "components/MinterInterfaces/MinterSetPriceERC20V4Interface"
7 | import MinterDAExpSettlementV1Interface from "components/MinterInterfaces/MinterDAExpSettlementV1Interface"
8 |
9 | export const getMintingInterface = (contractVersion: string, minterType: string | null) => {
10 | if (contractVersion === "V2") {
11 | return GenArt721MinterInterface
12 | } else if (contractVersion === "V3") {
13 | if (minterType === "MinterDAExpV4") return MinterDAExpV4Interface
14 | if (minterType === "MinterSetPriceV4") return MinterSetPriceV4Interface
15 | if (minterType === "MinterMerkleV5") return MinterMerkleV5Interface
16 | if (minterType === "MinterHolderV4") return MinterHolderV4Interface
17 | if (minterType === "MinterSetPriceERC20V4") return MinterSetPriceERC20V4Interface
18 | if (minterType === "MinterDAExpSettlementV1") return MinterDAExpSettlementV1Interface
19 | }
20 | return null
21 | }
22 |
--------------------------------------------------------------------------------
/src/utils/numbers.ts:
--------------------------------------------------------------------------------
1 | import { utils, BigNumber } from "ethers"
2 |
3 | export const multiplyBigNumberByFloat = function(x: BigNumber, y: number) {
4 | return BigNumber.from(Math.floor(x.toNumber()*y))
5 | }
6 |
7 | export const formatEtherFixed = function(priceWei: string, fractionDigits: number) {
8 | const priceEther = utils.formatEther(priceWei)
9 | const priceEtherFixed = parseFloat(priceEther).toFixed(fractionDigits)
10 | return priceEtherFixed
11 | }
--------------------------------------------------------------------------------
/src/utils/scriptJSON.ts:
--------------------------------------------------------------------------------
1 | export const parseJson = (json: string) => {
2 | try {
3 | return JSON.parse(json)
4 | } catch (error) {
5 | return null
6 | }
7 | }
8 |
9 | export const parseAspectRatio = (scriptJSON:string) => {
10 | const scriptParams = parseJson(scriptJSON)
11 |
12 | if (!scriptParams) {
13 | return 1
14 | }
15 |
16 | const { aspectRatio } = scriptParams
17 |
18 | if (typeof aspectRatio === "string") {
19 | if (aspectRatio.indexOf("/") !== -1) {
20 | const [numerator, denominator] = aspectRatio.split("/")
21 | return parseFloat(numerator) / parseFloat(denominator)
22 | } else {
23 | return parseFloat(aspectRatio)
24 | }
25 | }
26 | return aspectRatio
27 | }
28 |
29 | export const parseScriptType = (scriptJSON: string) => {
30 | const scriptParams = parseJson(scriptJSON)
31 | return scriptParams?.type
32 | }
33 |
--------------------------------------------------------------------------------
/src/utils/types.ts:
--------------------------------------------------------------------------------
1 | export interface Project {
2 | id: string
3 | contract: Contract
4 | projectId: BigInt
5 | name: string
6 | description: string
7 | artistName: string
8 | artistAddress: string
9 | invocations: BigInt
10 | maxInvocations: BigInt
11 | activatedAt: BigInt
12 | scriptJSON: string
13 | aspectRatio: number
14 | active: boolean
15 | paused: boolean
16 | complete: boolean
17 | tokens: Token[]
18 | pricePerTokenInWei: BigInt
19 | currencyAddress: string
20 | currencySymbol: string
21 | minterConfiguration?: MinterConfiguration
22 | }
23 |
24 | export interface Contract {
25 | id: string
26 | }
27 |
28 | export interface Account {
29 | id: string
30 | }
31 |
32 | export interface Token {
33 | id: string
34 | tokenId: string
35 | invocation: BigInt
36 | uri: string
37 | createdAt: BigInt
38 | owner?: Account
39 | }
40 |
41 | export interface MinterConfiguration {
42 | basePrice: BigInt
43 | startPrice: BigInt
44 | priceIsconfigured: boolean
45 | currencySymbol: string
46 | currencyAddress: string,
47 | startTime: BigInt,
48 | endTime: BigInt
49 | }
50 |
51 | export interface Trait {
52 | trait_type: string
53 | value: string
54 | }
55 |
56 | export enum OrderDirection {
57 | ASC = "asc",
58 | DESC = "desc"
59 | }
60 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "baseUrl": "./src",
11 | "skipLibCheck": true,
12 | "esModuleInterop": true,
13 | "allowSyntheticDefaultImports": true,
14 | "strict": true,
15 | "forceConsistentCasingInFileNames": true,
16 | "noFallthroughCasesInSwitch": true,
17 | "module": "esnext",
18 | "moduleResolution": "node",
19 | "resolveJsonModule": true,
20 | "isolatedModules": true,
21 | "noEmit": true,
22 | "jsx": "react-jsx"
23 | },
24 | "include": [
25 | "src"
26 | ]
27 | }
--------------------------------------------------------------------------------