diff --git a/.env.example b/.env.example index 2677607..e7ce350 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,4 @@ -APP_VERSION=0.3.8 +APP_VERSION=0.3.9 NEXT_PUBLIC_PINATA_ENDPOINT_URL=https://api.pinata.cloud/pinning/pinFileToIPFS NEXT_PUBLIC_SG721_CODE_ID=274 diff --git a/components/collections/creation/UploadDetails.tsx b/components/collections/creation/UploadDetails.tsx index 44ce386..857260b 100644 --- a/components/collections/creation/UploadDetails.tsx +++ b/components/collections/creation/UploadDetails.tsx @@ -1,4 +1,5 @@ /* eslint-disable eslint-comments/disable-enable-pair */ +/* eslint-disable no-nested-ternary */ /* eslint-disable no-misleading-character-class */ /* eslint-disable no-control-regex */ /* eslint-disable @typescript-eslint/no-loop-func */ @@ -50,7 +51,6 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho const [metadataFileArrayIndex, setMetadataFileArrayIndex] = useState(0) const [refreshMetadata, setRefreshMetadata] = useState(false) - //let baseMinterMetadataFile: File | undefined const [baseMinterMetadataFile, setBaseMinterMetadataFile] = useState() const assetFilesRef = useRef(null) @@ -98,7 +98,7 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho setAssetFilesArray([]) setMetadataFilesArray([]) if (event.target.files === null) return - if (minterType === 'vending') { + if (minterType === 'vending' || (minterType === 'base' && event.target.files.length > 1)) { //sort the files const sortedFiles = Array.from(event.target.files).sort((a, b) => naturalCompare(a.name, b.name)) //check if the sorted file names are in numerical order @@ -137,11 +137,14 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho const selectMetadata = (event: ChangeEvent) => { setMetadataFilesArray([]) if (event.target.files === null) return toast.error('No files selected.') - if (minterType === 'vending' && event.target.files.length !== assetFilesArray.length) { + if ( + (minterType === 'vending' || (minterType === 'base' && assetFilesArray.length > 1)) && + event.target.files.length !== assetFilesArray.length + ) { event.target.value = '' return toast.error('The number of metadata files should be equal to the number of asset files.') } - if (minterType === 'vending') { + if (minterType === 'vending' || (minterType === 'base' && assetFilesArray.length > 1)) { //sort the files const sortedFiles = Array.from(event.target.files).sort((a, b) => naturalCompare(a.name, b.name)) //check if the sorted file names are in numerical order @@ -149,7 +152,6 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho for (let i = 0; i < sortedFileNames.length; i++) { if (isNaN(Number(sortedFileNames[i])) || parseInt(sortedFileNames[i]) !== i + 1) { toast.error('The file names should be in numerical order starting from 1.') - //clear the input event.target.value = '' return } @@ -445,7 +447,7 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho 'before:absolute before:inset-0 before:hover:bg-white/5 before:transition', )} id="assetFiles" - multiple={minterType === 'vending'} + multiple onChange={selectAssets} ref={assetFilesRef} type="file" @@ -459,7 +461,11 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho className="block mt-5 mr-1 mb-1 ml-8 w-full font-bold text-white dark:text-gray-300" htmlFor="metadataFiles" > - {minterType === 'vending' ? 'Metadata Selection' : 'Metadata Selection (optional)'} + {minterType === 'vending' + ? 'Metadata Selection' + : assetFilesArray.length === 1 + ? 'Metadata Selection (optional)' + : 'Metadata Selection'}
)} - + 1}> 0 && minterType === 'base'}> - + + + + 1}> + + - 0}> + Promise updateStartTradingTime: (senderAddress: string, time?: Timestamp) => Promise + batchMint: (senderAddress: string, recipient: string, batchCount: number) => Promise } export interface BaseMinterMessages { mint: (tokenUri: string) => MintMessage updateStartTradingTime: (time: Timestamp) => UpdateStartTradingTimeMessage + batchMint: (recipient: string, batchNumber: number) => CustomMessage } export interface MintMessage { @@ -176,12 +180,52 @@ export const baseMinter = (client: SigningCosmWasmClient, txSigner: string): Bas return res.transactionHash } + const batchMint = async (senderAddress: string, baseUri: string, batchCount: number): Promise => { + const factoryParameters = await toast.promise(getFactoryParameters(), { + loading: 'Querying Factory Parameters...', + error: 'Querying Factory Parameters failed!', + success: 'Query successful! Minting...', + }) + console.log(factoryParameters.params.mint_fee_bps) + + const price = (await getConfig()).config?.mint_price.amount + if (!price) { + throw new Error( + 'Unable to retrieve a valid mint price. It may be that the given contract address does not belong to a Base Minter contract.', + ) + } + console.log((Number(price) * Number(factoryParameters.params.mint_fee_bps)) / 100) + + const executeContractMsgs: MsgExecuteContractEncodeObject[] = [] + for (let i = 0; i < batchCount; i++) { + const msg = { + mint: { token_uri: `${baseUri}/${i + 1}` }, + } + const executeContractMsg: MsgExecuteContractEncodeObject = { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: senderAddress, + contract: contractAddress, + msg: toUtf8(JSON.stringify(msg)), + funds: [coin((Number(price) * Number(factoryParameters.params.mint_fee_bps)) / 100 / 100, 'ustars')], + }), + } + + executeContractMsgs.push(executeContractMsg) + } + + const res = await client.signAndBroadcast(senderAddress, executeContractMsgs, 'auto', 'batch mint') + + return res.transactionHash + } + return { contractAddress, getConfig, getStatus, mint, updateStartTradingTime, + batchMint, } } @@ -237,9 +281,23 @@ export const baseMinter = (client: SigningCosmWasmClient, txSigner: string): Bas } } + const batchMint = (baseUri: string, batchCount: number): CustomMessage => { + const msg: Record[] = [] + for (let i = 0; i < batchCount; i++) { + msg.push({ mint: { token_uri: `${baseUri}/${i + 1}` } }) + } + return { + sender: txSigner, + contract: contractAddress, + msg, + funds: [], + } + } + return { mint, updateStartTradingTime, + batchMint, } } diff --git a/pages/collections/create.tsx b/pages/collections/create.tsx index 0be7ee8..b9de7f0 100644 --- a/pages/collections/create.tsx +++ b/pages/collections/create.tsx @@ -244,22 +244,28 @@ const CollectionCreationPage: NextPage = () => { ) setUploading(false) - - setBaseTokenUri( - `${baseUri}/${(uploadDetails.baseMinterMetadataFile as File).name.substring( - 0, - (uploadDetails.baseMinterMetadataFile as File).name.lastIndexOf('.'), - )}`, - ) + if (uploadDetails.assetFiles.length === 1) { + setBaseTokenUri( + `${baseUri}/${(uploadDetails.baseMinterMetadataFile as File).name.substring( + 0, + (uploadDetails.baseMinterMetadataFile as File).name.lastIndexOf('.'), + )}`, + ) + } else { + setBaseTokenUri(baseUri) + } setCoverImageUrl(coverImageUri) - - await instantiateBaseMinter( - `ipfs://${baseUri}/${(uploadDetails.baseMinterMetadataFile as File).name.substring( - 0, - (uploadDetails.baseMinterMetadataFile as File).name.lastIndexOf('.'), - )}`, - coverImageUri, - ) + 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) + } } else { setBaseTokenUri(uploadDetails?.baseTokenURI as string) setCoverImageUrl(uploadDetails?.imageUrl as string) @@ -292,27 +298,35 @@ const CollectionCreationPage: NextPage = () => { await uploadFiles() .then(async (baseUri) => { setUploading(false) - setBaseTokenUri( - `${baseUri}/${(uploadDetails.baseMinterMetadataFile as File).name.substring( - 0, - (uploadDetails.baseMinterMetadataFile as File).name.lastIndexOf('.'), - )}`, - ) - const result = await baseMinterContract - .use(baseMinterDetails?.existingBaseMinter as string) - - ?.mint( - wallet.address, - `ipfs://${baseUri}/${(uploadDetails.baseMinterMetadataFile as File).name.substring( + if (uploadDetails.assetFiles.length === 1) { + setBaseTokenUri( + `${baseUri}/${(uploadDetails.baseMinterMetadataFile as File).name.substring( 0, (uploadDetails.baseMinterMetadataFile as File).name.lastIndexOf('.'), )}`, ) + const result = await baseMinterContract + .use(baseMinterDetails?.existingBaseMinter as string) + + ?.mint( + wallet.address, + `ipfs://${baseUri}/${(uploadDetails.baseMinterMetadataFile as File).name.substring( + 0, + (uploadDetails.baseMinterMetadataFile as File).name.lastIndexOf('.'), + )}`, + ) + console.log(result) + return result + } + setBaseTokenUri(baseUri) + const result = await baseMinterContract + .use(baseMinterDetails?.existingBaseMinter as string) + ?.batchMint(wallet.address, `ipfs://${baseUri}`, uploadDetails.assetFiles.length) console.log(result) return result }) .then((result) => { - toast.success(`Token minted & appended to the collection successfully! Tx Hash: ${result}`, { + toast.success(`Token(s) minted & appended to the collection successfully! Tx Hash: ${result}`, { style: { maxWidth: 'none' }, duration: 5000, }) @@ -482,28 +496,51 @@ const CollectionCreationPage: NextPage = () => { setTransactionHash(data.transactionHash) setVendingMinterContractAddress(data.baseMinterAddress) setSg721ContractAddress(data.sg721Address) - await toast - .promise( - baseMinterContract - .use(data.baseMinterAddress) - - ?.mint(wallet.address, baseUri) as Promise, - { - loading: 'Minting token...', - success: (result) => { - setIsMintingComplete(true) - return `Token minted successfully! Tx Hash: ${result}` + if (uploadDetails?.assetFiles.length === 1) { + await toast + .promise( + baseMinterContract.use(data.baseMinterAddress)?.mint(wallet.address, baseUri) as Promise, + { + loading: 'Minting token...', + success: (result) => { + setIsMintingComplete(true) + return `Token minted successfully! Tx Hash: ${result}` + }, + error: (error) => `Failed to mint token: ${error.message}`, }, - error: (error) => `Failed to mint token: ${error.message}`, - }, - { style: { maxWidth: 'none' } }, - ) - .catch((error) => { - toast.error(error.message, { style: { maxWidth: 'none' } }) - setUploading(false) - setIsMintingComplete(false) - setCreatingCollection(false) - }) + { 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, + { + 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) + }) + } setUploading(false) setCreatingCollection(false) }) @@ -526,7 +563,7 @@ const CollectionCreationPage: NextPage = () => { uploadDetails.pinataSecretKey as string, ) .then((assetUri: string) => { - if (minterType === 'vending') { + if (minterType === 'vending' || (minterType === 'base' && uploadDetails.assetFiles.length > 1)) { const fileArray: File[] = [] let reader: FileReader @@ -577,7 +614,7 @@ const CollectionCreationPage: NextPage = () => { } reader.readAsText(uploadDetails.metadataFiles[i], 'utf8') } - } else if (minterType === 'base') { + } else if (minterType === 'base' && uploadDetails.assetFiles.length === 1) { const fileArray: File[] = [] const reader: FileReader = new FileReader() @@ -636,9 +673,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 (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') } @@ -1091,7 +1128,7 @@ const CollectionCreationPage: NextPage = () => { onClick={performUploadAndMintChecks} variant="solid" > - Mint & Append Token + Mint & Append Token(s)