Implement 1/1 minting UI
This commit is contained in:
parent
5c6c87eb9e
commit
fe4da95566
@ -2,14 +2,18 @@ import clsx from 'clsx'
|
|||||||
import { useCallback, useMemo, useState } from 'react'
|
import { useCallback, useMemo, useState } from 'react'
|
||||||
import { getAssetType } from 'utils/getAssetType'
|
import { getAssetType } from 'utils/getAssetType'
|
||||||
|
|
||||||
|
import type { MinterType } from './collections/actions/Combobox'
|
||||||
|
import { Conditional } from './Conditional'
|
||||||
|
|
||||||
interface AssetsPreviewProps {
|
interface AssetsPreviewProps {
|
||||||
assetFilesArray: File[]
|
assetFilesArray: File[]
|
||||||
updateMetadataFileIndex: (index: number) => void
|
updateMetadataFileIndex: (index: number) => void
|
||||||
|
minterType: MinterType
|
||||||
}
|
}
|
||||||
|
|
||||||
const ITEM_NUMBER = 12
|
const ITEM_NUMBER = 12
|
||||||
|
|
||||||
export const AssetsPreview = ({ assetFilesArray, updateMetadataFileIndex }: AssetsPreviewProps) => {
|
export const AssetsPreview = ({ assetFilesArray, updateMetadataFileIndex, minterType }: AssetsPreviewProps) => {
|
||||||
const [page, setPage] = useState(1)
|
const [page, setPage] = useState(1)
|
||||||
|
|
||||||
const totalPages = useMemo(() => Math.ceil(assetFilesArray.length / ITEM_NUMBER), [assetFilesArray])
|
const totalPages = useMemo(() => Math.ceil(assetFilesArray.length / ITEM_NUMBER), [assetFilesArray])
|
||||||
@ -116,6 +120,7 @@ export const AssetsPreview = ({ assetFilesArray, updateMetadataFileIndex }: Asse
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<div className="mt-2 w-[400px] h-[300px]">{renderImages()}</div>
|
<div className="mt-2 w-[400px] h-[300px]">{renderImages()}</div>
|
||||||
|
<Conditional test={minterType === 'vending'}>
|
||||||
<div className="mt-5 btn-group">
|
<div className="mt-5 btn-group">
|
||||||
<button className="text-white bg-plumbus-light btn" onClick={multiplePrevPage} type="button">
|
<button className="text-white bg-plumbus-light btn" onClick={multiplePrevPage} type="button">
|
||||||
««
|
««
|
||||||
@ -133,6 +138,7 @@ export const AssetsPreview = ({ assetFilesArray, updateMetadataFileIndex }: Asse
|
|||||||
»»
|
»»
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</Conditional>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ export const ConfirmationModal = (props: ConfirmationModalProps) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
Are you sure to create a collection with the specified assets, metadata and parameters?
|
Are you sure to proceed with the specified assets, metadata and parameters?
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end w-full">
|
<div className="flex justify-end w-full">
|
||||||
<Button className="px-0 mt-4 mr-5 mb-4 max-h-12 bg-gray-600 hover:bg-gray-600">
|
<Button className="px-0 mt-4 mr-5 mb-4 max-h-12 bg-gray-600 hover:bg-gray-600">
|
||||||
|
@ -35,7 +35,7 @@ export const MinterDetails = ({ onChange, minterType }: MinterDetailsProps) => {
|
|||||||
const wallet = useWallet()
|
const wallet = useWallet()
|
||||||
|
|
||||||
const [myBaseMinterContracts, setMyBaseMinterContracts] = useState<MinterInfo[]>([])
|
const [myBaseMinterContracts, setMyBaseMinterContracts] = useState<MinterInfo[]>([])
|
||||||
const [minterAcquisitionMethod, setMinterAcquisitionMethod] = useState<MinterAcquisitionMethod>('existing')
|
const [minterAcquisitionMethod, setMinterAcquisitionMethod] = useState<MinterAcquisitionMethod>('new')
|
||||||
|
|
||||||
const existingMinterState = useInputState({
|
const existingMinterState = useInputState({
|
||||||
id: 'existingMinter',
|
id: 'existingMinter',
|
||||||
@ -79,7 +79,7 @@ export const MinterDetails = ({ onChange, minterType }: MinterDetailsProps) => {
|
|||||||
minterContracts.map(async (minterContract: any) => {
|
minterContracts.map(async (minterContract: any) => {
|
||||||
await getMinterContractType(minterContract.minter)
|
await getMinterContractType(minterContract.minter)
|
||||||
.then((contractType) => {
|
.then((contractType) => {
|
||||||
if (contractType?.includes('sg-minter')) {
|
if (contractType?.includes('sg-base-minter')) {
|
||||||
setMyBaseMinterContracts((prevState) => [...prevState, minterContract])
|
setMyBaseMinterContracts((prevState) => [...prevState, minterContract])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -181,12 +181,12 @@ export const MinterDetails = ({ onChange, minterType }: MinterDetailsProps) => {
|
|||||||
<select
|
<select
|
||||||
className="mt-8 w-full max-w-lg text-sm bg-white/10 select select-bordered"
|
className="mt-8 w-full max-w-lg text-sm bg-white/10 select select-bordered"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
existingMinterState.onChange(e.target.value.slice(e.target.value.indexOf('-') + 2))
|
existingMinterState.onChange(e.target.value.slice(e.target.value.indexOf('stars1')))
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<option className="mt-2 text-lg bg-[#1A1A1A]" disabled selected>
|
<option className="mt-2 text-lg bg-[#1A1A1A]" disabled selected>
|
||||||
Select a Base Minter Contract
|
Select one of your existing Base Minter Contracts
|
||||||
</option>
|
</option>
|
||||||
{renderMinterContracts()}
|
{renderMinterContracts()}
|
||||||
</select>
|
</select>
|
||||||
|
@ -11,15 +11,20 @@ import { TextInput } from 'components/forms/FormInput'
|
|||||||
import { useInputState } from 'components/forms/FormInput.hooks'
|
import { useInputState } from 'components/forms/FormInput.hooks'
|
||||||
import { MetadataModal } from 'components/MetadataModal'
|
import { MetadataModal } from 'components/MetadataModal'
|
||||||
import type { ChangeEvent } from 'react'
|
import type { ChangeEvent } from 'react'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { toast } from 'react-hot-toast'
|
import { toast } from 'react-hot-toast'
|
||||||
import type { UploadServiceType } from 'services/upload'
|
import type { UploadServiceType } from 'services/upload'
|
||||||
import { naturalCompare } from 'utils/sort'
|
import { naturalCompare } from 'utils/sort'
|
||||||
|
|
||||||
|
import type { MinterType } from '../actions/Combobox'
|
||||||
|
import type { MinterAcquisitionMethod } from './MinterDetails'
|
||||||
|
|
||||||
export type UploadMethod = 'new' | 'existing'
|
export type UploadMethod = 'new' | 'existing'
|
||||||
|
|
||||||
interface UploadDetailsProps {
|
interface UploadDetailsProps {
|
||||||
onChange: (value: UploadDetailsDataProps) => void
|
onChange: (value: UploadDetailsDataProps) => void
|
||||||
|
minterType: MinterType
|
||||||
|
minterAcquisitionMethod?: MinterAcquisitionMethod
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UploadDetailsDataProps {
|
export interface UploadDetailsDataProps {
|
||||||
@ -34,7 +39,7 @@ export interface UploadDetailsDataProps {
|
|||||||
imageUrl?: string
|
imageUrl?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
export const UploadDetails = ({ onChange, minterType, minterAcquisitionMethod }: UploadDetailsProps) => {
|
||||||
const [assetFilesArray, setAssetFilesArray] = useState<File[]>([])
|
const [assetFilesArray, setAssetFilesArray] = useState<File[]>([])
|
||||||
const [metadataFilesArray, setMetadataFilesArray] = useState<File[]>([])
|
const [metadataFilesArray, setMetadataFilesArray] = useState<File[]>([])
|
||||||
const [uploadMethod, setUploadMethod] = useState<UploadMethod>('new')
|
const [uploadMethod, setUploadMethod] = useState<UploadMethod>('new')
|
||||||
@ -42,6 +47,9 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
|||||||
const [metadataFileArrayIndex, setMetadataFileArrayIndex] = useState(0)
|
const [metadataFileArrayIndex, setMetadataFileArrayIndex] = useState(0)
|
||||||
const [refreshMetadata, setRefreshMetadata] = useState(false)
|
const [refreshMetadata, setRefreshMetadata] = useState(false)
|
||||||
|
|
||||||
|
const assetFilesRef = useRef<HTMLInputElement | null>(null)
|
||||||
|
const metadataFilesRef = useRef<HTMLInputElement | null>(null)
|
||||||
|
|
||||||
const nftStorageApiKeyState = useInputState({
|
const nftStorageApiKeyState = useInputState({
|
||||||
id: 'nft-storage-api-key',
|
id: 'nft-storage-api-key',
|
||||||
name: 'nftStorageApiKey',
|
name: 'nftStorageApiKey',
|
||||||
@ -67,7 +75,7 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
|||||||
const baseTokenUriState = useInputState({
|
const baseTokenUriState = useInputState({
|
||||||
id: 'baseTokenUri',
|
id: 'baseTokenUri',
|
||||||
name: 'baseTokenUri',
|
name: 'baseTokenUri',
|
||||||
title: 'Base Token URI',
|
title: minterType === 'vending' ? 'Base Token URI' : 'Token URI',
|
||||||
placeholder: 'ipfs://',
|
placeholder: 'ipfs://',
|
||||||
defaultValue: '',
|
defaultValue: '',
|
||||||
})
|
})
|
||||||
@ -226,11 +234,13 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
|||||||
])
|
])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (assetFilesRef.current) assetFilesRef.current.value = ''
|
||||||
|
if (assetFilesRef.current) assetFilesRef.current.value = ''
|
||||||
setAssetFilesArray([])
|
setAssetFilesArray([])
|
||||||
setMetadataFilesArray([])
|
setMetadataFilesArray([])
|
||||||
baseTokenUriState.onChange('')
|
baseTokenUriState.onChange('')
|
||||||
coverImageUrlState.onChange('')
|
coverImageUrlState.onChange('')
|
||||||
}, [uploadMethod])
|
}, [uploadMethod, minterType, minterAcquisitionMethod])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="justify-items-start mb-3 rounded border-2 border-white/20 flex-column">
|
<div className="justify-items-start mb-3 rounded border-2 border-white/20 flex-column">
|
||||||
@ -251,7 +261,7 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
|||||||
className="inline-block py-1 px-2 text-gray peer-checked:text-white hover:text-white peer-checked:bg-black peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
|
className="inline-block py-1 px-2 text-gray peer-checked:text-white hover:text-white peer-checked:bg-black peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
|
||||||
htmlFor="inlineRadio2"
|
htmlFor="inlineRadio2"
|
||||||
>
|
>
|
||||||
Upload assets & metadata
|
{minterType === 'base' ? 'Upload asset & metadata' : 'Upload assets & metadata'}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 ml-2 font-bold form-check form-check-inline">
|
<div className="mt-3 ml-2 font-bold form-check form-check-inline">
|
||||||
@ -270,13 +280,13 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
|||||||
className="inline-block py-1 px-2 text-gray peer-checked:text-white hover:text-white peer-checked:bg-black peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
|
className="inline-block py-1 px-2 text-gray peer-checked:text-white hover:text-white peer-checked:bg-black peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
|
||||||
htmlFor="inlineRadio1"
|
htmlFor="inlineRadio1"
|
||||||
>
|
>
|
||||||
Use an existing base URI
|
{minterType === 'base' ? 'Use an existing Token URI' : 'Use an existing base URI'}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-3 py-5 pb-8">
|
<div className="p-3 py-5 pb-8">
|
||||||
<Conditional test={uploadMethod === 'existing'}>
|
<Conditional test={uploadMethod === 'existing' && minterType === 'vending'}>
|
||||||
<div className="ml-3 flex-column">
|
<div className="ml-3 flex-column">
|
||||||
<p className="mb-5 ml-5">
|
<p className="mb-5 ml-5">
|
||||||
Though Stargaze's sg721 contract allows for off-chain metadata storage, it is recommended to use a
|
Though Stargaze's sg721 contract allows for off-chain metadata storage, it is recommended to use a
|
||||||
@ -293,9 +303,35 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
|||||||
<div>
|
<div>
|
||||||
<TextInput {...baseTokenUriState} className="w-1/2" />
|
<TextInput {...baseTokenUriState} className="w-1/2" />
|
||||||
</div>
|
</div>
|
||||||
|
<Conditional test={minterType !== 'base'}>
|
||||||
<div>
|
<div>
|
||||||
<TextInput {...coverImageUrlState} className="mt-2 w-1/2" />
|
<TextInput {...coverImageUrlState} className="mt-2 w-1/2" />
|
||||||
</div>
|
</div>
|
||||||
|
</Conditional>
|
||||||
|
</div>
|
||||||
|
</Conditional>
|
||||||
|
<Conditional test={uploadMethod === 'existing' && minterType === 'base'}>
|
||||||
|
<div className="ml-3 flex-column">
|
||||||
|
<p className="mb-5 ml-5">
|
||||||
|
Though Stargaze's sg721 contract allows for off-chain metadata storage, it is recommended to use a
|
||||||
|
decentralized storage solution, such as IPFS. <br /> You may head over to{' '}
|
||||||
|
<Anchor className="font-bold text-plumbus hover:underline" href="https://nft.storage">
|
||||||
|
NFT.Storage
|
||||||
|
</Anchor>{' '}
|
||||||
|
or{' '}
|
||||||
|
<Anchor className="font-bold text-plumbus hover:underline" href="https://www.pinata.cloud/">
|
||||||
|
Pinata
|
||||||
|
</Anchor>{' '}
|
||||||
|
and upload your asset & metadata manually to get a URI for your token before minting.
|
||||||
|
</p>
|
||||||
|
<div>
|
||||||
|
<TextInput {...baseTokenUriState} className="w-1/2" />
|
||||||
|
</div>
|
||||||
|
<Conditional test={minterType !== 'base' || (minterType === 'base' && minterAcquisitionMethod === 'new')}>
|
||||||
|
<div>
|
||||||
|
<TextInput {...coverImageUrlState} className="mt-2 w-1/2" />
|
||||||
|
</div>
|
||||||
|
</Conditional>
|
||||||
</div>
|
</div>
|
||||||
</Conditional>
|
</Conditional>
|
||||||
<Conditional test={uploadMethod === 'new'}>
|
<Conditional test={uploadMethod === 'new'}>
|
||||||
@ -390,8 +426,9 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
|||||||
'before:absolute before:inset-0 before:hover:bg-white/5 before:transition',
|
'before:absolute before:inset-0 before:hover:bg-white/5 before:transition',
|
||||||
)}
|
)}
|
||||||
id="assetFiles"
|
id="assetFiles"
|
||||||
multiple
|
multiple={minterType === 'vending'}
|
||||||
onChange={selectAssets}
|
onChange={selectAssets}
|
||||||
|
ref={assetFilesRef}
|
||||||
type="file"
|
type="file"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -418,8 +455,9 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
|||||||
'before:absolute before:inset-0 before:hover:bg-white/5 before:transition',
|
'before:absolute before:inset-0 before:hover:bg-white/5 before:transition',
|
||||||
)}
|
)}
|
||||||
id="metadataFiles"
|
id="metadataFiles"
|
||||||
multiple
|
multiple={minterType === 'vending'}
|
||||||
onChange={selectMetadata}
|
onChange={selectMetadata}
|
||||||
|
ref={metadataFilesRef}
|
||||||
type="file"
|
type="file"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -435,7 +473,11 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Conditional test={assetFilesArray.length > 0}>
|
<Conditional test={assetFilesArray.length > 0}>
|
||||||
<AssetsPreview assetFilesArray={assetFilesArray} updateMetadataFileIndex={updateMetadataFileIndex} />
|
<AssetsPreview
|
||||||
|
assetFilesArray={assetFilesArray}
|
||||||
|
minterType={minterType}
|
||||||
|
updateMetadataFileIndex={updateMetadataFileIndex}
|
||||||
|
/>
|
||||||
</Conditional>
|
</Conditional>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -26,8 +26,10 @@ import { Conditional } from 'components/Conditional'
|
|||||||
import { LoadingModal } from 'components/LoadingModal'
|
import { LoadingModal } from 'components/LoadingModal'
|
||||||
import { useContracts } from 'contexts/contracts'
|
import { useContracts } from 'contexts/contracts'
|
||||||
import { useWallet } from 'contexts/wallet'
|
import { useWallet } from 'contexts/wallet'
|
||||||
import type { DispatchExecuteArgs } from 'contracts/vendingFactory/messages/execute'
|
import type { DispatchExecuteArgs as BaseFactoryDispatchExecuteArgs } from 'contracts/baseFactory/messages/execute'
|
||||||
import { dispatchExecute } from 'contracts/vendingFactory/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 type { NextPage } from 'next'
|
||||||
import { NextSeo } from 'next-seo'
|
import { NextSeo } from 'next-seo'
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||||
@ -35,6 +37,7 @@ import { toast } from 'react-hot-toast'
|
|||||||
import { upload } from 'services/upload'
|
import { upload } from 'services/upload'
|
||||||
import { compareFileArrays } from 'utils/compareFileArrays'
|
import { compareFileArrays } from 'utils/compareFileArrays'
|
||||||
import {
|
import {
|
||||||
|
BASE_FACTORY_ADDRESS,
|
||||||
BLOCK_EXPLORER_URL,
|
BLOCK_EXPLORER_URL,
|
||||||
NETWORK,
|
NETWORK,
|
||||||
SG721_CODE_ID,
|
SG721_CODE_ID,
|
||||||
@ -53,17 +56,24 @@ import { getAssetType } from '../../utils/getAssetType'
|
|||||||
const CollectionCreationPage: NextPage = () => {
|
const CollectionCreationPage: NextPage = () => {
|
||||||
const wallet = useWallet()
|
const wallet = useWallet()
|
||||||
const {
|
const {
|
||||||
|
baseMinter: baseMinterContract,
|
||||||
vendingMinter: vendingMinterContract,
|
vendingMinter: vendingMinterContract,
|
||||||
whitelist: whitelistContract,
|
whitelist: whitelistContract,
|
||||||
vendingFactory: vendingFactoryContract,
|
vendingFactory: vendingFactoryContract,
|
||||||
|
baseFactory: baseFactoryContract,
|
||||||
} = useContracts()
|
} = useContracts()
|
||||||
const scrollRef = useRef<HTMLDivElement>(null)
|
const scrollRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
const messages = useMemo(
|
const vendingFactoryMessages = useMemo(
|
||||||
() => vendingFactoryContract?.use(VENDING_FACTORY_ADDRESS),
|
() => vendingFactoryContract?.use(VENDING_FACTORY_ADDRESS),
|
||||||
[vendingFactoryContract, wallet.address],
|
[vendingFactoryContract, wallet.address],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const baseFactoryMessages = useMemo(
|
||||||
|
() => baseFactoryContract?.use(BASE_FACTORY_ADDRESS),
|
||||||
|
[baseFactoryContract, wallet.address],
|
||||||
|
)
|
||||||
|
|
||||||
const [uploadDetails, setUploadDetails] = useState<UploadDetailsDataProps | null>(null)
|
const [uploadDetails, setUploadDetails] = useState<UploadDetailsDataProps | null>(null)
|
||||||
const [collectionDetails, setCollectionDetails] = useState<CollectionDetailsDataProps | null>(null)
|
const [collectionDetails, setCollectionDetails] = useState<CollectionDetailsDataProps | null>(null)
|
||||||
const [minterDetails, setMinterDetails] = useState<MinterDetailsDataProps | null>(null)
|
const [minterDetails, setMinterDetails] = useState<MinterDetailsDataProps | null>(null)
|
||||||
@ -109,9 +119,8 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
try {
|
try {
|
||||||
setReadyToCreateBm(false)
|
setReadyToCreateBm(false)
|
||||||
checkUploadDetails()
|
checkUploadDetails()
|
||||||
checkCollectionDetails()
|
|
||||||
checkMintingDetails()
|
|
||||||
checkRoyaltyDetails()
|
checkRoyaltyDetails()
|
||||||
|
checkCollectionDetails()
|
||||||
checkWhitelistDetails()
|
checkWhitelistDetails()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setReadyToCreateBm(true)
|
setReadyToCreateBm(true)
|
||||||
@ -130,9 +139,6 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
try {
|
try {
|
||||||
setReadyToUploadAndMint(false)
|
setReadyToUploadAndMint(false)
|
||||||
checkUploadDetails()
|
checkUploadDetails()
|
||||||
checkCollectionDetails()
|
|
||||||
checkMintingDetails()
|
|
||||||
checkRoyaltyDetails()
|
|
||||||
checkWhitelistDetails()
|
checkWhitelistDetails()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setReadyToUploadAndMint(true)
|
setReadyToUploadAndMint(true)
|
||||||
@ -147,6 +153,12 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resetReadyFlags = () => {
|
||||||
|
setReadyToCreateVm(false)
|
||||||
|
setReadyToCreateBm(false)
|
||||||
|
setReadyToUploadAndMint(false)
|
||||||
|
}
|
||||||
|
|
||||||
const createVendingMinterCollection = async () => {
|
const createVendingMinterCollection = async () => {
|
||||||
try {
|
try {
|
||||||
setCreatingCollection(true)
|
setCreatingCollection(true)
|
||||||
@ -180,7 +192,7 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist()
|
else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist()
|
||||||
setWhitelistContractAddress(whitelist as string)
|
setWhitelistContractAddress(whitelist as string)
|
||||||
|
|
||||||
await instantiate(baseUri, coverImageUri, whitelist)
|
await instantiateVendingMinter(baseUri, coverImageUri, whitelist)
|
||||||
} else {
|
} else {
|
||||||
setBaseTokenUri(uploadDetails?.baseTokenURI as string)
|
setBaseTokenUri(uploadDetails?.baseTokenURI as string)
|
||||||
setCoverImageUrl(uploadDetails?.imageUrl as string)
|
setCoverImageUrl(uploadDetails?.imageUrl as string)
|
||||||
@ -190,7 +202,7 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist()
|
else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist()
|
||||||
setWhitelistContractAddress(whitelist as string)
|
setWhitelistContractAddress(whitelist as string)
|
||||||
|
|
||||||
await instantiate(baseTokenUri as string, coverImageUrl as string, whitelist)
|
await instantiateVendingMinter(baseTokenUri as string, coverImageUrl as string, whitelist)
|
||||||
}
|
}
|
||||||
setCreatingCollection(false)
|
setCreatingCollection(false)
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
@ -229,22 +241,12 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
setBaseTokenUri(baseUri)
|
setBaseTokenUri(baseUri)
|
||||||
setCoverImageUrl(coverImageUri)
|
setCoverImageUrl(coverImageUri)
|
||||||
|
|
||||||
let whitelist: string | undefined
|
await instantiateBaseMinter(baseUri, coverImageUri)
|
||||||
if (whitelistDetails?.whitelistType === 'existing') whitelist = whitelistDetails.contractAddress
|
|
||||||
else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist()
|
|
||||||
setWhitelistContractAddress(whitelist as string)
|
|
||||||
|
|
||||||
await instantiate(baseUri, coverImageUri, whitelist)
|
|
||||||
} else {
|
} else {
|
||||||
setBaseTokenUri(uploadDetails?.baseTokenURI as string)
|
setBaseTokenUri(uploadDetails?.baseTokenURI as string)
|
||||||
setCoverImageUrl(uploadDetails?.imageUrl as string)
|
setCoverImageUrl(uploadDetails?.imageUrl as string)
|
||||||
|
|
||||||
let whitelist: string | undefined
|
await instantiateBaseMinter(uploadDetails?.baseTokenURI as string, uploadDetails?.imageUrl as string)
|
||||||
if (whitelistDetails?.whitelistType === 'existing') whitelist = whitelistDetails.contractAddress
|
|
||||||
else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist()
|
|
||||||
setWhitelistContractAddress(whitelist as string)
|
|
||||||
|
|
||||||
await instantiate(baseTokenUri as string, coverImageUrl as string, whitelist)
|
|
||||||
}
|
}
|
||||||
setCreatingCollection(false)
|
setCreatingCollection(false)
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
@ -257,48 +259,49 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
|
|
||||||
const uploadAndMint = async () => {
|
const uploadAndMint = async () => {
|
||||||
try {
|
try {
|
||||||
|
if (!wallet.initialized) throw new Error('Wallet not connected')
|
||||||
|
if (!baseMinterContract) throw new Error('Contract not found')
|
||||||
setCreatingCollection(true)
|
setCreatingCollection(true)
|
||||||
setBaseTokenUri(null)
|
setBaseTokenUri(null)
|
||||||
setCoverImageUrl(null)
|
setCoverImageUrl(null)
|
||||||
setVendingMinterContractAddress(null)
|
setVendingMinterContractAddress(null)
|
||||||
setSg721ContractAddress(null)
|
setSg721ContractAddress(null)
|
||||||
setWhitelistContractAddress(null)
|
|
||||||
setTransactionHash(null)
|
setTransactionHash(null)
|
||||||
|
|
||||||
if (uploadDetails?.uploadMethod === 'new') {
|
if (uploadDetails?.uploadMethod === 'new') {
|
||||||
setUploading(true)
|
setUploading(true)
|
||||||
|
await uploadFiles()
|
||||||
const baseUri = await uploadFiles()
|
.then(async (baseUri) => {
|
||||||
//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)
|
setUploading(false)
|
||||||
|
|
||||||
setBaseTokenUri(baseUri)
|
setBaseTokenUri(baseUri)
|
||||||
setCoverImageUrl(coverImageUri)
|
const result = await baseMinterContract
|
||||||
|
.use(minterDetails?.existingMinter as string)
|
||||||
let whitelist: string | undefined
|
?.mint(wallet.address, `ipfs://${baseUri}`)
|
||||||
if (whitelistDetails?.whitelistType === 'existing') whitelist = whitelistDetails.contractAddress
|
console.log(result)
|
||||||
else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist()
|
return result
|
||||||
setWhitelistContractAddress(whitelist as string)
|
})
|
||||||
|
.then((result) => {
|
||||||
await instantiate(baseUri, coverImageUri, whitelist)
|
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)
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
setBaseTokenUri(uploadDetails?.baseTokenURI as string)
|
setBaseTokenUri(uploadDetails?.baseTokenURI as string)
|
||||||
setCoverImageUrl(uploadDetails?.imageUrl as string)
|
setUploading(false)
|
||||||
|
await baseMinterContract
|
||||||
let whitelist: string | undefined
|
.use(minterDetails?.existingMinter as string)
|
||||||
if (whitelistDetails?.whitelistType === 'existing') whitelist = whitelistDetails.contractAddress
|
?.mint(wallet.address, `ipfs://${uploadDetails?.baseTokenURI}`)
|
||||||
else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist()
|
.then((result) => {
|
||||||
setWhitelistContractAddress(whitelist as string)
|
toast.success(`Minted successfully! Tx Hash: ${result}`, { style: { maxWidth: 'none' }, duration: 5000 })
|
||||||
|
})
|
||||||
await instantiate(baseTokenUri as string, coverImageUrl as string, whitelist)
|
.catch((error) => {
|
||||||
|
toast.error(error.message, { style: { maxWidth: 'none' } })
|
||||||
|
setUploading(false)
|
||||||
|
setCreatingCollection(false)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
setCreatingCollection(false)
|
setCreatingCollection(false)
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
@ -332,9 +335,9 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
return data.contractAddress
|
return data.contractAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
const instantiate = async (baseUri: string, coverImageUri: string, whitelist?: string) => {
|
const instantiateVendingMinter = async (baseUri: string, coverImageUri: string, whitelist?: string) => {
|
||||||
if (!wallet.initialized) throw new Error('Wallet not connected')
|
if (!wallet.initialized) throw new Error('Wallet not connected')
|
||||||
if (!vendingMinterContract) throw new Error('Contract not found')
|
if (!vendingFactoryContract) throw new Error('Contract not found')
|
||||||
|
|
||||||
let royaltyInfo = null
|
let royaltyInfo = null
|
||||||
if (royaltyDetails?.royaltyType === 'new') {
|
if (royaltyDetails?.royaltyType === 'new') {
|
||||||
@ -378,19 +381,68 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload: DispatchExecuteArgs = {
|
const payload: VendingFactoryDispatchExecuteArgs = {
|
||||||
contract: VENDING_FACTORY_ADDRESS,
|
contract: VENDING_FACTORY_ADDRESS,
|
||||||
messages,
|
messages: vendingFactoryMessages,
|
||||||
txSigner: wallet.address,
|
txSigner: wallet.address,
|
||||||
msg,
|
msg,
|
||||||
funds: [coin('1000000000', 'ustars')],
|
funds: [coin('1000000000', 'ustars')],
|
||||||
}
|
}
|
||||||
const data = await dispatchExecute(payload)
|
const data = await vendingFactoryDispatchExecute(payload)
|
||||||
setTransactionHash(data.transactionHash)
|
setTransactionHash(data.transactionHash)
|
||||||
setVendingMinterContractAddress(data.vendingMinterAddress)
|
setVendingMinterContractAddress(data.vendingMinterAddress)
|
||||||
setSg721ContractAddress(data.sg721Address)
|
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')
|
||||||
|
|
||||||
|
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')],
|
||||||
|
}
|
||||||
|
const data = await baseFactoryDispatchExecute(payload)
|
||||||
|
setTransactionHash(data.transactionHash)
|
||||||
|
setVendingMinterContractAddress(data.baseMinterAddress)
|
||||||
|
setSg721ContractAddress(data.sg721Address)
|
||||||
|
}
|
||||||
|
|
||||||
const uploadFiles = async (): Promise<string> => {
|
const uploadFiles = async (): Promise<string> => {
|
||||||
if (!uploadDetails) throw new Error('Please upload asset and metadata')
|
if (!uploadDetails) throw new Error('Please upload asset and metadata')
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@ -459,6 +511,9 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
if (!uploadDetails) {
|
if (!uploadDetails) {
|
||||||
throw new Error('Please select assets and metadata')
|
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) {
|
if (uploadDetails.uploadMethod === 'new' && uploadDetails.assetFiles.length === 0) {
|
||||||
throw new Error('Please select the assets')
|
throw new Error('Please select the assets')
|
||||||
}
|
}
|
||||||
@ -478,6 +533,9 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
if (uploadDetails.uploadMethod === 'existing' && !uploadDetails.baseTokenURI?.includes('ipfs://')) {
|
if (uploadDetails.uploadMethod === 'existing' && !uploadDetails.baseTokenURI?.includes('ipfs://')) {
|
||||||
throw new Error('Please specify a valid base token URI')
|
throw new Error('Please specify a valid base token URI')
|
||||||
}
|
}
|
||||||
|
if (minterDetails?.minterAcquisitionMethod === 'existing' && !minterDetails.existingMinter) {
|
||||||
|
throw new Error('Please specify a valid Base Minter contract address')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkCollectionDetails = () => {
|
const checkCollectionDetails = () => {
|
||||||
@ -565,12 +623,27 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
setCoverImageUrl(uploadDetails?.imageUrl as string)
|
setCoverImageUrl(uploadDetails?.imageUrl as string)
|
||||||
}, [uploadDetails?.baseTokenURI, uploadDetails?.imageUrl])
|
}, [uploadDetails?.baseTokenURI, uploadDetails?.imageUrl])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
resetReadyFlags()
|
||||||
|
setVendingMinterContractAddress(null)
|
||||||
|
}, [minterType, minterDetails?.minterAcquisitionMethod])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<NextSeo title="Create Collection" />
|
<NextSeo
|
||||||
|
title={
|
||||||
|
minterType === 'base' && minterDetails?.minterAcquisitionMethod === 'existing'
|
||||||
|
? 'Mint Token'
|
||||||
|
: 'Create Collection'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="mt-5 space-y-5 text-center">
|
<div className="mt-5 space-y-5 text-center">
|
||||||
<h1 className="font-heading text-4xl font-bold">Create Collection</h1>
|
<h1 className="font-heading text-4xl font-bold">
|
||||||
|
{minterType === 'base' && minterDetails?.minterAcquisitionMethod === 'existing'
|
||||||
|
? 'Mint Token'
|
||||||
|
: 'Create Collection'}
|
||||||
|
</h1>
|
||||||
|
|
||||||
<Conditional test={uploading}>
|
<Conditional test={uploading}>
|
||||||
<LoadingModal />
|
<LoadingModal />
|
||||||
@ -689,7 +762,10 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="p-4 w-full h-full text-left bg-transparent"
|
className="p-4 w-full h-full text-left bg-transparent"
|
||||||
onClick={() => setMinterType('vending')}
|
onClick={() => {
|
||||||
|
setMinterType('vending')
|
||||||
|
resetReadyFlags()
|
||||||
|
}}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<h4 className="font-bold">Vending Minter</h4>
|
<h4 className="font-bold">Vending Minter</h4>
|
||||||
@ -708,7 +784,10 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="p-4 w-full h-full text-left bg-transparent"
|
className="p-4 w-full h-full text-left bg-transparent"
|
||||||
onClick={() => setMinterType('base')}
|
onClick={() => {
|
||||||
|
setMinterType('base')
|
||||||
|
resetReadyFlags()
|
||||||
|
}}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<h4 className="font-bold">Base Minter</h4>
|
<h4 className="font-bold">Base Minter</h4>
|
||||||
@ -725,7 +804,11 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="mx-10">
|
<div className="mx-10">
|
||||||
<UploadDetails onChange={setUploadDetails} />
|
<UploadDetails
|
||||||
|
minterAcquisitionMethod={minterDetails?.minterAcquisitionMethod}
|
||||||
|
minterType={minterType}
|
||||||
|
onChange={setUploadDetails}
|
||||||
|
/>
|
||||||
|
|
||||||
<Conditional
|
<Conditional
|
||||||
test={minterType === 'vending' || (minterType === 'base' && minterDetails?.minterAcquisitionMethod === 'new')}
|
test={minterType === 'vending' || (minterType === 'base' && minterDetails?.minterAcquisitionMethod === 'new')}
|
||||||
@ -794,7 +877,7 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
onClick={performBaseMinterChecks}
|
onClick={performBaseMinterChecks}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
>
|
>
|
||||||
Create BM Collection
|
Create Collection
|
||||||
</Button>
|
</Button>
|
||||||
</Conditional>
|
</Conditional>
|
||||||
<Conditional test={minterType === 'base' && minterDetails?.minterAcquisitionMethod === 'existing'}>
|
<Conditional test={minterType === 'base' && minterDetails?.minterAcquisitionMethod === 'existing'}>
|
||||||
|
Loading…
Reference in New Issue
Block a user