diff --git a/components/AssetsPreview.tsx b/components/AssetsPreview.tsx
index 4056657..d0ba432 100644
--- a/components/AssetsPreview.tsx
+++ b/components/AssetsPreview.tsx
@@ -2,14 +2,18 @@ import clsx from 'clsx'
import { useCallback, useMemo, useState } from 'react'
import { getAssetType } from 'utils/getAssetType'
+import type { MinterType } from './collections/actions/Combobox'
+import { Conditional } from './Conditional'
+
interface AssetsPreviewProps {
assetFilesArray: File[]
updateMetadataFileIndex: (index: number) => void
+ minterType: MinterType
}
const ITEM_NUMBER = 12
-export const AssetsPreview = ({ assetFilesArray, updateMetadataFileIndex }: AssetsPreviewProps) => {
+export const AssetsPreview = ({ assetFilesArray, updateMetadataFileIndex, minterType }: AssetsPreviewProps) => {
const [page, setPage] = useState(1)
const totalPages = useMemo(() => Math.ceil(assetFilesArray.length / ITEM_NUMBER), [assetFilesArray])
@@ -116,23 +120,25 @@ export const AssetsPreview = ({ assetFilesArray, updateMetadataFileIndex }: Asse
return (
{renderImages()}
-
-
- ««
-
-
- «
-
-
- Page {page}/{totalPages}
-
-
- »
-
-
- »»
-
-
+
+
+
+ ««
+
+
+ «
+
+
+ Page {page}/{totalPages}
+
+
+ »
+
+
+ »»
+
+
+
)
}
diff --git a/components/ConfirmationModal.tsx b/components/ConfirmationModal.tsx
index 82ceb70..edaf792 100644
--- a/components/ConfirmationModal.tsx
+++ b/components/ConfirmationModal.tsx
@@ -40,7 +40,7 @@ export const ConfirmationModal = (props: ConfirmationModalProps) => {
/>
- 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?
diff --git a/components/collections/creation/MinterDetails.tsx b/components/collections/creation/MinterDetails.tsx
index 3805085..1a86503 100644
--- a/components/collections/creation/MinterDetails.tsx
+++ b/components/collections/creation/MinterDetails.tsx
@@ -35,7 +35,7 @@ export const MinterDetails = ({ onChange, minterType }: MinterDetailsProps) => {
const wallet = useWallet()
const [myBaseMinterContracts, setMyBaseMinterContracts] = useState([])
- const [minterAcquisitionMethod, setMinterAcquisitionMethod] = useState('existing')
+ const [minterAcquisitionMethod, setMinterAcquisitionMethod] = useState('new')
const existingMinterState = useInputState({
id: 'existingMinter',
@@ -79,7 +79,7 @@ export const MinterDetails = ({ onChange, minterType }: MinterDetailsProps) => {
minterContracts.map(async (minterContract: any) => {
await getMinterContractType(minterContract.minter)
.then((contractType) => {
- if (contractType?.includes('sg-minter')) {
+ if (contractType?.includes('sg-base-minter')) {
setMyBaseMinterContracts((prevState) => [...prevState, minterContract])
}
})
@@ -181,12 +181,12 @@ export const MinterDetails = ({ onChange, minterType }: MinterDetailsProps) => {
{
- existingMinterState.onChange(e.target.value.slice(e.target.value.indexOf('-') + 2))
+ existingMinterState.onChange(e.target.value.slice(e.target.value.indexOf('stars1')))
e.preventDefault()
}}
>
- Select a Base Minter Contract
+ Select one of your existing Base Minter Contracts
{renderMinterContracts()}
diff --git a/components/collections/creation/UploadDetails.tsx b/components/collections/creation/UploadDetails.tsx
index 9ee9261..97779dd 100644
--- a/components/collections/creation/UploadDetails.tsx
+++ b/components/collections/creation/UploadDetails.tsx
@@ -11,15 +11,20 @@ import { TextInput } from 'components/forms/FormInput'
import { useInputState } from 'components/forms/FormInput.hooks'
import { MetadataModal } from 'components/MetadataModal'
import type { ChangeEvent } from 'react'
-import { useEffect, useState } from 'react'
+import { useEffect, useRef, useState } from 'react'
import { toast } from 'react-hot-toast'
import type { UploadServiceType } from 'services/upload'
import { naturalCompare } from 'utils/sort'
+import type { MinterType } from '../actions/Combobox'
+import type { MinterAcquisitionMethod } from './MinterDetails'
+
export type UploadMethod = 'new' | 'existing'
interface UploadDetailsProps {
onChange: (value: UploadDetailsDataProps) => void
+ minterType: MinterType
+ minterAcquisitionMethod?: MinterAcquisitionMethod
}
export interface UploadDetailsDataProps {
@@ -34,7 +39,7 @@ export interface UploadDetailsDataProps {
imageUrl?: string
}
-export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
+export const UploadDetails = ({ onChange, minterType, minterAcquisitionMethod }: UploadDetailsProps) => {
const [assetFilesArray, setAssetFilesArray] = useState([])
const [metadataFilesArray, setMetadataFilesArray] = useState([])
const [uploadMethod, setUploadMethod] = useState('new')
@@ -42,6 +47,9 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
const [metadataFileArrayIndex, setMetadataFileArrayIndex] = useState(0)
const [refreshMetadata, setRefreshMetadata] = useState(false)
+ const assetFilesRef = useRef(null)
+ const metadataFilesRef = useRef(null)
+
const nftStorageApiKeyState = useInputState({
id: 'nft-storage-api-key',
name: 'nftStorageApiKey',
@@ -67,7 +75,7 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
const baseTokenUriState = useInputState({
id: 'baseTokenUri',
name: 'baseTokenUri',
- title: 'Base Token URI',
+ title: minterType === 'vending' ? 'Base Token URI' : 'Token URI',
placeholder: 'ipfs://',
defaultValue: '',
})
@@ -226,11 +234,13 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
])
useEffect(() => {
+ if (assetFilesRef.current) assetFilesRef.current.value = ''
+ if (assetFilesRef.current) assetFilesRef.current.value = ''
setAssetFilesArray([])
setMetadataFilesArray([])
baseTokenUriState.onChange('')
coverImageUrlState.onChange('')
- }, [uploadMethod])
+ }, [uploadMethod, minterType, minterAcquisitionMethod])
return (
@@ -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"
htmlFor="inlineRadio2"
>
- Upload assets & metadata
+ {minterType === 'base' ? 'Upload asset & metadata' : 'Upload assets & metadata'}
@@ -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"
htmlFor="inlineRadio1"
>
- Use an existing base URI
+ {minterType === 'base' ? 'Use an existing Token URI' : 'Use an existing base URI'}
-
+
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) => {
+
+
+
+
+
+
+
+
+
+
+ Though Stargaze's sg721 contract allows for off-chain metadata storage, it is recommended to use a
+ decentralized storage solution, such as IPFS. You may head over to{' '}
+
+ NFT.Storage
+ {' '}
+ or{' '}
+
+ Pinata
+ {' '}
+ and upload your asset & metadata manually to get a URI for your token before minting.
+
-
+
+
+
+
+
+
@@ -390,8 +426,9 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
'before:absolute before:inset-0 before:hover:bg-white/5 before:transition',
)}
id="assetFiles"
- multiple
+ multiple={minterType === 'vending'}
onChange={selectAssets}
+ ref={assetFilesRef}
type="file"
/>
@@ -418,8 +455,9 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
'before:absolute before:inset-0 before:hover:bg-white/5 before:transition',
)}
id="metadataFiles"
- multiple
+ multiple={minterType === 'vending'}
onChange={selectMetadata}
+ ref={metadataFilesRef}
type="file"
/>
@@ -435,7 +473,11 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
0}>
-
+
diff --git a/pages/collections/create.tsx b/pages/collections/create.tsx
index 07f3c8f..d6c31bb 100644
--- a/pages/collections/create.tsx
+++ b/pages/collections/create.tsx
@@ -26,8 +26,10 @@ import { Conditional } from 'components/Conditional'
import { LoadingModal } from 'components/LoadingModal'
import { useContracts } from 'contexts/contracts'
import { useWallet } from 'contexts/wallet'
-import type { DispatchExecuteArgs } from 'contracts/vendingFactory/messages/execute'
-import { dispatchExecute } from 'contracts/vendingFactory/messages/execute'
+import type { DispatchExecuteArgs as BaseFactoryDispatchExecuteArgs } from 'contracts/baseFactory/messages/execute'
+import { dispatchExecute as baseFactoryDispatchExecute } from 'contracts/baseFactory/messages/execute'
+import type { DispatchExecuteArgs as VendingFactoryDispatchExecuteArgs } from 'contracts/vendingFactory/messages/execute'
+import { dispatchExecute as vendingFactoryDispatchExecute } from 'contracts/vendingFactory/messages/execute'
import type { NextPage } from 'next'
import { NextSeo } from 'next-seo'
import { useEffect, useMemo, useRef, useState } from 'react'
@@ -35,6 +37,7 @@ import { toast } from 'react-hot-toast'
import { upload } from 'services/upload'
import { compareFileArrays } from 'utils/compareFileArrays'
import {
+ BASE_FACTORY_ADDRESS,
BLOCK_EXPLORER_URL,
NETWORK,
SG721_CODE_ID,
@@ -53,17 +56,24 @@ import { getAssetType } from '../../utils/getAssetType'
const CollectionCreationPage: NextPage = () => {
const wallet = useWallet()
const {
+ baseMinter: baseMinterContract,
vendingMinter: vendingMinterContract,
whitelist: whitelistContract,
vendingFactory: vendingFactoryContract,
+ baseFactory: baseFactoryContract,
} = useContracts()
const scrollRef = useRef(null)
- const messages = useMemo(
+ const vendingFactoryMessages = useMemo(
() => vendingFactoryContract?.use(VENDING_FACTORY_ADDRESS),
[vendingFactoryContract, wallet.address],
)
+ const baseFactoryMessages = useMemo(
+ () => baseFactoryContract?.use(BASE_FACTORY_ADDRESS),
+ [baseFactoryContract, wallet.address],
+ )
+
const [uploadDetails, setUploadDetails] = useState(null)
const [collectionDetails, setCollectionDetails] = useState(null)
const [minterDetails, setMinterDetails] = useState(null)
@@ -109,9 +119,8 @@ const CollectionCreationPage: NextPage = () => {
try {
setReadyToCreateBm(false)
checkUploadDetails()
- checkCollectionDetails()
- checkMintingDetails()
checkRoyaltyDetails()
+ checkCollectionDetails()
checkWhitelistDetails()
.then(() => {
setReadyToCreateBm(true)
@@ -130,9 +139,6 @@ const CollectionCreationPage: NextPage = () => {
try {
setReadyToUploadAndMint(false)
checkUploadDetails()
- checkCollectionDetails()
- checkMintingDetails()
- checkRoyaltyDetails()
checkWhitelistDetails()
.then(() => {
setReadyToUploadAndMint(true)
@@ -147,6 +153,12 @@ const CollectionCreationPage: NextPage = () => {
}
}
+ const resetReadyFlags = () => {
+ setReadyToCreateVm(false)
+ setReadyToCreateBm(false)
+ setReadyToUploadAndMint(false)
+ }
+
const createVendingMinterCollection = async () => {
try {
setCreatingCollection(true)
@@ -180,7 +192,7 @@ const CollectionCreationPage: NextPage = () => {
else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist()
setWhitelistContractAddress(whitelist as string)
- await instantiate(baseUri, coverImageUri, whitelist)
+ await instantiateVendingMinter(baseUri, coverImageUri, whitelist)
} else {
setBaseTokenUri(uploadDetails?.baseTokenURI as string)
setCoverImageUrl(uploadDetails?.imageUrl as string)
@@ -190,7 +202,7 @@ const CollectionCreationPage: NextPage = () => {
else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist()
setWhitelistContractAddress(whitelist as string)
- await instantiate(baseTokenUri as string, coverImageUrl as string, whitelist)
+ await instantiateVendingMinter(baseTokenUri as string, coverImageUrl as string, whitelist)
}
setCreatingCollection(false)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -229,22 +241,12 @@ const CollectionCreationPage: NextPage = () => {
setBaseTokenUri(baseUri)
setCoverImageUrl(coverImageUri)
- let whitelist: string | undefined
- if (whitelistDetails?.whitelistType === 'existing') whitelist = whitelistDetails.contractAddress
- else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist()
- setWhitelistContractAddress(whitelist as string)
-
- await instantiate(baseUri, coverImageUri, whitelist)
+ await instantiateBaseMinter(baseUri, coverImageUri)
} else {
setBaseTokenUri(uploadDetails?.baseTokenURI as string)
setCoverImageUrl(uploadDetails?.imageUrl as string)
- let whitelist: string | undefined
- if (whitelistDetails?.whitelistType === 'existing') whitelist = whitelistDetails.contractAddress
- else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist()
- setWhitelistContractAddress(whitelist as string)
-
- await instantiate(baseTokenUri as string, coverImageUrl as string, whitelist)
+ await instantiateBaseMinter(uploadDetails?.baseTokenURI as string, uploadDetails?.imageUrl as string)
}
setCreatingCollection(false)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -257,48 +259,49 @@ const CollectionCreationPage: NextPage = () => {
const uploadAndMint = async () => {
try {
+ if (!wallet.initialized) throw new Error('Wallet not connected')
+ if (!baseMinterContract) throw new Error('Contract not found')
setCreatingCollection(true)
setBaseTokenUri(null)
setCoverImageUrl(null)
setVendingMinterContractAddress(null)
setSg721ContractAddress(null)
- setWhitelistContractAddress(null)
setTransactionHash(null)
+
if (uploadDetails?.uploadMethod === 'new') {
setUploading(true)
-
- const baseUri = await uploadFiles()
- //upload coverImageUri and append the file name
- const coverImageUri = await upload(
- collectionDetails?.imageFile as File[],
- uploadDetails.uploadService,
- 'cover',
- uploadDetails.nftStorageApiKey as string,
- uploadDetails.pinataApiKey as string,
- uploadDetails.pinataSecretKey as string,
- )
-
- setUploading(false)
-
- setBaseTokenUri(baseUri)
- setCoverImageUrl(coverImageUri)
-
- let whitelist: string | undefined
- if (whitelistDetails?.whitelistType === 'existing') whitelist = whitelistDetails.contractAddress
- else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist()
- setWhitelistContractAddress(whitelist as string)
-
- await instantiate(baseUri, coverImageUri, whitelist)
+ await uploadFiles()
+ .then(async (baseUri) => {
+ setUploading(false)
+ setBaseTokenUri(baseUri)
+ const result = await baseMinterContract
+ .use(minterDetails?.existingMinter as string)
+ ?.mint(wallet.address, `ipfs://${baseUri}`)
+ console.log(result)
+ return result
+ })
+ .then((result) => {
+ toast.success(`Minted successfully! Tx Hash: ${result}`, { style: { maxWidth: 'none' }, duration: 5000 })
+ })
+ .catch((error) => {
+ toast.error(error.message, { style: { maxWidth: 'none' } })
+ setUploading(false)
+ setCreatingCollection(false)
+ })
} else {
setBaseTokenUri(uploadDetails?.baseTokenURI as string)
- setCoverImageUrl(uploadDetails?.imageUrl as string)
-
- let whitelist: string | undefined
- if (whitelistDetails?.whitelistType === 'existing') whitelist = whitelistDetails.contractAddress
- else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist()
- setWhitelistContractAddress(whitelist as string)
-
- await instantiate(baseTokenUri as string, coverImageUrl as string, whitelist)
+ setUploading(false)
+ await baseMinterContract
+ .use(minterDetails?.existingMinter as string)
+ ?.mint(wallet.address, `ipfs://${uploadDetails?.baseTokenURI}`)
+ .then((result) => {
+ toast.success(`Minted successfully! Tx Hash: ${result}`, { style: { maxWidth: 'none' }, duration: 5000 })
+ })
+ .catch((error) => {
+ toast.error(error.message, { style: { maxWidth: 'none' } })
+ setUploading(false)
+ setCreatingCollection(false)
+ })
}
setCreatingCollection(false)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -332,9 +335,9 @@ const CollectionCreationPage: NextPage = () => {
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 (!vendingMinterContract) throw new Error('Contract not found')
+ if (!vendingFactoryContract) throw new Error('Contract not found')
let royaltyInfo = null
if (royaltyDetails?.royaltyType === 'new') {
@@ -378,19 +381,68 @@ const CollectionCreationPage: NextPage = () => {
},
}
- const payload: DispatchExecuteArgs = {
+ const payload: VendingFactoryDispatchExecuteArgs = {
contract: VENDING_FACTORY_ADDRESS,
- messages,
+ messages: vendingFactoryMessages,
txSigner: wallet.address,
msg,
funds: [coin('1000000000', 'ustars')],
}
- const data = await dispatchExecute(payload)
+ const data = await vendingFactoryDispatchExecute(payload)
setTransactionHash(data.transactionHash)
setVendingMinterContractAddress(data.vendingMinterAddress)
setSg721ContractAddress(data.sg721Address)
}
+ const instantiateBaseMinter = async (baseUri: string, coverImageUri: string) => {
+ if (!wallet.initialized) throw new Error('Wallet not connected')
+ if (!baseFactoryContract) throw new Error('Contract not found')
+
+ 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 => {
if (!uploadDetails) throw new Error('Please upload asset and metadata')
return new Promise((resolve, reject) => {
@@ -459,6 +511,9 @@ const CollectionCreationPage: NextPage = () => {
if (!uploadDetails) {
throw new Error('Please select assets and metadata')
}
+ if (minterType === 'base' && uploadDetails.uploadMethod === 'new' && uploadDetails.assetFiles.length > 1) {
+ throw new Error('Base Minter can only mint one asset at a time. Please select only one asset.')
+ }
if (uploadDetails.uploadMethod === 'new' && uploadDetails.assetFiles.length === 0) {
throw new Error('Please select the assets')
}
@@ -478,6 +533,9 @@ const CollectionCreationPage: NextPage = () => {
if (uploadDetails.uploadMethod === 'existing' && !uploadDetails.baseTokenURI?.includes('ipfs://')) {
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 = () => {
@@ -565,12 +623,27 @@ const CollectionCreationPage: NextPage = () => {
setCoverImageUrl(uploadDetails?.imageUrl as string)
}, [uploadDetails?.baseTokenURI, uploadDetails?.imageUrl])
+ useEffect(() => {
+ resetReadyFlags()
+ setVendingMinterContractAddress(null)
+ }, [minterType, minterDetails?.minterAcquisitionMethod])
+
return (
-
+
-
Create Collection
+
+ {minterType === 'base' && minterDetails?.minterAcquisitionMethod === 'existing'
+ ? 'Mint Token'
+ : 'Create Collection'}
+
@@ -689,7 +762,10 @@ const CollectionCreationPage: NextPage = () => {
>
setMinterType('vending')}
+ onClick={() => {
+ setMinterType('vending')
+ resetReadyFlags()
+ }}
type="button"
>
Vending Minter
@@ -708,7 +784,10 @@ const CollectionCreationPage: NextPage = () => {
>
setMinterType('base')}
+ onClick={() => {
+ setMinterType('base')
+ resetReadyFlags()
+ }}
type="button"
>
Base Minter
@@ -725,7 +804,11 @@ const CollectionCreationPage: NextPage = () => {
)}
-
+
{
onClick={performBaseMinterChecks}
variant="solid"
>
- Create BM Collection
+ Create Collection