- Choose a collection to which you want to mint
+ Choose a chain and collection, then continue to upload your art files.
+
state.blockchain = value as typeof state.blockchain"
/>
@@ -241,8 +401,8 @@ function deleteNFT(nft?: NFT) {
help="Select the collection to mass mint"
>
state.collection = (value || '') as typeof state.collection"
/>
+
+
+
+ No collections found on this chain. Create one first, then return here to mass mint.
+
+
+ Create collection
+
+
+
-
+
-
-
- {{ progress.message }}
-
-
- {{ hasEnoughBalance ? 'Mint NFTs' : 'Not Enough Funds' }}
+ Back
+
+
+
+ {{ numOfValidNFTs }} items loaded
+
+
+ Next: Review
+
+
+
+
+
+
+
+
+
+
+ Review your batch
+
+
+ Check that all items have the correct names, descriptions, prices, and properties before minting.
+
+
+
+
+
+
+ Items
+
+
+ {{ numOfValidNFTs }}
+
+
+
+
+ Missing descriptions
+
+
+ {{ numMissingDescriptions }}
+
+
+
+
+ Missing prices
+
+
+ {{ numMissingPrices }}
+
+
+
+
+
+
+ You can still edit individual items from the overview table in the previous step.
+
+
+ Open detailed review
+
+
+
+
+
+
+
+ Estimated Cost · {{ numOfValidNFTs }} items
+
+
+
+
- ({{ numOfValidNFTs }})
+ {{ hasEnoughBalance ? 'Sufficient balance' : 'Not enough balance' }}
-
-
+
+
+
+
+
+
+ Item Deposit
+
+ × {{ numOfValidNFTs }}
+
+
+
+
+
+
+ Metadata Deposit
+
+ × {{ numOfValidNFTs }}
+
+
+
+
+
+
+ Attribute Deposit
+
+ × {{ totalAttribute }}
+
+
+
+
+
+ Estimated Transaction Fee
+
+
+
+ Total Deposit
+
+
+
+
+ Deposits are refundable when items are burned. Network fees are charged separately per transaction.
+
+
+
+
+
+
+
+ Back
+
+ Next: Mint
+
+
+
-
-
- Est. Cost:
-
+
+
+
+
+
+ Mint your NFTs
+
+
+ When you are ready, start the mass mint flow. You will be guided through a detailed review and transaction signing.
+
+
-
+
+
+
+
+ {{ progress.message }}
+
+
+ {{ hasEnoughBalance ? 'Mint NFTs' : 'Not Enough Funds' }}
+
+ ({{ numOfValidNFTs }})
+
+
+
+
+
+
+
+
+ Est. Cost
+
+
+
+
+ You will confirm this transaction in your wallet. Deposits are returned when items are removed from the chain.
+
+
-
+
+
+
+ Back
+
+
+ Step 5 of 5
+
+
+
diff --git a/app/components/massmint/MassMintUploadStep.vue b/app/components/massmint/MassMintUploadStep.vue
new file mode 100644
index 00000000..5aa0818d
--- /dev/null
+++ b/app/components/massmint/MassMintUploadStep.vue
@@ -0,0 +1,289 @@
+
+
+
+
+
+
+ Upload your art files. Drag and drop a ZIP or select files.
+
+
+
emit('mediaZipLoaded', payload)"
+ />
+
+
+
+
+
+ Uploaded files
+
+
+ Drag and drop to change the order of your files.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ index + 1 }}
+
+
+
+
+
+
+
+
+
+
+ {{ nft.file?.name || `File #${nft.id}` }}
+
+
+ File · {{ formatFileSize(nft.file) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ #{{ index + 1 }}
+
+
+
+
+ {{ nft.file?.name || `File #${nft.id}` }}
+
+
+
+ {{ formatFileSize(nft.file) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Step 2 of 5 · Upload media files to continue.
+
+
+ Next: Metadata
+
+
+
+
diff --git a/app/components/massmint/OnBoardingCard.vue b/app/components/massmint/OnBoardingCard.vue
deleted file mode 100644
index a16418f5..00000000
--- a/app/components/massmint/OnBoardingCard.vue
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
-
-
-
-
- {{ title }}
-
-
-
-
-
-
-
-
diff --git a/app/components/massmint/Onboarding.vue b/app/components/massmint/Onboarding.vue
deleted file mode 100644
index d203dbde..00000000
--- a/app/components/massmint/Onboarding.vue
+++ /dev/null
@@ -1,196 +0,0 @@
-
-
-
-
-
-
-
- {{ $t('massmint.onboarding.pageTitle') }}
-
-
-
-
-
-
-
-
-
-
-
-
- {{ $t('massmint.onboarding.cards.1.subtitle') }}:
-
-
- {{ $t('massmint.onboarding.cards.1.instructions') }}
-
-
-
-
-
- {{ $t('massmint.onboarding.cards.1.codeStructure') }}:
-
-
-
-
-
-
-
-
-
-
-
-
- {{ $t('massmint.onboarding.downloadTemplate') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/components/massmint/index.ts b/app/components/massmint/index.ts
index 95e77f66..4af68fd6 100644
--- a/app/components/massmint/index.ts
+++ b/app/components/massmint/index.ts
@@ -1,6 +1,4 @@
export { default as MassMint } from './MassMint.vue'
-export { default as OnBoarding } from './Onboarding.vue'
-export { default as OnBoardingCard } from './OnBoardingCard.vue'
export { default as OverviewTable } from './OverviewTable.vue'
export * from './types'
export { default as UploadPictures } from './upload/UploadCompressedMedia.vue'
diff --git a/app/components/massmint/upload/UploadCompressedMedia.vue b/app/components/massmint/upload/UploadCompressedMedia.vue
index b7b51141..77174c20 100644
--- a/app/components/massmint/upload/UploadCompressedMedia.vue
+++ b/app/components/massmint/upload/UploadCompressedMedia.vue
@@ -6,7 +6,7 @@ interface Props {
}
interface Emits {
- (e: 'zipLoaded', data: { validFiles: any[], areAllFilesValid: boolean }): void
+ (e: 'zipLoaded', data: { validFiles: any[] }): void
}
withDefaults(defineProps
(), {
@@ -43,7 +43,6 @@ async function onFileSelected(file?: File) {
const zipFilePath = URL.createObjectURL(blob)
const {
- allValid,
loading: loadingZip,
validFiles,
warnings,
@@ -64,7 +63,6 @@ async function onFileSelected(file?: File) {
showCheckmark.value = true
emit('zipLoaded', {
validFiles: validFiles.value,
- areAllFilesValid: allValid.value,
})
URL.revokeObjectURL(zipFilePath)
@@ -80,7 +78,6 @@ async function onFileSelected(file?: File) {
console.error('Invalid file type.')
emit('zipLoaded', {
validFiles: [],
- areAllFilesValid: false,
})
}
}
diff --git a/app/components/massmint/utils.ts b/app/components/massmint/utils.ts
new file mode 100644
index 00000000..33612c67
--- /dev/null
+++ b/app/components/massmint/utils.ts
@@ -0,0 +1,64 @@
+import type { NFT } from './types'
+
+export type TemplateFormat = 'json' | 'csv' | 'txt'
+
+export const attributesExample = [
+ { value: 'white', trait_type: 'color' },
+ { value: 'happy', trait_type: 'expression' },
+] as const
+
+/**
+ * Generate metadata template content for the given format and NFTs.
+ */
+export function generateTemplateContent(format: TemplateFormat, nfts: NFT[]): string {
+ if (format === 'json') {
+ const items = nfts.map((nft, index) => ({
+ file: nft.file?.name || '',
+ ...(index === 0
+ ? { name: 'Art #1', price: 1, description: 'Description for the Art #1', attributes: [...attributesExample] }
+ : { name: '', description: '', price: '', attributes: [] }
+ ),
+ }))
+ return JSON.stringify(items, null, 2)
+ }
+
+ if (format === 'csv') {
+ const header = 'file,name,description,attributes,price'
+ const attributesCsvExample = 'color:white;expression:happy'
+ const rows = nfts.map((nft, index) => {
+ const file = nft.file?.name || ''
+ if (index === 0) {
+ return `${file},Art #1,Description for the Art #1,${attributesCsvExample},1`
+ }
+ return `${file},,,,`
+ })
+ return [header, ...rows].join('\n')
+ }
+
+ // txt
+ const attributesTxtExample = '[{"value": "white","trait_type": "color"},{"value": "happy","trait_type": "expression"}]'
+ const blocks = nfts.map((nft, index) => {
+ const fileName = nft.file?.name || ''
+ const isFirst = index === 0
+ return [
+ `file: ${fileName}`,
+ isFirst ? 'name: Art #1' : 'name: ',
+ isFirst ? 'description: Description for the Art #1' : 'description: ',
+ isFirst ? `attributes: ${attributesTxtExample}` : 'attributes: ',
+ isFirst ? 'price: 1' : 'price: ',
+ '',
+ ].join('\n')
+ })
+ return blocks.join('\n')
+}
+
+export function convertNftsToMap(
+ items: T[],
+): Record {
+ return items
+ .map((item, i) => ({ ...item, id: i + 1 } as T & { id: number }))
+ .reduce((acc, nft) => {
+ acc[nft.id] = nft
+ return acc
+ }, {} as Record)
+}
diff --git a/app/composables/massmint/parsers/parseTxt.ts b/app/composables/massmint/parsers/parseTxt.ts
index 9758720b..7fd9604b 100644
--- a/app/composables/massmint/parsers/parseTxt.ts
+++ b/app/composables/massmint/parsers/parseTxt.ts
@@ -70,8 +70,14 @@ function updateEntry(entry: Record, line: string) {
entry.price = price
entry.currency = currency
}
- else if (fieldName === 'attributes') {
- entry.attributes = JSON.parse(fieldValue)
+ else if (fieldName === 'attributes' && fieldValue) {
+ try {
+ entry.attributes = JSON.parse(fieldValue)
+ }
+ catch {
+ console.error('Invalid attributes JSON in TXT metadata block')
+ entry.attributes = {}
+ }
}
else {
entry[fieldName] = fieldValue
diff --git a/app/i18n/locales/en.json b/app/i18n/locales/en.json
index e39a0cd7..225d1037 100644
--- a/app/i18n/locales/en.json
+++ b/app/i18n/locales/en.json
@@ -401,30 +401,6 @@
"variationLoadingTimeMeans3": "Takes too long to render, no clear way to fix that, the artist needs to find a way to cut down time."
},
"massmint": {
- "onboarding": {
- "pageTitle": "Mass Mint Onboarding",
- "skip": "Skip",
- "next": "Next",
- "done": "Done",
- "downloadTemplate": "Download file template",
- "cards": {
- "0": {
- "title": "Prepare All Your Files",
- "content": "Start your mass minting by compressing all your art files into a .ZIP for upload. Aside from your artwork, each NFT requires a name, description, and price.\n\nTo streamline the process, prepare these details in advance in a separate file. This avoids manually entering information for each NFT. Further guidance on formatting this file will follow in the next step."
- },
- "1": {
- "title": "How To Name Your NFTs",
- "content": "",
- "subtitle": "Instructions",
- "instructions": "Please ensure that the file field corresponds to the image file name (e.g., set \"file\": img1.jpg in the file field if the image has a file name of \"img1.jpg\"). Note that the order of items in the description file determines the order during mass minting.",
- "codeStructure": "Code Structure"
- },
- "2": {
- "title": "You Need To Have A Collection",
- "content": "In order to use the Mass Mint feature you need to have an **NFT collection created** to which you will mint all of your NFTs. If you do not have a collection created yet, you can create one in Mass Mint.\n\nDon't have a collection? [Create One](/create/collection)"
- }
- }
- }
},
"trades": {
"anyNftFrom": "Any NFT From",
diff --git a/app/pages/massmint/index.vue b/app/pages/massmint/index.vue
index 4f4bc4f8..fa4f0b15 100644
--- a/app/pages/massmint/index.vue
+++ b/app/pages/massmint/index.vue
@@ -3,18 +3,12 @@ import { MassMint } from '~/components/massmint'
definePageMeta({
title: 'Mass Mint',
- layout: 'default',
+ layout: 'no-footer',
})
-
-const router = useRouter()
-
-function handleBackToOnboarding() {
- router.push('/massmint/onboarding')
-}
-
+
diff --git a/app/pages/massmint/onboarding.vue b/app/pages/massmint/onboarding.vue
deleted file mode 100644
index 601def8e..00000000
--- a/app/pages/massmint/onboarding.vue
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-