Merge pull request #121 from public-awesome/develop

Sync development > main
This commit is contained in:
Adnan Deniz corlu 2023-03-07 18:05:05 +03:00 committed by GitHub
commit 1a076026e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 328 additions and 101 deletions

View File

@ -1,9 +1,9 @@
APP_VERSION=0.4.7
APP_VERSION=0.4.8
NEXT_PUBLIC_PINATA_ENDPOINT_URL=https://api.pinata.cloud/pinning/pinFileToIPFS
NEXT_PUBLIC_SG721_CODE_ID=793
NEXT_PUBLIC_VENDING_MINTER_CODE_ID=275
NEXT_PUBLIC_VENDING_FACTORY_ADDRESS="stars1s48apjumprma0d64ge88dmu7lr8exu02tlw90xxupgds0s25gfasx447dn"
NEXT_PUBLIC_SG721_CODE_ID=1702
NEXT_PUBLIC_VENDING_MINTER_CODE_ID=1701
NEXT_PUBLIC_VENDING_FACTORY_ADDRESS="stars1xz4d6wzxqn3udgsm5qnr78y032xng4r2ycv7aw6mjtsuw59s2n9s93ec0v"
NEXT_PUBLIC_BASE_FACTORY_ADDRESS="stars1c6juqgd7cm80afpmuszun66rl9zdc4kgfht8fk34tfq3zk87l78sdxngzv"
NEXT_PUBLIC_SG721_NAME_ADDRESS="stars1fx74nkqkw2748av8j7ew7r3xt9cgjqduwn8m0ur5lhe49uhlsasszc5fhr"
NEXT_PUBLIC_BASE_MINTER_CODE_ID=613

View File

