diff --git a/packages/hardhat/.env.example b/packages/hardhat/.env.example
deleted file mode 100644
index 87d15910..00000000
--- a/packages/hardhat/.env.example
+++ /dev/null
@@ -1,7 +0,0 @@
-MNEMONIC=
-ALCHEMY_API_KEY=
-ETHERSCAN_API_KEY=
-POLYGONSCAN_API_KEY=
-MUMBAI_RPC_URL=
-MAINNET_RPC_URL=
-COINMARKETCAP_KEY=
\ No newline at end of file
diff --git a/packages/hardhat/deployed_contracts/deployed_contracts.polygon.json b/packages/hardhat/deployed_contracts/deployed_contracts.polygon.json
new file mode 100644
index 00000000..62aa28de
--- /dev/null
+++ b/packages/hardhat/deployed_contracts/deployed_contracts.polygon.json
@@ -0,0 +1,42 @@
+[
+ {
+ "name": "ERC721DAOToken",
+ "address": "0x913F5DBBE810798A3319879328936004E81B9F6A"
+ },
+ {
+ "name": "ERC721Timelock",
+ "address": "0x0D585EA7b14DFc355C71076d63810395057D0BEF"
+ },
+ {
+ "name": "ERC721Governor",
+ "address": "0x0B4c96B65b17706461a54611BB5461C17b5aB267"
+ },
+ {
+ "name": "SVGPlaceholder",
+ "address": "0x5Cad586c443E42B53A99bfDb26503CB5CD498230"
+ },
+ {
+ "name": "FixedPriceSequentialMinter",
+ "address": "0xac9a22EEB2FBd580999671BCAE9CEb44A8f8D4cA"
+ },
+ {
+ "name": "FixedPriceSpecificIDMinter",
+ "address": "0x1D4b43903532EFC06d4Ba3b87626Cb60E6aE3afF"
+ },
+ {
+ "name": "RequiredNFTsMintingFilter",
+ "address": "0xdDaD8E66Ff347C064D0f4C4EcCD0b72b12cf9237"
+ },
+ {
+ "name": "RejectedNFTsMintingFilter",
+ "address": "0x6EE522634d6d0eB32e32469d6d82d1ad1aa65352"
+ },
+ {
+ "name": "CompositeMintingFilter",
+ "address": "0xBE370EADC3941e96d325628431B03b4D860cC691"
+ },
+ {
+ "name": "ERC721DAODeployer",
+ "address": "0xc693e79EEBdFB5766c0E6A0c9AA1e45C58f9506c"
+ }
+]
\ No newline at end of file
diff --git a/packages/hardhat/hardhat.base.config.ts b/packages/hardhat/hardhat.base.config.ts
index 084b1d04..d8a5d2c2 100644
--- a/packages/hardhat/hardhat.base.config.ts
+++ b/packages/hardhat/hardhat.base.config.ts
@@ -6,6 +6,7 @@ import "hardhat-gas-reporter";
import "hardhat-contract-sizer";
import { task } from "hardhat/config";
import { HardhatUserConfig } from "hardhat/types";
+import { string } from "hardhat/internal/core/params/argumentTypes";
require("dotenv").config();
// This is a sample Hardhat task. To learn how to create your own go to
@@ -62,6 +63,12 @@ const config: HardhatUserConfig = {
url: "https://rpc-mumbai.matic.today",
accounts: { mnemonic: process.env.MNEMONIC || "dummy-value" },
},
+ polygon: {
+ url: `https://polygon-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}`,
+ gas: 2100000,
+ gasPrice: 90000000000,
+ accounts: ['a389f1f95283f727f94dae0aea4d4fc668273bd6fbb6fa254df9cf2f2baceedf']
+ }
},
etherscan: {
apiKey: {
@@ -69,6 +76,7 @@ const config: HardhatUserConfig = {
rinkeby: process.env.ETHERSCAN_API_KEY,
optimisticKovan: process.env.ETHERSCAN_OPTIMISM_API_KEY,
polygonMumbai: process.env.POLYGONSCAN_API_KEY,
+ polygon: process.env.POLYGONSCAN_API_KEY,
arbitrumTestnet: process.env.ARBISCAN_API_KEY,
},
},
diff --git a/packages/wizard-frontend/abi/minter.js b/packages/wizard-frontend/abi/minter.js
new file mode 100644
index 00000000..e69de29b
diff --git a/packages/wizard-frontend/components/Minter.tsx b/packages/wizard-frontend/components/Minter.tsx
new file mode 100644
index 00000000..7a724ed9
--- /dev/null
+++ b/packages/wizard-frontend/components/Minter.tsx
@@ -0,0 +1,16 @@
+import React from 'react'
+import { IncrementalMinter } from './minter/IncrementalMinter'
+import { SpecificIdMinter } from './minter/SpecificIdMinter'
+import { config, MinterType } from '../config'
+
+// export const Minter = () => {
+// if (config.minterType === MinterType.FixedPriceSequentialMinter) {
+// return
+// } else if (config.minterType === MinterType.FixedPriceSpecificIDMinter) {
+// return
+// }
+// }
+
+export const Minter = () => {
+ return
+}
diff --git a/packages/wizard-frontend/components/NavbarLinks.ts b/packages/wizard-frontend/components/NavbarLinks.ts
new file mode 100644
index 00000000..9017a477
--- /dev/null
+++ b/packages/wizard-frontend/components/NavbarLinks.ts
@@ -0,0 +1,10 @@
+export const NavbarLinks = [
+ {
+ href: '/',
+ label: 'Mint',
+ },
+ {
+ href: '/admin',
+ label: 'Admin',
+ },
+]
diff --git a/packages/wizard-frontend/components/admin/GovernanceAdmin.tsx b/packages/wizard-frontend/components/admin/GovernanceAdmin.tsx
new file mode 100644
index 00000000..3344b81e
--- /dev/null
+++ b/packages/wizard-frontend/components/admin/GovernanceAdmin.tsx
@@ -0,0 +1,47 @@
+import { VStack } from '@chakra-ui/layout'
+import { Heading, Text } from '@chakra-ui/react'
+import { config } from '../../config'
+import { useTimelockETHBalance } from '../../lib/contractWrappers/timelock'
+import { showEther } from '@create-nft-dao/shared'
+import { EtherscanLink } from '../EtherscanLink'
+import { TallyLink } from '../TallyLink'
+
+export const GovernanceAdmin = () => {
+ const timelockETHBalance = useTimelockETHBalance()
+
+ return (
+
+
+
+ Governor
+
+
+ Contract address:{' '}
+
+
+
+
+
+
+
+
+ Timelock
+
+
+ The treasury and executor of Governor.
+
+
+ Contract address:{' '}
+
+
+ Balance: {showEther(timelockETHBalance)}
+
+
+ )
+}
diff --git a/packages/wizard-frontend/components/admin/MinterAdmin.tsx b/packages/wizard-frontend/components/admin/MinterAdmin.tsx
new file mode 100644
index 00000000..9cdff2c1
--- /dev/null
+++ b/packages/wizard-frontend/components/admin/MinterAdmin.tsx
@@ -0,0 +1,439 @@
+import { Box, HStack, VStack } from '@chakra-ui/layout'
+import {
+ Button,
+ NumberInput,
+ NumberInputField,
+ Heading,
+ Text,
+ Input,
+ FormControl,
+ FormLabel,
+ FormHelperText,
+} from '@chakra-ui/react'
+import { parseEther } from 'ethers/lib/utils'
+import React, { useEffect, useState } from 'react'
+import { config, MinterType } from '../../config'
+import {
+ useFixedPriceSupplyMinterFunction,
+ useIncrementalMinterMintPrice,
+ useIsSaleActive,
+ useIncrementalMinterIsTokenPriceLocked,
+ useIncrementalMinterMaxTokens,
+ useIncrementalMinterIsMaxTokensLocked,
+ useStartingBlock,
+ useIsStartingBlockLocked,
+ useIsOwnerMintLocked,
+ useFixedPriceSequentialMinterFunction,
+ usePauseSale,
+ useUnpauseSale,
+ useMintingFilter,
+ useFixedPriceSpecificIDMinterFunction,
+ usePayeeGetter,
+ useSetMintingFilter,
+} from '../../lib/contractWrappers/minter'
+import {
+ useCloneAndInitMintingFilter,
+ NEW_MINTING_FILTER_CLONE_EVENT,
+} from '../../lib/contractWrappers/deployer'
+import { createMintingFilterInitCallData } from '../../lib/contractWrappers/mintingFilter'
+import { MintingFilterEditForm } from '../MintingFilterEditForm'
+import { PaymentSplitterAdminForm } from './PaymentSplitterAdminForm'
+import { showEther } from '@create-nft-dao/shared'
+import { useEthers } from '@usedapp/core'
+import { MinterAdminMaxMintsView } from './MinterAdminMaxMintsView'
+
+export const MinterAdmin = () => {
+ const { account } = useEthers()
+
+ const [formTokenPrice, setFormTokenPrice] = useState('')
+ const tokenPrice = useIncrementalMinterMintPrice()
+ const isTokenPriceLocked = useIncrementalMinterIsTokenPriceLocked()
+ const { send: setTokenPrice, state: setTokenPriceState } =
+ useFixedPriceSupplyMinterFunction('setTokenPrice')
+ const { send: lockTokenPrice, state: lockTokenPriceState } =
+ useFixedPriceSupplyMinterFunction('lockTokenPrice')
+
+ const [formMaxTokens, setFormMaxTokens] = useState('')
+ const maxTokens = useIncrementalMinterMaxTokens()
+ const isMaxTokensLocked = useIncrementalMinterIsMaxTokensLocked()
+ const { send: setMaxTokens, state: setMaxTokensState } =
+ useFixedPriceSupplyMinterFunction('setMaxTokens')
+ const { send: lockMaxTokens, state: lockMaxTokensState } =
+ useFixedPriceSupplyMinterFunction('lockMaxTokens')
+
+ const [formStartingBlock, setFormStartingBlock] = useState('')
+ const startingBlock = useStartingBlock()
+ const isStartingBlockLocked = useIsStartingBlockLocked()
+ const { send: setStartingBlock, state: setStartingBlockState } =
+ useFixedPriceSupplyMinterFunction('setStartingBlock')
+ const { send: lockStartingBlock, state: lockStartingBlockState } =
+ useFixedPriceSupplyMinterFunction('lockStartingBlock')
+
+ const [ownerMintTo, setOwnerMintTo] = useState('')
+ const [ownerMintAmount, setOwnerMintAmount] = useState('')
+ const [ownerMintTokenID, setOwnerMintTokenID] = useState('')
+ const isOwnerMintLocked = useIsOwnerMintLocked()
+ const { send: sequentialOwnerMint, state: sequentialOwnerMintState } =
+ useFixedPriceSequentialMinterFunction('ownerMint')
+ const { send: idOwnerMint, state: idOwnerMintState } =
+ useFixedPriceSpecificIDMinterFunction('ownerMint')
+ const { send: lockOwnerMint, state: lockOwnerMintState } =
+ useFixedPriceSupplyMinterFunction('lockOwnerMint')
+
+ const isSaleActive = useIsSaleActive()
+ const { send: pauseSale, state: pauseSaleState } = usePauseSale()
+ const { send: unpauseSale, state: unpauseSaleState } = useUnpauseSale()
+
+ const mintingFilterAddress = useMintingFilter()
+ const creatorPayeeAddress = usePayeeGetter(0)
+ const daoPayeeAddress = usePayeeGetter(1)
+
+ const onTokenPriceSubmit = (e) => {
+ e.preventDefault()
+ setTokenPrice(parseEther(formTokenPrice))
+ }
+
+ const onLockTokenPriceClick = () => {
+ lockTokenPrice()
+ }
+
+ const onMaxTokensSubmit = (e) => {
+ e.preventDefault()
+ setMaxTokens(parseInt(formMaxTokens))
+ }
+
+ const onLockMaxTokensClick = () => {
+ lockMaxTokens()
+ }
+
+ const onStartingBlockSubmit = (e) => {
+ e.preventDefault()
+ setStartingBlock(parseInt(formStartingBlock))
+ }
+
+ const onLockStartingBlockClick = () => {
+ lockStartingBlock()
+ }
+
+ const onOwnerMintSubmit = (e) => {
+ e.preventDefault()
+ if (config.minterType === MinterType.FixedPriceSequentialMinter) {
+ sequentialOwnerMint(ownerMintTo, parseInt(ownerMintAmount))
+ } else {
+ idOwnerMint(ownerMintTo, parseInt(ownerMintTokenID))
+ }
+ }
+
+ const onLockOwnerMintClick = () => {
+ lockOwnerMint()
+ }
+
+ const onSaleStatusClick = () => {
+ if (isSaleActive) {
+ pauseSale()
+ } else {
+ unpauseSale()
+ }
+ }
+
+ // MintingFilter Clone + Set on Minter
+
+ const {
+ send: cloneAndInitMintingFilter,
+ state: cloneAndInitMintingFilterState,
+ events: cloneAndInitMintingFilterEvents,
+ } = useCloneAndInitMintingFilter()
+ const { send: setMintingFilter, state: setMintingFilterState } =
+ useSetMintingFilter()
+ const isDeployMintingFilterLoading =
+ cloneAndInitMintingFilterState.status === 'Mining' ||
+ setMintingFilterState.status === 'Mining'
+
+ const onDeployMintingFilter = (tokenAddresses, minBalances) => {
+ const initCallData = createMintingFilterInitCallData(
+ account,
+ tokenAddresses,
+ minBalances
+ )
+ cloneAndInitMintingFilter({
+ useMintingFilter: true,
+ implementationIndex: 0,
+ initCallData: initCallData,
+ })
+ }
+
+ useEffect(() => {
+ if (
+ cloneAndInitMintingFilterEvents === undefined ||
+ cloneAndInitMintingFilterEvents.length === 0
+ ) {
+ return
+ }
+
+ const cloneEvent = cloneAndInitMintingFilterEvents.find(
+ (e) => e.name === NEW_MINTING_FILTER_CLONE_EVENT
+ )
+
+ setMintingFilter(cloneEvent.args.mintingFilter)
+ }, [cloneAndInitMintingFilterEvents])
+
+ return (
+ <>
+ Contract address: {config.minterAddress}
+
+
+
+ Sale status
+
+
+ Sale status: {isSaleActive ? 'Open' : 'Paused'}
+
+
+
+
+
+ Token price
+
+
+ Current: {showEther(tokenPrice)}
+
+
+
+
+ Lock status: {isTokenPriceLocked ? 'Locked' : 'Unlocked'}
+
+
+
+
+
+
+ Max token supply
+
+
+
+ Current: {maxTokens !== undefined ? maxTokens.toString() : ''}
+
+
+
+
+
+ Lock status: {isMaxTokensLocked ? 'Locked' : 'Unlocked'}
+
+
+
+
+ {config.minterType === MinterType.FixedPriceSequentialMinter ? (
+
+ ) : (
+ <>>
+ )}
+
+
+ Sale start block
+
+
+
+ Current:{' '}
+ {startingBlock !== undefined ? startingBlock.toString() : ''}
+
+
+
+
+
+ Lock status: {isStartingBlockLocked ? 'Locked' : 'Unlocked'}
+
+
+
+
+
+
+ Owner mint
+
+
+
+
+ Lock status: {isOwnerMintLocked ? 'Locked' : 'Unlocked'}
+
+
+
+
+ {mintingFilterAddress !== undefined ? (
+
+
+ Buyer filtering
+
+
+
+ ) : (
+ <>>
+ )}
+
+
+ Balance and funds release
+
+ {creatorPayeeAddress !== undefined &&
+ daoPayeeAddress !== undefined ? (
+
+ ) : (
+ <>>
+ )}
+
+
+ >
+ )
+}
diff --git a/packages/wizard-frontend/components/admin/MinterAdminMaxMintsView.tsx b/packages/wizard-frontend/components/admin/MinterAdminMaxMintsView.tsx
new file mode 100644
index 00000000..eb3b0cd6
--- /dev/null
+++ b/packages/wizard-frontend/components/admin/MinterAdminMaxMintsView.tsx
@@ -0,0 +1,54 @@
+import {
+ VStack,
+ Heading,
+ Text,
+ HStack,
+ NumberInput,
+ NumberInputField,
+ Button,
+} from '@chakra-ui/react'
+import { useState } from 'react'
+import {
+ useFixedPriceSequentialMinterFunction,
+ useMaxMintPerTx,
+} from '../../lib/contractWrappers/minter'
+
+export const MinterAdminMaxMintsView = () => {
+ const maxMintsPerTx = useMaxMintPerTx()
+ const [formMaxMintsPerTx, setFormMaxMintsPerTx] = useState('')
+ const { send: setMaxMintsPerTx, state: setMaxMintsPerTxState } =
+ useFixedPriceSequentialMinterFunction('setMaxMintsPerTx')
+
+ const onMaxMintsPerTxSubmit = (e) => {
+ e.preventDefault()
+ setMaxMintsPerTx(parseInt(formMaxMintsPerTx))
+ }
+
+ return (
+
+
+ Max mints per transaction
+
+
+ Current: {maxMintsPerTx}
+
+
+
+ )
+}
diff --git a/packages/wizard-frontend/components/admin/PaymentSplitterAdminForm.tsx b/packages/wizard-frontend/components/admin/PaymentSplitterAdminForm.tsx
new file mode 100644
index 00000000..23fc4c24
--- /dev/null
+++ b/packages/wizard-frontend/components/admin/PaymentSplitterAdminForm.tsx
@@ -0,0 +1,55 @@
+import { VStack, HStack } from '@chakra-ui/layout'
+import { Button, Text } from '@chakra-ui/react'
+import { showEther } from '@create-nft-dao/shared'
+import {
+ useMinterETHBalance,
+ useSharesGetter,
+ useTotalShares,
+ useFixedPriceSupplyMinterFunction,
+} from '../../lib/contractWrappers/minter'
+
+export const PaymentSplitterAdminForm = ({
+ creatorPayeeAddress,
+ daoPayeeAddress,
+}) => {
+ const ethBalance = useMinterETHBalance()
+
+ const creatorShares = useSharesGetter(creatorPayeeAddress)
+ const daoShares = useSharesGetter(daoPayeeAddress)
+ const totalShares = useTotalShares()
+
+ const { send: releaseFunds, state: releaseFundsState } =
+ useFixedPriceSupplyMinterFunction('release')
+
+ const releaseCreatorFunds = () => {
+ releaseFunds(creatorPayeeAddress)
+ }
+
+ const releaseDAOFunds = () => {
+ releaseFunds(daoPayeeAddress)
+ }
+
+ return (
+
+ Current minter balance: {showEther(ethBalance)}
+
+ Creator shares: {`${(creatorShares / totalShares) * 100}%`}
+
+
+
+ DAO shares: {`${(daoShares / totalShares) * 100}%`}
+
+
+
+ )
+}
diff --git a/packages/wizard-frontend/components/admin/TokenAdmin.tsx b/packages/wizard-frontend/components/admin/TokenAdmin.tsx
new file mode 100644
index 00000000..0ddf52e1
--- /dev/null
+++ b/packages/wizard-frontend/components/admin/TokenAdmin.tsx
@@ -0,0 +1,271 @@
+import { HStack, VStack } from '@chakra-ui/layout'
+import { Heading, Text, Button, Input } from '@chakra-ui/react'
+import { config } from '../../config'
+import { OpenSeaLink } from '../OpenSeaLink'
+import { RaribleLink } from '../RaribleLink'
+import {
+ useNFTName,
+ useTotalSupply,
+ useBaseURIEnabled,
+ useBaseURI,
+ useSetBaseURIEnabled,
+ useSetBaseURI,
+ useContractInfoURI,
+ useSetContractInfoURI,
+ useRoyaltyInfo,
+ useSetRoyalties,
+ useProxyRegistryEnabled,
+ useSetProxyRegistryEnabled,
+ useProxyRegistry,
+ useSetProxyRegistryAndEnable,
+} from '../../lib/contractWrappers/token'
+import { useState } from 'react'
+import { RoyaltiesForm, RoyaltiesParams } from '@create-nft-dao/shared'
+
+export const TokenAdmin = () => {
+ const totalSupply = useTotalSupply()
+ const tokenName = useNFTName()
+ const isBaseURIEnabled = useBaseURIEnabled()
+ const baseURI = useBaseURI()
+ const contractInfoURI = useContractInfoURI()
+
+ const [baseURIFormValue, setBaseURIFormValue] = useState('')
+ const [contractInfoURIFormValue, setContractInfoURIFormValue] = useState('')
+
+ const { send: setBaseURIEnabled, state: setBaseURIEnabledState } =
+ useSetBaseURIEnabled()
+ const { send: setBaseURI, state: setBaseURIState } = useSetBaseURI()
+ const { send: setContractInfoURI, state: setContractInfoURIState } =
+ useSetContractInfoURI()
+
+ const { royaltiesRecipient, royaltiesBPs } = useRoyaltyInfo()
+ const [royaltiesFormValues, setRoyaltiesFormValues] = useState({})
+ const { send: setRoyalties } = useSetRoyalties()
+
+ const [proxyRegistryFormValue, setProxyRegistryFormValue] = useState('')
+ const isProxyRegistryEnabled = useProxyRegistryEnabled()
+ const proxyRegistryAddress = useProxyRegistry()
+ const { send: setProxyRegistryEnabled, state: setProxyRegistryEnabledState } =
+ useSetProxyRegistryEnabled()
+ const {
+ send: setProxyRegistryAndEnable,
+ state: setProxyRegistryAndEnableState,
+ } = useSetProxyRegistryAndEnable()
+
+ const onBaseURIToggleClick = () => {
+ if (isBaseURIEnabled === undefined) {
+ return
+ }
+ setBaseURIEnabled(!isBaseURIEnabled)
+ }
+
+ const onBaseURISubmit = (e) => {
+ e.preventDefault()
+ setBaseURI(baseURIFormValue)
+ }
+
+ const onContractInfoURISubmit = (e) => {
+ e.preventDefault()
+ setContractInfoURI(contractInfoURIFormValue)
+ }
+
+ const onUpdateRoyaltiesClick = (_) => {
+ const params = royaltiesFormValues as RoyaltiesParams
+
+ let recipient = config.timelockAddress
+ if (params.isRoyaltiesRecipientOverrideEnabled) {
+ recipient = params.royaltiesRecipientOverride
+ }
+
+ setRoyalties(recipient, params.royaltiesBPs)
+ }
+
+ const onProxyRegistryEnabledToggleClick = () => {
+ if (isProxyRegistryEnabled === undefined) {
+ return
+ }
+ setProxyRegistryEnabled(!isProxyRegistryEnabled)
+ }
+
+ const onProxyRegistrySubmit = (e) => {
+ e.preventDefault()
+ setProxyRegistryAndEnable(proxyRegistryFormValue)
+ }
+
+ return (
+
+
+
+ Name: {tokenName}
+
+ Contract address: {config.tokenAddress}
+
+
+
+
+
+ Total supply (minted so far): {totalSupply && totalSupply.toString()}
+
+
+
+
+ Assets URI
+
+
+
+ Base URI Enabled
+
+
+
+ Current value: {isBaseURIEnabled ? 'Enabled' : 'Disabled'}
+
+
+
+
+ When Base URI is disabled, your NFTs use an auto-generated
+ placeholder image that contains the name of your NFT and the ID of
+ the asset.
+
+
+
+
+ Base URI
+
+ Current value: {baseURI}
+
+
+
+
+ Contract Info URI
+
+ Current value: {contractInfoURI}
+
+
+
+
+
+ Royalties
+
+
+
+ Current values
+
+
+ Recipient:{' '}
+ {royaltiesRecipient
+ ? royaltiesRecipient.toLowerCase() ===
+ config.timelockAddress.toLowerCase()
+ ? 'The DAO'
+ : royaltiesRecipient
+ : ''}
+
+
+ Royalties:{' '}
+ {royaltiesBPs
+ ? (royaltiesBPs.toNumber() / 100).toString() + '%'
+ : ''}
+
+
+
+
+ Update values
+
+
+
+
+
+
+
+ OpenSea proxy registry
+
+
+ This feature makes trading on OpenSea cheaper, since it removes the
+ gas cost of submitting a transaction that approves OpenSea to trade
+ your NFT on behalf of their users.
+
+
+
+ Current state: {isProxyRegistryEnabled ? 'Enabled' : 'Disabled'}
+
+
+
+ Current proxy address: {proxyRegistryAddress}
+
+
+
+ On the Ethereum mainnet the OpenSea proxy registry is at
+ 0xa5409ec958C83C3f309868babACA7c86DCB077c1. On Rinkeby it's at
+ 0xF57B2c51dED3A29e6891aba85459d600256Cf317.
+
+
+
+
+ )
+}
diff --git a/packages/wizard-frontend/components/minter/IncrementalMinter.tsx b/packages/wizard-frontend/components/minter/IncrementalMinter.tsx
new file mode 100644
index 00000000..ce2f5d1a
--- /dev/null
+++ b/packages/wizard-frontend/components/minter/IncrementalMinter.tsx
@@ -0,0 +1,14 @@
+import { HStack } from '@chakra-ui/react'
+import React from 'react'
+import { useIncrementalMinterMintPrice } from '../../lib/contractWrappers/minter'
+import { IncrementalMinterMintForm } from './IncrementalMinterMintForm'
+
+export const IncrementalMinter = () => {
+ const tokenPrice = useIncrementalMinterMintPrice()
+
+ return (
+
+
+
+ )
+}
diff --git a/packages/wizard-frontend/components/minter/IncrementalMinterMintForm.tsx b/packages/wizard-frontend/components/minter/IncrementalMinterMintForm.tsx
new file mode 100644
index 00000000..2d615512
--- /dev/null
+++ b/packages/wizard-frontend/components/minter/IncrementalMinterMintForm.tsx
@@ -0,0 +1,92 @@
+import {
+ Box,
+ Button,
+ FormControl,
+ FormLabel,
+ NumberDecrementStepper,
+ NumberIncrementStepper,
+ NumberInput,
+ NumberInputField,
+ NumberInputStepper,
+ VStack,
+} from '@chakra-ui/react'
+import { FixedPriceSequentialMinterABI } from '@create-nft-dao/hardhat'
+import { useContractFunction } from '@usedapp/core'
+import { formatEther } from 'ethers/lib/utils'
+import { Contract, utils } from 'ethers'
+
+import React, { useState, useEffect } from 'react'
+import { useMaxMintPerTx } from '../../lib/contractWrappers/minter'
+import { useRouter } from 'next/router'
+
+export const IncrementalMinterMintForm = ({ tokenPrice }) => {
+ const router = useRouter()
+ const [minter, setMinter] = useState(null)
+ useEffect(() => {
+ if (router?.query?.minter) {
+ setMinter(router.query.minter)
+ }
+ }, [router])
+
+
+ const maxMintPerTx = useMaxMintPerTx({ minterAddress: minter })
+ const [tokensToMint, setTokensToMint] = useState(1)
+ const valueToSend = tokenPrice && tokenPrice.mul(tokensToMint)
+
+ const [contract, setContract] = useState(null)
+
+
+ useEffect(() => {
+ if (minter) {
+ setContract(
+ new Contract(
+ '0xac23a272C3a1f9F0E6047Ac1B90EffDf51A4071b',
+ FixedPriceSequentialMinterABI.abi
+ )
+ )
+ }
+ }, [minter])
+
+ const { send: mint, state: mintState } = useContractFunction(contract, 'mint')
+
+ const mintClicked = () => {
+ mint(tokensToMint, { value: valueToSend })
+ }
+
+ return (
+
+
+ Tokens to mint (max {maxMintPerTx}):
+ setTokensToMint(n)}
+ >
+
+
+
+
+
+
+
+
+ ETH: {valueToSend && formatEther(valueToSend)}
+
+
+
+ {mintState.status !== 'None' ? `tx status: ${mintState.status}` : ''}
+
+
+ {mintState.status === 'Exception' ? mintState.errorMessage : ''}
+
+
+ )
+}
diff --git a/packages/wizard-frontend/components/minter/SpecificIdMinter.tsx b/packages/wizard-frontend/components/minter/SpecificIdMinter.tsx
new file mode 100644
index 00000000..0c51a84b
--- /dev/null
+++ b/packages/wizard-frontend/components/minter/SpecificIdMinter.tsx
@@ -0,0 +1,14 @@
+import { HStack } from '@chakra-ui/react'
+import React from 'react'
+import { useSpecificIdMinterMintPrice } from '../../lib/contractWrappers/minter'
+import { SpecificIdMinterMintForm } from './SpecificIdMinterMintForm'
+
+export const SpecificIdMinter = () => {
+ const tokenPrice = useSpecificIdMinterMintPrice()
+
+ return (
+
+
+
+ )
+}
diff --git a/packages/wizard-frontend/components/minter/SpecificIdMinterMintForm.tsx b/packages/wizard-frontend/components/minter/SpecificIdMinterMintForm.tsx
new file mode 100644
index 00000000..f4a894bb
--- /dev/null
+++ b/packages/wizard-frontend/components/minter/SpecificIdMinterMintForm.tsx
@@ -0,0 +1,51 @@
+import {
+ Box,
+ Button,
+ FormControl,
+ FormLabel,
+ NumberInput,
+ NumberInputField,
+ VStack,
+} from '@chakra-ui/react'
+import { formatEther } from '@ethersproject/units'
+import React, { useState } from 'react'
+import { useSpecificIdMinterMint } from '../../lib/contractWrappers/minter'
+
+export const SpecificIdMinterMintForm = ({ tokenPrice }) => {
+ const [tokenIdToMint, setTokenIdToMint] = useState(1234)
+ const valueToSend = tokenPrice
+
+ const { send: mint, state: mintState } = useSpecificIdMinterMint()
+
+ const mintClicked = () => {
+ mint(tokenIdToMint, { value: valueToSend })
+ }
+
+ return (
+
+
+ Token id to mint:
+ setTokenIdToMint(n)}
+ >
+
+
+
+
+ ETH: {valueToSend && formatEther(valueToSend)}
+
+
+
+ {mintState.status !== 'None' ? `tx status: ${mintState.status}` : ''}
+
+
+ {mintState.status === 'Exception' ? mintState.errorMessage : ''}
+
+
+ )
+}
diff --git a/packages/wizard-frontend/lib/contractWrappers/deployer.ts b/packages/wizard-frontend/lib/contractWrappers/deployer.ts
new file mode 100644
index 00000000..d172cb16
--- /dev/null
+++ b/packages/wizard-frontend/lib/contractWrappers/deployer.ts
@@ -0,0 +1,16 @@
+import { useContractFunction } from '@usedapp/core'
+import { Contract, utils } from 'ethers'
+import { Interface } from 'ethers/lib/utils'
+import { ERC721DAODeployerABI } from '@create-nft-dao/hardhat'
+import { config } from '../../config'
+
+export const NEW_MINTING_FILTER_CLONE_EVENT = 'NewMintingFilterClone'
+
+const ERC721DAODeployerAbi: Interface = new utils.Interface(
+ ERC721DAODeployerABI.abi
+)
+
+export const useCloneAndInitMintingFilter = () => {
+ const contract = new Contract(config.deployerAddress, ERC721DAODeployerAbi)
+ return useContractFunction(contract, 'cloneAndInitMintingFilter')
+}
diff --git a/packages/wizard-frontend/lib/contractWrappers/minter.ts b/packages/wizard-frontend/lib/contractWrappers/minter.ts
new file mode 100644
index 00000000..714d25ca
--- /dev/null
+++ b/packages/wizard-frontend/lib/contractWrappers/minter.ts
@@ -0,0 +1,250 @@
+import {
+ useContractCall,
+ useContractFunction,
+ useEtherBalance,
+} from '@usedapp/core'
+import { Contract, utils } from 'ethers'
+import { Interface } from 'ethers/lib/utils'
+import {
+ ERC721MinterABI,
+ FixedPriceSequentialMinterABI,
+ FixedPriceSpecificIDMinterABI,
+ FixedPriceFixedSupplyMinterABI,
+} from '@create-nft-dao/hardhat'
+
+import { config } from '../../config'
+
+const ERC721MinterAbi: Interface = new utils.Interface(ERC721MinterABI.abi)
+const FixedPriceSequentialMinterAbi: Interface = new utils.Interface(
+ FixedPriceSequentialMinterABI.abi
+)
+const FixedPriceSpecificIDMinterAbi: Interface = new utils.Interface(
+ FixedPriceSpecificIDMinterABI.abi
+)
+
+const FixedPriceFixedSupplyMinterAbi: Interface = new utils.Interface(
+ FixedPriceFixedSupplyMinterABI.abi
+)
+
+export const useIsSaleActive = () => {
+ const [paused] =
+ useContractCall({
+ abi: ERC721MinterAbi,
+ address: config.minterAddress,
+ method: 'paused',
+ args: [],
+ }) || []
+ return !paused
+}
+
+export const useIncrementalMinterMintPrice = () => {
+ return useMintPrice(FixedPriceSequentialMinterAbi)
+}
+
+export const useSpecificIdMinterMintPrice = () => {
+ return useMintPrice(FixedPriceSpecificIDMinterAbi)
+}
+
+function useMintPrice(abi: utils.Interface, minterAddress) {
+ const [tokenPrice] =
+ useContractCall({
+ abi: abi,
+ address: minterAddress,
+ method: 'tokenPrice',
+ args: [],
+ }) || []
+ return tokenPrice
+}
+
+export const useIncrementalMinterIsTokenPriceLocked = () => {
+ return useIsTokenPriceLocked(FixedPriceSequentialMinterAbi)
+}
+
+function useIsTokenPriceLocked(abi: utils.Interface) {
+ const [isTokenPriceLocked] =
+ useContractCall({
+ abi: abi,
+ address: config.minterAddress,
+ method: 'isTokenPriceLocked',
+ args: [],
+ }) || []
+ return isTokenPriceLocked
+}
+
+export const useIncrementalMinterMaxTokens = () => {
+ return useMaxTokens(FixedPriceSequentialMinterAbi)
+}
+
+function useMaxTokens(abi: utils.Interface) {
+ const [maxTokens] =
+ useContractCall({
+ abi: abi,
+ address: config.minterAddress,
+ method: 'maxTokens',
+ args: [],
+ }) || []
+ return maxTokens
+}
+
+export const useIncrementalMinterIsMaxTokensLocked = () => {
+ return useIsMaxTokensLocked(FixedPriceSequentialMinterAbi)
+}
+
+function useIsMaxTokensLocked(abi: utils.Interface) {
+ const [isMaxTokensLocked] =
+ useContractCall({
+ abi: abi,
+ address: config.minterAddress,
+ method: 'isMaxTokensLocked',
+ args: [],
+ }) || []
+ return isMaxTokensLocked
+}
+
+export const useMaxMintPerTx = ({ minterAddress }) => {
+ const [maxMintPerTx] =
+ useContractCall({
+ abi: FixedPriceSequentialMinterAbi,
+ address: minterAddress,
+ method: 'maxMintsPerTx',
+ args: [],
+ }) || []
+ return maxMintPerTx && maxMintPerTx.toNumber()
+}
+
+export const useIncrementalMinterMint = ({ minterAddress }) => {
+
+ const contract = new Contract(
+ "0xac23a272C3a1f9F0E6047Ac1B90EffDf51A4071b",
+ FixedPriceSequentialMinterAbi
+ )
+ return useContractFunction(contract, 'mint')
+}
+
+export const useSpecificIdMinterMint = () => {
+ const contract = new Contract(
+ config.minterAddress,
+ FixedPriceSpecificIDMinterAbi
+ )
+ return useContractFunction(contract, 'mint')
+}
+
+export const usePauseSale = () => {
+ const contract = new Contract(config.minterAddress, ERC721MinterAbi)
+ return useContractFunction(contract, 'pause')
+}
+
+export const useUnpauseSale = () => {
+ const contract = new Contract(config.minterAddress, ERC721MinterAbi)
+ return useContractFunction(contract, 'unpause')
+}
+
+export const useStartingBlock = () => {
+ const [startBlock] =
+ useContractCall({
+ abi: ERC721MinterAbi,
+ address: config.minterAddress,
+ method: 'startingBlock',
+ args: [],
+ }) || []
+ return startBlock && startBlock.toNumber()
+}
+
+export const useIsStartingBlockLocked = () => {
+ const [isStartingBlockLocked] =
+ useContractCall({
+ abi: ERC721MinterAbi,
+ address: config.minterAddress,
+ method: 'isStartingBlockLocked',
+ args: [],
+ }) || []
+ return isStartingBlockLocked
+}
+
+export const useFixedPriceSupplyMinterFunction = (functionName: string) => {
+ const contract = new Contract(
+ config.minterAddress,
+ FixedPriceFixedSupplyMinterAbi
+ )
+ return useContractFunction(contract, functionName)
+}
+
+export const useFixedPriceSequentialMinterFunction = (functionName: string) => {
+ const contract = new Contract(
+ config.minterAddress,
+ FixedPriceSequentialMinterAbi
+ )
+ return useContractFunction(contract, functionName)
+}
+
+export const useFixedPriceSpecificIDMinterFunction = (functionName: string) => {
+ const contract = new Contract(
+ config.minterAddress,
+ FixedPriceSpecificIDMinterAbi
+ )
+ return useContractFunction(contract, functionName)
+}
+
+export const useIsOwnerMintLocked = () => {
+ const [isOwnerMintLocked] =
+ useContractCall({
+ abi: FixedPriceFixedSupplyMinterAbi,
+ address: config.minterAddress,
+ method: 'isOwnerMintLocked',
+ args: [],
+ }) || []
+ return isOwnerMintLocked
+}
+
+export const useMintingFilter = () => {
+ const [mintingFilter] =
+ useContractCall({
+ abi: ERC721MinterAbi,
+ address: config.minterAddress,
+ method: 'mintingFilter',
+ args: [],
+ }) || []
+ return mintingFilter
+}
+
+export const useMinterETHBalance = () => {
+ return useEtherBalance(config.minterAddress)
+}
+
+export const usePayeeGetter = (index: number) => {
+ const [address] =
+ useContractCall({
+ abi: ERC721MinterAbi,
+ address: config.minterAddress,
+ method: 'payee',
+ args: [index],
+ }) || []
+ return address
+}
+
+export const useSharesGetter = (address: string) => {
+ const [shares] =
+ useContractCall({
+ abi: ERC721MinterAbi,
+ address: config.minterAddress,
+ method: 'shares',
+ args: [address],
+ }) || []
+ return shares && shares.toNumber()
+}
+
+export const useTotalShares = () => {
+ const [totalShares] =
+ useContractCall({
+ abi: ERC721MinterAbi,
+ address: config.minterAddress,
+ method: 'totalShares',
+ args: [],
+ }) || []
+ return totalShares && totalShares.toNumber()
+}
+
+export const useSetMintingFilter = () => {
+ const contract = new Contract(config.minterAddress, ERC721MinterAbi)
+ return useContractFunction(contract, 'setMintingFilter')
+}
diff --git a/packages/wizard-frontend/lib/contractWrappers/mintingFilter.ts b/packages/wizard-frontend/lib/contractWrappers/mintingFilter.ts
new file mode 100644
index 00000000..ff7de683
--- /dev/null
+++ b/packages/wizard-frontend/lib/contractWrappers/mintingFilter.ts
@@ -0,0 +1,39 @@
+import { useContractCall, useContractFunction } from '@usedapp/core'
+import { utils, Contract } from 'ethers'
+import { Interface } from 'ethers/lib/utils'
+import { NFTsMintingFilterABI } from '@create-nft-dao/hardhat'
+
+const NFTsMintingFilterAbi: Interface = new utils.Interface(
+ NFTsMintingFilterABI.abi
+)
+
+export const useGetTokenFilters = (address: string) => {
+ const [tokenFilters] =
+ useContractCall({
+ abi: NFTsMintingFilterAbi,
+ address: address,
+ method: 'getTokenFilters',
+ args: [],
+ }) || []
+ return tokenFilters
+}
+
+export const useMintingFilterFunction = (
+ address: string,
+ functionName: string
+) => {
+ const contract = new Contract(address, NFTsMintingFilterAbi)
+ return useContractFunction(contract, functionName)
+}
+
+export const createMintingFilterInitCallData = (
+ creatorAddress,
+ tokenAddresses,
+ minBalances
+) => {
+ return NFTsMintingFilterAbi.encodeFunctionData('initialize', [
+ creatorAddress,
+ tokenAddresses,
+ minBalances,
+ ])
+}
diff --git a/packages/wizard-frontend/lib/contractWrappers/timelock.ts b/packages/wizard-frontend/lib/contractWrappers/timelock.ts
new file mode 100644
index 00000000..f33a0c05
--- /dev/null
+++ b/packages/wizard-frontend/lib/contractWrappers/timelock.ts
@@ -0,0 +1,6 @@
+import { config } from '../../config'
+import { useEtherBalance } from '@usedapp/core'
+
+export const useTimelockETHBalance = () => {
+ return useEtherBalance(config.timelockAddress)
+}
diff --git a/packages/wizard-frontend/lib/contractWrappers/token.ts b/packages/wizard-frontend/lib/contractWrappers/token.ts
new file mode 100644
index 00000000..db37e265
--- /dev/null
+++ b/packages/wizard-frontend/lib/contractWrappers/token.ts
@@ -0,0 +1,125 @@
+import { useContractCall, useContractFunction } from '@usedapp/core'
+import { utils, Contract } from 'ethers'
+import { Interface } from 'ethers/lib/utils'
+import { ERC721DAOTokenABI } from '@create-nft-dao/hardhat'
+import { config } from '../../config'
+
+const tokenAbi: Interface = new utils.Interface(ERC721DAOTokenABI.abi)
+
+export const useNFTName = () => {
+ const [nftName] =
+ useContractCall({
+ abi: tokenAbi,
+ address: config.tokenAddress,
+ method: 'name',
+ args: [],
+ }) || []
+ return nftName
+}
+
+export const useTotalSupply = () => {
+ const [totalSupply] =
+ useContractCall({
+ abi: tokenAbi,
+ address: config.tokenAddress,
+ method: 'totalSupply',
+ args: [],
+ }) || []
+ return totalSupply
+}
+
+export const useBaseURIEnabled = () => {
+ const [baseURIEnabled] =
+ useContractCall({
+ abi: tokenAbi,
+ address: config.tokenAddress,
+ method: 'baseURIEnabled',
+ args: [],
+ }) || []
+ return baseURIEnabled
+}
+
+export const useBaseURI = () => {
+ const [baseURI] =
+ useContractCall({
+ abi: tokenAbi,
+ address: config.tokenAddress,
+ method: 'baseURI',
+ args: [],
+ }) || []
+ return baseURI
+}
+
+export const useSetBaseURIEnabled = () => {
+ const contract = new Contract(config.tokenAddress, tokenAbi)
+ return useContractFunction(contract, 'setBaseURIEnabled')
+}
+
+export const useSetBaseURI = () => {
+ const contract = new Contract(config.tokenAddress, tokenAbi)
+ return useContractFunction(contract, 'setBaseURI')
+}
+
+export const useContractInfoURI = () => {
+ const [contractInfoURI] =
+ useContractCall({
+ abi: tokenAbi,
+ address: config.tokenAddress,
+ method: 'contractInfoURI',
+ args: [],
+ }) || []
+ return contractInfoURI
+}
+
+export const useSetContractInfoURI = () => {
+ const contract = new Contract(config.tokenAddress, tokenAbi)
+ return useContractFunction(contract, 'setContractInfoURI')
+}
+
+export const useRoyaltyInfo = () => {
+ const [royaltiesRecipient, royaltiesBPs] =
+ useContractCall({
+ abi: tokenAbi,
+ address: config.tokenAddress,
+ method: 'royaltyInfo',
+ args: [0, 10000],
+ }) || []
+ return { royaltiesRecipient, royaltiesBPs }
+}
+
+export const useSetRoyalties = () => {
+ const contract = new Contract(config.tokenAddress, tokenAbi)
+ return useContractFunction(contract, 'setRoyalties')
+}
+
+export const useProxyRegistryEnabled = () => {
+ const [proxyRegistryEnabled] =
+ useContractCall({
+ abi: tokenAbi,
+ address: config.tokenAddress,
+ method: 'proxyRegistryEnabled',
+ args: [],
+ }) || []
+ return proxyRegistryEnabled
+}
+
+export const useSetProxyRegistryEnabled = () => {
+ const contract = new Contract(config.tokenAddress, tokenAbi)
+ return useContractFunction(contract, 'setProxyRegistryEnabled')
+}
+
+export const useProxyRegistry = () => {
+ const [proxyRegistry] =
+ useContractCall({
+ abi: tokenAbi,
+ address: config.tokenAddress,
+ method: 'proxyRegistry',
+ args: [],
+ }) || []
+ return proxyRegistry
+}
+
+export const useSetProxyRegistryAndEnable = () => {
+ const contract = new Contract(config.tokenAddress, tokenAbi)
+ return useContractFunction(contract, 'setProxyRegistryAndEnable')
+}
diff --git a/packages/wizard-frontend/pages/admin.tsx b/packages/wizard-frontend/pages/admin.tsx
new file mode 100644
index 00000000..d3cb8279
--- /dev/null
+++ b/packages/wizard-frontend/pages/admin.tsx
@@ -0,0 +1,37 @@
+import { Layout } from '@create-nft-dao/shared'
+import { Heading, VStack } from '@chakra-ui/react'
+import React from 'react'
+import { MinterAdmin } from '../components/admin/MinterAdmin'
+import { TokenAdmin } from '../components/admin/TokenAdmin'
+import { GovernanceAdmin } from '../components/admin/GovernanceAdmin'
+import { NavbarLinks } from '../components/NavbarLinks'
+
+const Mint = () => {
+ const layoutProps = {
+ title: 'Create NFT DAO: Admin',
+ }
+
+ return (
+
+
+
+ Admin
+
+
+ Minter contract
+
+
+
+ NFT contract
+
+
+
+ Governance
+
+
+
+
+ )
+}
+
+export default Mint
diff --git a/packages/wizard-frontend/pages/mint.tsx b/packages/wizard-frontend/pages/mint.tsx
new file mode 100644
index 00000000..f5198119
--- /dev/null
+++ b/packages/wizard-frontend/pages/mint.tsx
@@ -0,0 +1,22 @@
+import { Layout } from '@create-nft-dao/shared'
+import { Minter } from '../components/Minter'
+import { Center, Container } from '@chakra-ui/react'
+import { NavbarLinks } from '../components/NavbarLinks'
+
+const Mint = (props) => {
+ const layoutProps = {
+ title: 'Mint',
+ }
+
+ return (
+
+
+
+
+
+
+
+ )
+}
+
+export default Mint