Launchpad V2 sync (#34)
* V2 Sync * v2 sync * Launchpad V2 sync * Update trading start time description * Add explicit_content to CollectionDetails update dependencies * Minor UI changes * Update MintPriceMessage interface * Add symbolState.value to CollectionDetails update dependencies * Add external_link to Collection Details * Remove the tab Instantiate from the minter contract dashboard * Add price check for update_minting_price * Implement dynamic per address limit check * Add checks for trading start time * Update Minter Contract Dashboard Instantiate Tab - 1 * Update Minter Contract Dashboard Instantiate Tab - 2 * Remove Instantiate tab from SG721 Contract Dashboard * Update whitelist contract helpers * Update whitelist instantiate fee wrt member limit Co-authored-by: name-user1 <eray@deuslabs.fi> Co-authored-by: Serkan Reis <serkanreis@gmail.com>
This commit is contained in:
co-authored by
name-user1
Serkan Reis
parent
2c8f66a5d6
commit
039b8b424b
@@ -1,11 +1,6 @@
|
||||
import type { LinkTabProps } from './LinkTab'
|
||||
|
||||
export const sg721LinkTabs: LinkTabProps[] = [
|
||||
{
|
||||
title: 'Instantiate',
|
||||
description: `Create a new SG721 contract`,
|
||||
href: '/contracts/sg721/instantiate',
|
||||
},
|
||||
{
|
||||
title: 'Query',
|
||||
description: `Dispatch queries with your SG721 contract`,
|
||||
|
||||
@@ -90,14 +90,22 @@ export const CollectionActions = ({
|
||||
subtitle: 'Address of the whitelist contract',
|
||||
})
|
||||
|
||||
const priceState = useNumberInputState({
|
||||
id: 'update-mint-price',
|
||||
name: 'updateMintPrice',
|
||||
title: 'Update Mint Price',
|
||||
subtitle: 'New minting price in STARS',
|
||||
})
|
||||
|
||||
const showWhitelistField = type === 'set_whitelist'
|
||||
const showDateField = type === 'update_start_time'
|
||||
const showDateField = isEitherType(type, ['update_start_time', 'update_start_trading_time'])
|
||||
const showLimitField = type === 'update_per_address_limit'
|
||||
const showTokenIdField = isEitherType(type, ['transfer', 'mint_for', 'burn'])
|
||||
const showNumberOfTokensField = type === 'batch_mint'
|
||||
const showTokenIdListField = isEitherType(type, ['batch_burn', 'batch_transfer'])
|
||||
const showRecipientField = isEitherType(type, ['transfer', 'mint_to', 'mint_for', 'batch_mint', 'batch_transfer'])
|
||||
const showAirdropFileField = type === 'airdrop'
|
||||
const showPriceField = type === 'update_mint_price'
|
||||
|
||||
const payload: DispatchExecuteArgs = {
|
||||
whitelist: whitelistState.value,
|
||||
@@ -114,6 +122,7 @@ export const CollectionActions = ({
|
||||
recipients: airdropArray,
|
||||
txSigner: wallet.address,
|
||||
type,
|
||||
price: priceState.value.toString(),
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@@ -140,6 +149,11 @@ export const CollectionActions = ({
|
||||
if (minterContractAddress === '' && sg721ContractAddress === '') {
|
||||
throw new Error('Please enter minter and sg721 contract addresses!')
|
||||
}
|
||||
if (type === 'update_mint_price' && priceState.value < 50) {
|
||||
console.log('here')
|
||||
throw new Error('Mint price must be at least 50 STARS')
|
||||
}
|
||||
|
||||
const txHash = await toast.promise(dispatchExecute(payload), {
|
||||
error: `${type.charAt(0).toUpperCase() + type.slice(1)} execute failed!`,
|
||||
loading: 'Executing message...',
|
||||
@@ -172,6 +186,7 @@ export const CollectionActions = ({
|
||||
{showTokenIdField && <NumberInput {...tokenIdState} />}
|
||||
{showTokenIdListField && <TextInput {...tokenIdListState} />}
|
||||
{showNumberOfTokensField && <NumberInput {...batchNumberState} />}
|
||||
{showPriceField && <NumberInput {...priceState} />}
|
||||
{showAirdropFileField && (
|
||||
<FormGroup
|
||||
subtitle="CSV file that contains the airdrop addresses and the amount of tokens allocated for each address. Should start with the following header row: address,amount"
|
||||
|
||||
@@ -6,11 +6,15 @@ import { useSG721Contract } from 'contracts/sg721'
|
||||
export type ActionType = typeof ACTION_TYPES[number]
|
||||
|
||||
export const ACTION_TYPES = [
|
||||
'mint',
|
||||
'purge',
|
||||
'update_mint_price',
|
||||
'mint_to',
|
||||
'mint_for',
|
||||
'batch_mint',
|
||||
'set_whitelist',
|
||||
'update_start_time',
|
||||
'update_start_trading_time',
|
||||
'update_per_address_limit',
|
||||
'withdraw',
|
||||
'transfer',
|
||||
@@ -19,6 +23,7 @@ export const ACTION_TYPES = [
|
||||
'batch_burn',
|
||||
'shuffle',
|
||||
'airdrop',
|
||||
'burn_remaining',
|
||||
] as const
|
||||
|
||||
export interface ActionListItem {
|
||||
@@ -28,6 +33,21 @@ export interface ActionListItem {
|
||||
}
|
||||
|
||||
export const ACTION_LIST: ActionListItem[] = [
|
||||
{
|
||||
id: 'mint',
|
||||
name: 'Mint',
|
||||
description: `Mint a token`,
|
||||
},
|
||||
{
|
||||
id: 'purge',
|
||||
name: 'Purge',
|
||||
description: `Purge`,
|
||||
},
|
||||
{
|
||||
id: 'update_mint_price',
|
||||
name: 'Update Mint Price',
|
||||
description: `Update mint price`,
|
||||
},
|
||||
{
|
||||
id: 'mint_to',
|
||||
name: 'Mint To',
|
||||
@@ -50,9 +70,14 @@ export const ACTION_LIST: ActionListItem[] = [
|
||||
},
|
||||
{
|
||||
id: 'update_start_time',
|
||||
name: 'Update Start Time',
|
||||
name: 'Update Minting Start Time',
|
||||
description: `Update start time for minting`,
|
||||
},
|
||||
{
|
||||
id: 'update_start_trading_time',
|
||||
name: 'Update Trading Start Time',
|
||||
description: `Update start time for trading`,
|
||||
},
|
||||
{
|
||||
id: 'update_per_address_limit',
|
||||
name: 'Update Tokens Per Address Limit',
|
||||
@@ -93,6 +118,11 @@ export const ACTION_LIST: ActionListItem[] = [
|
||||
name: 'Airdrop Tokens',
|
||||
description: 'Airdrop tokens to given addresses',
|
||||
},
|
||||
{
|
||||
id: 'burn_remaining',
|
||||
name: 'Burn Remaining Tokens',
|
||||
description: 'Burn remaining tokens',
|
||||
},
|
||||
]
|
||||
|
||||
export interface DispatchExecuteProps {
|
||||
@@ -111,11 +141,15 @@ export type DispatchExecuteArgs = {
|
||||
txSigner: string
|
||||
} & (
|
||||
| { type: undefined }
|
||||
| { type: Select<'mint'> }
|
||||
| { type: Select<'purge'> }
|
||||
| { type: Select<'update_mint_price'>; price: string }
|
||||
| { type: Select<'mint_to'>; recipient: string }
|
||||
| { type: Select<'mint_for'>; recipient: string; tokenId: number }
|
||||
| { type: Select<'batch_mint'>; recipient: string; batchNumber: number }
|
||||
| { type: Select<'set_whitelist'>; whitelist: string }
|
||||
| { type: Select<'update_start_time'>; startTime: string }
|
||||
| { type: Select<'update_start_trading_time'>; startTime: string }
|
||||
| { type: Select<'update_per_address_limit'>; limit: number }
|
||||
| { type: Select<'shuffle'> }
|
||||
| { type: Select<'withdraw'> }
|
||||
@@ -124,6 +158,7 @@ export type DispatchExecuteArgs = {
|
||||
| { type: Select<'burn'>; tokenId: number }
|
||||
| { type: Select<'batch_burn'>; tokenIds: string }
|
||||
| { type: Select<'airdrop'>; recipients: string[] }
|
||||
| { type: Select<'burn_remaining'> }
|
||||
)
|
||||
|
||||
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||
@@ -132,6 +167,15 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||
throw new Error('Cannot execute actions')
|
||||
}
|
||||
switch (args.type) {
|
||||
case 'mint': {
|
||||
return minterMessages.mint(txSigner)
|
||||
}
|
||||
case 'purge': {
|
||||
return minterMessages.purge(txSigner)
|
||||
}
|
||||
case 'update_mint_price': {
|
||||
return minterMessages.updateMintPrice(txSigner, args.price)
|
||||
}
|
||||
case 'mint_to': {
|
||||
return minterMessages.mintTo(txSigner, args.recipient)
|
||||
}
|
||||
@@ -147,6 +191,9 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||
case 'update_start_time': {
|
||||
return minterMessages.updateStartTime(txSigner, args.startTime)
|
||||
}
|
||||
case 'update_start_trading_time': {
|
||||
return minterMessages.updateStartTradingTime(txSigner, args.startTime)
|
||||
}
|
||||
case 'update_per_address_limit': {
|
||||
return minterMessages.updatePerAddressLimit(txSigner, args.limit)
|
||||
}
|
||||
@@ -171,6 +218,9 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||
case 'airdrop': {
|
||||
return minterMessages.airdrop(txSigner, args.recipients)
|
||||
}
|
||||
case 'burn_remaining': {
|
||||
return minterMessages.burnRemaining(txSigner)
|
||||
}
|
||||
default: {
|
||||
throw new Error('Unknown action')
|
||||
}
|
||||
@@ -184,6 +234,15 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => {
|
||||
const { messages: sg721Messages } = useSG721Contract()
|
||||
const { minterContract, sg721Contract } = args
|
||||
switch (args.type) {
|
||||
case 'mint': {
|
||||
return minterMessages(minterContract)?.mint()
|
||||
}
|
||||
case 'purge': {
|
||||
return minterMessages(minterContract)?.purge()
|
||||
}
|
||||
case 'update_mint_price': {
|
||||
return minterMessages(minterContract)?.updateMintPrice(args.price)
|
||||
}
|
||||
case 'mint_to': {
|
||||
return minterMessages(minterContract)?.mintTo(args.recipient)
|
||||
}
|
||||
@@ -199,6 +258,9 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => {
|
||||
case 'update_start_time': {
|
||||
return minterMessages(minterContract)?.updateStartTime(args.startTime)
|
||||
}
|
||||
case 'update_start_trading_time': {
|
||||
return minterMessages(minterContract)?.updateStartTradingTime(args.startTime)
|
||||
}
|
||||
case 'update_per_address_limit': {
|
||||
return minterMessages(minterContract)?.updatePerAddressLimit(args.limit)
|
||||
}
|
||||
@@ -223,6 +285,9 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => {
|
||||
case 'airdrop': {
|
||||
return minterMessages(minterContract)?.airdrop(args.recipients)
|
||||
}
|
||||
case 'burn_remaining': {
|
||||
return minterMessages(minterContract)?.burnRemaining()
|
||||
}
|
||||
default: {
|
||||
return {}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import clsx from 'clsx'
|
||||
import { FormControl } from 'components/FormControl'
|
||||
import { FormGroup } from 'components/FormGroup'
|
||||
import { useInputState } from 'components/forms/FormInput.hooks'
|
||||
import { InputDateTime } from 'components/InputDateTime'
|
||||
import type { ChangeEvent } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
@@ -26,10 +27,14 @@ export interface CollectionDetailsDataProps {
|
||||
symbol: string
|
||||
imageFile: File[]
|
||||
externalLink?: string
|
||||
startTradingTime?: string
|
||||
explicit: boolean
|
||||
}
|
||||
|
||||
export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl }: CollectionDetailsProps) => {
|
||||
const [coverImage, setCoverImage] = useState<File | null>(null)
|
||||
const [timestamp, setTimestamp] = useState<Date | undefined>()
|
||||
const [explicit, setExplicit] = useState<boolean>(false)
|
||||
|
||||
const nameState = useInputState({
|
||||
id: 'name',
|
||||
@@ -67,6 +72,8 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl }: Col
|
||||
symbol: symbolState.value,
|
||||
imageFile: coverImage ? [coverImage] : [],
|
||||
externalLink: externalLinkState.value,
|
||||
startTradingTime: timestamp ? (timestamp.getTime() * 1_000_000).toString() : '',
|
||||
explicit,
|
||||
}
|
||||
onChange(data)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -74,7 +81,15 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl }: Col
|
||||
toast.error(error.message)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [nameState.value, descriptionState.value, coverImage, externalLinkState.value])
|
||||
}, [
|
||||
nameState.value,
|
||||
descriptionState.value,
|
||||
symbolState.value,
|
||||
externalLinkState.value,
|
||||
coverImage,
|
||||
timestamp,
|
||||
explicit,
|
||||
])
|
||||
|
||||
const selectCoverImage = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.files === null) return toast.error('Error selecting cover image')
|
||||
@@ -92,12 +107,20 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl }: Col
|
||||
reader.readAsArrayBuffer(event.target.files[0])
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
console.log(explicit)
|
||||
}, [explicit])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FormGroup subtitle="Information about your collection" title="Collection Details">
|
||||
<TextInput {...nameState} isRequired />
|
||||
<TextInput {...descriptionState} isRequired />
|
||||
<TextInput {...symbolState} isRequired />
|
||||
<TextInput {...externalLinkState} />
|
||||
<FormControl htmlId="timestamp" subtitle="Trading start time (local)" title="Trading Start Time (optional)">
|
||||
<InputDateTime minDate={new Date()} onChange={(date) => setTimestamp(date)} value={timestamp} />
|
||||
</FormControl>
|
||||
|
||||
<FormControl isRequired={uploadMethod === 'new'} title="Cover Image">
|
||||
{uploadMethod === 'new' && (
|
||||
@@ -135,8 +158,51 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl }: Col
|
||||
<span className="italic font-light ">Waiting for cover image URL to be specified.</span>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
<TextInput {...externalLinkState} />
|
||||
<div className="flex flex-col space-y-2">
|
||||
<div>
|
||||
<div className="flex">
|
||||
<span className="mt-1 text-sm first-letter:capitalize">
|
||||
Does the collection contain explicit content?
|
||||
</span>
|
||||
<div className="ml-2 font-bold form-check form-check-inline">
|
||||
<input
|
||||
checked={explicit}
|
||||
className="peer sr-only"
|
||||
id="explicitRadio1"
|
||||
name="explicitRadioOptions1"
|
||||
onClick={() => {
|
||||
setExplicit(true)
|
||||
}}
|
||||
type="radio"
|
||||
/>
|
||||
<label
|
||||
className="inline-block py-1 px-2 text-sm text-gray peer-checked:text-white hover:text-white peer-checked:bg-black hover:rounded-sm peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
|
||||
htmlFor="explicitRadio1"
|
||||
>
|
||||
YES
|
||||
</label>
|
||||
</div>
|
||||
<div className="ml-2 font-bold form-check form-check-inline">
|
||||
<input
|
||||
checked={!explicit}
|
||||
className="peer sr-only"
|
||||
id="explicitRadio2"
|
||||
name="explicitRadioOptions2"
|
||||
onClick={() => {
|
||||
setExplicit(false)
|
||||
}}
|
||||
type="radio"
|
||||
/>
|
||||
<label
|
||||
className="inline-block py-1 px-2 text-sm text-gray peer-checked:text-white hover:text-white peer-checked:bg-black hover:rounded-sm peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
|
||||
htmlFor="explicitRadio2"
|
||||
>
|
||||
NO
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</FormGroup>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -69,6 +69,26 @@ export const TextInput = forwardRef<HTMLInputElement, FormInputProps>(
|
||||
//
|
||||
)
|
||||
|
||||
export const CheckBoxInput = forwardRef<HTMLInputElement, FormInputProps>(
|
||||
function CheckBoxInput(props, ref) {
|
||||
return (
|
||||
<div className="flex flex-col space-y-2">
|
||||
<label className="flex flex-col space-y-1" htmlFor="explicit">
|
||||
<span className="font-bold first-letter:capitalize">Explicit Content</span>
|
||||
</label>
|
||||
<input
|
||||
className="placeholder:text-white/50 bg-white/10 rounded border-2 border-white/20 focus:ring focus:ring-plumbus-20"
|
||||
id="explicit"
|
||||
name="explicit"
|
||||
type="checkbox"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
//
|
||||
)
|
||||
|
||||
export const UrlInput = forwardRef<HTMLInputElement, FormInputProps>(
|
||||
function UrlInput(props, ref) {
|
||||
return <FormInput {...props} ref={ref} type="url" />
|
||||
|
||||
Reference in New Issue
Block a user