2022-08-03 07:25:08 +00:00
/* eslint-disable eslint-comments/disable-enable-pair */
2023-02-26 09:42:21 +00:00
/* eslint-disable no-nested-ternary */
2023-01-24 11:14:29 +00:00
2022-08-16 07:04:33 +00:00
/* eslint-disable @typescript-eslint/restrict-template-expressions */
2022-08-04 09:16:42 +00:00
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
2022-08-03 07:25:08 +00:00
2023-04-05 11:54:34 +00:00
import { toUtf8 } from '@cosmjs/encoding'
2022-08-04 09:16:42 +00:00
import { coin } from '@cosmjs/proto-signing'
2022-12-13 12:52:43 +00:00
import clsx from 'clsx'
2022-08-10 12:04:18 +00:00
import { Alert } from 'components/Alert'
2022-08-10 09:42:00 +00:00
import { Anchor } from 'components/Anchor'
2023-04-04 12:30:05 +00:00
import { AnchorButton } from 'components/AnchorButton'
2022-08-15 08:50:15 +00:00
import { Button } from 'components/Button'
2022-08-04 09:16:42 +00:00
import {
CollectionDetails ,
MintingDetails ,
RoyaltyDetails ,
UploadDetails ,
WhitelistDetails ,
} from 'components/collections/creation'
2022-12-26 13:17:09 +00:00
import type { BaseMinterDetailsDataProps } from 'components/collections/creation/BaseMinterDetails'
import { BaseMinterDetails } from 'components/collections/creation/BaseMinterDetails'
2022-08-04 09:16:42 +00:00
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'
2022-08-10 09:07:05 +00:00
import { Conditional } from 'components/Conditional'
import { LoadingModal } from 'components/LoadingModal'
2022-08-04 09:16:42 +00:00
import { useContracts } from 'contexts/contracts'
import { useWallet } from 'contexts/wallet'
2022-12-13 18:50:42 +00:00
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'
2022-07-27 06:49:36 +00:00
import type { NextPage } from 'next'
import { NextSeo } from 'next-seo'
2022-10-21 01:02:52 +00:00
import { useEffect , useMemo , useRef , useState } from 'react'
2022-08-03 07:25:08 +00:00
import { toast } from 'react-hot-toast'
import { upload } from 'services/upload'
2022-08-09 11:42:55 +00:00
import { compareFileArrays } from 'utils/compareFileArrays'
2022-09-23 17:36:34 +00:00
import {
2022-12-13 18:50:42 +00:00
BASE_FACTORY_ADDRESS ,
2023-02-26 09:36:34 +00:00
BASE_FACTORY_UPDATABLE_ADDRESS ,
2022-09-23 17:36:34 +00:00
BLOCK_EXPLORER_URL ,
NETWORK ,
SG721_CODE_ID ,
2023-02-26 09:36:34 +00:00
SG721_UPDATABLE_CODE_ID ,
2022-09-23 17:36:34 +00:00
STARGAZE_URL ,
2022-10-21 01:02:52 +00:00
VENDING_FACTORY_ADDRESS ,
2023-02-26 09:36:34 +00:00
VENDING_FACTORY_UPDATABLE_ADDRESS ,
2022-09-23 17:36:34 +00:00
WHITELIST_CODE_ID ,
} from 'utils/constants'
2022-07-27 06:49:36 +00:00
import { withMetadata } from 'utils/layout'
import { links } from 'utils/links'
2022-08-03 07:25:08 +00:00
2022-12-13 12:52:43 +00:00
import type { MinterType } from '../../components/collections/actions/Combobox'
2022-08-16 07:04:33 +00:00
import type { UploadMethod } from '../../components/collections/creation/UploadDetails'
2022-09-01 06:27:23 +00:00
import { ConfirmationModal } from '../../components/ConfirmationModal'
2022-08-09 09:08:10 +00:00
import { getAssetType } from '../../utils/getAssetType'
2023-01-12 10:01:11 +00:00
import { isValidAddress } from '../../utils/isValidAddress'
2022-08-09 09:08:10 +00:00
2022-08-04 09:16:42 +00:00
const CollectionCreationPage : NextPage = ( ) = > {
const wallet = useWallet ( )
2022-10-21 01:02:52 +00:00
const {
2022-12-13 18:50:42 +00:00
baseMinter : baseMinterContract ,
2022-12-09 08:27:50 +00:00
vendingMinter : vendingMinterContract ,
2022-10-21 01:02:52 +00:00
whitelist : whitelistContract ,
vendingFactory : vendingFactoryContract ,
2022-12-13 18:50:42 +00:00
baseFactory : baseFactoryContract ,
2022-10-21 01:02:52 +00:00
} = useContracts ( )
2022-08-10 12:04:18 +00:00
const scrollRef = useRef < HTMLDivElement > ( null )
2022-10-21 04:14:49 +00:00
2022-12-13 18:50:42 +00:00
const vendingFactoryMessages = useMemo (
2022-10-21 01:02:52 +00:00
( ) = > vendingFactoryContract ? . use ( VENDING_FACTORY_ADDRESS ) ,
[ vendingFactoryContract , wallet . address ] ,
)
2022-08-04 09:16:42 +00:00
2022-12-13 18:50:42 +00:00
const baseFactoryMessages = useMemo (
( ) = > baseFactoryContract ? . use ( BASE_FACTORY_ADDRESS ) ,
[ baseFactoryContract , wallet . address ] ,
)
2022-08-04 09:16:42 +00:00
const [ uploadDetails , setUploadDetails ] = useState < UploadDetailsDataProps | null > ( null )
const [ collectionDetails , setCollectionDetails ] = useState < CollectionDetailsDataProps | null > ( null )
2022-12-26 13:17:09 +00:00
const [ baseMinterDetails , setBaseMinterDetails ] = useState < BaseMinterDetailsDataProps | null > ( null )
2022-08-04 09:16:42 +00:00
const [ mintingDetails , setMintingDetails ] = useState < MintingDetailsDataProps | null > ( null )
const [ whitelistDetails , setWhitelistDetails ] = useState < WhitelistDetailsDataProps | null > ( null )
const [ royaltyDetails , setRoyaltyDetails ] = useState < RoyaltyDetailsDataProps | null > ( null )
2022-12-13 12:52:43 +00:00
const [ minterType , setMinterType ] = useState < MinterType > ( 'vending' )
2022-08-04 09:16:42 +00:00
2023-04-01 13:45:49 +00:00
const [ vendingMinterCreationFee , setVendingMinterCreationFee ] = useState < string | null > ( null )
const [ baseMinterCreationFee , setBaseMinterCreationFee ] = useState < string | null > ( null )
const [ vendingMinterUpdatableCreationFee , setVendingMinterUpdatableCreationFee ] = useState < string | null > ( null )
const [ baseMinterUpdatableCreationFee , setBaseMinterUpdatableCreationFee ] = useState < string | null > ( null )
2022-08-10 09:07:05 +00:00
const [ uploading , setUploading ] = useState ( false )
2022-12-26 13:17:09 +00:00
const [ isMintingComplete , setIsMintingComplete ] = useState ( false )
2022-09-02 18:45:42 +00:00
const [ creatingCollection , setCreatingCollection ] = useState ( false )
2022-12-13 12:52:43 +00:00
const [ readyToCreateVm , setReadyToCreateVm ] = useState ( false )
const [ readyToCreateBm , setReadyToCreateBm ] = useState ( false )
const [ readyToUploadAndMint , setReadyToUploadAndMint ] = useState ( false )
2022-12-09 08:27:50 +00:00
const [ vendingMinterContractAddress , setVendingMinterContractAddress ] = useState < string | null > ( null )
2022-08-10 12:04:18 +00:00
const [ sg721ContractAddress , setSg721ContractAddress ] = useState < string | null > ( null )
2022-09-11 17:54:28 +00:00
const [ whitelistContractAddress , setWhitelistContractAddress ] = useState < string | null | undefined > ( null )
2022-08-10 12:04:18 +00:00
const [ baseTokenUri , setBaseTokenUri ] = useState < string | null > ( null )
2022-08-16 07:04:33 +00:00
const [ coverImageUrl , setCoverImageUrl ] = useState < string | null > ( null )
2022-08-04 09:16:42 +00:00
const [ transactionHash , setTransactionHash ] = useState < string | null > ( null )
2022-12-13 12:52:43 +00:00
const performVendingMinterChecks = ( ) = > {
2022-09-01 06:27:23 +00:00
try {
2022-12-13 12:52:43 +00:00
setReadyToCreateVm ( false )
2022-09-08 06:22:27 +00:00
checkUploadDetails ( )
checkCollectionDetails ( )
checkMintingDetails ( )
2023-04-05 11:54:34 +00:00
void checkRoyaltyDetails ( )
2022-10-06 13:16:58 +00:00
. then ( ( ) = > {
2023-04-05 11:54:34 +00:00
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 )
} )
2022-10-06 13:16:58 +00:00
} )
. catch ( ( err ) = > {
2023-04-05 11:54:34 +00:00
toast . error ( ` Error in Royalty Details: ${ err . message } ` , { style : { maxWidth : 'none' } } )
2022-12-13 12:52:43 +00:00
setReadyToCreateVm ( false )
2022-10-06 13:16:58 +00:00
} )
2022-09-01 06:27:23 +00:00
} catch ( error : any ) {
2022-11-02 07:53:17 +00:00
toast . error ( error . message , { style : { maxWidth : 'none' } } )
2022-09-01 06:27:23 +00:00
setUploading ( false )
2022-12-21 07:36:43 +00:00
setReadyToCreateVm ( false )
2022-09-01 06:27:23 +00:00
}
}
2022-12-13 12:52:43 +00:00
const performBaseMinterChecks = ( ) = > {
try {
setReadyToCreateBm ( false )
checkUploadDetails ( )
2022-12-13 18:50:42 +00:00
checkCollectionDetails ( )
2023-04-05 11:54:34 +00:00
void checkRoyaltyDetails ( )
2022-12-13 12:52:43 +00:00
. then ( ( ) = > {
2023-04-05 11:54:34 +00:00
checkWhitelistDetails ( )
. then ( ( ) = > {
checkwalletBalance ( )
setReadyToCreateBm ( true )
} )
. catch ( ( err ) = > {
toast . error ( ` ${ err . message } ` , { style : { maxWidth : 'none' } } )
setReadyToCreateBm ( false )
} )
2022-12-13 12:52:43 +00:00
} )
. catch ( ( err ) = > {
2023-04-05 11:54:34 +00:00
toast . error ( ` Error in Royalty Configuration: ${ err . message } ` , { style : { maxWidth : 'none' } } )
2022-12-13 12:52:43 +00:00
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 ) = > {
2023-04-04 08:46:52 +00:00
toast . error ( ` ${ err . message } ` , { style : { maxWidth : 'none' } } )
2022-12-13 12:52:43 +00:00
setReadyToUploadAndMint ( false )
} )
} catch ( error : any ) {
toast . error ( error . message , { style : { maxWidth : 'none' } } )
setUploading ( false )
}
}
2022-12-13 18:50:42 +00:00
const resetReadyFlags = ( ) = > {
setReadyToCreateVm ( false )
setReadyToCreateBm ( false )
setReadyToUploadAndMint ( false )
}
2022-12-13 12:52:43 +00:00
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 )
2022-12-13 18:50:42 +00:00
await instantiateVendingMinter ( baseUri , coverImageUri , whitelist )
2022-12-13 12:52:43 +00:00
} 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 )
2022-12-13 18:50:42 +00:00
await instantiateVendingMinter ( baseTokenUri as string , coverImageUrl as string , whitelist )
2022-12-13 12:52:43 +00:00
}
setCreatingCollection ( false )
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch ( error : any ) {
2023-02-04 08:10:06 +00:00
toast . error ( error . message , { style : { maxWidth : 'none' } , duration : 10000 } )
2022-12-13 12:52:43 +00:00
setCreatingCollection ( false )
setUploading ( false )
}
}
const createBaseMinterCollection = async ( ) = > {
try {
setCreatingCollection ( true )
setBaseTokenUri ( null )
setCoverImageUrl ( null )
setVendingMinterContractAddress ( null )
2022-12-26 13:17:09 +00:00
setIsMintingComplete ( false )
2022-12-13 12:52:43 +00:00
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 )
2023-01-16 13:48:38 +00:00
if ( uploadDetails . assetFiles . length === 1 ) {
setBaseTokenUri (
` ${ baseUri } / ${ ( uploadDetails . baseMinterMetadataFile as File ) . name . substring (
0 ,
( uploadDetails . baseMinterMetadataFile as File ) . name . lastIndexOf ( '.' ) ,
) } ` ,
)
} else {
setBaseTokenUri ( baseUri )
}
2022-12-13 12:52:43 +00:00
setCoverImageUrl ( coverImageUri )
2023-01-16 13:48:38 +00:00
if ( uploadDetails . assetFiles . length === 1 ) {
await instantiateBaseMinter (
` ipfs:// ${ baseUri } / ${ ( uploadDetails . baseMinterMetadataFile as File ) . name . substring (
0 ,
( uploadDetails . baseMinterMetadataFile as File ) . name . lastIndexOf ( '.' ) ,
) } ` ,
coverImageUri ,
)
} else {
await instantiateBaseMinter ( ` ipfs:// ${ baseUri } ` , coverImageUri )
}
2022-12-13 12:52:43 +00:00
} else {
setBaseTokenUri ( uploadDetails ? . baseTokenURI as string )
setCoverImageUrl ( uploadDetails ? . imageUrl as string )
2022-12-13 18:50:42 +00:00
await instantiateBaseMinter ( uploadDetails ? . baseTokenURI as string , uploadDetails ? . imageUrl as string )
2022-12-13 12:52:43 +00:00
}
setCreatingCollection ( false )
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch ( error : any ) {
2023-02-04 08:10:06 +00:00
toast . error ( error . message , { style : { maxWidth : 'none' } , duration : 10000 } )
2022-12-13 12:52:43 +00:00
setCreatingCollection ( false )
setUploading ( false )
}
}
const uploadAndMint = async ( ) = > {
2022-08-04 09:16:42 +00:00
try {
2022-12-13 18:50:42 +00:00
if ( ! wallet . initialized ) throw new Error ( 'Wallet not connected' )
if ( ! baseMinterContract ) throw new Error ( 'Contract not found' )
2022-09-02 18:45:42 +00:00
setCreatingCollection ( true )
2023-04-04 12:30:05 +00:00
setIsMintingComplete ( false )
2022-08-10 12:04:18 +00:00
setBaseTokenUri ( null )
2022-08-16 07:04:33 +00:00
setCoverImageUrl ( null )
2022-12-09 08:27:50 +00:00
setVendingMinterContractAddress ( null )
2022-08-10 12:04:18 +00:00
setSg721ContractAddress ( null )
setTransactionHash ( null )
2022-08-16 07:04:33 +00:00
if ( uploadDetails ? . uploadMethod === 'new' ) {
2022-12-26 13:17:09 +00:00
console . log ( JSON . stringify ( uploadDetails . baseMinterMetadataFile ? . text ( ) ) )
2022-08-16 07:04:33 +00:00
setUploading ( true )
2022-12-13 18:50:42 +00:00
await uploadFiles ( )
. then ( async ( baseUri ) = > {
setUploading ( false )
2023-01-17 13:23:17 +00:00
if ( uploadDetails . assetFiles . length === 1 ) {
setBaseTokenUri (
` ${ baseUri } / ${ ( uploadDetails . baseMinterMetadataFile as File ) . name . substring (
2022-12-26 13:17:09 +00:00
0 ,
( uploadDetails . baseMinterMetadataFile as File ) . name . lastIndexOf ( '.' ) ,
) } ` ,
)
2023-01-17 13:23:17 +00:00
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
}
setBaseTokenUri ( baseUri )
const result = await baseMinterContract
. use ( baseMinterDetails ? . existingBaseMinter as string )
? . batchMint ( wallet . address , ` ipfs:// ${ baseUri } ` , uploadDetails . assetFiles . length )
2022-12-13 18:50:42 +00:00
console . log ( result )
return result
} )
. then ( ( result ) = > {
2023-03-31 16:25:32 +00:00
toast . success ( ` Token(s) minted & added to the collection successfully! Tx Hash: ${ result } ` , {
2023-01-08 17:09:05 +00:00
style : { maxWidth : 'none' } ,
duration : 5000 ,
} )
2022-12-26 13:17:09 +00:00
setIsMintingComplete ( true )
2023-04-04 12:30:05 +00:00
setSg721ContractAddress ( baseMinterDetails ? . selectedCollectionAddress as string )
setTransactionHash ( result as string )
2022-12-13 18:50:42 +00:00
} )
. catch ( ( error ) = > {
toast . error ( error . message , { style : { maxWidth : 'none' } } )
setUploading ( false )
setCreatingCollection ( false )
2022-12-26 13:17:09 +00:00
setIsMintingComplete ( false )
2022-12-13 18:50:42 +00:00
} )
2022-08-16 07:04:33 +00:00
} else {
setBaseTokenUri ( uploadDetails ? . baseTokenURI as string )
2022-12-13 18:50:42 +00:00
setUploading ( false )
await baseMinterContract
2022-12-26 13:17:09 +00:00
. use ( baseMinterDetails ? . existingBaseMinter as string )
? . mint ( wallet . address , ` ${ uploadDetails ? . baseTokenURI ? . trim ( ) } ` )
2022-12-13 18:50:42 +00:00
. then ( ( result ) = > {
2023-03-31 16:25:32 +00:00
toast . success ( ` Token minted & added to the collection successfully! Tx Hash: ${ result } ` , {
2023-01-08 17:09:05 +00:00
style : { maxWidth : 'none' } ,
duration : 5000 ,
} )
2023-04-04 12:30:05 +00:00
setIsMintingComplete ( true )
setSg721ContractAddress ( baseMinterDetails ? . selectedCollectionAddress as string )
setTransactionHash ( result )
2022-12-13 18:50:42 +00:00
} )
. catch ( ( error ) = > {
toast . error ( error . message , { style : { maxWidth : 'none' } } )
setUploading ( false )
setCreatingCollection ( false )
} )
2022-08-16 07:04:33 +00:00
}
2022-09-02 18:45:42 +00:00
setCreatingCollection ( false )
2022-08-04 09:16:42 +00:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch ( error : any ) {
2022-11-02 07:53:17 +00:00
toast . error ( error . message , { style : { maxWidth : 'none' } } )
2022-09-02 18:45:42 +00:00
setCreatingCollection ( false )
2022-08-10 09:07:05 +00:00
setUploading ( false )
2022-08-04 09:16:42 +00:00
}
2022-08-03 07:25:08 +00:00
}
2022-08-04 09:16:42 +00:00
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 ,
2022-10-21 01:02:52 +00:00
mint_price : coin ( String ( Number ( whitelistDetails ? . unitPrice ) ) , 'ustars' ) ,
2022-08-04 09:16:42 +00:00
per_address_limit : whitelistDetails?.perAddressLimit ,
member_limit : whitelistDetails?.memberLimit ,
2023-03-08 20:53:12 +00:00
admins : whitelistDetails?.admins || [ wallet . address ] ,
admins_mutable : whitelistDetails?.adminsMutable ,
2022-08-03 07:25:08 +00:00
}
2022-08-04 09:16:42 +00:00
2023-03-18 06:48:49 +00:00
const data = await whitelistContract . instantiate (
WHITELIST_CODE_ID ,
msg ,
'Stargaze Whitelist Contract' ,
wallet . address ,
)
2022-08-04 09:16:42 +00:00
return data . contractAddress
2022-08-03 07:25:08 +00:00
}
2022-12-13 18:50:42 +00:00
const instantiateVendingMinter = async ( baseUri : string , coverImageUri : string , whitelist? : string ) = > {
2022-08-04 09:16:42 +00:00
if ( ! wallet . initialized ) throw new Error ( 'Wallet not connected' )
2022-12-13 18:50:42 +00:00
if ( ! vendingFactoryContract ) throw new Error ( 'Contract not found' )
2022-08-04 09:16:42 +00:00
let royaltyInfo = null
2022-08-05 11:13:27 +00:00
if ( royaltyDetails ? . royaltyType === 'new' ) {
2022-08-04 09:16:42 +00:00
royaltyInfo = {
2022-08-09 11:42:55 +00:00
payment_address : royaltyDetails.paymentAddress ,
share : ( Number ( royaltyDetails . share ) / 100 ) . toString ( ) ,
2022-08-03 07:25:08 +00:00
}
}
2022-09-01 06:27:23 +00:00
2022-08-04 09:16:42 +00:00
const msg = {
2022-10-21 01:02:52 +00:00
create_minter : {
init_msg : {
2022-10-27 07:15:26 +00:00
base_token_uri : ` ${ uploadDetails ? . uploadMethod === 'new' ? ` ipfs:// ${ baseUri } ` : ` ${ baseUri } ` } ` ,
2022-10-21 01:02:52 +00:00
start_time : mintingDetails?.startTime ,
num_tokens : mintingDetails?.numTokens ,
2023-03-18 13:11:40 +00:00
payment_address : mintingDetails?.paymentAddress ? mintingDetails.paymentAddress : undefined ,
2022-10-21 01:02:52 +00:00
mint_price : {
amount : mintingDetails?.unitPrice ,
denom : 'ustars' ,
} ,
per_address_limit : mintingDetails?.perAddressLimit ,
whitelist ,
} ,
collection_params : {
2023-02-26 09:36:34 +00:00
code_id : collectionDetails?.updatable ? SG721_UPDATABLE_CODE_ID : SG721_CODE_ID ,
2022-10-21 01:02:52 +00:00
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 ,
} ,
2022-08-04 09:16:42 +00:00
} ,
} ,
2022-08-03 07:25:08 +00:00
}
2022-12-13 18:50:42 +00:00
const payload : VendingFactoryDispatchExecuteArgs = {
2023-02-26 09:36:34 +00:00
contract : collectionDetails?.updatable ? VENDING_FACTORY_UPDATABLE_ADDRESS : VENDING_FACTORY_ADDRESS ,
2022-12-13 18:50:42 +00:00
messages : vendingFactoryMessages ,
2022-10-21 01:02:52 +00:00
txSigner : wallet.address ,
msg ,
2023-04-01 13:45:49 +00:00
funds : [
coin (
collectionDetails ? . updatable
? ( vendingMinterUpdatableCreationFee as string )
: ( vendingMinterCreationFee as string ) ,
'ustars' ,
) ,
] ,
2023-02-26 10:08:03 +00:00
updatable : collectionDetails?.updatable ,
2022-10-21 01:02:52 +00:00
}
2022-12-13 18:50:42 +00:00
const data = await vendingFactoryDispatchExecute ( payload )
2022-08-04 09:16:42 +00:00
setTransactionHash ( data . transactionHash )
2022-12-09 08:27:50 +00:00
setVendingMinterContractAddress ( data . vendingMinterAddress )
2022-10-21 01:02:52 +00:00
setSg721ContractAddress ( data . sg721Address )
2022-08-04 09:16:42 +00:00
}
2022-12-13 18:50:42 +00:00
const instantiateBaseMinter = async ( baseUri : string , coverImageUri : string ) = > {
if ( ! wallet . initialized ) throw new Error ( 'Wallet not connected' )
if ( ! baseFactoryContract ) throw new Error ( 'Contract not found' )
2022-12-15 11:59:05 +00:00
if ( ! baseMinterContract ) throw new Error ( 'Contract not found' )
2022-12-13 18:50:42 +00:00
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 : {
2023-02-26 09:36:34 +00:00
code_id : collectionDetails?.updatable ? SG721_UPDATABLE_CODE_ID : SG721_CODE_ID ,
2022-12-13 18:50:42 +00:00
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 ,
2023-04-03 19:22:27 +00:00
start_trading_time : null , //collectionDetails?.startTradingTime || null,
2022-12-13 18:50:42 +00:00
} ,
} ,
} ,
}
const payload : BaseFactoryDispatchExecuteArgs = {
2023-02-26 09:36:34 +00:00
contract : collectionDetails?.updatable ? BASE_FACTORY_UPDATABLE_ADDRESS : BASE_FACTORY_ADDRESS ,
2022-12-13 18:50:42 +00:00
messages : baseFactoryMessages ,
txSigner : wallet.address ,
msg ,
2023-04-01 13:45:49 +00:00
funds : [
coin (
collectionDetails ? . updatable ? ( baseMinterUpdatableCreationFee as string ) : ( baseMinterCreationFee as string ) ,
'ustars' ,
) ,
] ,
2023-02-26 10:08:03 +00:00
updatable : collectionDetails?.updatable ,
2022-12-13 18:50:42 +00:00
}
2022-12-15 11:59:05 +00:00
await baseFactoryDispatchExecute ( payload )
. then ( async ( data ) = > {
setTransactionHash ( data . transactionHash )
setVendingMinterContractAddress ( data . baseMinterAddress )
setSg721ContractAddress ( data . sg721Address )
2023-02-04 07:47:38 +00:00
if ( uploadDetails ? . assetFiles . length === 1 || uploadDetails ? . uploadMethod === 'existing' ) {
2023-01-16 13:48:38 +00:00
await toast
. promise (
baseMinterContract . use ( data . baseMinterAddress ) ? . mint ( wallet . address , baseUri ) as Promise < string > ,
{
loading : 'Minting token...' ,
success : ( result ) = > {
setIsMintingComplete ( true )
return ` Token minted successfully! Tx Hash: ${ result } `
} ,
error : ( error ) = > ` Failed to mint token: ${ error . message } ` ,
2022-12-26 13:17:09 +00:00
} ,
2023-01-16 13:48:38 +00:00
{ style : { maxWidth : 'none' } } ,
)
. catch ( ( error ) = > {
toast . error ( error . message , { style : { maxWidth : 'none' } } )
setUploading ( false )
setIsMintingComplete ( false )
setCreatingCollection ( false )
} )
} else {
console . log ( 'Here' )
console . log ( data . baseMinterAddress )
await toast
. promise (
baseMinterContract
. use ( data . baseMinterAddress )
? . batchMint ( wallet . address , baseUri , uploadDetails ? . assetFiles . length as number ) as Promise < string > ,
{
loading : 'Minting tokens...' ,
success : ( result ) = > {
setIsMintingComplete ( true )
return ` Tokens minted successfully! Tx Hash: ${ result } `
} ,
error : ( error ) = > ` Failed to mint tokens: ${ error . message } ` ,
} ,
{ style : { maxWidth : 'none' } } ,
)
. catch ( ( error ) = > {
toast . error ( error . message , { style : { maxWidth : 'none' } } )
setUploading ( false )
setIsMintingComplete ( false )
setCreatingCollection ( false )
} )
}
2022-12-15 11:59:05 +00:00
setUploading ( false )
setCreatingCollection ( false )
} )
. catch ( ( error ) = > {
toast . error ( error . message , { style : { maxWidth : 'none' } } )
setUploading ( false )
setCreatingCollection ( false )
} )
2022-12-13 18:50:42 +00:00
}
2022-08-04 09:16:42 +00:00
const uploadFiles = async ( ) : Promise < string > = > {
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 ) = > {
2023-01-16 13:48:38 +00:00
if ( minterType === 'vending' || ( minterType === 'base' && uploadDetails . assetFiles . length > 1 ) ) {
2022-12-26 13:17:09 +00:00
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' ||
2023-01-24 11:14:29 +00:00
getAssetType ( uploadDetails . assetFiles [ i ] . name ) === 'video' ||
getAssetType ( uploadDetails . assetFiles [ i ] . name ) === 'html'
2022-12-26 13:17:09 +00:00
) {
data . animation_url = ` ipfs:// ${ assetUri } / ${ uploadDetails . assetFiles [ i ] . name } `
}
2023-01-24 11:14:29 +00:00
if ( getAssetType ( uploadDetails . assetFiles [ i ] . name ) !== 'html' )
data . image = ` ipfs:// ${ assetUri } / ${ uploadDetails . assetFiles [ i ] . name } `
2022-12-26 13:17:09 +00:00
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' )
}
2023-01-16 13:48:38 +00:00
} else if ( minterType === 'base' && uploadDetails . assetFiles . length === 1 ) {
2022-12-26 13:17:09 +00:00
const fileArray : File [ ] = [ ]
const reader : FileReader = new FileReader ( )
2022-09-01 06:27:23 +00:00
2022-08-04 09:16:42 +00:00
reader . onload = ( e ) = > {
const data : any = JSON . parse ( e . target ? . result as string )
2022-09-01 06:27:23 +00:00
2022-08-09 09:08:10 +00:00
if (
2022-12-26 13:17:09 +00:00
getAssetType ( uploadDetails . assetFiles [ 0 ] . name ) === 'audio' ||
getAssetType ( uploadDetails . assetFiles [ 0 ] . name ) === 'video'
2022-08-09 09:08:10 +00:00
) {
2022-12-26 13:17:09 +00:00
data . animation_url = ` ipfs:// ${ assetUri } / ${ uploadDetails . assetFiles [ 0 ] . name } `
2022-08-09 09:08:10 +00:00
}
2022-09-01 06:27:23 +00:00
2022-12-26 13:17:09 +00:00
data . image = ` ipfs:// ${ assetUri } / ${ uploadDetails . assetFiles [ 0 ] . name } `
2022-09-01 06:27:23 +00:00
2022-08-04 09:16:42 +00:00
const metadataFileBlob = new Blob ( [ JSON . stringify ( data ) ] , {
type : 'application/json' ,
} )
2022-09-01 06:27:23 +00:00
2022-12-26 13:17:09 +00:00
console . log ( 'Name: ' , ( uploadDetails . baseMinterMetadataFile as File ) . name )
2022-08-10 06:57:20 +00:00
const updatedMetadataFile = new File (
[ metadataFileBlob ] ,
2022-12-26 13:17:09 +00:00
( uploadDetails . baseMinterMetadataFile as File ) . name . substring (
0 ,
( uploadDetails . baseMinterMetadataFile as File ) . name . lastIndexOf ( '.' ) ,
) ,
2022-08-10 06:57:20 +00:00
{
type : 'application/json' ,
} ,
)
2022-09-01 06:27:23 +00:00
2022-08-04 09:16:42 +00:00
fileArray . push ( updatedMetadataFile )
}
reader . onloadend = ( ) = > {
2022-12-26 13:17:09 +00:00
upload (
fileArray ,
uploadDetails . uploadService ,
'metadata' ,
uploadDetails . nftStorageApiKey as string ,
uploadDetails . pinataApiKey as string ,
uploadDetails . pinataSecretKey as string ,
)
. then ( resolve )
. catch ( reject )
2022-08-04 09:16:42 +00:00
}
2022-12-26 13:17:09 +00:00
console . log ( 'File: ' , uploadDetails . baseMinterMetadataFile )
reader . readAsText ( uploadDetails . baseMinterMetadataFile as File , 'utf8' )
2022-08-04 09:16:42 +00:00
}
2022-08-03 07:25:08 +00:00
} )
2022-08-04 09:16:42 +00:00
. catch ( reject )
} )
}
const checkUploadDetails = ( ) = > {
2022-12-13 18:58:40 +00:00
if ( ! wallet . initialized ) throw new Error ( 'Wallet not connected.' )
2022-08-04 09:16:42 +00:00
if ( ! uploadDetails ) {
2022-08-10 06:57:20 +00:00
throw new Error ( 'Please select assets and metadata' )
2022-08-04 09:16:42 +00:00
}
2023-02-04 08:28:41 +00:00
if (
minterType === 'base' &&
uploadDetails . uploadMethod === 'new' &&
uploadDetails . assetFiles . length > 1 &&
uploadDetails . metadataFiles . length === 0
) {
throw new Error ( 'Please select metadata files' )
}
2022-08-16 07:04:33 +00:00
if ( uploadDetails . uploadMethod === 'new' && uploadDetails . assetFiles . length === 0 ) {
2022-08-10 06:57:20 +00:00
throw new Error ( 'Please select the assets' )
2022-08-04 09:16:42 +00:00
}
2022-12-26 13:17:09 +00:00
if ( minterType === 'vending' && uploadDetails . uploadMethod === 'new' && uploadDetails . metadataFiles . length === 0 ) {
2022-08-10 06:57:20 +00:00
throw new Error ( 'Please select the metadata files' )
2022-08-04 09:16:42 +00:00
}
2022-12-15 10:58:38 +00:00
if ( uploadDetails . uploadMethod === 'new' && minterType === 'vending' )
compareFileArrays ( uploadDetails . assetFiles , uploadDetails . metadataFiles )
2022-08-16 07:04:33 +00:00
if ( uploadDetails . uploadMethod === 'new' ) {
if ( uploadDetails . uploadService === 'nft-storage' ) {
if ( uploadDetails . nftStorageApiKey === '' ) {
2022-09-11 17:54:28 +00:00
throw new Error ( 'Please enter a valid NFT.Storage API key' )
2022-08-16 07:04:33 +00:00
}
} else if ( uploadDetails . pinataApiKey === '' || uploadDetails . pinataSecretKey === '' ) {
throw new Error ( 'Please enter Pinata API and secret keys' )
2022-08-03 07:25:08 +00:00
}
2022-08-16 07:04:33 +00:00
}
if ( uploadDetails . uploadMethod === 'existing' && ! uploadDetails . baseTokenURI ? . includes ( 'ipfs://' ) ) {
throw new Error ( 'Please specify a valid base token URI' )
}
2022-12-26 13:17:09 +00:00
if ( baseMinterDetails ? . baseMinterAcquisitionMethod === 'existing' && ! baseMinterDetails . existingBaseMinter ) {
2022-12-13 18:50:42 +00:00
throw new Error ( 'Please specify a valid Base Minter contract address' )
}
2022-08-03 07:25:08 +00:00
}
2022-08-04 09:16:42 +00:00
const checkCollectionDetails = ( ) = > {
if ( ! collectionDetails ) throw new Error ( 'Please fill out the collection details' )
2022-08-09 11:42:55 +00:00
if ( collectionDetails . name === '' ) throw new Error ( 'Collection name is required' )
if ( collectionDetails . description === '' ) throw new Error ( 'Collection description is required' )
2023-01-08 16:53:58 +00:00
if ( collectionDetails . description . length > 512 )
throw new Error ( 'Collection description cannot exceed 512 characters' )
2022-08-16 07:04:33 +00:00
if ( uploadDetails ? . uploadMethod === 'new' && collectionDetails . imageFile . length === 0 )
throw new Error ( 'Collection cover image is required' )
2022-10-21 01:02:52 +00:00
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' )
2023-01-09 06:38:13 +00:00
if ( collectionDetails . externalLink ) {
try {
const url = new URL ( collectionDetails . externalLink )
} catch ( e : any ) {
throw new Error ( ` Invalid external link: Make sure to include the protocol (e.g. https://) ` )
}
}
2022-08-03 07:25:08 +00:00
}
2022-08-04 09:16:42 +00:00
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' )
2022-08-09 11:42:55 +00:00
if ( Number ( mintingDetails . unitPrice ) < 50000000 )
throw new Error ( 'Invalid unit price: The minimum unit price is 50 STARS' )
if (
2022-12-26 13:17:09 +00:00
! mintingDetails . perAddressLimit ||
2022-08-09 11:42:55 +00:00
mintingDetails . perAddressLimit < 1 ||
mintingDetails . perAddressLimit > 50 ||
mintingDetails . perAddressLimit > mintingDetails . numTokens
)
throw new Error ( 'Invalid limit for tokens per address' )
2023-03-03 08:05:16 +00:00
if ( mintingDetails . numTokens < 100 && mintingDetails . perAddressLimit > 3 )
throw new Error (
'Invalid limit for tokens per address. Tokens per address limit cannot exceed 3 for collections with less than 100 tokens in total.' ,
)
2022-10-21 01:02:52 +00:00
if (
2023-03-03 08:05:16 +00:00
mintingDetails . numTokens >= 100 &&
mintingDetails . perAddressLimit > Math . ceil ( ( mintingDetails . numTokens / 100 ) * 3 )
2022-10-21 01:02:52 +00:00
)
2023-03-03 08:05:16 +00:00
throw new Error (
'Invalid limit for tokens per address. Tokens per address limit cannot exceed 3% of the total number of tokens in the collection.' ,
)
2022-08-04 09:16:42 +00:00
if ( mintingDetails . startTime === '' ) throw new Error ( 'Start time is required' )
2022-09-01 06:27:23 +00:00
if ( Number ( mintingDetails . startTime ) < new Date ( ) . getTime ( ) * 1000000 ) throw new Error ( 'Invalid start time' )
2023-03-27 09:18:39 +00:00
if (
mintingDetails . paymentAddress &&
( ! isValidAddress ( mintingDetails . paymentAddress ) || ! mintingDetails . paymentAddress . startsWith ( 'stars1' ) )
)
2023-03-18 13:11:40 +00:00
throw new Error ( 'Invalid payment address' )
2022-08-04 09:16:42 +00:00
}
2022-10-06 13:16:58 +00:00
const checkWhitelistDetails = async ( ) = > {
2022-08-04 09:16:42 +00:00
if ( ! whitelistDetails ) throw new Error ( 'Please fill out the whitelist details' )
2022-08-05 11:13:27 +00:00
if ( whitelistDetails . whitelistType === 'existing' ) {
if ( whitelistDetails . contractAddress === '' ) throw new Error ( 'Whitelist contract address is required' )
2022-10-06 13:16:58 +00:00
else {
const contract = whitelistContract ? . use ( whitelistDetails . contractAddress )
//check if the address belongs to a whitelist contract (see performChecks())
const config = await contract ? . config ( )
2022-11-18 16:15:06 +00:00
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 ` )
}
2023-03-04 09:43:04 +00:00
if ( mintingDetails ? . numTokens && config ? . per_address_limit ) {
if ( mintingDetails . numTokens >= 100 && Number ( config . per_address_limit ) > 50 ) {
throw Error (
` Invalid limit for tokens per address ( ${ config . per_address_limit } tokens). Tokens per address limit cannot exceed 50 regardless of the total number of tokens. ` ,
)
} else if (
mintingDetails . numTokens >= 100 &&
Number ( config . per_address_limit ) > Math . ceil ( ( mintingDetails . numTokens / 100 ) * 3 )
) {
throw Error (
` Invalid limit for tokens per address ( ${ config . per_address_limit } tokens). Tokens per address limit cannot exceed 3% of the total number of tokens in the collection. ` ,
)
} else if ( mintingDetails . numTokens < 100 && Number ( config . per_address_limit ) > 3 ) {
throw Error (
` Invalid limit for tokens per address ( ${ config . per_address_limit } tokens). Tokens per address limit cannot exceed 3 for collections with less than 100 tokens in total. ` ,
)
}
}
2022-10-06 13:16:58 +00:00
}
2022-08-09 11:42:55 +00:00
} else if ( whitelistDetails . whitelistType === 'new' ) {
2022-08-04 09:16:42 +00:00
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' )
2022-08-09 11:42:55 +00:00
if ( Number ( whitelistDetails . unitPrice ) < 25000000 )
throw new Error ( 'Invalid unit price: The minimum unit price for whitelisted addresses is 25 STARS' )
2022-08-04 09:16:42 +00:00
if ( whitelistDetails . startTime === '' ) throw new Error ( 'Start time is required' )
if ( whitelistDetails . endTime === '' ) throw new Error ( 'End time is required' )
2022-12-21 07:36:43 +00:00
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' )
2023-03-18 08:38:44 +00:00
if ( Number ( whitelistDetails . startTime ) >= Number ( whitelistDetails . endTime ) )
throw new Error ( 'Whitelist start time cannot be equal to or later than the whitelist end time' )
2022-11-18 16:15:06 +00:00
if ( Number ( whitelistDetails . startTime ) !== Number ( mintingDetails ? . startTime ) )
throw new Error ( 'Whitelist start time must be the same as the minting start time' )
2023-03-04 09:43:04 +00:00
if ( whitelistDetails . perAddressLimit && mintingDetails ? . numTokens ) {
if ( mintingDetails . numTokens >= 100 && whitelistDetails . perAddressLimit > 50 ) {
throw Error (
` Invalid limit for tokens per address. Tokens per address limit cannot exceed 50 regardless of the total number of tokens. ` ,
)
} else if (
mintingDetails . numTokens >= 100 &&
whitelistDetails . perAddressLimit > Math . ceil ( ( mintingDetails . numTokens / 100 ) * 3 )
) {
throw Error (
` Invalid limit for tokens per address. Tokens per address limit cannot exceed 3% of the total number of tokens in the collection. ` ,
)
} else if ( mintingDetails . numTokens < 100 && whitelistDetails . perAddressLimit > 3 ) {
throw Error (
` Invalid limit for tokens per address. Tokens per address limit cannot exceed 3 for collections with less than 100 tokens in total. ` ,
)
}
}
2022-08-04 09:16:42 +00:00
}
2022-08-03 07:25:08 +00:00
}
2023-04-05 11:54:34 +00:00
const checkRoyaltyDetails = async ( ) = > {
2022-08-04 09:16:42 +00:00
if ( ! royaltyDetails ) throw new Error ( 'Please fill out the royalty details' )
2022-08-05 11:13:27 +00:00
if ( royaltyDetails . royaltyType === 'new' ) {
2022-09-01 06:27:23 +00:00
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' )
2022-08-05 11:13:27 +00:00
if ( royaltyDetails . paymentAddress === '' ) throw new Error ( 'Royalty payment address is required' )
2023-01-12 10:01:11 +00:00
if ( ! isValidAddress ( royaltyDetails . paymentAddress ) ) {
if ( royaltyDetails . paymentAddress . trim ( ) . endsWith ( '.stars' ) ) {
throw new Error ( 'Royalty payment address could not be resolved' )
}
throw new Error ( 'Invalid royalty payment address' )
}
2023-04-05 11:54:34 +00:00
const contractInfoResponse = await wallet . client
? . queryContractRaw (
royaltyDetails . paymentAddress ,
toUtf8 ( Buffer . from ( Buffer . from ( 'contract_info' ) . toString ( 'hex' ) , 'hex' ) . toString ( ) ) ,
)
. catch ( ( e ) = > {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
if ( e . message . includes ( 'bech32' ) ) throw new Error ( 'Invalid royalty payment address.' )
console . log ( e . message )
} )
if ( contractInfoResponse !== undefined ) {
const contractInfo = JSON . parse ( new TextDecoder ( ) . decode ( contractInfoResponse as Uint8Array ) )
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
if ( contractInfo && ! contractInfo . contract . includes ( 'splits' ) )
throw new Error ( 'The provided royalty payment address does not belong to a splits contract.' )
else console . log ( contractInfo )
}
2022-08-05 11:13:27 +00:00
}
2022-08-03 07:25:08 +00:00
}
2022-12-21 07:36:43 +00:00
2023-04-01 13:45:49 +00:00
const fetchFactoryParameters = async ( ) = > {
const client = wallet . client
if ( ! client ) return
if ( BASE_FACTORY_ADDRESS ) {
const baseFactoryParameters = await client . queryContractSmart ( BASE_FACTORY_ADDRESS , { params : { } } )
setBaseMinterCreationFee ( baseFactoryParameters ? . params ? . creation_fee ? . amount )
}
if ( BASE_FACTORY_UPDATABLE_ADDRESS ) {
const baseFactoryUpdatableParameters = await client . queryContractSmart ( BASE_FACTORY_UPDATABLE_ADDRESS , {
params : { } ,
} )
setBaseMinterUpdatableCreationFee ( baseFactoryUpdatableParameters ? . params ? . creation_fee ? . amount )
}
if ( VENDING_FACTORY_ADDRESS ) {
const vendingFactoryParameters = await client . queryContractSmart ( VENDING_FACTORY_ADDRESS , { params : { } } )
setVendingMinterCreationFee ( vendingFactoryParameters ? . params ? . creation_fee ? . amount )
}
if ( VENDING_FACTORY_UPDATABLE_ADDRESS ) {
const vendingFactoryUpdatableParameters = await client . queryContractSmart ( VENDING_FACTORY_UPDATABLE_ADDRESS , {
params : { } ,
} )
setVendingMinterUpdatableCreationFee ( vendingFactoryUpdatableParameters ? . params ? . creation_fee ? . amount )
}
}
2022-12-21 07:36:43 +00:00
const checkwalletBalance = ( ) = > {
if ( ! wallet . initialized ) throw new Error ( 'Wallet not connected.' )
2023-01-18 07:24:25 +00:00
if ( minterType === 'vending' && whitelistDetails ? . whitelistType === 'new' && whitelistDetails . memberLimit ) {
2023-02-26 09:42:21 +00:00
const amountNeeded =
Math . ceil ( Number ( whitelistDetails . memberLimit ) / 1000 ) * 100000000 +
2023-04-01 13:45:49 +00:00
( collectionDetails ? . updatable ? Number ( vendingMinterUpdatableCreationFee ) : Number ( vendingMinterCreationFee ) )
2022-12-21 07:36:43 +00:00
if ( amountNeeded >= Number ( wallet . balance [ 0 ] . amount ) )
2023-03-18 13:23:24 +00:00
throw new Error (
` Insufficient wallet balance to instantiate the required contracts. Needed amount: ${ (
amountNeeded / 1000000
) . toString ( ) } STARS ` ,
)
2022-12-21 07:36:43 +00:00
} else {
2023-02-26 09:42:21 +00:00
const amountNeeded =
minterType === 'vending'
? collectionDetails ? . updatable
2023-04-01 13:45:49 +00:00
? Number ( vendingMinterUpdatableCreationFee )
: Number ( vendingMinterCreationFee )
2023-02-26 09:42:21 +00:00
: collectionDetails ? . updatable
2023-04-01 13:45:49 +00:00
? Number ( baseMinterUpdatableCreationFee )
: Number ( baseMinterCreationFee )
2022-12-21 07:36:43 +00:00
if ( amountNeeded >= Number ( wallet . balance [ 0 ] . amount ) )
2023-03-18 13:23:24 +00:00
throw new Error (
` Insufficient wallet balance to instantiate the required contracts. Needed amount: ${ (
amountNeeded / 1000000
) . toString ( ) } STARS ` ,
)
2022-12-21 07:36:43 +00:00
}
}
2022-08-10 12:04:18 +00:00
useEffect ( ( ) = > {
2023-04-04 12:30:05 +00:00
if ( vendingMinterContractAddress !== null || isMintingComplete )
scrollRef . current ? . scrollIntoView ( { behavior : 'smooth' } )
} , [ vendingMinterContractAddress , isMintingComplete ] )
2022-08-03 07:25:08 +00:00
2022-08-16 07:04:33 +00:00
useEffect ( ( ) = > {
setBaseTokenUri ( uploadDetails ? . baseTokenURI as string )
setCoverImageUrl ( uploadDetails ? . imageUrl as string )
} , [ uploadDetails ? . baseTokenURI , uploadDetails ? . imageUrl ] )
2022-12-13 18:50:42 +00:00
useEffect ( ( ) = > {
resetReadyFlags ( )
setVendingMinterContractAddress ( null )
2022-12-26 13:17:09 +00:00
setIsMintingComplete ( false )
} , [ minterType , baseMinterDetails ? . baseMinterAcquisitionMethod , uploadDetails ? . uploadMethod ] )
2022-12-13 18:50:42 +00:00
2023-04-01 13:45:49 +00:00
useEffect ( ( ) = > {
void fetchFactoryParameters ( )
} , [ wallet . client ] )
2022-07-27 06:49:36 +00:00
return (
< div >
2022-12-13 18:50:42 +00:00
< NextSeo
title = {
2022-12-26 13:17:09 +00:00
minterType === 'base' && baseMinterDetails ? . baseMinterAcquisitionMethod === 'existing'
2023-03-31 16:25:32 +00:00
? 'Add Token'
2022-12-13 18:50:42 +00:00
: 'Create Collection'
}
/ >
2022-07-27 06:49:36 +00:00
2022-08-04 11:19:25 +00:00
< div className = "mt-5 space-y-5 text-center" >
2022-12-13 18:50:42 +00:00
< h1 className = "font-heading text-4xl font-bold" >
2022-12-26 13:17:09 +00:00
{ minterType === 'base' && baseMinterDetails ? . baseMinterAcquisitionMethod === 'existing'
2023-03-31 16:25:32 +00:00
? 'Add Token'
2022-12-13 18:50:42 +00:00
: 'Create Collection' }
< / h1 >
2022-07-27 06:49:36 +00:00
2022-08-10 09:07:05 +00:00
< Conditional test = { uploading } >
< LoadingModal / >
< / Conditional >
2022-07-27 06:49:36 +00:00
< p >
Make sure you check our { ' ' }
2022-08-10 12:04:18 +00:00
< Anchor className = "font-bold text-plumbus hover:underline" external href = { links [ 'Docs' ] } >
2022-07-27 06:49:36 +00:00
documentation
< / Anchor > { ' ' }
on how to create your collection
< / p >
< / div >
2022-08-10 12:04:18 +00:00
< div className = "mx-10" ref = { scrollRef } >
2023-04-04 12:30:05 +00:00
< Conditional test = { vendingMinterContractAddress !== null || isMintingComplete } >
2022-08-10 12:04:18 +00:00
< Alert className = "mt-5" type = "info" >
< div >
2022-12-26 13:17:09 +00:00
< Conditional test = { minterType === 'vending' || isMintingComplete } >
{ minterType === 'vending' ? 'Base Token URI: ' : 'Token URI: ' } { ' ' }
{ uploadDetails ? . uploadMethod === 'new' && (
< Anchor
className = "text-stargaze hover:underline"
external
2023-04-01 13:45:49 +00:00
href = { ` https://ipfs-gw.stargaze-apis.com/ipfs/ ${ baseTokenUri as string } ` }
2022-12-26 13:17:09 +00:00
>
ipfs : //{baseTokenUri as string}
< / Anchor >
) }
{ uploadDetails ? . uploadMethod === 'existing' && (
< Anchor
className = "text-stargaze hover:underline"
external
2023-04-01 13:45:49 +00:00
href = { ` https://ipfs-gw.stargaze-apis.com/ipfs/ ${ baseTokenUri ? . substring (
2022-12-26 13:17:09 +00:00
baseTokenUri . lastIndexOf ( 'ipfs://' ) + 7 ,
) } / ` }
>
ipfs : //{baseTokenUri?.substring(baseTokenUri.lastIndexOf('ipfs://') + 7)}
< / Anchor >
) }
< br / >
2023-04-04 12:30:05 +00:00
< Conditional test = { vendingMinterContractAddress === null && isMintingComplete } >
Transaction Hash : { ' ' }
< Conditional test = { NETWORK === 'testnet' } >
< Anchor
className = "text-stargaze hover:underline"
external
href = { ` ${ BLOCK_EXPLORER_URL } /tx/ ${ transactionHash as string } ` }
>
{ transactionHash }
< / Anchor >
< / Conditional >
< Conditional test = { NETWORK === 'mainnet' } >
< Anchor
className = "text-stargaze hover:underline"
external
href = { ` ${ BLOCK_EXPLORER_URL } /txs/ ${ transactionHash as string } ` }
>
{ transactionHash }
< / Anchor >
< / Conditional >
< br / >
Minter Contract Address : { ' ' }
< Anchor
className = "text-stargaze hover:underline"
external
href = { ` /contracts/baseMinter/query/?contractAddress= ${
baseMinterDetails ? . existingBaseMinter as string
} ` }
>
{ baseMinterDetails ? . existingBaseMinter as string }
< / Anchor >
< br / >
SG721 Contract Address : { ' ' }
< Anchor
className = "text-stargaze hover:underline"
external
href = { ` /contracts/sg721/query/?contractAddress= ${ sg721ContractAddress as string } ` }
>
{ sg721ContractAddress }
< / Anchor >
< br / >
< div className = "flex flex-row mt-2" >
< AnchorButton className = "text-white" external href = { ` ${ STARGAZE_URL } /profile/ ${ wallet . address } ` } >
Visit Your Profile
< / AnchorButton >
< AnchorButton
className = "ml-2 text-white"
external
href = { ` ${ STARGAZE_URL } /marketplace/ ${ sg721ContractAddress as string } / ${
baseMinterDetails ? . collectionTokenCount
? ( baseMinterDetails . collectionTokenCount + 1 ) . toString ( )
: '1'
} ` }
>
View the Token ( s ) on Marketplace
< / AnchorButton >
< / div >
< / Conditional >
2022-12-26 13:17:09 +00:00
< / Conditional >
2023-04-04 12:30:05 +00:00
< Conditional test = { vendingMinterContractAddress !== null } >
Minter Contract Address : { ' ' }
2022-09-11 17:54:28 +00:00
< Anchor
className = "text-stargaze hover:underline"
external
2023-04-04 12:30:05 +00:00
href = {
minterType === 'vending'
? ` /contracts/vendingMinter/query/?contractAddress= ${ vendingMinterContractAddress as string } `
: ` /contracts/baseMinter/query/?contractAddress= ${ vendingMinterContractAddress as string } `
}
2022-09-11 17:54:28 +00:00
>
2023-04-04 12:30:05 +00:00
{ vendingMinterContractAddress }
2022-09-11 17:54:28 +00:00
< / Anchor >
< br / >
2023-04-04 12:30:05 +00:00
SG721 Contract Address : { ' ' }
2022-09-23 17:36:34 +00:00
< Anchor
className = "text-stargaze hover:underline"
external
2023-04-04 12:30:05 +00:00
href = { ` /contracts/sg721/query/?contractAddress= ${ sg721ContractAddress as string } ` }
2022-09-23 17:36:34 +00:00
>
2023-04-04 12:30:05 +00:00
{ sg721ContractAddress }
2022-09-23 17:36:34 +00:00
< / Anchor >
2023-04-04 12:30:05 +00:00
< br / >
< Conditional test = { whitelistContractAddress !== null && whitelistContractAddress !== undefined } >
Whitelist Contract Address : { ' ' }
2023-04-03 09:40:33 +00:00
< Anchor
2023-04-04 12:30:05 +00:00
className = "text-stargaze hover:underline"
2023-04-03 09:40:33 +00:00
external
2023-04-04 12:30:05 +00:00
href = { ` /contracts/whitelist/query/?contractAddress= ${ whitelistContractAddress as string } ` }
2023-04-03 09:40:33 +00:00
>
2023-04-04 12:30:05 +00:00
{ whitelistContractAddress }
2023-04-03 09:40:33 +00:00
< / Anchor >
2023-04-04 12:30:05 +00:00
< br / >
< / Conditional >
Transaction Hash : { ' ' }
< Conditional test = { NETWORK === 'testnet' } >
< Anchor
className = "text-stargaze hover:underline"
external
href = { ` ${ BLOCK_EXPLORER_URL } /tx/ ${ transactionHash as string } ` }
>
{ transactionHash }
< / Anchor >
< / Conditional >
< Conditional test = { NETWORK === 'mainnet' } >
2023-04-03 09:40:33 +00:00
< Anchor
2023-04-04 12:30:05 +00:00
className = "text-stargaze hover:underline"
2023-04-03 09:40:33 +00:00
external
2023-04-04 12:30:05 +00:00
href = { ` ${ BLOCK_EXPLORER_URL } /txs/ ${ transactionHash as string } ` }
2023-04-03 09:40:33 +00:00
>
2023-04-04 12:30:05 +00:00
{ transactionHash }
2023-04-03 09:40:33 +00:00
< / Anchor >
2023-04-04 12:30:05 +00:00
< / Conditional >
< Conditional test = { minterType === 'vending' } >
< Button className = "mt-2" >
< Anchor
className = "text-white"
external
href = { ` ${ STARGAZE_URL } /launchpad/ ${ vendingMinterContractAddress as string } ` }
>
View on Launchpad
< / Anchor >
< / Button >
< / Conditional >
< Conditional test = { minterType === 'base' } >
< div className = "flex flex-row mt-2" >
< AnchorButton className = "text-white" external href = { ` ${ STARGAZE_URL } /profile/ ${ wallet . address } ` } >
Visit Your Profile
< / AnchorButton >
< AnchorButton
className = "ml-2 text-white"
external
href = { ` ${ STARGAZE_URL } /marketplace/ ${ sg721ContractAddress as string } ${
! isMintingComplete ? ` ?sort=price_asc ` : ` /1 `
} ` }
>
View the Collection on Marketplace
< / AnchorButton >
< / div >
< / Conditional >
2023-04-03 09:40:33 +00:00
< / Conditional >
2022-08-10 12:04:18 +00:00
< / div >
< / Alert >
< / Conditional >
< / div >
2022-12-19 14:38:51 +00:00
{ /* To be removed */ }
< Conditional test = { BASE_FACTORY_ADDRESS === undefined } >
< div className = "mx-10 mt-5" / >
< / Conditional >
< Conditional test = { BASE_FACTORY_ADDRESS !== undefined } >
{ /* /To be removed */ }
< div >
2022-12-13 12:52:43 +00:00
< div
className = { clsx (
2022-12-19 14:38:51 +00:00
'mx-10 mt-5' ,
'grid before:absolute relative grid-cols-2 grid-flow-col items-stretch rounded' ,
'before:inset-x-0 before:bottom-0 before:border-white/25' ,
2022-12-13 12:52:43 +00:00
) }
>
2022-12-19 14:38:51 +00:00
< div
className = { clsx (
'isolate space-y-1 border-2' ,
'first-of-type:rounded-tl-md last-of-type:rounded-tr-md' ,
minterType === 'vending' ? 'border-stargaze' : 'border-transparent' ,
minterType !== 'vending' ? 'bg-stargaze/5 hover:bg-stargaze/80' : 'hover:bg-white/5' ,
) }
2022-12-13 12:52:43 +00:00
>
2022-12-19 14:38:51 +00:00
< button
className = "p-4 w-full h-full text-left bg-transparent"
onClick = { ( ) = > {
setMinterType ( 'vending' )
resetReadyFlags ( )
} }
type = "button"
>
2023-01-05 11:38:01 +00:00
< h4 className = "font-bold" > Standard Collection < / h4 >
2022-12-19 14:38:51 +00:00
< span className = "text-sm text-white/80 line-clamp-2" >
2023-01-05 11:38:01 +00:00
A non - appendable collection that facilitates primary market vending machine style minting
2022-12-19 14:38:51 +00:00
< / span >
< / button >
< / div >
< div
className = { clsx (
'isolate space-y-1 border-2' ,
'first-of-type:rounded-tl-md last-of-type:rounded-tr-md' ,
minterType === 'base' ? 'border-stargaze' : 'border-transparent' ,
minterType !== 'base' ? 'bg-stargaze/5 hover:bg-stargaze/80' : 'hover:bg-white/5' ,
) }
2022-12-13 12:52:43 +00:00
>
2022-12-19 14:38:51 +00:00
< button
className = "p-4 w-full h-full text-left bg-transparent"
onClick = { ( ) = > {
setMinterType ( 'base' )
resetReadyFlags ( )
} }
type = "button"
>
2023-01-05 11:09:57 +00:00
< h4 className = "font-bold" > 1 / 1 Collection < / h4 >
< span className = "text-sm text-white/80 line-clamp-2" >
2023-01-05 11:38:01 +00:00
An appendable collection that only allows for direct secondary market listing of tokens
2023-01-05 11:09:57 +00:00
< / span >
2022-12-19 14:38:51 +00:00
< / button >
< / div >
2022-12-13 12:52:43 +00:00
< / div >
< / div >
2022-12-19 14:38:51 +00:00
< / Conditional >
2022-12-13 12:52:43 +00:00
{ minterType === 'base' && (
< div >
2022-12-26 13:17:09 +00:00
< BaseMinterDetails minterType = { minterType } onChange = { setBaseMinterDetails } / >
2022-12-13 12:52:43 +00:00
< / div >
) }
2022-08-05 11:13:27 +00:00
< div className = "mx-10" >
2022-12-13 18:50:42 +00:00
< UploadDetails
2022-12-26 13:17:09 +00:00
baseMinterAcquisitionMethod = { baseMinterDetails ? . baseMinterAcquisitionMethod }
2022-12-13 18:50:42 +00:00
minterType = { minterType }
onChange = { setUploadDetails }
/ >
2022-08-05 11:13:27 +00:00
2022-12-13 12:52:43 +00:00
< Conditional
2022-12-26 13:17:09 +00:00
test = {
minterType === 'vending' ||
( minterType === 'base' && baseMinterDetails ? . baseMinterAcquisitionMethod === 'new' )
}
2022-12-13 12:52:43 +00:00
>
< div className = "flex justify-between py-3 px-8 rounded border-2 border-white/20 grid-col-2" >
< Conditional
test = {
2022-12-26 13:17:09 +00:00
minterType === 'vending' ||
( minterType === 'base' && baseMinterDetails ? . baseMinterAcquisitionMethod === 'new' )
2022-12-13 12:52:43 +00:00
}
>
< CollectionDetails
coverImageUrl = { coverImageUrl as string }
2022-12-26 13:17:09 +00:00
minterType = { minterType }
2022-12-13 12:52:43 +00:00
onChange = { setCollectionDetails }
uploadMethod = { uploadDetails ? . uploadMethod as UploadMethod }
/ >
< / Conditional >
< Conditional test = { minterType === 'vending' } >
< MintingDetails
numberOfTokens = { uploadDetails ? . assetFiles . length }
onChange = { setMintingDetails }
uploadMethod = { uploadDetails ? . uploadMethod as UploadMethod }
/ >
< / Conditional >
< / div >
< / Conditional >
< Conditional
2022-12-26 13:17:09 +00:00
test = {
minterType === 'vending' ||
( minterType === 'base' && baseMinterDetails ? . baseMinterAcquisitionMethod === 'new' )
}
2022-12-13 12:52:43 +00:00
>
< div className = "my-6" >
< Conditional test = { minterType === 'vending' } >
< WhitelistDetails onChange = { setWhitelistDetails } / >
< div className = "my-6" / >
< / Conditional >
< RoyaltyDetails onChange = { setRoyaltyDetails } / >
< / div >
< / Conditional >
< Conditional test = { readyToCreateVm && minterType === 'vending' } >
< ConfirmationModal confirm = { createVendingMinterCollection } / >
< / Conditional >
< Conditional
2022-12-26 13:17:09 +00:00
test = { readyToCreateBm && minterType === 'base' && baseMinterDetails ? . baseMinterAcquisitionMethod === 'new' }
2022-12-13 12:52:43 +00:00
>
< ConfirmationModal confirm = { createBaseMinterCollection } / >
< / Conditional >
< Conditional
2022-12-26 13:17:09 +00:00
test = {
readyToUploadAndMint &&
minterType === 'base' &&
baseMinterDetails ? . baseMinterAcquisitionMethod === 'existing'
}
2022-12-13 12:52:43 +00:00
>
< ConfirmationModal confirm = { uploadAndMint } / >
< / Conditional >
2022-09-01 06:27:23 +00:00
< div className = "flex justify-end w-full" >
2022-12-13 12:52:43 +00:00
< Conditional test = { minterType === 'vending' } >
< Button
className = "relative justify-center p-2 mb-6 max-h-12 text-white bg-plumbus hover:bg-plumbus-light border-0"
isLoading = { creatingCollection }
onClick = { performVendingMinterChecks }
variant = "solid"
>
Create Collection
< / Button >
< / Conditional >
2022-12-26 13:17:09 +00:00
< Conditional test = { minterType === 'base' && baseMinterDetails ? . baseMinterAcquisitionMethod === 'new' } >
2022-12-13 12:52:43 +00:00
< Button
className = "relative justify-center p-2 mb-6 max-h-12 text-white bg-plumbus hover:bg-plumbus-light border-0"
isLoading = { creatingCollection }
onClick = { performBaseMinterChecks }
variant = "solid"
>
2022-12-13 18:50:42 +00:00
Create Collection
2022-12-13 12:52:43 +00:00
< / Button >
< / Conditional >
2022-12-26 13:17:09 +00:00
< Conditional test = { minterType === 'base' && baseMinterDetails ? . baseMinterAcquisitionMethod === 'existing' } >
2022-12-13 12:52:43 +00:00
< Button
className = "relative justify-center p-2 mb-6 max-h-12 text-white bg-plumbus hover:bg-plumbus-light border-0"
isLoading = { creatingCollection }
onClick = { performUploadAndMintChecks }
variant = "solid"
>
2023-03-31 16:25:32 +00:00
Mint & Add Token ( s )
2022-12-13 12:52:43 +00:00
< / Button >
< / Conditional >
2022-09-01 06:27:23 +00:00
< / div >
2022-08-03 07:25:08 +00:00
< / div >
2022-07-27 06:49:36 +00:00
< / div >
)
}
2022-08-04 09:16:42 +00:00
export default withMetadata ( CollectionCreationPage , { center : false } )