/* eslint-disable eslint-comments/disable-enable-pair */ /* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ import { coin } from '@cosmjs/proto-signing' import clsx from 'clsx' import { Alert } from 'components/Alert' import { Anchor } from 'components/Anchor' import { Button } from 'components/Button' import { CollectionDetails, MintingDetails, RoyaltyDetails, UploadDetails, WhitelistDetails, } from 'components/collections/creation' import type { BaseMinterDetailsDataProps } from 'components/collections/creation/BaseMinterDetails' import { BaseMinterDetails } from 'components/collections/creation/BaseMinterDetails' import type { CollectionDetailsDataProps } from 'components/collections/creation/CollectionDetails' import type { MintingDetailsDataProps } from 'components/collections/creation/MintingDetails' import type { RoyaltyDetailsDataProps } from 'components/collections/creation/RoyaltyDetails' import type { UploadDetailsDataProps } from 'components/collections/creation/UploadDetails' import type { WhitelistDetailsDataProps } from 'components/collections/creation/WhitelistDetails' import { Conditional } from 'components/Conditional' import { LoadingModal } from 'components/LoadingModal' import { useContracts } from 'contexts/contracts' import { useWallet } from 'contexts/wallet' import type { DispatchExecuteArgs as BaseFactoryDispatchExecuteArgs } from 'contracts/baseFactory/messages/execute' import { dispatchExecute as baseFactoryDispatchExecute } from 'contracts/baseFactory/messages/execute' import type { DispatchExecuteArgs as VendingFactoryDispatchExecuteArgs } from 'contracts/vendingFactory/messages/execute' import { dispatchExecute as vendingFactoryDispatchExecute } from 'contracts/vendingFactory/messages/execute' import type { NextPage } from 'next' import { NextSeo } from 'next-seo' import { useEffect, useMemo, useRef, useState } from 'react' import { toast } from 'react-hot-toast' import { upload } from 'services/upload' import { compareFileArrays } from 'utils/compareFileArrays' import { BASE_FACTORY_ADDRESS, BLOCK_EXPLORER_URL, NETWORK, SG721_CODE_ID, STARGAZE_URL, VENDING_FACTORY_ADDRESS, WHITELIST_CODE_ID, } from 'utils/constants' import { withMetadata } from 'utils/layout' import { links } from 'utils/links' import type { MinterType } from '../../components/collections/actions/Combobox' import type { UploadMethod } from '../../components/collections/creation/UploadDetails' import { ConfirmationModal } from '../../components/ConfirmationModal' import { getAssetType } from '../../utils/getAssetType' const CollectionCreationPage: NextPage = () => { const wallet = useWallet() const { baseMinter: baseMinterContract, vendingMinter: vendingMinterContract, whitelist: whitelistContract, vendingFactory: vendingFactoryContract, baseFactory: baseFactoryContract, } = useContracts() const scrollRef = useRef(null) const vendingFactoryMessages = useMemo( () => vendingFactoryContract?.use(VENDING_FACTORY_ADDRESS), [vendingFactoryContract, wallet.address], ) const baseFactoryMessages = useMemo( () => baseFactoryContract?.use(BASE_FACTORY_ADDRESS), [baseFactoryContract, wallet.address], ) const [uploadDetails, setUploadDetails] = useState(null) const [collectionDetails, setCollectionDetails] = useState(null) const [baseMinterDetails, setBaseMinterDetails] = useState(null) const [mintingDetails, setMintingDetails] = useState(null) const [whitelistDetails, setWhitelistDetails] = useState(null) const [royaltyDetails, setRoyaltyDetails] = useState(null) const [minterType, setMinterType] = useState('vending') const [uploading, setUploading] = useState(false) const [isMintingComplete, setIsMintingComplete] = useState(false) const [creatingCollection, setCreatingCollection] = useState(false) const [readyToCreateVm, setReadyToCreateVm] = useState(false) const [readyToCreateBm, setReadyToCreateBm] = useState(false) const [readyToUploadAndMint, setReadyToUploadAndMint] = useState(false) const [vendingMinterContractAddress, setVendingMinterContractAddress] = useState(null) const [sg721ContractAddress, setSg721ContractAddress] = useState(null) const [whitelistContractAddress, setWhitelistContractAddress] = useState(null) const [baseTokenUri, setBaseTokenUri] = useState(null) const [coverImageUrl, setCoverImageUrl] = useState(null) const [transactionHash, setTransactionHash] = useState(null) const performVendingMinterChecks = () => { try { setReadyToCreateVm(false) checkUploadDetails() checkCollectionDetails() checkMintingDetails() checkRoyaltyDetails() checkWhitelistDetails() .then(() => { checkwalletBalance() setReadyToCreateVm(true) }) .catch((err) => { if (String(err.message).includes('Insufficient wallet balance')) toast.error(`${err.message}`, { style: { maxWidth: 'none' } }) else toast.error(`Error in Whitelist Configuration: ${err.message}`, { style: { maxWidth: 'none' } }) setReadyToCreateVm(false) }) } catch (error: any) { toast.error(error.message, { style: { maxWidth: 'none' } }) setUploading(false) setReadyToCreateVm(false) } } const performBaseMinterChecks = () => { try { setReadyToCreateBm(false) checkUploadDetails() checkRoyaltyDetails() checkCollectionDetails() checkWhitelistDetails() .then(() => { setReadyToCreateBm(true) }) .catch((err) => { toast.error(`Error in Whitelist Configuration: ${err.message}`, { style: { maxWidth: 'none' } }) setReadyToCreateBm(false) }) } catch (error: any) { toast.error(error.message, { style: { maxWidth: 'none' } }) setUploading(false) } } const performUploadAndMintChecks = () => { try { setReadyToUploadAndMint(false) checkUploadDetails() checkWhitelistDetails() .then(() => { setReadyToUploadAndMint(true) }) .catch((err) => { toast.error(`Error in Whitelist Configuration: ${err.message}`, { style: { maxWidth: 'none' } }) setReadyToUploadAndMint(false) }) } catch (error: any) { toast.error(error.message, { style: { maxWidth: 'none' } }) setUploading(false) } } const resetReadyFlags = () => { setReadyToCreateVm(false) setReadyToCreateBm(false) setReadyToUploadAndMint(false) } const createVendingMinterCollection = async () => { try { setCreatingCollection(true) setBaseTokenUri(null) setCoverImageUrl(null) setVendingMinterContractAddress(null) setSg721ContractAddress(null) setWhitelistContractAddress(null) setTransactionHash(null) if (uploadDetails?.uploadMethod === 'new') { setUploading(true) const baseUri = await uploadFiles() //upload coverImageUri and append the file name const coverImageUri = await upload( collectionDetails?.imageFile as File[], uploadDetails.uploadService, 'cover', uploadDetails.nftStorageApiKey as string, uploadDetails.pinataApiKey as string, uploadDetails.pinataSecretKey as string, ) setUploading(false) setBaseTokenUri(baseUri) setCoverImageUrl(coverImageUri) let whitelist: string | undefined if (whitelistDetails?.whitelistType === 'existing') whitelist = whitelistDetails.contractAddress else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist() setWhitelistContractAddress(whitelist as string) await instantiateVendingMinter(baseUri, coverImageUri, whitelist) } else { setBaseTokenUri(uploadDetails?.baseTokenURI as string) setCoverImageUrl(uploadDetails?.imageUrl as string) let whitelist: string | undefined if (whitelistDetails?.whitelistType === 'existing') whitelist = whitelistDetails.contractAddress else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist() setWhitelistContractAddress(whitelist as string) await instantiateVendingMinter(baseTokenUri as string, coverImageUrl as string, whitelist) } setCreatingCollection(false) // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { toast.error(error.message, { style: { maxWidth: 'none' } }) setCreatingCollection(false) setUploading(false) } } const createBaseMinterCollection = async () => { try { setCreatingCollection(true) setBaseTokenUri(null) setCoverImageUrl(null) setVendingMinterContractAddress(null) setIsMintingComplete(false) setSg721ContractAddress(null) setWhitelistContractAddress(null) setTransactionHash(null) if (uploadDetails?.uploadMethod === 'new') { setUploading(true) const baseUri = await uploadFiles() //upload coverImageUri and append the file name const coverImageUri = await upload( collectionDetails?.imageFile as File[], uploadDetails.uploadService, 'cover', uploadDetails.nftStorageApiKey as string, uploadDetails.pinataApiKey as string, uploadDetails.pinataSecretKey as string, ) setUploading(false) setBaseTokenUri( `${baseUri}/${(uploadDetails.baseMinterMetadataFile as File).name.substring( 0, (uploadDetails.baseMinterMetadataFile as File).name.lastIndexOf('.'), )}`, ) setCoverImageUrl(coverImageUri) await instantiateBaseMinter( `ipfs://${baseUri}/${(uploadDetails.baseMinterMetadataFile as File).name.substring( 0, (uploadDetails.baseMinterMetadataFile as File).name.lastIndexOf('.'), )}`, coverImageUri, ) } else { setBaseTokenUri(uploadDetails?.baseTokenURI as string) setCoverImageUrl(uploadDetails?.imageUrl as string) await instantiateBaseMinter(uploadDetails?.baseTokenURI as string, uploadDetails?.imageUrl as string) } setCreatingCollection(false) // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { toast.error(error.message, { style: { maxWidth: 'none' } }) setCreatingCollection(false) setUploading(false) } } const uploadAndMint = async () => { try { if (!wallet.initialized) throw new Error('Wallet not connected') if (!baseMinterContract) throw new Error('Contract not found') setCreatingCollection(true) setBaseTokenUri(null) setCoverImageUrl(null) setVendingMinterContractAddress(null) setSg721ContractAddress(null) setTransactionHash(null) if (uploadDetails?.uploadMethod === 'new') { console.log(JSON.stringify(uploadDetails.baseMinterMetadataFile?.text())) setUploading(true) await uploadFiles() .then(async (baseUri) => { setUploading(false) setBaseTokenUri( `${baseUri}/${(uploadDetails.baseMinterMetadataFile as File).name.substring( 0, (uploadDetails.baseMinterMetadataFile as File).name.lastIndexOf('.'), )}`, ) const result = await baseMinterContract .use(baseMinterDetails?.existingBaseMinter as string) ?.mint( wallet.address, `ipfs://${baseUri}/${(uploadDetails.baseMinterMetadataFile as File).name.substring( 0, (uploadDetails.baseMinterMetadataFile as File).name.lastIndexOf('.'), )}`, ) console.log(result) return result }) .then((result) => { toast.success(`Minted successfully! Tx Hash: ${result}`, { style: { maxWidth: 'none' }, duration: 5000 }) setIsMintingComplete(true) }) .catch((error) => { toast.error(error.message, { style: { maxWidth: 'none' } }) setUploading(false) setCreatingCollection(false) setIsMintingComplete(false) }) } else { setBaseTokenUri(uploadDetails?.baseTokenURI as string) setUploading(false) await baseMinterContract .use(baseMinterDetails?.existingBaseMinter as string) ?.mint(wallet.address, `${uploadDetails?.baseTokenURI?.trim()}`) .then((result) => { toast.success(`Minted successfully! Tx Hash: ${result}`, { style: { maxWidth: 'none' }, duration: 5000 }) }) .catch((error) => { toast.error(error.message, { style: { maxWidth: 'none' } }) setUploading(false) setCreatingCollection(false) }) } setCreatingCollection(false) // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { toast.error(error.message, { style: { maxWidth: 'none' } }) setCreatingCollection(false) setUploading(false) } } const instantiateWhitelist = async () => { if (!wallet.initialized) throw new Error('Wallet not connected') if (!whitelistContract) throw new Error('Contract not found') const msg = { members: whitelistDetails?.members, start_time: whitelistDetails?.startTime, end_time: whitelistDetails?.endTime, mint_price: coin(String(Number(whitelistDetails?.unitPrice)), 'ustars'), per_address_limit: whitelistDetails?.perAddressLimit, member_limit: whitelistDetails?.memberLimit, } const data = await whitelistContract.instantiate( WHITELIST_CODE_ID, msg, 'Stargaze Whitelist Contract', wallet.address, ) return data.contractAddress } const instantiateVendingMinter = async (baseUri: string, coverImageUri: string, whitelist?: string) => { if (!wallet.initialized) throw new Error('Wallet not connected') if (!vendingFactoryContract) throw new Error('Contract not found') let royaltyInfo = null if (royaltyDetails?.royaltyType === 'new') { royaltyInfo = { payment_address: royaltyDetails.paymentAddress, share: (Number(royaltyDetails.share) / 100).toString(), } } const msg = { create_minter: { init_msg: { base_token_uri: `${uploadDetails?.uploadMethod === 'new' ? `ipfs://${baseUri}` : `${baseUri}`}`, start_time: mintingDetails?.startTime, num_tokens: mintingDetails?.numTokens, mint_price: { amount: mintingDetails?.unitPrice, denom: 'ustars', }, per_address_limit: mintingDetails?.perAddressLimit, whitelist, }, collection_params: { code_id: SG721_CODE_ID, name: collectionDetails?.name, symbol: collectionDetails?.symbol, info: { creator: wallet.address, description: collectionDetails?.description, image: `${ uploadDetails?.uploadMethod === 'new' ? `ipfs://${coverImageUri}/${collectionDetails?.imageFile[0].name as string}` : `${coverImageUri}` }`, external_link: collectionDetails?.externalLink, explicit_content: collectionDetails?.explicit, royalty_info: royaltyInfo, start_trading_time: collectionDetails?.startTradingTime || null, }, }, }, } const payload: VendingFactoryDispatchExecuteArgs = { contract: VENDING_FACTORY_ADDRESS, messages: vendingFactoryMessages, txSigner: wallet.address, msg, funds: [coin('1000000000', 'ustars')], } const data = await vendingFactoryDispatchExecute(payload) setTransactionHash(data.transactionHash) setVendingMinterContractAddress(data.vendingMinterAddress) setSg721ContractAddress(data.sg721Address) } const instantiateBaseMinter = async (baseUri: string, coverImageUri: string) => { if (!wallet.initialized) throw new Error('Wallet not connected') if (!baseFactoryContract) throw new Error('Contract not found') if (!baseMinterContract) throw new Error('Contract not found') let royaltyInfo = null if (royaltyDetails?.royaltyType === 'new') { royaltyInfo = { payment_address: royaltyDetails.paymentAddress, share: (Number(royaltyDetails.share) / 100).toString(), } } const msg = { create_minter: { init_msg: null, collection_params: { code_id: SG721_CODE_ID, name: collectionDetails?.name, symbol: collectionDetails?.symbol, info: { creator: wallet.address, description: collectionDetails?.description, image: `${ uploadDetails?.uploadMethod === 'new' ? `ipfs://${coverImageUri}/${collectionDetails?.imageFile[0].name as string}` : `${coverImageUri}` }`, external_link: collectionDetails?.externalLink, explicit_content: collectionDetails?.explicit, royalty_info: royaltyInfo, start_trading_time: collectionDetails?.startTradingTime || null, }, }, }, } const payload: BaseFactoryDispatchExecuteArgs = { contract: BASE_FACTORY_ADDRESS, messages: baseFactoryMessages, txSigner: wallet.address, msg, funds: [coin('1000000000', 'ustars')], } await baseFactoryDispatchExecute(payload) .then(async (data) => { setTransactionHash(data.transactionHash) setVendingMinterContractAddress(data.baseMinterAddress) setSg721ContractAddress(data.sg721Address) await toast .promise( baseMinterContract .use(data.baseMinterAddress) ?.mint(wallet.address, baseUri) as Promise, { loading: 'Minting token...', success: (result) => { setIsMintingComplete(true) return `Token minted successfully! Tx Hash: ${result}` }, error: (error) => `Failed to mint token: ${error.message}`, }, { style: { maxWidth: 'none' } }, ) .catch((error) => { toast.error(error.message, { style: { maxWidth: 'none' } }) setUploading(false) setIsMintingComplete(false) setCreatingCollection(false) }) setUploading(false) setCreatingCollection(false) }) .catch((error) => { toast.error(error.message, { style: { maxWidth: 'none' } }) setUploading(false) setCreatingCollection(false) }) } const uploadFiles = async (): Promise => { if (!uploadDetails) throw new Error('Please upload asset and metadata') return new Promise((resolve, reject) => { upload( uploadDetails.assetFiles, uploadDetails.uploadService, 'assets', uploadDetails.nftStorageApiKey as string, uploadDetails.pinataApiKey as string, uploadDetails.pinataSecretKey as string, ) .then((assetUri: string) => { if (minterType === 'vending') { const fileArray: File[] = [] let reader: FileReader for (let i = 0; i < uploadDetails.metadataFiles.length; i++) { reader = new FileReader() reader.onload = (e) => { const data: any = JSON.parse(e.target?.result as string) if ( getAssetType(uploadDetails.assetFiles[i].name) === 'audio' || getAssetType(uploadDetails.assetFiles[i].name) === 'video' ) { data.animation_url = `ipfs://${assetUri}/${uploadDetails.assetFiles[i].name}` } data.image = `ipfs://${assetUri}/${uploadDetails.assetFiles[i].name}` const metadataFileBlob = new Blob([JSON.stringify(data)], { type: 'application/json', }) const updatedMetadataFile = new File( [metadataFileBlob], uploadDetails.metadataFiles[i].name.substring( 0, uploadDetails.metadataFiles[i].name.lastIndexOf('.'), ), { type: 'application/json', }, ) fileArray.push(updatedMetadataFile) } reader.onloadend = () => { if (i === uploadDetails.metadataFiles.length - 1) { upload( fileArray, uploadDetails.uploadService, 'metadata', uploadDetails.nftStorageApiKey as string, uploadDetails.pinataApiKey as string, uploadDetails.pinataSecretKey as string, ) .then(resolve) .catch(reject) } } reader.readAsText(uploadDetails.metadataFiles[i], 'utf8') } } else if (minterType === 'base') { const fileArray: File[] = [] const reader: FileReader = new FileReader() reader.onload = (e) => { const data: any = JSON.parse(e.target?.result as string) if ( getAssetType(uploadDetails.assetFiles[0].name) === 'audio' || getAssetType(uploadDetails.assetFiles[0].name) === 'video' ) { data.animation_url = `ipfs://${assetUri}/${uploadDetails.assetFiles[0].name}` } data.image = `ipfs://${assetUri}/${uploadDetails.assetFiles[0].name}` const metadataFileBlob = new Blob([JSON.stringify(data)], { type: 'application/json', }) console.log('Name: ', (uploadDetails.baseMinterMetadataFile as File).name) const updatedMetadataFile = new File( [metadataFileBlob], (uploadDetails.baseMinterMetadataFile as File).name.substring( 0, (uploadDetails.baseMinterMetadataFile as File).name.lastIndexOf('.'), ), { type: 'application/json', }, ) fileArray.push(updatedMetadataFile) } reader.onloadend = () => { upload( fileArray, uploadDetails.uploadService, 'metadata', uploadDetails.nftStorageApiKey as string, uploadDetails.pinataApiKey as string, uploadDetails.pinataSecretKey as string, ) .then(resolve) .catch(reject) } console.log('File: ', uploadDetails.baseMinterMetadataFile) reader.readAsText(uploadDetails.baseMinterMetadataFile as File, 'utf8') } }) .catch(reject) }) } const checkUploadDetails = () => { if (!wallet.initialized) throw new Error('Wallet not connected.') if (!uploadDetails) { throw new Error('Please select assets and metadata') } if (minterType === 'base' && uploadDetails.uploadMethod === 'new' && uploadDetails.assetFiles.length > 1) { throw new Error('Base Minter can only mint one asset at a time. Please select only one asset.') } if (uploadDetails.uploadMethod === 'new' && uploadDetails.assetFiles.length === 0) { throw new Error('Please select the assets') } if (minterType === 'vending' && uploadDetails.uploadMethod === 'new' && uploadDetails.metadataFiles.length === 0) { throw new Error('Please select the metadata files') } if (uploadDetails.uploadMethod === 'new' && minterType === 'vending') compareFileArrays(uploadDetails.assetFiles, uploadDetails.metadataFiles) if (uploadDetails.uploadMethod === 'new') { if (uploadDetails.uploadService === 'nft-storage') { if (uploadDetails.nftStorageApiKey === '') { throw new Error('Please enter a valid NFT.Storage API key') } } else if (uploadDetails.pinataApiKey === '' || uploadDetails.pinataSecretKey === '') { throw new Error('Please enter Pinata API and secret keys') } } if (uploadDetails.uploadMethod === 'existing' && !uploadDetails.baseTokenURI?.includes('ipfs://')) { throw new Error('Please specify a valid base token URI') } if (baseMinterDetails?.baseMinterAcquisitionMethod === 'existing' && !baseMinterDetails.existingBaseMinter) { throw new Error('Please specify a valid Base Minter contract address') } } const checkCollectionDetails = () => { if (!collectionDetails) throw new Error('Please fill out the collection details') if (collectionDetails.name === '') throw new Error('Collection name is required') if (collectionDetails.description === '') throw new Error('Collection description is required') if (uploadDetails?.uploadMethod === 'new' && collectionDetails.imageFile.length === 0) throw new Error('Collection cover image is required') if ( collectionDetails.startTradingTime && Number(collectionDetails.startTradingTime) < new Date().getTime() * 1000000 ) throw new Error('Invalid trading start time') if ( collectionDetails.startTradingTime && Number(collectionDetails.startTradingTime) < Number(mintingDetails?.startTime) ) throw new Error('Trading start time must be after minting start time') } const checkMintingDetails = () => { if (!mintingDetails) throw new Error('Please fill out the minting details') if (mintingDetails.numTokens < 1 || mintingDetails.numTokens > 10000) throw new Error('Invalid number of tokens') if (Number(mintingDetails.unitPrice) < 50000000) throw new Error('Invalid unit price: The minimum unit price is 50 STARS') if ( !mintingDetails.perAddressLimit || mintingDetails.perAddressLimit < 1 || mintingDetails.perAddressLimit > 50 || mintingDetails.perAddressLimit > mintingDetails.numTokens ) throw new Error('Invalid limit for tokens per address') if ( mintingDetails.numTokens > 100 && mintingDetails.numTokens < 100 * mintingDetails.perAddressLimit && mintingDetails.perAddressLimit > mintingDetails.numTokens / 100 ) throw new Error('Invalid limit for tokens per address. The limit cannot exceed 1% of the total number of tokens.') if (mintingDetails.startTime === '') throw new Error('Start time is required') if (Number(mintingDetails.startTime) < new Date().getTime() * 1000000) throw new Error('Invalid start time') } const checkWhitelistDetails = async () => { if (!whitelistDetails) throw new Error('Please fill out the whitelist details') if (whitelistDetails.whitelistType === 'existing') { if (whitelistDetails.contractAddress === '') throw new Error('Whitelist contract address is required') else { const contract = whitelistContract?.use(whitelistDetails.contractAddress) //check if the address belongs to a whitelist contract (see performChecks()) const config = await contract?.config() if (Number(config?.start_time) !== Number(mintingDetails?.startTime)) { const whitelistStartDate = new Date(Number(config?.start_time) / 1000000) throw Error(`Whitelist start time (${whitelistStartDate.toLocaleString()}) does not match minting start time`) } if ( mintingDetails?.numTokens && config?.per_address_limit && mintingDetails.numTokens > 100 && Number(config.per_address_limit) > mintingDetails.numTokens / 100 ) throw Error( `Invalid limit for tokens per address (${config.per_address_limit} tokens). The limit cannot exceed 1% of the total number of tokens.`, ) } } else if (whitelistDetails.whitelistType === 'new') { if (whitelistDetails.members?.length === 0) throw new Error('Whitelist member list cannot be empty') if (whitelistDetails.unitPrice === '') throw new Error('Whitelist unit price is required') if (Number(whitelistDetails.unitPrice) < 25000000) throw new Error('Invalid unit price: The minimum unit price for whitelisted addresses is 25 STARS') if (whitelistDetails.startTime === '') throw new Error('Start time is required') if (whitelistDetails.endTime === '') throw new Error('End time is required') if (!whitelistDetails.perAddressLimit || whitelistDetails.perAddressLimit === 0) throw new Error('Per address limit is required') if (!whitelistDetails.memberLimit || whitelistDetails.memberLimit === 0) throw new Error('Member limit is required') if (Number(whitelistDetails.startTime) > Number(whitelistDetails.endTime)) throw new Error('Whitelist start time cannot be later than whitelist end time') if (Number(whitelistDetails.startTime) !== Number(mintingDetails?.startTime)) throw new Error('Whitelist start time must be the same as the minting start time') if ( mintingDetails?.numTokens && whitelistDetails.perAddressLimit && mintingDetails.numTokens > 100 && whitelistDetails.perAddressLimit > mintingDetails.numTokens / 100 ) throw Error( `Invalid limit for tokens per address (${whitelistDetails.perAddressLimit} tokens). The limit cannot exceed 1% of the total number of tokens.`, ) } } const checkRoyaltyDetails = () => { if (!royaltyDetails) throw new Error('Please fill out the royalty details') if (royaltyDetails.royaltyType === 'new') { if (royaltyDetails.share === 0) throw new Error('Royalty share percentage is required') if (royaltyDetails.share > 100 || royaltyDetails.share < 0) throw new Error('Invalid royalty share percentage') if (royaltyDetails.paymentAddress === '') throw new Error('Royalty payment address is required') } } const checkwalletBalance = () => { if (!wallet.initialized) throw new Error('Wallet not connected.') if (whitelistDetails?.whitelistType === 'new' && whitelistDetails.memberLimit) { const amountNeeded = Math.ceil(Number(whitelistDetails.memberLimit) / 1000) * 100000000 + 1000000000 if (amountNeeded >= Number(wallet.balance[0].amount)) throw new Error('Insufficient wallet balance to instantiate the required contracts.') } else { const amountNeeded = 1000000000 if (amountNeeded >= Number(wallet.balance[0].amount)) throw new Error('Insufficient wallet balance to instantiate the required contracts.') } } useEffect(() => { if (vendingMinterContractAddress !== null) scrollRef.current?.scrollIntoView({ behavior: 'smooth' }) }, [vendingMinterContractAddress]) useEffect(() => { setBaseTokenUri(uploadDetails?.baseTokenURI as string) setCoverImageUrl(uploadDetails?.imageUrl as string) }, [uploadDetails?.baseTokenURI, uploadDetails?.imageUrl]) useEffect(() => { resetReadyFlags() setVendingMinterContractAddress(null) setIsMintingComplete(false) }, [minterType, baseMinterDetails?.baseMinterAcquisitionMethod, uploadDetails?.uploadMethod]) return (

{minterType === 'base' && baseMinterDetails?.baseMinterAcquisitionMethod === 'existing' ? 'Mint Token' : 'Create Collection'}

Make sure you check our{' '} documentation {' '} on how to create your collection

{minterType === 'vending' ? 'Base Token URI: ' : 'Token URI: '}{' '} {uploadDetails?.uploadMethod === 'new' && ( ipfs://{baseTokenUri as string} )} {uploadDetails?.uploadMethod === 'existing' && ( ipfs://{baseTokenUri?.substring(baseTokenUri.lastIndexOf('ipfs://') + 7)} )}
Minter Contract Address:{' '} {vendingMinterContractAddress}
SG721 Contract Address:{' '} {sg721ContractAddress}
Whitelist Contract Address:{' '} {whitelistContractAddress}
Transaction Hash: {' '} {transactionHash} {transactionHash}
{/* To be removed */}
{/* /To be removed */}
{minterType === 'base' && (
)}
) } export default withMetadata(CollectionCreationPage, { center: false })