├── .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 | [![GitPOAPs](https://public-api.gitpoap.io/v1/repo/ArtBlocks/artblocks-engine-react/badge)](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 | [![Deploy with Vercel](https://vercel.com/button)](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 | 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 | 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 | artblocks 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 | 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 | 62 | 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 | 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 | 186 | 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 | 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 |