From e5cb8faf511c4e95485239e6eb3e954f06f20528 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Mon, 16 Jan 2023 13:24:23 +0300 Subject: [PATCH 1/7] Enable multiple asset selection & preview for 1/1 Minting --- .../collections/creation/UploadDetails.tsx | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/components/collections/creation/UploadDetails.tsx b/components/collections/creation/UploadDetails.tsx index 44ce386..f10ad44 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 */ @@ -445,7 +446,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 +460,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}> + Date: Mon, 16 Jan 2023 15:16:34 +0300 Subject: [PATCH 2/7] Implement batchMint() for contracts/baseMinter --- contracts/baseMinter/contract.ts | 60 +++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/contracts/baseMinter/contract.ts b/contracts/baseMinter/contract.ts index ac5d3c6..674a851 100644 --- a/contracts/baseMinter/contract.ts +++ b/contracts/baseMinter/contract.ts @@ -1,8 +1,10 @@ -import type { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate' +import type { MsgExecuteContractEncodeObject, SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate' +import { toUtf8 } from '@cosmjs/encoding' import type { Coin } from '@cosmjs/proto-signing' import { coin } from '@cosmjs/proto-signing' import type { logs } from '@cosmjs/stargate' import type { Timestamp } from '@stargazezone/types/contracts/minter/shared-types' +import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' import toast from 'react-hot-toast' import { BASE_FACTORY_ADDRESS } from 'utils/constants' @@ -32,11 +34,13 @@ export interface BaseMinterInstance { //Execute mint: (senderAddress: string, tokenUri: string) => 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}` }, + } + 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}` } }) + } + return { + sender: txSigner, + contract: contractAddress, + msg, + funds: [], + } + } + return { mint, updateStartTradingTime, + batchMint, } } From 5ff7f6f6497ed1990a65a9c7d9f330d1a4af03ea Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Mon, 16 Jan 2023 16:48:38 +0300 Subject: [PATCH 3/7] Implement batch minting for 1/1 Collection Creation --- .../collections/creation/UploadDetails.tsx | 9 +- contracts/baseMinter/contract.ts | 4 +- pages/collections/create.tsx | 111 +++++++++++------- 3 files changed, 77 insertions(+), 47 deletions(-) diff --git a/components/collections/creation/UploadDetails.tsx b/components/collections/creation/UploadDetails.tsx index f10ad44..8df4d4e 100644 --- a/components/collections/creation/UploadDetails.tsx +++ b/components/collections/creation/UploadDetails.tsx @@ -51,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) @@ -138,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 @@ -150,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 } diff --git a/contracts/baseMinter/contract.ts b/contracts/baseMinter/contract.ts index 674a851..c986daa 100644 --- a/contracts/baseMinter/contract.ts +++ b/contracts/baseMinter/contract.ts @@ -199,7 +199,7 @@ export const baseMinter = (client: SigningCosmWasmClient, txSigner: string): Bas const executeContractMsgs: MsgExecuteContractEncodeObject[] = [] for (let i = 0; i < batchCount; i++) { const msg = { - mint: { token_uri: `${baseUri}/${i}` }, + mint: { token_uri: `${baseUri}/${i + 1}` }, } const executeContractMsg: MsgExecuteContractEncodeObject = { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -284,7 +284,7 @@ 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}` } }) + msg.push({ mint: { token_uri: `${baseUri}/${i + 1}` } }) } return { sender: txSigner, diff --git a/pages/collections/create.tsx b/pages/collections/create.tsx index 0be7ee8..edba828 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) @@ -482,28 +488,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 +555,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 +606,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 +665,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') } From a372f70e31dfdc2bbc366095d841a82a2e398e82 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Tue, 17 Jan 2023 16:23:17 +0300 Subject: [PATCH 4/7] Implement batch minting for appending tokens --- pages/collections/create.tsx | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/pages/collections/create.tsx b/pages/collections/create.tsx index edba828..b9de7f0 100644 --- a/pages/collections/create.tsx +++ b/pages/collections/create.tsx @@ -298,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, }) @@ -1120,7 +1128,7 @@ const CollectionCreationPage: NextPage = () => { onClick={performUploadAndMintChecks} variant="solid" > - Mint & Append Token + Mint & Append Token(s) From ff726f6a2737b72cb5d0ef0f6212a7de53aea0a3 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Tue, 17 Jan 2023 16:26:56 +0300 Subject: [PATCH 5/7] Update asset selection checks for 1/1 collections --- components/collections/creation/UploadDetails.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/collections/creation/UploadDetails.tsx b/components/collections/creation/UploadDetails.tsx index 8df4d4e..89bc1a6 100644 --- a/components/collections/creation/UploadDetails.tsx +++ b/components/collections/creation/UploadDetails.tsx @@ -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' && 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 From 75bbcb011b4bf2e03545fc8897a44b425250d8d0 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Tue, 17 Jan 2023 16:39:05 +0300 Subject: [PATCH 6/7] Update asset selection checks for 1/1 collections - 2 --- components/collections/creation/UploadDetails.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/collections/creation/UploadDetails.tsx b/components/collections/creation/UploadDetails.tsx index 89bc1a6..857260b 100644 --- a/components/collections/creation/UploadDetails.tsx +++ b/components/collections/creation/UploadDetails.tsx @@ -98,7 +98,7 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho setAssetFilesArray([]) setMetadataFilesArray([]) if (event.target.files === null) return - if (minterType === 'vending' || (minterType === 'base' && assetFilesArray.length > 1)) { + 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 From c1f716637c3696eb80011c7b251121a78ac46510 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Tue, 17 Jan 2023 16:41:23 +0300 Subject: [PATCH 7/7] 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 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/package.json b/package.json index ee4df12..193c433 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "stargaze-studio", - "version": "0.3.8", + "version": "0.3.9", "workspaces": [ "packages/*" ],