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:
name-user1
2022-10-20 19:02:52 -06:00
committed by GitHub
co-authored by name-user1 Serkan Reis
parent 2c8f66a5d6
commit 039b8b424b
24 changed files with 787 additions and 308 deletions
-5
View File
@@ -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`,
+16 -1
View File
@@ -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"
+66 -1
View File
@@ -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>
)
+20
View File
@@ -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" />