2022-08-03 07:25:08 +00:00
|
|
|
/* eslint-disable eslint-comments/disable-enable-pair */
|
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
|
|
|
|
2022-08-04 09:16:42 +00:00
|
|
|
import { coin } from '@cosmjs/proto-signing'
|
2022-07-27 06:49:36 +00:00
|
|
|
import Anchor from 'components/Anchor'
|
2022-08-03 07:25:08 +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'
|
|
|
|
import type { CollectionDetailsDataProps } from 'components/collections/creation/CollectionDetails'
|
|
|
|
import type { MintingDetailsDataProps } from 'components/collections/creation/MintingDetails'
|
|
|
|
import type { RoyaltyDetailsDataProps } from 'components/collections/creation/RoyaltyDetails'
|
|
|
|
import type { UploadDetailsDataProps } from 'components/collections/creation/UploadDetails'
|
|
|
|
import type { WhitelistDetailsDataProps } from 'components/collections/creation/WhitelistDetails'
|
|
|
|
import { useContracts } from 'contexts/contracts'
|
|
|
|
import { useWallet } from 'contexts/wallet'
|
2022-07-27 06:49:36 +00:00
|
|
|
import type { NextPage } from 'next'
|
|
|
|
import { NextSeo } from 'next-seo'
|
2022-08-03 07:25:08 +00:00
|
|
|
import { useState } from 'react'
|
2022-08-04 09:16:42 +00:00
|
|
|
import useCollapse from 'react-collapsed'
|
2022-08-03 07:25:08 +00:00
|
|
|
import { toast } from 'react-hot-toast'
|
|
|
|
import type { UploadServiceType } from 'services/upload'
|
|
|
|
import { upload } from 'services/upload'
|
2022-08-04 09:16:42 +00:00
|
|
|
import { MINTER_CODE_ID, SG721_CODE_ID, 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-08-04 09:16:42 +00:00
|
|
|
const CollectionCreationPage: NextPage = () => {
|
|
|
|
const wallet = useWallet()
|
|
|
|
const { minter: minterContract, whitelist: whitelistContract } = useContracts()
|
|
|
|
|
|
|
|
const { getCollapseProps, getToggleProps, isExpanded } = useCollapse()
|
|
|
|
const toggleProps = getToggleProps()
|
|
|
|
const collapseProps = getCollapseProps()
|
|
|
|
|
|
|
|
const [uploadDetails, setUploadDetails] = useState<UploadDetailsDataProps | null>(null)
|
|
|
|
const [collectionDetails, setCollectionDetails] = useState<CollectionDetailsDataProps | null>(null)
|
|
|
|
const [mintingDetails, setMintingDetails] = useState<MintingDetailsDataProps | null>(null)
|
|
|
|
const [whitelistDetails, setWhitelistDetails] = useState<WhitelistDetailsDataProps | null>(null)
|
|
|
|
const [royaltyDetails, setRoyaltyDetails] = useState<RoyaltyDetailsDataProps | null>(null)
|
|
|
|
|
|
|
|
const [contractAddress, setContractAddress] = useState<string | null>(null)
|
|
|
|
const [transactionHash, setTransactionHash] = useState<string | null>(null)
|
|
|
|
|
|
|
|
const createCollection = async () => {
|
|
|
|
try {
|
|
|
|
checkUploadDetails()
|
|
|
|
checkCollectionDetails()
|
|
|
|
checkMintingDetails()
|
|
|
|
checkWhitelistDetails()
|
|
|
|
checkRoyaltyDetails()
|
|
|
|
|
|
|
|
const baseUri = await uploadFiles()
|
|
|
|
const coverImageUri = await upload(
|
|
|
|
collectionDetails?.imageFile as File[],
|
|
|
|
uploadDetails?.uploadService as UploadServiceType,
|
|
|
|
'cover',
|
|
|
|
uploadDetails?.nftStorageApiKey as string,
|
|
|
|
uploadDetails?.pinataApiKey as string,
|
|
|
|
uploadDetails?.pinataSecretKey as string,
|
|
|
|
)
|
|
|
|
|
2022-08-05 11:13:27 +00:00
|
|
|
let whitelist: string | undefined
|
|
|
|
if (whitelistDetails?.whitelistType === 'existing') whitelist = whitelistDetails.contractAddress
|
|
|
|
else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist()
|
2022-08-04 09:16:42 +00:00
|
|
|
|
|
|
|
await instantate(baseUri, coverImageUri, whitelist)
|
2022-08-03 07:25:08 +00:00
|
|
|
|
2022-08-04 09:16:42 +00:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
|
|
} catch (error: any) {
|
|
|
|
toast.error(error.message)
|
|
|
|
}
|
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,
|
|
|
|
unit_price: coin(String(Number(whitelistDetails?.unitPrice) * 1000000), 'ustars'),
|
|
|
|
per_address_limit: whitelistDetails?.perAddressLimit,
|
|
|
|
member_limit: whitelistDetails?.memberLimit,
|
2022-08-03 07:25:08 +00:00
|
|
|
}
|
2022-08-04 09:16:42 +00:00
|
|
|
|
|
|
|
const data = await whitelistContract.instantiate(
|
|
|
|
WHITELIST_CODE_ID,
|
|
|
|
msg,
|
|
|
|
'Stargaze Whitelist Contract',
|
|
|
|
wallet.address,
|
|
|
|
)
|
|
|
|
|
|
|
|
return data.contractAddress
|
2022-08-03 07:25:08 +00:00
|
|
|
}
|
|
|
|
|
2022-08-04 09:16:42 +00:00
|
|
|
const instantate = async (baseUri: string, coverImageUri: string, whitelist?: string) => {
|
|
|
|
if (!wallet.initialized) throw new Error('Wallet not connected')
|
|
|
|
if (!minterContract) throw new Error('Contract not found')
|
|
|
|
|
|
|
|
let royaltyInfo = null
|
2022-08-05 11:13:27 +00:00
|
|
|
if (royaltyDetails?.royaltyType === 'new') {
|
2022-08-04 09:16:42 +00:00
|
|
|
royaltyInfo = {
|
|
|
|
paymentAddress: royaltyDetails.paymentAddress,
|
|
|
|
share: royaltyDetails.share,
|
2022-08-03 07:25:08 +00:00
|
|
|
}
|
|
|
|
}
|
2022-08-04 09:16:42 +00:00
|
|
|
|
|
|
|
const msg = {
|
|
|
|
base_token_uri: baseUri,
|
|
|
|
num_tokens: mintingDetails?.numTokens,
|
|
|
|
sg721_code_id: SG721_CODE_ID,
|
|
|
|
sg721_instantiate_msg: {
|
|
|
|
name: collectionDetails?.name,
|
|
|
|
symbol: 'SYMBOL',
|
|
|
|
minter: wallet.address,
|
|
|
|
collection_info: {
|
|
|
|
creator: wallet.address,
|
|
|
|
description: collectionDetails?.description,
|
|
|
|
image: coverImageUri,
|
|
|
|
external_link: collectionDetails?.externalLink,
|
|
|
|
royalty_info: royaltyInfo,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
per_address_limit: mintingDetails?.perAddressLimit,
|
|
|
|
unit_price: coin(String(Number(mintingDetails?.unitPrice) * 1000000), 'ustars'),
|
|
|
|
whitelist_address: whitelist,
|
|
|
|
start_time: mintingDetails?.startTime,
|
2022-08-03 07:25:08 +00:00
|
|
|
}
|
|
|
|
|
2022-08-04 09:16:42 +00:00
|
|
|
const data = await minterContract.instantiate(MINTER_CODE_ID, msg, 'Stargaze Minter Contract', wallet.address)
|
|
|
|
|
|
|
|
setTransactionHash(data.transactionHash)
|
|
|
|
setContractAddress(data.contractAddress)
|
|
|
|
}
|
|
|
|
|
|
|
|
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) => {
|
|
|
|
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)
|
|
|
|
data.image = `ipfs://${assetUri}/${uploadDetails.assetFiles[i].name}`
|
|
|
|
const metadataFileBlob = new Blob([JSON.stringify(data)], {
|
|
|
|
type: 'application/json',
|
|
|
|
})
|
|
|
|
const updatedMetadataFile = new File([metadataFileBlob], uploadDetails.metadataFiles[i].name, {
|
|
|
|
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')
|
|
|
|
}
|
2022-08-03 07:25:08 +00:00
|
|
|
})
|
2022-08-04 09:16:42 +00:00
|
|
|
.catch(reject)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
const checkUploadDetails = () => {
|
|
|
|
if (!uploadDetails) {
|
|
|
|
throw new Error('Please upload asset and metadata')
|
|
|
|
}
|
|
|
|
if (uploadDetails.assetFiles.length === 0) {
|
|
|
|
throw new Error('Please upload assets')
|
|
|
|
}
|
|
|
|
if (uploadDetails.metadataFiles.length === 0) {
|
|
|
|
throw new Error('Please upload metadatas')
|
|
|
|
}
|
|
|
|
if (uploadDetails.uploadService === 'nft-storage') {
|
|
|
|
if (uploadDetails.nftStorageApiKey === '') {
|
|
|
|
throw new Error('Please enter NFT Storage api key')
|
2022-08-03 07:25:08 +00:00
|
|
|
}
|
2022-08-04 09:16:42 +00:00
|
|
|
} else if (uploadDetails.pinataApiKey === '' || uploadDetails.pinataSecretKey === '') {
|
|
|
|
throw new Error('Please enter Pinata api key and secret key')
|
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')
|
|
|
|
if (collectionDetails.name === '') throw new Error('Name is required')
|
|
|
|
if (collectionDetails.description === '') throw new Error('Description is required')
|
|
|
|
if (collectionDetails.imageFile.length === 0) throw new Error('Cover image is required')
|
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')
|
|
|
|
if (Number(mintingDetails.unitPrice) < 500) throw new Error('Invalid unit price')
|
|
|
|
if (mintingDetails.perAddressLimit < 1 || mintingDetails.perAddressLimit > 50)
|
|
|
|
throw new Error('Per address limit is required')
|
|
|
|
if (mintingDetails.startTime === '') throw new Error('Start time is required')
|
|
|
|
}
|
|
|
|
|
|
|
|
const checkWhitelistDetails = () => {
|
|
|
|
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-08-04 09:16:42 +00:00
|
|
|
} else {
|
|
|
|
if (whitelistDetails.members?.length === 0) throw new Error('Whitelist member list cannot be empty')
|
|
|
|
if (whitelistDetails.unitPrice === '') throw new Error('Whitelist unit price is required')
|
|
|
|
if (whitelistDetails.startTime === '') throw new Error('Start time is required')
|
|
|
|
if (whitelistDetails.endTime === '') throw new Error('End time is required')
|
|
|
|
if (whitelistDetails.perAddressLimit === 0) throw new Error('Per address limit is required')
|
|
|
|
if (whitelistDetails.memberLimit === 0) throw new Error('Member limit is required')
|
|
|
|
}
|
2022-08-03 07:25:08 +00:00
|
|
|
}
|
|
|
|
|
2022-08-04 09:16:42 +00:00
|
|
|
const checkRoyaltyDetails = () => {
|
|
|
|
if (!royaltyDetails) throw new Error('Please fill out the royalty details')
|
2022-08-05 11:13:27 +00:00
|
|
|
if (royaltyDetails.royaltyType === 'new') {
|
|
|
|
if (royaltyDetails.share === 0) throw new Error('Royalty share is required')
|
|
|
|
if (royaltyDetails.paymentAddress === '') throw new Error('Royalty payment address is required')
|
|
|
|
}
|
2022-08-03 07:25:08 +00:00
|
|
|
}
|
|
|
|
|
2022-07-27 06:49:36 +00:00
|
|
|
return (
|
|
|
|
<div>
|
|
|
|
<NextSeo title="Create Collection" />
|
|
|
|
|
2022-08-04 11:19:25 +00:00
|
|
|
<div className="mt-5 space-y-5 text-center">
|
|
|
|
<h1 className="font-heading text-4xl font-bold">Create Collection</h1>
|
2022-07-27 06:49:36 +00:00
|
|
|
|
|
|
|
<p>
|
|
|
|
Make sure you check our{' '}
|
|
|
|
<Anchor className="font-bold text-plumbus hover:underline" href={links['Docs']}>
|
|
|
|
documentation
|
|
|
|
</Anchor>{' '}
|
|
|
|
on how to create your collection
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
|
2022-08-05 11:13:27 +00:00
|
|
|
<div className="mx-10">
|
|
|
|
<UploadDetails onChange={setUploadDetails} />
|
|
|
|
|
|
|
|
<div className="flex justify-between py-3 px-8 rounded border-2 border-white/20 grid-col-2">
|
|
|
|
<CollectionDetails onChange={setCollectionDetails} />
|
|
|
|
<MintingDetails onChange={setMintingDetails} />
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className="flex justify-between my-6">
|
|
|
|
<Button {...toggleProps} isWide type="button" variant="outline">
|
|
|
|
{isExpanded ? 'Hide' : 'Show'} Advanced Details
|
|
|
|
</Button>
|
|
|
|
<Button isWide onClick={createCollection} variant="solid">
|
|
|
|
Create Collection
|
|
|
|
</Button>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<section {...collapseProps} className="mb-10">
|
|
|
|
<WhitelistDetails onChange={setWhitelistDetails} />
|
|
|
|
<div className="my-6" />
|
|
|
|
<RoyaltyDetails onChange={setRoyaltyDetails} />
|
|
|
|
</section>
|
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 })
|