Merge pull request #183 from public-awesome/metadata-uri-validation
(Base) Token URI validation during collection creation
This commit is contained in:
commit
8a7f093d39
@ -1,4 +1,4 @@
|
||||
APP_VERSION=0.6.9
|
||||
APP_VERSION=0.7.0
|
||||
|
||||
NEXT_PUBLIC_PINATA_ENDPOINT_URL=https://api.pinata.cloud/pinning/pinFileToIPFS
|
||||
NEXT_PUBLIC_SG721_CODE_ID=2595
|
||||
|
@ -13,6 +13,7 @@ import { useInputState } from 'components/forms/FormInput.hooks'
|
||||
import { MetadataInput } from 'components/MetadataInput'
|
||||
import { MetadataModal } from 'components/MetadataModal'
|
||||
import { SingleAssetPreview } from 'components/SingleAssetPreview'
|
||||
import { Tooltip } from 'components/Tooltip'
|
||||
import { addLogItem } from 'contexts/log'
|
||||
import type { ChangeEvent } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
@ -336,7 +337,14 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho
|
||||
and upload your assets & metadata manually to get a base URI for your collection.
|
||||
</p>
|
||||
<div>
|
||||
<TextInput {...baseTokenUriState} className="ml-4 w-1/2" />
|
||||
<Tooltip
|
||||
backgroundColor="bg-blue-500"
|
||||
className="mb-2 ml-20"
|
||||
label="The base token URI that points to the IPFS folder containing the metadata files."
|
||||
placement="top"
|
||||
>
|
||||
<TextInput {...baseTokenUriState} className="ml-4 w-1/2" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Conditional test={minterType !== 'base'}>
|
||||
<div>
|
||||
@ -360,7 +368,14 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho
|
||||
and upload your asset & metadata manually to get a URI for your token before minting.
|
||||
</p>
|
||||
<div>
|
||||
<TextInput {...baseTokenUriState} className="ml-4 w-1/2" />
|
||||
<Tooltip
|
||||
backgroundColor="bg-blue-500"
|
||||
className="mb-2 ml-4"
|
||||
label="The token URI that points directly to the metadata file stored on IPFS."
|
||||
placement="top"
|
||||
>
|
||||
<TextInput {...baseTokenUriState} className="ml-4 w-1/2" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Conditional
|
||||
test={minterType !== 'base' || (minterType === 'base' && baseMinterAcquisitionMethod === 'new')}
|
||||
|
@ -12,6 +12,7 @@ import { useInputState } from 'components/forms/FormInput.hooks'
|
||||
import { MetadataInput } from 'components/MetadataInput'
|
||||
import { MetadataModal } from 'components/MetadataModal'
|
||||
import { SingleAssetPreview } from 'components/SingleAssetPreview'
|
||||
import { Tooltip } from 'components/Tooltip'
|
||||
import { addLogItem } from 'contexts/log'
|
||||
import type { ChangeEvent } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
@ -291,7 +292,14 @@ export const OffChainMetadataUploadDetails = ({
|
||||
and upload your asset & metadata manually to get a URI for your token before minting.
|
||||
</p>
|
||||
<div>
|
||||
<TextInput {...tokenUriState} className="ml-4 w-1/2" />
|
||||
<Tooltip
|
||||
backgroundColor="bg-blue-500"
|
||||
className="mb-2 ml-28"
|
||||
label="The token URI that points directly to the metadata file stored on IPFS."
|
||||
placement="top"
|
||||
>
|
||||
<TextInput {...tokenUriState} className="ml-4 w-1/2" />
|
||||
</Tooltip>
|
||||
<TextInput {...coverImageUrlState} className="mt-2 ml-4 w-1/2" />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -28,6 +28,7 @@ import {
|
||||
} from 'utils/constants'
|
||||
import { getAssetType } from 'utils/getAssetType'
|
||||
import { isValidAddress } from 'utils/isValidAddress'
|
||||
import { checkTokenUri } from 'utils/isValidTokenUri'
|
||||
import { uid } from 'utils/random'
|
||||
|
||||
import { type CollectionDetailsDataProps, CollectionDetails } from './CollectionDetails'
|
||||
@ -101,23 +102,30 @@ export const OpenEditionMinterCreator = ({
|
||||
const performOpenEditionMinterChecks = () => {
|
||||
try {
|
||||
setReadyToCreate(false)
|
||||
checkUploadDetails()
|
||||
checkCollectionDetails()
|
||||
checkMintingDetails()
|
||||
void checkRoyaltyDetails()
|
||||
void checkUploadDetails()
|
||||
.then(() => {
|
||||
void checkwalletBalance()
|
||||
void checkRoyaltyDetails()
|
||||
.then(() => {
|
||||
setReadyToCreate(true)
|
||||
void checkwalletBalance()
|
||||
.then(() => {
|
||||
setReadyToCreate(true)
|
||||
})
|
||||
.catch((error: any) => {
|
||||
toast.error(`Error in Wallet Balance: ${error.message}`, { style: { maxWidth: 'none' } })
|
||||
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
|
||||
setReadyToCreate(false)
|
||||
})
|
||||
})
|
||||
.catch((error: any) => {
|
||||
toast.error(`Error in Wallet Balance: ${error.message}`, { style: { maxWidth: 'none' } })
|
||||
toast.error(`Error in Royalty Details: ${error.message}`, { style: { maxWidth: 'none' } })
|
||||
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
|
||||
setReadyToCreate(false)
|
||||
})
|
||||
})
|
||||
.catch((error: any) => {
|
||||
toast.error(`Error in Royalty Details: ${error.message}`, { style: { maxWidth: 'none' } })
|
||||
toast.error(`Error in Upload Details: ${error.message}`, { style: { maxWidth: 'none' } })
|
||||
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
|
||||
setReadyToCreate(false)
|
||||
})
|
||||
@ -129,7 +137,7 @@ export const OpenEditionMinterCreator = ({
|
||||
}
|
||||
}
|
||||
|
||||
const checkUploadDetails = () => {
|
||||
const checkUploadDetails = async () => {
|
||||
if (!wallet.initialized) throw new Error('Wallet not connected.')
|
||||
if (
|
||||
(metadataStorageMethod === 'off-chain' && !offChainMetadataUploadDetails) ||
|
||||
@ -200,6 +208,9 @@ export const OpenEditionMinterCreator = ({
|
||||
throw new Error('Please enter a valid cover image URL')
|
||||
}
|
||||
}
|
||||
if (offChainMetadataUploadDetails?.uploadMethod === 'existing') {
|
||||
await checkTokenUri(offChainMetadataUploadDetails.tokenURI as string)
|
||||
}
|
||||
}
|
||||
|
||||
const checkCollectionDetails = () => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "stargaze-studio",
|
||||
"version": "0.6.9",
|
||||
"version": "0.7.0",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
|
@ -63,6 +63,7 @@ import {
|
||||
WHITELIST_CODE_ID,
|
||||
WHITELIST_FLEX_CODE_ID,
|
||||
} from 'utils/constants'
|
||||
import { checkTokenUri } from 'utils/isValidTokenUri'
|
||||
import { withMetadata } from 'utils/layout'
|
||||
import { links } from 'utils/links'
|
||||
import { uid } from 'utils/random'
|
||||
@ -140,26 +141,34 @@ const CollectionCreationPage: NextPage = () => {
|
||||
checkUploadDetails()
|
||||
checkCollectionDetails()
|
||||
checkMintingDetails()
|
||||
void checkRoyaltyDetails()
|
||||
void checkExistingTokenURI()
|
||||
.then(() => {
|
||||
checkWhitelistDetails()
|
||||
void checkRoyaltyDetails()
|
||||
.then(() => {
|
||||
checkwalletBalance()
|
||||
setReadyToCreateVm(true)
|
||||
checkWhitelistDetails()
|
||||
.then(() => {
|
||||
checkwalletBalance()
|
||||
setReadyToCreateVm(true)
|
||||
})
|
||||
.catch((error) => {
|
||||
if (String(error.message).includes('Insufficient wallet balance')) {
|
||||
toast.error(`${error.message}`, { style: { maxWidth: 'none' } })
|
||||
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
|
||||
} else {
|
||||
toast.error(`Error in Whitelist Configuration: ${error.message}`, { style: { maxWidth: 'none' } })
|
||||
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
|
||||
}
|
||||
setReadyToCreateVm(false)
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
if (String(error.message).includes('Insufficient wallet balance')) {
|
||||
toast.error(`${error.message}`, { style: { maxWidth: 'none' } })
|
||||
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
|
||||
} else {
|
||||
toast.error(`Error in Whitelist Configuration: ${error.message}`, { style: { maxWidth: 'none' } })
|
||||
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
|
||||
}
|
||||
toast.error(`Error in Royalty Details: ${error.message}`, { style: { maxWidth: 'none' } })
|
||||
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
|
||||
setReadyToCreateVm(false)
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error(`Error in Royalty Details: ${error.message}`, { style: { maxWidth: 'none' } })
|
||||
toast.error(`Error in Base Token URI: ${error.message}`, { style: { maxWidth: 'none' } })
|
||||
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
|
||||
setReadyToCreateVm(false)
|
||||
})
|
||||
@ -176,21 +185,29 @@ const CollectionCreationPage: NextPage = () => {
|
||||
setReadyToCreateBm(false)
|
||||
checkUploadDetails()
|
||||
checkCollectionDetails()
|
||||
void checkRoyaltyDetails()
|
||||
void checkExistingTokenURI()
|
||||
.then(() => {
|
||||
checkWhitelistDetails()
|
||||
void checkRoyaltyDetails()
|
||||
.then(() => {
|
||||
checkwalletBalance()
|
||||
setReadyToCreateBm(true)
|
||||
checkWhitelistDetails()
|
||||
.then(() => {
|
||||
checkwalletBalance()
|
||||
setReadyToCreateBm(true)
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error(`${error.message}`, { style: { maxWidth: 'none' } })
|
||||
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
|
||||
setReadyToCreateBm(false)
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error(`${error.message}`, { style: { maxWidth: 'none' } })
|
||||
toast.error(`Error in Royalty Configuration: ${error.message}`, { style: { maxWidth: 'none' } })
|
||||
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
|
||||
setReadyToCreateBm(false)
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error(`Error in Royalty Configuration: ${error.message}`, { style: { maxWidth: 'none' } })
|
||||
toast.error(`Error in Existing Token URI: ${error.message}`, { style: { maxWidth: 'none' } })
|
||||
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
|
||||
setReadyToCreateBm(false)
|
||||
})
|
||||
@ -205,12 +222,20 @@ const CollectionCreationPage: NextPage = () => {
|
||||
try {
|
||||
setReadyToUploadAndMint(false)
|
||||
checkUploadDetails()
|
||||
checkWhitelistDetails()
|
||||
checkExistingTokenURI()
|
||||
.then(() => {
|
||||
setReadyToUploadAndMint(true)
|
||||
checkWhitelistDetails()
|
||||
.then(() => {
|
||||
setReadyToUploadAndMint(true)
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error(`${error.message}`, { style: { maxWidth: 'none' } })
|
||||
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
|
||||
setReadyToUploadAndMint(false)
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error(`${error.message}`, { style: { maxWidth: 'none' } })
|
||||
toast.error(`Error in Token URI: ${error.message}`, { style: { maxWidth: 'none' } })
|
||||
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
|
||||
setReadyToUploadAndMint(false)
|
||||
})
|
||||
@ -818,6 +843,15 @@ const CollectionCreationPage: NextPage = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const checkExistingTokenURI = async () => {
|
||||
if (minterType === 'vending' && uploadDetails && uploadDetails.uploadMethod === 'existing') {
|
||||
await checkTokenUri(uploadDetails.baseTokenURI as string, true)
|
||||
}
|
||||
if (minterType === 'base' && uploadDetails && uploadDetails.uploadMethod === 'existing') {
|
||||
await checkTokenUri(uploadDetails.baseTokenURI as string)
|
||||
}
|
||||
}
|
||||
|
||||
const checkCollectionDetails = () => {
|
||||
if (!collectionDetails) throw new Error('Please fill out the collection details')
|
||||
if (collectionDetails.name === '') throw new Error('Collection name is required')
|
||||
|
71
utils/isValidTokenUri.ts
Normal file
71
utils/isValidTokenUri.ts
Normal file
@ -0,0 +1,71 @@
|
||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
export const checkTokenUri = async (tokenUri: string, isBaseTokenUri?: boolean) => {
|
||||
if (isBaseTokenUri) {
|
||||
await fetch(tokenUri.replace('ipfs://', 'https://ipfs.io/ipfs/').concat(tokenUri.endsWith('/') ? '1' : '/1'))
|
||||
.then((res) =>
|
||||
res
|
||||
.json()
|
||||
.then((data) => {
|
||||
if (!data.image) {
|
||||
throw Error('Metadata validation failed. The metadata files must contain an image URL.')
|
||||
}
|
||||
if (!data.image.startsWith('ipfs://')) {
|
||||
throw Error('Metadata file validation failed: The corresponding value for image must be an IPFS URL.')
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
throw Error(
|
||||
`Metadata validation failed. Please check that the metadata files in the IPFS folder are valid JSON.`,
|
||||
)
|
||||
}),
|
||||
)
|
||||
.catch(async () => {
|
||||
await fetch(
|
||||
tokenUri.replace('ipfs://', 'https://ipfs.io/ipfs/').concat(tokenUri.endsWith('/') ? '1.json' : '/1.json'),
|
||||
)
|
||||
.then((response) =>
|
||||
response
|
||||
.json()
|
||||
.then((file) => {
|
||||
if (!file.image) {
|
||||
throw Error('Metadata validation failed. The metadata files must contain an image URL.')
|
||||
}
|
||||
if (!file.image.startsWith('ipfs://')) {
|
||||
throw Error('Metadata file validation failed: The corresponding value for image must be an IPFS URL.')
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
throw Error(
|
||||
`Metadata validation failed. Please check that the metadata files in the IPFS folder are valid JSON.`,
|
||||
)
|
||||
}),
|
||||
)
|
||||
.catch(() => {
|
||||
throw Error(
|
||||
`Unable to fetch metadata from ${tokenUri}. Metadata validation failed. Please check that the base token URI points to an IPFS folder with metadata files in it.`,
|
||||
)
|
||||
})
|
||||
})
|
||||
} else {
|
||||
await fetch(tokenUri.replace('ipfs://', 'https://ipfs.io/ipfs/'))
|
||||
.then((res) =>
|
||||
res
|
||||
.json()
|
||||
.then((file) => {
|
||||
if (!file.image) {
|
||||
throw Error('Token URI must contain an image URL.')
|
||||
}
|
||||
if (!file.image.startsWith('ipfs://')) {
|
||||
throw Error('Metadata file: The corresponding value for image must be an IPFS URL.')
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
throw Error(`Metadata file could not be parsed. Please check that it is valid JSON.`)
|
||||
}),
|
||||
)
|
||||
.catch(() => {
|
||||
throw Error(`Unable to fetch metadata from ${tokenUri}`)
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user