From aba25199ea96f0a557696e65343a268ffed3a6b9 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Mon, 3 Jul 2023 16:05:06 +0300 Subject: [PATCH 1/6] Add helper to validate token URI --- utils/isValidTokenUri.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 utils/isValidTokenUri.ts diff --git a/utils/isValidTokenUri.ts b/utils/isValidTokenUri.ts new file mode 100644 index 0000000..0ca2d44 --- /dev/null +++ b/utils/isValidTokenUri.ts @@ -0,0 +1,21 @@ +/* eslint-disable eslint-comments/disable-enable-pair */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +export const checkTokenUri = async (tokenUri: string) => { + const file = await fetch(tokenUri.replace('ipfs://', 'https://ipfs.io/ipfs/')) + .then((res) => + res.json().catch((err: any) => { + throw Error(`Metadata file could not be parsed. Please check that it is valid JSON.`) + }), + ) + .catch((err: any) => { + throw Error(`Unable to fetch metadata from ${tokenUri}`) + }) + + if (!file.image) { + throw Error('Token URI must contain an image URL.') + } + if (file.image && !file.image.startsWith('ipfs://')) { + throw Error('Metadata file: The corresponding value for image must be an IPFS URL.') + } +} From 2b5c5f3c323a610ed95d527e926f19d55f87e3d6 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Mon, 3 Jul 2023 16:06:14 +0300 Subject: [PATCH 2/6] Add token URI validation for open edition collection creation --- .../openEdition/OpenEditionMinterCreator.tsx | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/components/openEdition/OpenEditionMinterCreator.tsx b/components/openEdition/OpenEditionMinterCreator.tsx index b05295c..3f0fce3 100644 --- a/components/openEdition/OpenEditionMinterCreator.tsx +++ b/components/openEdition/OpenEditionMinterCreator.tsx @@ -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 = () => { From f9559bf7eb09a24d5936de11acfb926cd99e554a Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Mon, 3 Jul 2023 17:17:16 +0300 Subject: [PATCH 3/6] Add token URI validation for 1/1 collection creation & token addition --- pages/collections/create.tsx | 41 ++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/pages/collections/create.tsx b/pages/collections/create.tsx index f13280e..84a4425 100644 --- a/pages/collections/create.tsx +++ b/pages/collections/create.tsx @@ -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' @@ -176,21 +177,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 +214,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 +835,12 @@ const CollectionCreationPage: NextPage = () => { } } + const checkExistingTokenURI = async () => { + 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') From f3b36f2d17a29293b53fbaf2eabd959185703c54 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Mon, 3 Jul 2023 17:19:54 +0300 Subject: [PATCH 4/6] Bump Studio version --- .env.example | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index b9a4f58..fe5b903 100644 --- a/.env.example +++ b/.env.example @@ -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 diff --git a/package.json b/package.json index 89b8e9a..f35e212 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "stargaze-studio", - "version": "0.6.9", + "version": "0.7.0", "workspaces": [ "packages/*" ], From 23ef3ab3ea242985d5264ee41fd28f111542e285 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Tue, 4 Jul 2023 08:20:28 +0300 Subject: [PATCH 5/6] Update token URI validation logic to cover standard collections as well --- pages/collections/create.tsx | 35 +++++++++------ utils/isValidTokenUri.ts | 84 ++++++++++++++++++++++++++++-------- 2 files changed, 90 insertions(+), 29 deletions(-) diff --git a/pages/collections/create.tsx b/pages/collections/create.tsx index 84a4425..c732ef3 100644 --- a/pages/collections/create.tsx +++ b/pages/collections/create.tsx @@ -141,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) }) @@ -836,6 +844,9 @@ 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) } diff --git a/utils/isValidTokenUri.ts b/utils/isValidTokenUri.ts index 0ca2d44..ac857fa 100644 --- a/utils/isValidTokenUri.ts +++ b/utils/isValidTokenUri.ts @@ -1,21 +1,71 @@ /* eslint-disable eslint-comments/disable-enable-pair */ /* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -export const checkTokenUri = async (tokenUri: string) => { - const file = await fetch(tokenUri.replace('ipfs://', 'https://ipfs.io/ipfs/')) - .then((res) => - res.json().catch((err: any) => { - throw Error(`Metadata file could not be parsed. Please check that it is valid JSON.`) - }), - ) - .catch((err: any) => { - throw Error(`Unable to fetch metadata from ${tokenUri}`) - }) - - if (!file.image) { - throw Error('Token URI must contain an image URL.') - } - if (file.image && !file.image.startsWith('ipfs://')) { - throw Error('Metadata file: The corresponding value for image must be an IPFS URL.') +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}`) + }) } } From 82f19267f3f04426fb1a07d706ab7969a88fa1c9 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Tue, 4 Jul 2023 08:38:59 +0300 Subject: [PATCH 6/6] Add tooltips for (Base) Token URI fields --- .../collections/creation/UploadDetails.tsx | 19 +++++++++++++++++-- .../OffChainMetadataUploadDetails.tsx | 10 +++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/components/collections/creation/UploadDetails.tsx b/components/collections/creation/UploadDetails.tsx index e6b0f4c..a9b7a1d 100644 --- a/components/collections/creation/UploadDetails.tsx +++ b/components/collections/creation/UploadDetails.tsx @@ -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.

- + + +
@@ -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.

- + + +
- + + +