@ -6,6 +6,8 @@ import { usePopper } from 'react-popper'
export interface TooltipProps extends ComponentProps<'div'> {
label: ReactNode
children: ReactElement
placement?: 'top' | 'bottom' | 'left' | 'right'
backgroundColor?: string
}
export const Tooltip = ({ label, children, ...props }: TooltipProps) => {
@ -14,7 +16,7 @@ export const Tooltip = ({ label, children, ...props }: TooltipProps) => {
const [show, setShow] = useState(false)
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: 'top',
placement: props.placement ? props.placement : 'top',
})
return (
@ -32,7 +34,11 @@ export const Tooltip = ({ label, children, ...props }: TooltipProps) => {
<div
{...props}
{...attributes.popper}
className={clsx('py-1 px-2 m-1 text-sm bg-black/80 rounded shadow-md', props.className)}
className={clsx(
'py-1 px-2 m-1 text-sm rounded shadow-md',
props.backgroundColor ? props.backgroundColor : 'bg-slate-900',
props.className,
)}
ref={setPopperElement}
style={{ ...styles.popper, ...props.style }}
>

View File

@ -3,24 +3,30 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { toUtf8 } from '@cosmjs/encoding'
import clsx from 'clsx'
import { Conditional } from 'components/Conditional'
import { FormControl } from 'components/FormControl'
import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks'
import { useMetadataAttributesState } from 'components/forms/MetadataAttributes.hooks'
import { InputDateTime } from 'components/InputDateTime'
import { useWallet } from 'contexts/wallet'
import type { Trait } from 'contracts/badgeHub'
import { useEffect, useState } from 'react'
import type { ChangeEvent } from 'react'
import { useEffect, useRef, useState } from 'react'
import { toast } from 'react-hot-toast'
import { BADGE_HUB_ADDRESS } from 'utils/constants'
import { AddressInput, NumberInput, TextInput } from '../../forms/FormInput'
import { MetadataAttributes } from '../../forms/MetadataAttributes'
import { Tooltip } from '../../Tooltip'
import type { MintRule, UploadMethod } from './ImageUploadDetails'
interface BadgeDetailsProps {
onChange: (data: BadgeDetailsDataProps) => void
uploadMethod: UploadMethod | undefined
mintRule: MintRule
metadataSize: number
}
export interface BadgeDetailsDataProps {
@ -38,10 +44,14 @@ export interface BadgeDetailsDataProps {
youtube_url?: string
}
export const BadgeDetails = ({ onChange }: BadgeDetailsProps) => {
export const BadgeDetails = ({ metadataSize, onChange }: BadgeDetailsProps) => {
const wallet = useWallet()
const [timestamp, setTimestamp] = useState<Date | undefined>(undefined)
const [transferrable, setTransferrable] = useState<boolean>(false)
const [metadataFile, setMetadataFile] = useState<File>()
const [metadataFeeRate, setMetadataFeeRate] = useState<number>(0)
const metadataFileRef = useRef<HTMLInputElement | null>(null)
const managerState = useInputState({
id: 'manager-address',
@ -109,6 +119,79 @@ export const BadgeDetails = ({ onChange }: BadgeDetailsProps) => {
subtitle: 'YouTube URL for the badge',
})
const parseMetadata = async () => {
try {
let parsedMetadata: any
if (metadataFile) {
attributesState.reset()
parsedMetadata = JSON.parse(await metadataFile.text())
if (!parsedMetadata.attributes || parsedMetadata.attributes.length === 0) {
attributesState.add({
trait_type: '',
value: '',
})
} else {
for (let i = 0; i < parsedMetadata.attributes.length; i++) {
attributesState.add({
trait_type: parsedMetadata.attributes[i].trait_type,
value: parsedMetadata.attributes[i].value,
})
}
}
nameState.onChange(parsedMetadata.name ? parsedMetadata.name : '')
descriptionState.onChange(parsedMetadata.description ? parsedMetadata.description : '')
externalUrlState.onChange(parsedMetadata.external_url ? parsedMetadata.external_url : '')
youtubeUrlState.onChange(parsedMetadata.youtube_url ? parsedMetadata.youtube_url : '')
animationUrlState.onChange(parsedMetadata.animation_url ? parsedMetadata.animation_url : '')
backgroundColorState.onChange(parsedMetadata.background_color ? parsedMetadata.background_color : '')
imageDataState.onChange(parsedMetadata.image_data ? parsedMetadata.image_data : '')
} else {
attributesState.reset()
nameState.onChange('')
descriptionState.onChange('')
externalUrlState.onChange('')
youtubeUrlState.onChange('')
animationUrlState.onChange('')
backgroundColorState.onChange('')
imageDataState.onChange('')
}
} catch (error) {
toast.error('Error parsing metadata file: Invalid JSON format.')
if (metadataFileRef.current) metadataFileRef.current.value = ''
setMetadataFile(undefined)
}
}
const selectMetadata = (event: ChangeEvent<HTMLInputElement>) => {
setMetadataFile(undefined)
if (event.target.files === null) return
let selectedFile: File
const reader = new FileReader()
reader.onload = (e) => {
if (!event.target.files) return toast.error('No file selected.')
if (!e.target?.result) return toast.error('Error parsing file.')
selectedFile = new File([e.target.result], event.target.files[0].name, { type: 'application/json' })
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (event.target.files[0]) reader.readAsArrayBuffer(event.target.files[0])
else return toast.error('No file selected.')
reader.onloadend = () => {
if (!event.target.files) return toast.error('No file selected.')
setMetadataFile(selectedFile)
}
}
useEffect(() => {
void parseMetadata()
if (!metadataFile)
attributesState.add({
trait_type: '',
value: '',
})
}, [metadataFile])
useEffect(() => {
try {
const data: BadgeDetailsDataProps = {
@ -155,13 +238,25 @@ export const BadgeDetails = ({ onChange }: BadgeDetailsProps) => {
])
useEffect(() => {
if (attributesState.values.length === 0)
attributesState.add({
trait_type: '',
value: '',
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const retrieveFeeRate = async () => {
try {
if (wallet.client) {
const feeRateRaw = await wallet.client.queryContractRaw(
BADGE_HUB_ADDRESS,
toUtf8(Buffer.from(Buffer.from('fee_rate').toString('hex'), 'hex').toString()),
)
console.log('Fee Rate Raw: ', feeRateRaw)
const feeRate = JSON.parse(new TextDecoder().decode(feeRateRaw as Uint8Array))
setMetadataFeeRate(Number(feeRate.metadata))
}
} catch (error) {
toast.error('Error retrieving metadata fee rate.')
setMetadataFeeRate(0)
console.log('Error retrieving fee rate: ', error)
}
}
void retrieveFeeRate()
}, [wallet.client])
return (
<div>
@ -172,19 +267,35 @@ export const BadgeDetails = ({ onChange }: BadgeDetailsProps) => {
<TextInput className="mt-2" {...descriptionState} />
<NumberInput className="mt-2" {...maxSupplyState} />
<TextInput className="mt-2" {...externalUrlState} />
<FormControl className="mt-2" htmlId="expiry-date" subtitle="Badge minting expiry date" title="Expiry Date">
<InputDateTime minDate={new Date()} onChange={(date) => setTimestamp(date)} value={timestamp} />
</FormControl>
<div className="mt-2 form-control">
<label className="justify-start cursor-pointer label">
<span className="mr-4 font-bold">Transferrable</span>
<input
checked={transferrable}
className={`toggle ${transferrable ? `bg-stargaze` : `bg-gray-600`}`}
onClick={() => setTransferrable(!transferrable)}
type="checkbox"
/>
</label>
<div className="grid grid-cols-2">
<div className="mt-2 w-1/3 form-control">
<label className="justify-start cursor-pointer label">
<span className="mr-4 font-bold">Transferrable</span>
<input
checked={transferrable}
className={`toggle ${transferrable ? `bg-stargaze` : `bg-gray-600`}`}
onClick={() => setTransferrable(!transferrable)}
type="checkbox"
/>
</label>
</div>
<Conditional test={managerState.value !== ''}>
<Tooltip
backgroundColor="bg-stargaze"
className="bg-yellow-600"
label="This is only an estimate. Be sure to check the final amount before signing the transaction."
placement="bottom"
>
<div className="grid grid-cols-2 ml-12 w-full">
<div className="mt-4 font-bold">Fee Estimate:</div>
<span className="mt-4">{(metadataSize * Number(metadataFeeRate)) / 1000000} stars</span>
</div>
</Tooltip>
</Conditional>
</div>
</div>
<div className={clsx('ml-10')}>
@ -197,6 +308,40 @@ export const BadgeDetails = ({ onChange }: BadgeDetailsProps) => {
title="Traits"
/>
</div>
<div className="w-full">
<Tooltip
backgroundColor="bg-blue-500"
label="A metadata file can be selected to automatically fill in the related fields."
placement="bottom"
>
<div>
<label
className="block mt-2 mr-1 mb-1 w-full font-bold text-white dark:text-gray-300"
htmlFor="assetFile"
>
Metadata File Selection (optional)
</label>
<div
className={clsx(
'flex relative justify-center items-center mt-2 space-y-4 w-full h-32',
'rounded border-2 border-white/20 border-dashed',
)}
>
<input
accept="application/json"
className={clsx(
'file:py-2 file:px-4 file:mr-4 file:bg-plumbus-light file:rounded file:border-0 cursor-pointer',
'before:absolute before:inset-0 before:hover:bg-white/5 before:transition',
)}
id="metadataFile"
onChange={selectMetadata}
ref={metadataFileRef}
type="file"
/>
</div>
</div>
</Tooltip>
</div>
</div>
</div>
</div>

View File

@ -172,7 +172,7 @@ export const ImageUploadDetails = ({ onChange, mintRule }: ImageUploadDetailsPro
</div>
</div>
<div className="p-3 py-5 pb-8">
<div className="p-3 py-5 pb-4">
<Conditional test={uploadMethod === 'existing'}>
<div className="ml-3 flex-column">
<p className="mb-5 ml-5">
@ -187,8 +187,17 @@ export const ImageUploadDetails = ({ onChange, mintRule }: ImageUploadDetailsPro
</Anchor>{' '}
and upload your image manually to get an image URL for your badge.
</p>
<div>
<TextInput {...imageUrlState} className="mt-2 ml-4 w-1/2" />
<div className="flex flex-row w-full">
<TextInput {...imageUrlState} className="mt-2 ml-6 w-full max-w-2xl" />
<Conditional test={imageUrlState.value !== ''}>
<div className="mt-2 ml-4 w-1/4 border-2 border-dashed">
<img
alt="badge-preview"
className="w-full"
src={imageUrlState.value.replace('IPFS://', 'ipfs://').replace(/,/g, '').replace(/"/g, '').trim()}
/>
</div>
</Conditional>
</div>
</div>
</Conditional>
@ -252,31 +261,33 @@ export const ImageUploadDetails = ({ onChange, mintRule }: ImageUploadDetailsPro
<div className="mt-6">
<div className="grid grid-cols-2">
<div className="w-full">
<div>
<label
className="block mt-5 mr-1 mb-1 ml-8 w-full font-bold text-white dark:text-gray-300"
htmlFor="assetFile"
>
Image Selection
</label>
<div
className={clsx(
'flex relative justify-center items-center mx-8 mt-2 space-y-4 w-full h-32',
'rounded border-2 border-white/20 border-dashed',
)}
>
<input
accept="image/*"
<div>
<div className="w-full">
<div>
<label
className="block mt-5 mr-1 mb-1 ml-8 w-full font-bold text-white dark:text-gray-300"
htmlFor="assetFile"
>
Image Selection
</label>
<div
className={clsx(
'file:py-2 file:px-4 file:mr-4 file:bg-plumbus-light file:rounded file:border-0 cursor-pointer',
'before:absolute before:inset-0 before:hover:bg-white/5 before:transition',
'flex relative justify-center items-center mx-8 mt-2 space-y-4 w-full h-32',
'rounded border-2 border-white/20 border-dashed',
)}
id="assetFile"
onChange={selectAsset}
ref={assetFileRef}
type="file"
/>
>
<input
accept="image/*"
className={clsx(
'file:py-2 file:px-4 file:mr-4 file:bg-plumbus-light file:rounded file:border-0 cursor-pointer',
'before:absolute before:inset-0 before:hover:bg-white/5 before:transition',
)}
id="assetFile"
onChange={selectAsset}
ref={assetFileRef}
type="file"
/>
</div>
</div>
</div>
</div>

View File

@ -151,7 +151,7 @@ export const CollectionActions = ({
name: 'royaltyShare',
title: 'Share Percentage',
subtitle: 'Percentage of royalties to be paid',
placeholder: '8%',
placeholder: '5%',
})
const showTokenUriField = type === 'mint_token_uri'

View File

@ -36,7 +36,7 @@ export const RoyaltyDetails = ({ onChange }: RoyaltyDetailsProps) => {
name: 'royaltyShare',
title: 'Share Percentage',
subtitle: 'Percentage of royalties to be paid',
placeholder: '8%',
placeholder: '5%',
})
useEffect(() => {

View File

@ -73,9 +73,10 @@ export function MetadataAttribute({ id, isLast, onAdd, onChange, onRemove, defau
}, [traitTypeState.value, traitValueState.value, id])
return (
<div className="grid relative grid-cols-[1fr_1fr_auto] space-x-2">
<div className="grid relative 2xl:grid-cols-[1fr_1fr_auto] 2xl:space-x-2">
<TraitTypeInput {...traitTypeState} />
<TraitValueInput {...traitValueState} />
<div className="flex justify-end items-end pb-2 w-8">
<button
className="flex justify-center items-center p-2 bg-stargaze-80 hover:bg-plumbus-60 rounded-full"

View File

@ -1,6 +1,6 @@
{
"name": "stargaze-studio",
"version": "0.4.7",
"version": "0.4.8",
"workspaces": [
"packages/*"
],

View File

@ -1,4 +1,5 @@
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable no-nested-ternary */
@ -22,12 +23,14 @@ import { useInputState } from 'components/forms/FormInput.hooks'
import { Tooltip } from 'components/Tooltip'
import { useContracts } from 'contexts/contracts'
import { useWallet } from 'contexts/wallet'
import type { Badge } from 'contracts/badgeHub'
import type { DispatchExecuteArgs as BadgeHubDispatchExecuteArgs } from 'contracts/badgeHub/messages/execute'
import { dispatchExecute as badgeHubDispatchExecute } from 'contracts/badgeHub/messages/execute'
import * as crypto from 'crypto'
import { toPng } from 'html-to-image'
import type { NextPage } from 'next'
import { NextSeo } from 'next-seo'
import sizeof from 'object-sizeof'
import { QRCodeSVG } from 'qrcode.react'
import { useEffect, useMemo, useRef, useState } from 'react'
import { toast } from 'react-hot-toast'
@ -59,6 +62,7 @@ const BadgeCreationPage: NextPage = () => {
const [readyToCreateBadge, setReadyToCreateBadge] = useState(false)
const [mintRule, setMintRule] = useState<MintRule>('by_key')
const [resolvedMinterAddress, setResolvedMinterAddress] = useState<string>('')
const [tempBadge, setTempBadge] = useState<Badge>()
const [badgeId, setBadgeId] = useState<string | null>(null)
const [imageUrl, setImageUrl] = useState<string | null>(null)
@ -138,6 +142,37 @@ const BadgeCreationPage: NextPage = () => {
void resolveMinterAddress()
}, [designatedMinterState.value])
useEffect(() => {
const badge = {
manager: badgeDetails?.manager as string,
metadata: {
name: badgeDetails?.name || undefined,
description: badgeDetails?.description || undefined,
image: imageUrl || undefined,
image_data: badgeDetails?.image_data || undefined,
external_url: badgeDetails?.external_url || undefined,
attributes: badgeDetails?.attributes || undefined,
background_color: badgeDetails?.background_color || undefined,
animation_url: badgeDetails?.animation_url || undefined,
youtube_url: badgeDetails?.youtube_url || undefined,
},
transferrable: badgeDetails?.transferrable as boolean,
rule:
mintRule === 'by_key'
? {
by_key: keyState.value,
}
: mintRule === 'by_minter'
? {
by_minter: resolvedMinterAddress,
}
: 'by_keys',
expiry: badgeDetails?.expiry || undefined,
max_supply: badgeDetails?.max_supply || undefined,
}
setTempBadge(badge)
}, [badgeDetails, keyState.value, mintRule, resolvedMinterAddress, imageUrl])
const createNewBadge = async () => {
try {
if (!wallet.initialized) throw new Error('Wallet not connected')
@ -568,20 +603,27 @@ const BadgeCreationPage: NextPage = () => {
mintRule !== 'by_key' ? 'bg-stargaze/5 hover:bg-stargaze/80' : 'hover:bg-white/5',
)}
>
<button
className="p-4 w-full h-full text-left bg-transparent"
onClick={() => {
setMintRule('by_key')
setReadyToCreateBadge(false)
setBadgeId(null)
}}
type="button"
<Tooltip
backgroundColor="bg-blue-500"
className="m-0 w-1/3"
label="The same single private key can be utilized by multiple users to share badge minting authority. Ideal for projects with multiple administrators."
placement="bottom"
>
<h4 className="font-bold">Mint Rule: By Key</h4>
<span className="text-sm text-white/80 line-clamp-2">
Badges can be minted more than once with a badge specific message signed by a designated private key.
</span>
</button>
<button
className="p-4 w-full h-full text-left bg-transparent"
onClick={() => {
setMintRule('by_key')
setReadyToCreateBadge(false)
setBadgeId(null)
}}
type="button"
>
<h4 className="font-bold">Mint Rule: By Key</h4>
<span className="text-sm text-white/80 line-clamp-4">
Multiple badges can be minted to different addresses by the owner of a single designated key.
</span>
</button>
</Tooltip>
</div>
<div
className={clsx(
@ -591,20 +633,27 @@ const BadgeCreationPage: NextPage = () => {
mintRule !== 'by_keys' ? 'bg-stargaze/5 hover:bg-stargaze/80' : 'hover:bg-white/5',
)}
>
<button
className="p-4 w-full h-full text-left bg-transparent"
onClick={() => {
setMintRule('by_keys')
setReadyToCreateBadge(false)
setBadgeId(null)
}}
type="button"
<Tooltip
backgroundColor="bg-blue-500"
className="m-0 w-1/3"
label="The key pairs are intended to be saved and shared with others. Each user can claim a badge separately using the key pair that they received."
placement="bottom"
>
<h4 className="font-bold">Mint Rule: By Keys</h4>
<span className="text-sm text-white/80 line-clamp-2">
Similar to the By Key rule, however each designated private key can only be used once to mint a badge.
</span>
</button>
<button
className="p-4 w-full h-full text-left bg-transparent"
onClick={() => {
setMintRule('by_keys')
setReadyToCreateBadge(false)
setBadgeId(null)
}}
type="button"
>
<h4 className="font-bold">Mint Rule: By Keys</h4>
<span className="text-sm text-white/80 line-clamp-4">
Multiple key pairs are generated and designated to be only used once to mint a single badge.
</span>
</button>
</Tooltip>
</div>
<div
className={clsx(
@ -614,20 +663,28 @@ const BadgeCreationPage: NextPage = () => {
mintRule !== 'by_minter' ? 'bg-stargaze/5 hover:bg-stargaze/80' : 'hover:bg-white/5',
)}
>
<button
className="p-4 w-full h-full text-left bg-transparent"
onClick={() => {
setMintRule('by_minter')
setReadyToCreateBadge(false)
setBadgeId(null)
}}
type="button"
<Tooltip
backgroundColor="bg-blue-500"
className="m-0 w-1/3"
label="The most basic approach. However, having just one authorized address for minting badges might limit your ability to delegate that responsibility."
placement="bottom"
>
<h4 className="font-bold">Mint Rule: By Minter</h4>
<span className="text-sm text-white/80 line-clamp-2">
Badges can be minted by a designated minter account.
</span>
</button>
<button
className="p-4 w-full h-full text-left bg-transparent"
onClick={() => {
setMintRule('by_minter')
setReadyToCreateBadge(false)
setBadgeId(null)
}}
type="button"
>
<h4 className="font-bold">Mint Rule: By Minter</h4>
<span className="text-sm text-white/80 line-clamp-4">
No key designation. Multiple badges can be minted to different addresses by a pre-determined minter
address.
</span>
</button>
</Tooltip>
</div>
</div>
</div>
@ -671,6 +728,11 @@ const BadgeCreationPage: NextPage = () => {
<div className="flex justify-between py-3 px-8 rounded border-2 border-white/20 grid-col-2">
<BadgeDetails
metadataSize={
tempBadge?.metadata.image === undefined
? Number(sizeof(tempBadge)) + Number(sizeof(tempBadge?.metadata.attributes)) + 150
: Number(sizeof(tempBadge)) + Number(sizeof(tempBadge.metadata.attributes))
}
mintRule={mintRule}
onChange={setBadgeDetails}
uploadMethod={imageUploadDetails?.uploadMethod ? imageUploadDetails.uploadMethod : 'new'}

View File

@ -49,12 +49,12 @@ const BadgeList: NextPage = () => {
{myBadges.map((badge: any, index: any) => {
return (
<tr key={index}>
<td className="w-[55%] bg-black">
<td className="w-[35%] bg-black">
<div className="flex items-center space-x-3">
<div className="avatar">
<div className="w-28 h-28 mask mask-squircle">
<img
alt="Cover"
alt="badge-preview"
src={
(badge?.image as string).startsWith('ipfs')
? `https://ipfs-gw.stargaze-apis.com/ipfs/${(badge?.image as string).substring(7)}`
@ -64,13 +64,15 @@ const BadgeList: NextPage = () => {
</div>
</div>
<div className="pl-2">
<p className="overflow-auto max-w-xs font-bold no-scrollbar ">{badge.name}</p>
<p className="overflow-auto max-w-xs font-bold no-scrollbar ">
{badge.name ? badge.name : 'No name provided.'}
</p>
<p className="max-w-xs text-sm truncate opacity-50">Badge ID: {badge.tokenId}</p>
</div>
</div>
</td>
<td className="overflow-auto w-[35%] max-w-xl bg-black no-scrollbar">
{badge.description}
<td className="overflow-auto w-[55%] max-w-xl bg-black no-scrollbar">
{badge.description ? badge.description : 'No description provided.'}
{/* <br /> */}
{/* <span className="badge badge-ghost badge-sm"></span> */}
</td>

View File

@ -105,7 +105,7 @@ const BaseMinterInstantiatePage: NextPage = () => {
name: 'royaltyShare',
title: 'Share Percentage',
subtitle: 'Percentage of royalties to be paid',
placeholder: '8%',
placeholder: '5%',
})
const { data, isLoading, mutate } = useMutation(

View File

@ -103,7 +103,7 @@ const VendingMinterInstantiatePage: NextPage = () => {
name: 'royaltyShare',
title: 'Share Percentage',
subtitle: 'Percentage of royalties to be paid',
placeholder: '8%',
placeholder: '5%',
})
const unitPriceState = useNumberInputState({