Merge pull request #156 from public-awesome/develop
Sync development > main
This commit is contained in:
commit
cbbc6c5272
@ -1,16 +1,19 @@
|
||||
APP_VERSION=0.5.8
|
||||
APP_VERSION=0.5.9
|
||||
|
||||
NEXT_PUBLIC_PINATA_ENDPOINT_URL=https://api.pinata.cloud/pinning/pinFileToIPFS
|
||||
NEXT_PUBLIC_SG721_CODE_ID=1911
|
||||
NEXT_PUBLIC_SG721_UPDATABLE_CODE_ID=1912
|
||||
NEXT_PUBLIC_VENDING_MINTER_CODE_ID=1909
|
||||
NEXT_PUBLIC_VENDING_MINTER_FLEX_CODE_ID=2080
|
||||
NEXT_PUBLIC_BASE_MINTER_CODE_ID=1910
|
||||
NEXT_PUBLIC_VENDING_FACTORY_ADDRESS="stars1ynec878x5phexq3hj4zdgvp6r5ayfmxks38kvunwyjugqn3hqeqq3cgtuw"
|
||||
NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_ADDRESS="stars1fnfywcnzzwledr93at65qm8gf953tjxgh6u2u4r8n9vsdv7u75eqe7ecn3"
|
||||
NEXT_PUBLIC_VENDING_FACTORY_FLEX_ADDRESS="stars1l5v0vgly8r9jw4exeajnftymr29kn70n23gpl2g5fylaww2pzkhq0rks7c"
|
||||
NEXT_PUBLIC_BASE_FACTORY_ADDRESS="stars10rmaxgnjvskuumgv7e2awqkhdqdcygkwrz8a8vvt88szj7fc7xlq5jcs3f"
|
||||
NEXT_PUBLIC_BASE_FACTORY_UPDATABLE_ADDRESS="stars13pw8r33dsnghlxfj2upaywf38z2fc6npuw9maq9e5cpet4v285sscgzjp2"
|
||||
NEXT_PUBLIC_SG721_NAME_ADDRESS="stars1fx74nkqkw2748av8j7ew7r3xt9cgjqduwn8m0ur5lhe49uhlsasszc5fhr"
|
||||
NEXT_PUBLIC_WHITELIST_CODE_ID=1913
|
||||
NEXT_PUBLIC_WHITELIST_FLEX_CODE_ID=2005
|
||||
NEXT_PUBLIC_BADGE_HUB_CODE_ID=1336
|
||||
NEXT_PUBLIC_BADGE_HUB_ADDRESS="stars1dacun0xn7z73qzdcmq27q3xn6xuprg8e2ugj364784al2v27tklqynhuqa"
|
||||
NEXT_PUBLIC_BADGE_NFT_CODE_ID=1337
|
||||
|
116
components/WhitelistFlexUpload.tsx
Normal file
116
components/WhitelistFlexUpload.tsx
Normal file
@ -0,0 +1,116 @@
|
||||
import { toUtf8 } from '@cosmjs/encoding'
|
||||
import clsx from 'clsx'
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import React, { useState } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { SG721_NAME_ADDRESS } from 'utils/constants'
|
||||
import { csvToFlexList } from 'utils/csvToFlexList'
|
||||
import { isValidAddress } from 'utils/isValidAddress'
|
||||
import { isValidFlexListFile } from 'utils/isValidFlexListFile'
|
||||
|
||||
export interface WhitelistFlexMember {
|
||||
address: string
|
||||
mint_count: number
|
||||
}
|
||||
|
||||
interface WhitelistFlexUploadProps {
|
||||
onChange: (data: WhitelistFlexMember[]) => void
|
||||
}
|
||||
|
||||
export const WhitelistFlexUpload = ({ onChange }: WhitelistFlexUploadProps) => {
|
||||
const wallet = useWallet()
|
||||
const [resolvedMemberData, setResolvedMemberData] = useState<WhitelistFlexMember[]>([])
|
||||
|
||||
const resolveMemberData = async (memberData: WhitelistFlexMember[]) => {
|
||||
if (!memberData.length) return []
|
||||
await new Promise((resolve) => {
|
||||
let i = 0
|
||||
memberData.map(async (data) => {
|
||||
if (!wallet.client) throw new Error('Wallet not connected')
|
||||
await wallet.client
|
||||
.queryContractRaw(
|
||||
SG721_NAME_ADDRESS,
|
||||
toUtf8(
|
||||
Buffer.from(
|
||||
`0006${Buffer.from('tokens').toString('hex')}${Buffer.from(
|
||||
data.address.trim().substring(0, data.address.lastIndexOf('.stars')),
|
||||
).toString('hex')}`,
|
||||
'hex',
|
||||
).toString(),
|
||||
),
|
||||
)
|
||||
.then((res) => {
|
||||
const tokenUri = JSON.parse(new TextDecoder().decode(res as Uint8Array)).token_uri
|
||||
if (tokenUri && isValidAddress(tokenUri))
|
||||
resolvedMemberData.push({ address: tokenUri, mint_count: Number(data.mint_count) })
|
||||
else toast.error(`Resolved address is empty or invalid for the name: ${data.address}`)
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e)
|
||||
toast.error(`Error resolving address for the name: ${data.address}`)
|
||||
})
|
||||
|
||||
i++
|
||||
if (i === memberData.length) resolve(resolvedMemberData)
|
||||
})
|
||||
})
|
||||
return resolvedMemberData
|
||||
}
|
||||
|
||||
const onFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setResolvedMemberData([])
|
||||
if (!event.target.files) return toast.error('Error opening file')
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (!event.target.files[0]?.name.endsWith('.csv')) {
|
||||
toast.error('Please select a .csv file!')
|
||||
return onChange([])
|
||||
}
|
||||
const reader = new FileReader()
|
||||
reader.onload = async (e: ProgressEvent<FileReader>) => {
|
||||
try {
|
||||
if (!e.target?.result) return toast.error('Error parsing file.')
|
||||
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
||||
const memberData = csvToFlexList(e.target.result.toString())
|
||||
console.log(memberData)
|
||||
if (!isValidFlexListFile(memberData)) {
|
||||
event.target.value = ''
|
||||
return onChange([])
|
||||
}
|
||||
await resolveMemberData(memberData.filter((data) => data.address.trim().endsWith('.stars'))).finally(() => {
|
||||
return onChange(
|
||||
memberData
|
||||
.filter((data) => data.address.startsWith('stars') && !data.address.endsWith('.stars'))
|
||||
.map((data) => ({
|
||||
address: data.address.trim(),
|
||||
mint_count: Number(data.mint_count),
|
||||
}))
|
||||
.concat(resolvedMemberData),
|
||||
)
|
||||
})
|
||||
} catch (error: any) {
|
||||
toast.error(error.message, { style: { maxWidth: 'none' } })
|
||||
}
|
||||
}
|
||||
reader.readAsText(event.target.files[0])
|
||||
}
|
||||
|
||||
return (
|
||||
<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=".csv"
|
||||
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="whitelist-flex-file"
|
||||
onChange={onFileChange}
|
||||
type="file"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -4,6 +4,8 @@ import { AddressList } from 'components/forms/AddressList'
|
||||
import { useAddressListState } from 'components/forms/AddressList.hooks'
|
||||
import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks'
|
||||
import { InputDateTime } from 'components/InputDateTime'
|
||||
import type { WhitelistFlexMember } from 'components/WhitelistFlexUpload'
|
||||
import { WhitelistFlexUpload } from 'components/WhitelistFlexUpload'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { isValidAddress } from 'utils/isValidAddress'
|
||||
|
||||
@ -17,9 +19,10 @@ interface WhitelistDetailsProps {
|
||||
}
|
||||
|
||||
export interface WhitelistDetailsDataProps {
|
||||
whitelistType: WhitelistState
|
||||
whitelistState: WhitelistState
|
||||
whitelistType: WhitelistType
|
||||
contractAddress?: string
|
||||
members?: string[]
|
||||
members?: string[] | WhitelistFlexMember[]
|
||||
unitPrice?: string
|
||||
startTime?: string
|
||||
endTime?: string
|
||||
@ -31,11 +34,15 @@ export interface WhitelistDetailsDataProps {
|
||||
|
||||
type WhitelistState = 'none' | 'existing' | 'new'
|
||||
|
||||
type WhitelistType = 'standard' | 'flex'
|
||||
|
||||
export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
|
||||
const [whitelistState, setWhitelistState] = useState<WhitelistState>('none')
|
||||
const [whitelistType, setWhitelistType] = useState<WhitelistType>('standard')
|
||||
const [startDate, setStartDate] = useState<Date | undefined>(undefined)
|
||||
const [endDate, setEndDate] = useState<Date | undefined>(undefined)
|
||||
const [whitelistArray, setWhitelistArray] = useState<string[]>([])
|
||||
const [whitelistStandardArray, setWhitelistStandardArray] = useState<string[]>([])
|
||||
const [whitelistFlexArray, setWhitelistFlexArray] = useState<WhitelistFlexMember[]>([])
|
||||
const [adminsMutable, setAdminsMutable] = useState<boolean>(true)
|
||||
|
||||
const whitelistAddressState = useInputState({
|
||||
@ -72,19 +79,29 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
|
||||
const addressListState = useAddressListState()
|
||||
|
||||
const whitelistFileOnChange = (data: string[]) => {
|
||||
setWhitelistArray(data)
|
||||
setWhitelistStandardArray(data)
|
||||
}
|
||||
|
||||
const whitelistFlexFileOnChange = (whitelistData: WhitelistFlexMember[]) => {
|
||||
setWhitelistFlexArray(whitelistData)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setWhitelistStandardArray([])
|
||||
setWhitelistFlexArray([])
|
||||
}, [whitelistType])
|
||||
|
||||
useEffect(() => {
|
||||
const data: WhitelistDetailsDataProps = {
|
||||
whitelistType: whitelistState,
|
||||
whitelistState,
|
||||
whitelistType,
|
||||
contractAddress: whitelistAddressState.value
|
||||
.toLowerCase()
|
||||
.replace(/,/g, '')
|
||||
.replace(/"/g, '')
|
||||
.replace(/'/g, '')
|
||||
.replace(/ /g, ''),
|
||||
members: whitelistArray,
|
||||
members: whitelistType === 'standard' ? whitelistStandardArray : whitelistFlexArray,
|
||||
unitPrice: unitPriceState.value ? (Number(unitPriceState.value) * 1_000_000).toString() : '',
|
||||
startTime: startDate ? (startDate.getTime() * 1_000_000).toString() : '',
|
||||
endTime: endDate ? (endDate.getTime() * 1_000_000).toString() : '',
|
||||
@ -108,7 +125,8 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
|
||||
perAddressLimitState.value,
|
||||
startDate,
|
||||
endDate,
|
||||
whitelistArray,
|
||||
whitelistStandardArray,
|
||||
whitelistFlexArray,
|
||||
whitelistState,
|
||||
addressListState.values,
|
||||
adminsMutable,
|
||||
@ -125,6 +143,7 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
|
||||
name="whitelistRadioOptions1"
|
||||
onClick={() => {
|
||||
setWhitelistState('none')
|
||||
setWhitelistType('standard')
|
||||
}}
|
||||
type="radio"
|
||||
value="None"
|
||||
@ -181,11 +200,54 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
|
||||
</Conditional>
|
||||
|
||||
<Conditional test={whitelistState === 'new'}>
|
||||
<div className="flex justify-between mb-5 ml-6 max-w-[300px] text-lg font-bold">
|
||||
<div className="form-check form-check-inline">
|
||||
<input
|
||||
checked={whitelistType === 'standard'}
|
||||
className="peer sr-only"
|
||||
id="inlineRadio7"
|
||||
name="inlineRadioOptions7"
|
||||
onClick={() => {
|
||||
setWhitelistType('standard')
|
||||
}}
|
||||
type="radio"
|
||||
value="nft-storage"
|
||||
/>
|
||||
<label
|
||||
className="inline-block py-1 px-2 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="inlineRadio7"
|
||||
>
|
||||
Standard Whitelist
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="form-check form-check-inline">
|
||||
<input
|
||||
checked={whitelistType === 'flex'}
|
||||
className="peer sr-only"
|
||||
id="inlineRadio8"
|
||||
name="inlineRadioOptions8"
|
||||
onClick={() => {
|
||||
setWhitelistType('flex')
|
||||
}}
|
||||
type="radio"
|
||||
value="flex"
|
||||
/>
|
||||
<label
|
||||
className="inline-block py-1 px-2 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="inlineRadio8"
|
||||
>
|
||||
Whitelist Flex
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2">
|
||||
<FormGroup subtitle="Information about your minting settings" title="Whitelist Minting Details">
|
||||
<NumberInput isRequired {...unitPriceState} />
|
||||
<NumberInput isRequired {...memberLimitState} />
|
||||
<NumberInput isRequired {...perAddressLimitState} />
|
||||
<Conditional test={whitelistType === 'standard'}>
|
||||
<NumberInput isRequired {...perAddressLimitState} />
|
||||
</Conditional>
|
||||
<FormControl
|
||||
htmlId="start-date"
|
||||
isRequired
|
||||
@ -226,11 +288,24 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
|
||||
title="Administrator Addresses"
|
||||
/>
|
||||
</div>
|
||||
<FormGroup subtitle="TXT file that contains the whitelisted addresses" title="Whitelist File">
|
||||
<WhitelistUpload onChange={whitelistFileOnChange} />
|
||||
</FormGroup>
|
||||
<Conditional test={whitelistArray.length > 0}>
|
||||
<JsonPreview content={whitelistArray} initialState title="File Contents" />
|
||||
<Conditional test={whitelistType === 'standard'}>
|
||||
<FormGroup subtitle="TXT file that contains the whitelisted addresses" title="Whitelist File">
|
||||
<WhitelistUpload onChange={whitelistFileOnChange} />
|
||||
</FormGroup>
|
||||
<Conditional test={whitelistStandardArray.length > 0}>
|
||||
<JsonPreview content={whitelistStandardArray} initialState title="File Contents" />
|
||||
</Conditional>
|
||||
</Conditional>
|
||||
<Conditional test={whitelistType === 'flex'}>
|
||||
<FormGroup
|
||||
subtitle="CSV file that contains the whitelisted addresses and their corresponding mint counts"
|
||||
title="Whitelist File"
|
||||
>
|
||||
<WhitelistFlexUpload onChange={whitelistFlexFileOnChange} />
|
||||
</FormGroup>
|
||||
<Conditional test={whitelistFlexArray.length > 0}>
|
||||
<JsonPreview content={whitelistFlexArray} initialState={false} title="File Contents" />
|
||||
</Conditional>
|
||||
</Conditional>
|
||||
</div>
|
||||
</div>
|
||||
|
32
components/forms/FlexMemberAttributes.hooks.ts
Normal file
32
components/forms/FlexMemberAttributes.hooks.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import type { WhitelistFlexMember } from 'components/WhitelistFlexUpload'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { uid } from 'utils/random'
|
||||
|
||||
export function useFlexMemberAttributesState() {
|
||||
const [record, setRecord] = useState<Record<string, WhitelistFlexMember>>(() => ({}))
|
||||
|
||||
const entries = useMemo(() => Object.entries(record), [record])
|
||||
const values = useMemo(() => Object.values(record), [record])
|
||||
|
||||
function add(attribute: WhitelistFlexMember = { address: '', mint_count: 0 }) {
|
||||
setRecord((prev) => ({ ...prev, [uid()]: attribute }))
|
||||
}
|
||||
|
||||
function update(key: string, attribute = record[key]) {
|
||||
setRecord((prev) => ({ ...prev, [key]: attribute }))
|
||||
}
|
||||
|
||||
function remove(key: string) {
|
||||
return setRecord((prev) => {
|
||||
const latest = { ...prev }
|
||||
delete latest[key]
|
||||
return latest
|
||||
})
|
||||
}
|
||||
|
||||
function reset() {
|
||||
setRecord({})
|
||||
}
|
||||
|
||||
return { entries, values, add, update, remove, reset }
|
||||
}
|
133
components/forms/FlexMemberAttributes.tsx
Normal file
133
components/forms/FlexMemberAttributes.tsx
Normal file
@ -0,0 +1,133 @@
|
||||
import { toUtf8 } from '@cosmjs/encoding'
|
||||
import { FormControl } from 'components/FormControl'
|
||||
import { AddressInput, NumberInput } from 'components/forms/FormInput'
|
||||
import type { WhitelistFlexMember } from 'components/WhitelistFlexUpload'
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import { useEffect, useId, useMemo } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
import { FaMinus, FaPlus } from 'react-icons/fa'
|
||||
import { SG721_NAME_ADDRESS } from 'utils/constants'
|
||||
import { isValidAddress } from 'utils/isValidAddress'
|
||||
|
||||
import { useInputState, useNumberInputState } from './FormInput.hooks'
|
||||
|
||||
export interface FlexMemberAttributesProps {
|
||||
title: string
|
||||
subtitle?: string
|
||||
isRequired?: boolean
|
||||
attributes: [string, WhitelistFlexMember][]
|
||||
onAdd: () => void
|
||||
onChange: (key: string, attribute: WhitelistFlexMember) => void
|
||||
onRemove: (key: string) => void
|
||||
}
|
||||
|
||||
export function FlexMemberAttributes(props: FlexMemberAttributesProps) {
|
||||
const { title, subtitle, isRequired, attributes, onAdd, onChange, onRemove } = props
|
||||
|
||||
return (
|
||||
<FormControl isRequired={isRequired} subtitle={subtitle} title={title}>
|
||||
{attributes.map(([id], i) => (
|
||||
<FlexMemberAttribute
|
||||
key={`ma-${id}`}
|
||||
defaultAttribute={attributes[i][1]}
|
||||
id={id}
|
||||
isLast={i === attributes.length - 1}
|
||||
onAdd={onAdd}
|
||||
onChange={onChange}
|
||||
onRemove={onRemove}
|
||||
/>
|
||||
))}
|
||||
</FormControl>
|
||||
)
|
||||
}
|
||||
|
||||
export interface MemberAttributeProps {
|
||||
id: string
|
||||
isLast: boolean
|
||||
onAdd: FlexMemberAttributesProps['onAdd']
|
||||
onChange: FlexMemberAttributesProps['onChange']
|
||||
onRemove: FlexMemberAttributesProps['onRemove']
|
||||
defaultAttribute: WhitelistFlexMember
|
||||
}
|
||||
|
||||
export function FlexMemberAttribute({ id, isLast, onAdd, onChange, onRemove, defaultAttribute }: MemberAttributeProps) {
|
||||
const wallet = useWallet()
|
||||
const Icon = useMemo(() => (isLast ? FaPlus : FaMinus), [isLast])
|
||||
|
||||
const htmlId = useId()
|
||||
|
||||
const addressState = useInputState({
|
||||
id: `ma-address-${htmlId}`,
|
||||
name: `ma-address-${htmlId}`,
|
||||
title: `Address`,
|
||||
defaultValue: defaultAttribute.address,
|
||||
})
|
||||
|
||||
const mintCountState = useNumberInputState({
|
||||
id: `mint-count-${htmlId}`,
|
||||
name: `mint-count-${htmlId}`,
|
||||
title: `Mint Count`,
|
||||
defaultValue: defaultAttribute.mint_count,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
onChange(id, { address: addressState.value, mint_count: mintCountState.value })
|
||||
}, [addressState.value, mintCountState.value, id])
|
||||
|
||||
const resolveAddress = async (name: string) => {
|
||||
if (!wallet.client) throw new Error('Wallet not connected')
|
||||
await wallet.client
|
||||
.queryContractRaw(
|
||||
SG721_NAME_ADDRESS,
|
||||
toUtf8(
|
||||
Buffer.from(
|
||||
`0006${Buffer.from('tokens').toString('hex')}${Buffer.from(name).toString('hex')}`,
|
||||
'hex',
|
||||
).toString(),
|
||||
),
|
||||
)
|
||||
.then((res) => {
|
||||
const tokenUri = JSON.parse(new TextDecoder().decode(res as Uint8Array)).token_uri
|
||||
if (tokenUri && isValidAddress(tokenUri)) onChange(id, { address: tokenUri, mint_count: mintCountState.value })
|
||||
else {
|
||||
toast.error(`Resolved address is empty or invalid for the name: ${name}.stars`)
|
||||
onChange(id, { address: '', mint_count: mintCountState.value })
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.error(`Error resolving address for the name: ${name}.stars`)
|
||||
console.error(err)
|
||||
onChange(id, { address: '', mint_count: mintCountState.value })
|
||||
})
|
||||
}
|
||||
useEffect(() => {
|
||||
if (addressState.value.endsWith('.stars')) {
|
||||
void resolveAddress(addressState.value.split('.')[0])
|
||||
} else {
|
||||
onChange(id, {
|
||||
address: addressState.value,
|
||||
mint_count: mintCountState.value,
|
||||
})
|
||||
}
|
||||
}, [addressState.value, id])
|
||||
|
||||
return (
|
||||
<div className="grid relative md:grid-cols-[50%_43%_7%] lg:grid-cols-[65%_28%_7%] 2xl:grid-cols-[70%_23%_7%] 2xl:space-x-2">
|
||||
<AddressInput {...addressState} />
|
||||
<NumberInput className="ml-2" {...mintCountState} />
|
||||
|
||||
<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"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
isLast ? onAdd() : onRemove(id)
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<Icon className="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
/* eslint-disable no-nested-ternary */
|
||||
import type { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
||||
import type { Coin } from '@cosmjs/proto-signing'
|
||||
import type { logs } from '@cosmjs/stargate'
|
||||
import { VENDING_FACTORY_ADDRESS } from 'utils/constants'
|
||||
import { VENDING_FACTORY_ADDRESS, VENDING_FACTORY_FLEX_ADDRESS } from 'utils/constants'
|
||||
|
||||
import { VENDING_FACTORY_UPDATABLE_ADDRESS } from '../../utils/constants'
|
||||
|
||||
@ -23,11 +25,17 @@ export interface VendingFactoryInstance {
|
||||
msg: Record<string, unknown>,
|
||||
funds: Coin[],
|
||||
updatable?: boolean,
|
||||
flex?: boolean,
|
||||
) => Promise<CreateVendingMinterResponse>
|
||||
}
|
||||
|
||||
export interface VendingFactoryMessages {
|
||||
createVendingMinter: (msg: Record<string, unknown>, funds: Coin[], updatable?: boolean) => CreateVendingMinterMessage
|
||||
createVendingMinter: (
|
||||
msg: Record<string, unknown>,
|
||||
funds: Coin[],
|
||||
updatable?: boolean,
|
||||
flex?: boolean,
|
||||
) => CreateVendingMinterMessage
|
||||
}
|
||||
|
||||
export interface CreateVendingMinterMessage {
|
||||
@ -53,10 +61,11 @@ export const vendingFactory = (client: SigningCosmWasmClient, txSigner: string):
|
||||
msg: Record<string, unknown>,
|
||||
funds: Coin[],
|
||||
updatable?: boolean,
|
||||
flex?: boolean,
|
||||
): Promise<CreateVendingMinterResponse> => {
|
||||
const result = await client.execute(
|
||||
senderAddress,
|
||||
updatable ? VENDING_FACTORY_UPDATABLE_ADDRESS : VENDING_FACTORY_ADDRESS,
|
||||
flex ? VENDING_FACTORY_FLEX_ADDRESS : updatable ? VENDING_FACTORY_UPDATABLE_ADDRESS : VENDING_FACTORY_ADDRESS,
|
||||
msg,
|
||||
'auto',
|
||||
'',
|
||||
@ -82,6 +91,7 @@ export const vendingFactory = (client: SigningCosmWasmClient, txSigner: string):
|
||||
msg: Record<string, unknown>,
|
||||
funds: Coin[],
|
||||
updatable?: boolean,
|
||||
flex?: boolean,
|
||||
): CreateVendingMinterMessage => {
|
||||
return {
|
||||
sender: txSigner,
|
||||
|
@ -11,6 +11,7 @@ export interface DispatchExecuteArgs {
|
||||
msg: Record<string, unknown>
|
||||
funds: Coin[]
|
||||
updatable?: boolean
|
||||
flex?: boolean
|
||||
}
|
||||
|
||||
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||
@ -18,12 +19,12 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||
if (!messages) {
|
||||
throw new Error('cannot dispatch execute, messages is not defined')
|
||||
}
|
||||
return messages.createVendingMinter(txSigner, args.msg, args.funds, args.updatable)
|
||||
return messages.createVendingMinter(txSigner, args.msg, args.funds, args.updatable, args.flex)
|
||||
}
|
||||
|
||||
export const previewExecutePayload = (args: DispatchExecuteArgs) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const { messages } = useVendingFactoryContract()
|
||||
const { contract } = args
|
||||
return messages(contract)?.createVendingMinter(args.msg, args.funds, args.updatable)
|
||||
return messages(contract)?.createVendingMinter(args.msg, args.funds, args.updatable, args.flex)
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import type { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
||||
import type { Coin } from '@cosmjs/proto-signing'
|
||||
import { coin } from '@cosmjs/proto-signing'
|
||||
import type { WhitelistFlexMember } from 'components/WhitelistFlexUpload'
|
||||
|
||||
export interface InstantiateResponse {
|
||||
readonly contractAddress: string
|
||||
@ -30,7 +31,7 @@ export interface WhiteListInstance {
|
||||
//Execute
|
||||
updateStartTime: (startTime: string) => Promise<string>
|
||||
updateEndTime: (endTime: string) => Promise<string>
|
||||
addMembers: (memberList: string[]) => Promise<string>
|
||||
addMembers: (memberList: string[] | WhitelistFlexMember[]) => Promise<string>
|
||||
removeMembers: (memberList: string[]) => Promise<string>
|
||||
updatePerAddressLimit: (limit: number) => Promise<string>
|
||||
increaseMemberLimit: (limit: number) => Promise<string>
|
||||
@ -41,7 +42,7 @@ export interface WhiteListInstance {
|
||||
export interface WhitelistMessages {
|
||||
updateStartTime: (startTime: string) => UpdateStartTimeMessage
|
||||
updateEndTime: (endTime: string) => UpdateEndTimeMessage
|
||||
addMembers: (memberList: string[]) => AddMembersMessage
|
||||
addMembers: (memberList: string[] | WhitelistFlexMember[]) => AddMembersMessage
|
||||
removeMembers: (memberList: string[]) => RemoveMembersMessage
|
||||
updatePerAddressLimit: (limit: number) => UpdatePerAddressLimitMessage
|
||||
increaseMemberLimit: (limit: number) => IncreaseMemberLimitMessage
|
||||
@ -86,7 +87,7 @@ export interface AddMembersMessage {
|
||||
sender: string
|
||||
contract: string
|
||||
msg: {
|
||||
add_members: { to_add: string[] }
|
||||
add_members: { to_add: string[] | WhitelistFlexMember[] }
|
||||
}
|
||||
funds: Coin[]
|
||||
}
|
||||
@ -182,7 +183,7 @@ export const WhiteList = (client: SigningCosmWasmClient, txSigner: string): Whit
|
||||
return res.transactionHash
|
||||
}
|
||||
|
||||
const addMembers = async (memberList: string[]): Promise<string> => {
|
||||
const addMembers = async (memberList: string[] | WhitelistFlexMember[]): Promise<string> => {
|
||||
const res = await client.execute(
|
||||
txSigner,
|
||||
contractAddress,
|
||||
@ -307,7 +308,7 @@ export const WhiteList = (client: SigningCosmWasmClient, txSigner: string): Whit
|
||||
}
|
||||
}
|
||||
|
||||
const addMembers = (memberList: string[]) => {
|
||||
const addMembers = (memberList: string[] | WhitelistFlexMember[]) => {
|
||||
return {
|
||||
sender: txSigner,
|
||||
contract: contractAddress,
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { WhitelistFlexMember } from '../../../components/WhitelistFlexUpload'
|
||||
import type { WhiteListInstance } from '../index'
|
||||
import { useWhiteListContract } from '../index'
|
||||
|
||||
@ -68,23 +69,16 @@ export interface DispatchExecuteProps {
|
||||
[k: string]: unknown
|
||||
}
|
||||
|
||||
type Select<T extends ExecuteType> = T
|
||||
|
||||
/** @see {@link WhiteListInstance} */
|
||||
export type DispatchExecuteArgs = {
|
||||
export interface DispatchExecuteArgs {
|
||||
contract: string
|
||||
messages?: WhiteListInstance
|
||||
} & (
|
||||
| { type: undefined }
|
||||
| { type: Select<'update_start_time'>; timestamp: string }
|
||||
| { type: Select<'update_end_time'>; timestamp: string }
|
||||
| { type: Select<'add_members'>; members: string[] }
|
||||
| { type: Select<'remove_members'>; members: string[] }
|
||||
| { type: Select<'update_per_address_limit'>; limit: number }
|
||||
| { type: Select<'increase_member_limit'>; limit: number }
|
||||
| { type: Select<'update_admins'>; admins: string[] }
|
||||
| { type: Select<'freeze'> }
|
||||
)
|
||||
type: string | undefined
|
||||
timestamp: string
|
||||
members: string[] | WhitelistFlexMember[]
|
||||
limit: number
|
||||
admins: string[]
|
||||
}
|
||||
|
||||
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||
const { messages } = args
|
||||
@ -105,7 +99,7 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||
return messages.addMembers(args.members)
|
||||
}
|
||||
case 'remove_members': {
|
||||
return messages.removeMembers(args.members)
|
||||
return messages.removeMembers(args.members as string[])
|
||||
}
|
||||
case 'update_per_address_limit': {
|
||||
return messages.updatePerAddressLimit(args.limit)
|
||||
@ -140,7 +134,7 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => {
|
||||
return messages(contract)?.addMembers(args.members)
|
||||
}
|
||||
case 'remove_members': {
|
||||
return messages(contract)?.removeMembers(args.members)
|
||||
return messages(contract)?.removeMembers(args.members as string[])
|
||||
}
|
||||
case 'update_per_address_limit': {
|
||||
return messages(contract)?.updatePerAddressLimit(args.limit)
|
||||
|
3
env.d.ts
vendored
3
env.d.ts
vendored
@ -17,9 +17,12 @@ declare namespace NodeJS {
|
||||
readonly NEXT_PUBLIC_SG721_CODE_ID: string
|
||||
readonly NEXT_PUBLIC_SG721_UPDATABLE_CODE_ID: string
|
||||
readonly NEXT_PUBLIC_WHITELIST_CODE_ID: string
|
||||
readonly NEXT_PUBLIC_WHITELIST_FLEX_CODE_ID: string
|
||||
readonly NEXT_PUBLIC_VENDING_MINTER_CODE_ID: string
|
||||
readonly NEXT_PUBLIC_VENDING_MINTER_FLEX_CODE_ID: string
|
||||
readonly NEXT_PUBLIC_VENDING_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_FACTORY_FLEX_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_BASE_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_BASE_FACTORY_UPDATABLE_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_SG721_NAME_ADDRESS: string
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "stargaze-studio",
|
||||
"version": "0.5.8",
|
||||
"version": "0.5.9",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
|
@ -145,7 +145,7 @@ const CollectionActionsPage: NextPage = () => {
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
setMinterType('base')
|
||||
setSg721Type('base')
|
||||
console.log('Unable to retrieve contract type. Defaulting to "base".')
|
||||
})
|
||||
}, [debouncedSg721ContractState, wallet.client])
|
||||
|
@ -49,8 +49,10 @@ import {
|
||||
SG721_UPDATABLE_CODE_ID,
|
||||
STARGAZE_URL,
|
||||
VENDING_FACTORY_ADDRESS,
|
||||
VENDING_FACTORY_FLEX_ADDRESS,
|
||||
VENDING_FACTORY_UPDATABLE_ADDRESS,
|
||||
WHITELIST_CODE_ID,
|
||||
WHITELIST_FLEX_CODE_ID,
|
||||
} from 'utils/constants'
|
||||
import { withMetadata } from 'utils/layout'
|
||||
import { links } from 'utils/links'
|
||||
@ -93,9 +95,11 @@ const CollectionCreationPage: NextPage = () => {
|
||||
const [vendingMinterCreationFee, setVendingMinterCreationFee] = useState<string | null>(null)
|
||||
const [baseMinterCreationFee, setBaseMinterCreationFee] = useState<string | null>(null)
|
||||
const [vendingMinterUpdatableCreationFee, setVendingMinterUpdatableCreationFee] = useState<string | null>(null)
|
||||
const [vendingMinterFlexCreationFee, setVendingMinterFlexCreationFee] = useState<string | null>(null)
|
||||
const [baseMinterUpdatableCreationFee, setBaseMinterUpdatableCreationFee] = useState<string | null>(null)
|
||||
const [minimumMintPrice, setMinimumMintPrice] = useState<string | null>('0')
|
||||
const [minimumUpdatableMintPrice, setMinimumUpdatableMintPrice] = useState<string | null>('0')
|
||||
const [minimumFlexMintPrice, setMinimumFlexMintPrice] = useState<string | null>('0')
|
||||
|
||||
const [uploading, setUploading] = useState(false)
|
||||
const [isMintingComplete, setIsMintingComplete] = useState(false)
|
||||
@ -221,8 +225,8 @@ const CollectionCreationPage: NextPage = () => {
|
||||
setCoverImageUrl(coverImageUri)
|
||||
|
||||
let whitelist: string | undefined
|
||||
if (whitelistDetails?.whitelistType === 'existing') whitelist = whitelistDetails.contractAddress
|
||||
else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist()
|
||||
if (whitelistDetails?.whitelistState === 'existing') whitelist = whitelistDetails.contractAddress
|
||||
else if (whitelistDetails?.whitelistState === 'new') whitelist = await instantiateWhitelist()
|
||||
setWhitelistContractAddress(whitelist as string)
|
||||
|
||||
await instantiateVendingMinter(baseUri, coverImageUri, whitelist)
|
||||
@ -231,8 +235,8 @@ const CollectionCreationPage: NextPage = () => {
|
||||
setCoverImageUrl(uploadDetails?.imageUrl as string)
|
||||
|
||||
let whitelist: string | undefined
|
||||
if (whitelistDetails?.whitelistType === 'existing') whitelist = whitelistDetails.contractAddress
|
||||
else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist()
|
||||
if (whitelistDetails?.whitelistState === 'existing') whitelist = whitelistDetails.contractAddress
|
||||
else if (whitelistDetails?.whitelistState === 'new') whitelist = await instantiateWhitelist()
|
||||
setWhitelistContractAddress(whitelist as string)
|
||||
|
||||
await instantiateVendingMinter(baseTokenUri as string, coverImageUrl as string, whitelist)
|
||||
@ -401,7 +405,7 @@ const CollectionCreationPage: NextPage = () => {
|
||||
if (!wallet.initialized) throw new Error('Wallet not connected')
|
||||
if (!whitelistContract) throw new Error('Contract not found')
|
||||
|
||||
const msg = {
|
||||
const standardMsg = {
|
||||
members: whitelistDetails?.members,
|
||||
start_time: whitelistDetails?.startTime,
|
||||
end_time: whitelistDetails?.endTime,
|
||||
@ -412,9 +416,19 @@ const CollectionCreationPage: NextPage = () => {
|
||||
admins_mutable: whitelistDetails?.adminsMutable,
|
||||
}
|
||||
|
||||
const flexMsg = {
|
||||
members: whitelistDetails?.members,
|
||||
start_time: whitelistDetails?.startTime,
|
||||
end_time: whitelistDetails?.endTime,
|
||||
mint_price: coin(String(Number(whitelistDetails?.unitPrice)), 'ustars'),
|
||||
member_limit: whitelistDetails?.memberLimit,
|
||||
admins: whitelistDetails?.admins || [wallet.address],
|
||||
admins_mutable: whitelistDetails?.adminsMutable,
|
||||
}
|
||||
|
||||
const data = await whitelistContract.instantiate(
|
||||
WHITELIST_CODE_ID,
|
||||
msg,
|
||||
whitelistDetails?.whitelistType === 'standard' ? WHITELIST_CODE_ID : WHITELIST_FLEX_CODE_ID,
|
||||
whitelistDetails?.whitelistType === 'standard' ? standardMsg : flexMsg,
|
||||
'Stargaze Whitelist Contract',
|
||||
wallet.address,
|
||||
)
|
||||
@ -469,20 +483,40 @@ const CollectionCreationPage: NextPage = () => {
|
||||
},
|
||||
}
|
||||
|
||||
console.log('Whitelist State: ', whitelistDetails?.whitelistState)
|
||||
console.log('Whitelist Type: ', whitelistDetails?.whitelistType)
|
||||
console.log(
|
||||
'Factory Address: ',
|
||||
whitelistDetails?.whitelistState !== 'none' && whitelistDetails?.whitelistType === 'flex'
|
||||
? VENDING_FACTORY_FLEX_ADDRESS
|
||||
: collectionDetails?.updatable
|
||||
? VENDING_FACTORY_UPDATABLE_ADDRESS
|
||||
: VENDING_FACTORY_ADDRESS,
|
||||
)
|
||||
|
||||
console.log('Whitelist: ', whitelist)
|
||||
const payload: VendingFactoryDispatchExecuteArgs = {
|
||||
contract: collectionDetails?.updatable ? VENDING_FACTORY_UPDATABLE_ADDRESS : VENDING_FACTORY_ADDRESS,
|
||||
contract:
|
||||
whitelistDetails?.whitelistState !== 'none' && whitelistDetails?.whitelistType === 'flex'
|
||||
? VENDING_FACTORY_FLEX_ADDRESS
|
||||
: collectionDetails?.updatable
|
||||
? VENDING_FACTORY_UPDATABLE_ADDRESS
|
||||
: VENDING_FACTORY_ADDRESS,
|
||||
messages: vendingFactoryMessages,
|
||||
txSigner: wallet.address,
|
||||
msg,
|
||||
funds: [
|
||||
coin(
|
||||
collectionDetails?.updatable
|
||||
whitelistDetails?.whitelistState !== 'none' && whitelistDetails?.whitelistType === 'flex'
|
||||
? (vendingMinterFlexCreationFee as string)
|
||||
: collectionDetails?.updatable
|
||||
? (vendingMinterUpdatableCreationFee as string)
|
||||
: (vendingMinterCreationFee as string),
|
||||
'ustars',
|
||||
),
|
||||
],
|
||||
updatable: collectionDetails?.updatable,
|
||||
flex: whitelistDetails?.whitelistState !== 'none' && whitelistDetails?.whitelistType === 'flex',
|
||||
}
|
||||
const data = await vendingFactoryDispatchExecute(payload)
|
||||
setTransactionHash(data.transactionHash)
|
||||
@ -786,7 +820,10 @@ const CollectionCreationPage: NextPage = () => {
|
||||
const checkMintingDetails = () => {
|
||||
if (!mintingDetails) throw new Error('Please fill out the minting details')
|
||||
if (mintingDetails.numTokens < 1 || mintingDetails.numTokens > 10000) throw new Error('Invalid number of tokens')
|
||||
if (collectionDetails?.updatable) {
|
||||
if (whitelistDetails?.whitelistState !== 'none' && whitelistDetails?.whitelistType === 'flex') {
|
||||
if (Number(mintingDetails.unitPrice) < Number(minimumFlexMintPrice))
|
||||
throw new Error(`Invalid unit price: The minimum unit price is ${Number(minimumFlexMintPrice) / 1000000} STARS`)
|
||||
} else if (collectionDetails?.updatable) {
|
||||
if (Number(mintingDetails.unitPrice) < Number(minimumUpdatableMintPrice))
|
||||
throw new Error(
|
||||
`Invalid unit price: The minimum unit price is ${Number(minimumUpdatableMintPrice) / 1000000} STARS`,
|
||||
@ -822,12 +859,14 @@ const CollectionCreationPage: NextPage = () => {
|
||||
|
||||
const checkWhitelistDetails = async () => {
|
||||
if (!whitelistDetails) throw new Error('Please fill out the whitelist details')
|
||||
if (whitelistDetails.whitelistType === 'existing') {
|
||||
if (whitelistDetails.whitelistState === 'existing') {
|
||||
if (whitelistDetails.contractAddress === '') throw new Error('Whitelist contract address is required')
|
||||
else {
|
||||
const contract = whitelistContract?.use(whitelistDetails.contractAddress)
|
||||
//check if the address belongs to a whitelist contract (see performChecks())
|
||||
const config = await contract?.config()
|
||||
if (JSON.stringify(config).includes('whale_cap')) whitelistDetails.whitelistType = 'flex'
|
||||
else whitelistDetails.whitelistType = 'standard'
|
||||
if (Number(config?.start_time) !== Number(mintingDetails?.startTime)) {
|
||||
const whitelistStartDate = new Date(Number(config?.start_time) / 1000000)
|
||||
throw Error(`Whitelist start time (${whitelistStartDate.toLocaleString()}) does not match minting start time`)
|
||||
@ -852,14 +891,17 @@ const CollectionCreationPage: NextPage = () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (whitelistDetails.whitelistType === 'new') {
|
||||
} else if (whitelistDetails.whitelistState === 'new') {
|
||||
if (whitelistDetails.members?.length === 0) throw new Error('Whitelist member list cannot be empty')
|
||||
if (whitelistDetails.unitPrice === '') throw new Error('Whitelist unit price is required')
|
||||
if (Number(whitelistDetails.unitPrice) < 0)
|
||||
throw new Error('Invalid unit price: The unit price cannot be negative')
|
||||
if (whitelistDetails.startTime === '') throw new Error('Start time is required')
|
||||
if (whitelistDetails.endTime === '') throw new Error('End time is required')
|
||||
if (!whitelistDetails.perAddressLimit || whitelistDetails.perAddressLimit === 0)
|
||||
if (
|
||||
whitelistDetails.whitelistType === 'standard' &&
|
||||
(!whitelistDetails.perAddressLimit || whitelistDetails.perAddressLimit === 0)
|
||||
)
|
||||
throw new Error('Per address limit is required')
|
||||
if (!whitelistDetails.memberLimit || whitelistDetails.memberLimit === 0)
|
||||
throw new Error('Member limit is required')
|
||||
@ -945,14 +987,25 @@ const CollectionCreationPage: NextPage = () => {
|
||||
setVendingMinterUpdatableCreationFee(vendingFactoryUpdatableParameters?.params?.creation_fee?.amount)
|
||||
setMinimumUpdatableMintPrice(vendingFactoryUpdatableParameters?.params?.min_mint_price?.amount)
|
||||
}
|
||||
if (VENDING_FACTORY_FLEX_ADDRESS) {
|
||||
const vendingFactoryFlexParameters = await client.queryContractSmart(VENDING_FACTORY_FLEX_ADDRESS, {
|
||||
params: {},
|
||||
})
|
||||
setVendingMinterFlexCreationFee(vendingFactoryFlexParameters?.params?.creation_fee?.amount)
|
||||
setMinimumFlexMintPrice(vendingFactoryFlexParameters?.params?.min_mint_price?.amount)
|
||||
}
|
||||
}
|
||||
|
||||
const checkwalletBalance = () => {
|
||||
if (!wallet.initialized) throw new Error('Wallet not connected.')
|
||||
if (minterType === 'vending' && whitelistDetails?.whitelistType === 'new' && whitelistDetails.memberLimit) {
|
||||
if (minterType === 'vending' && whitelistDetails?.whitelistState === 'new' && whitelistDetails.memberLimit) {
|
||||
const amountNeeded =
|
||||
Math.ceil(Number(whitelistDetails.memberLimit) / 1000) * 100000000 +
|
||||
(collectionDetails?.updatable ? Number(vendingMinterUpdatableCreationFee) : Number(vendingMinterCreationFee))
|
||||
(whitelistDetails.whitelistType === 'flex'
|
||||
? Number(vendingMinterFlexCreationFee)
|
||||
: collectionDetails?.updatable
|
||||
? Number(vendingMinterUpdatableCreationFee)
|
||||
: Number(vendingMinterCreationFee))
|
||||
if (amountNeeded >= Number(wallet.balance[0].amount))
|
||||
throw new Error(
|
||||
`Insufficient wallet balance to instantiate the required contracts. Needed amount: ${(
|
||||
@ -962,7 +1015,9 @@ const CollectionCreationPage: NextPage = () => {
|
||||
} else {
|
||||
const amountNeeded =
|
||||
minterType === 'vending'
|
||||
? collectionDetails?.updatable
|
||||
? whitelistDetails?.whitelistState === 'existing' && whitelistDetails.whitelistType === 'flex'
|
||||
? Number(vendingMinterFlexCreationFee)
|
||||
: collectionDetails?.updatable
|
||||
? Number(vendingMinterUpdatableCreationFee)
|
||||
: Number(vendingMinterCreationFee)
|
||||
: collectionDetails?.updatable
|
||||
|
@ -1,3 +1,6 @@
|
||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
/* eslint-disable no-nested-ternary */
|
||||
import { toUtf8 } from '@cosmjs/encoding'
|
||||
import { Alert } from 'components/Alert'
|
||||
import { Button } from 'components/Button'
|
||||
import { Conditional } from 'components/Conditional'
|
||||
@ -7,6 +10,8 @@ import { useExecuteComboboxState } from 'components/contracts/whitelist/ExecuteC
|
||||
import { FormControl } from 'components/FormControl'
|
||||
import { AddressList } from 'components/forms/AddressList'
|
||||
import { useAddressListState } from 'components/forms/AddressList.hooks'
|
||||
import { FlexMemberAttributes } from 'components/forms/FlexMemberAttributes'
|
||||
import { useFlexMemberAttributesState } from 'components/forms/FlexMemberAttributes.hooks'
|
||||
import { AddressInput, NumberInput } from 'components/forms/FormInput'
|
||||
import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks'
|
||||
import { InputDateTime } from 'components/InputDateTime'
|
||||
@ -14,6 +19,8 @@ import { JsonPreview } from 'components/JsonPreview'
|
||||
import { LinkTabs } from 'components/LinkTabs'
|
||||
import { whitelistLinkTabs } from 'components/LinkTabs.data'
|
||||
import { TransactionHash } from 'components/TransactionHash'
|
||||
import type { WhitelistFlexMember } from 'components/WhitelistFlexUpload'
|
||||
import { WhitelistFlexUpload } from 'components/WhitelistFlexUpload'
|
||||
import { WhitelistUpload } from 'components/WhitelistUpload'
|
||||
import { useContracts } from 'contexts/contracts'
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
@ -27,6 +34,7 @@ import { useEffect, useMemo, useState } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { FaArrowRight } from 'react-icons/fa'
|
||||
import { useMutation } from 'react-query'
|
||||
import { useDebounce } from 'utils/debounce'
|
||||
import { isValidAddress } from 'utils/isValidAddress'
|
||||
import { withMetadata } from 'utils/layout'
|
||||
import { links } from 'utils/links'
|
||||
@ -37,6 +45,8 @@ const WhitelistExecutePage: NextPage = () => {
|
||||
|
||||
const [lastTx, setLastTx] = useState('')
|
||||
const [memberList, setMemberList] = useState<string[]>([])
|
||||
const [flexMemberList, setFlexMemberList] = useState<WhitelistFlexMember[]>([])
|
||||
const [whitelistType, setWhitelistType] = useState<'standard' | 'flex'>('standard')
|
||||
|
||||
const comboboxState = useExecuteComboboxState()
|
||||
const type = comboboxState.value?.id
|
||||
@ -45,6 +55,8 @@ const WhitelistExecutePage: NextPage = () => {
|
||||
|
||||
const addressListState = useAddressListState()
|
||||
|
||||
const flexAddressListState = useFlexMemberAttributesState()
|
||||
|
||||
const contractState = useInputState({
|
||||
id: 'contract-address',
|
||||
name: 'contract-address',
|
||||
@ -53,6 +65,8 @@ const WhitelistExecutePage: NextPage = () => {
|
||||
})
|
||||
const contractAddress = contractState.value
|
||||
|
||||
const debouncedWhitelistContractState = useDebounce(contractState.value, 300)
|
||||
|
||||
const limitState = useNumberInputState({
|
||||
id: 'limit',
|
||||
name: 'limit',
|
||||
@ -63,7 +77,9 @@ const WhitelistExecutePage: NextPage = () => {
|
||||
|
||||
const showLimitState = isEitherType(type, ['update_per_address_limit', 'increase_member_limit'])
|
||||
const showTimestamp = isEitherType(type, ['update_start_time', 'update_end_time'])
|
||||
const showMemberList = isEitherType(type, ['add_members', 'remove_members'])
|
||||
const showMemberList = isEitherType(type, ['add_members'])
|
||||
const showFlexMemberList = isEitherType(type, ['add_members'])
|
||||
const showRemoveMemberList = isEitherType(type, ['remove_members'])
|
||||
const showAdminList = isEitherType(type, ['update_admins'])
|
||||
|
||||
const messages = useMemo(() => contract?.use(contractState.value), [contract, contractState.value])
|
||||
@ -73,14 +89,44 @@ const WhitelistExecutePage: NextPage = () => {
|
||||
type,
|
||||
limit: limitState.value,
|
||||
timestamp: timestamp ? (timestamp.getTime() * 1_000_000).toString() : '',
|
||||
members: [
|
||||
...new Set(
|
||||
addressListState.values
|
||||
.map((a) => a.address.trim())
|
||||
.filter((address) => address !== '' && isValidAddress(address.trim()) && address.startsWith('stars'))
|
||||
.concat(memberList),
|
||||
),
|
||||
],
|
||||
members:
|
||||
whitelistType === 'standard'
|
||||
? [
|
||||
...new Set(
|
||||
addressListState.values
|
||||
.map((a) => a.address.trim())
|
||||
.filter((address) => address !== '' && isValidAddress(address.trim()) && address.startsWith('stars'))
|
||||
.concat(memberList),
|
||||
),
|
||||
]
|
||||
: type === 'add_members'
|
||||
? [
|
||||
...new Set(
|
||||
flexAddressListState.values
|
||||
.concat(flexMemberList)
|
||||
.filter((obj, index, self) => index === self.findIndex((t) => t.address.trim() === obj.address.trim()))
|
||||
.filter(
|
||||
(member) =>
|
||||
member.address !== '' &&
|
||||
isValidAddress(member.address.trim()) &&
|
||||
member.address.startsWith('stars'),
|
||||
)
|
||||
.map((member) => {
|
||||
return {
|
||||
address: member.address.trim(),
|
||||
mint_count: Math.round(member.mint_count),
|
||||
}
|
||||
}),
|
||||
),
|
||||
]
|
||||
: [
|
||||
...new Set(
|
||||
addressListState.values
|
||||
.map((a) => a.address.trim())
|
||||
.filter((address) => address !== '' && isValidAddress(address.trim()) && address.startsWith('stars'))
|
||||
.concat(memberList),
|
||||
),
|
||||
],
|
||||
admins: [
|
||||
...new Set(
|
||||
addressListState.values
|
||||
@ -122,11 +168,55 @@ const WhitelistExecutePage: NextPage = () => {
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [contractAddress])
|
||||
|
||||
useEffect(() => {
|
||||
const initial = new URL(document.URL).searchParams.get('contractAddress')
|
||||
if (initial && initial.length > 0) contractState.onChange(initial)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
flexAddressListState.reset()
|
||||
flexAddressListState.add({
|
||||
address: '',
|
||||
mint_count: 0,
|
||||
})
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
async function getWhitelistContractType() {
|
||||
if (wallet.client && debouncedWhitelistContractState.length > 0) {
|
||||
const client = wallet.client
|
||||
const data = await toast.promise(
|
||||
client.queryContractRaw(
|
||||
debouncedWhitelistContractState,
|
||||
toUtf8(Buffer.from(Buffer.from('contract_info').toString('hex'), 'hex').toString()),
|
||||
),
|
||||
{
|
||||
loading: 'Retrieving Whitelist type...',
|
||||
error: 'Whitelist type retrieval failed.',
|
||||
success: 'Whitelist type retrieved.',
|
||||
},
|
||||
)
|
||||
const contractType: string = JSON.parse(new TextDecoder().decode(data as Uint8Array)).contract
|
||||
console.log(contractType)
|
||||
return contractType
|
||||
}
|
||||
}
|
||||
void getWhitelistContractType()
|
||||
.then((contractType) => {
|
||||
if (contractType?.includes('flex')) {
|
||||
setWhitelistType('flex')
|
||||
} else {
|
||||
setWhitelistType('standard')
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
setWhitelistType('standard')
|
||||
console.log('Unable to retrieve contract type. Defaulting to "standard".')
|
||||
})
|
||||
}, [debouncedWhitelistContractState, wallet.client])
|
||||
|
||||
return (
|
||||
<section className="py-6 px-12 space-y-4">
|
||||
<NextSeo title="Execute Whitelist Contract" />
|
||||
@ -154,7 +244,7 @@ const WhitelistExecutePage: NextPage = () => {
|
||||
<InputDateTime minDate={new Date()} onChange={(date) => setTimestamp(date)} value={timestamp} />
|
||||
</FormControl>
|
||||
</Conditional>
|
||||
<Conditional test={showMemberList || showAdminList}>
|
||||
<Conditional test={(whitelistType === 'standard' && showMemberList) || showAdminList || showRemoveMemberList}>
|
||||
<AddressList
|
||||
entries={addressListState.entries}
|
||||
isRequired
|
||||
@ -164,13 +254,30 @@ const WhitelistExecutePage: NextPage = () => {
|
||||
subtitle={type === 'update_admins' ? 'Enter the admin addresses' : 'Enter the member addresses'}
|
||||
title="Addresses"
|
||||
/>
|
||||
<Conditional test={showMemberList}>
|
||||
<Conditional test={whitelistType === 'standard' && showMemberList}>
|
||||
<Alert className="mt-8" type="info">
|
||||
You may optionally choose a text file of additional member addresses.
|
||||
</Alert>
|
||||
<WhitelistUpload onChange={setMemberList} />
|
||||
</Conditional>
|
||||
</Conditional>
|
||||
|
||||
<Conditional test={whitelistType === 'flex' && showFlexMemberList}>
|
||||
<FlexMemberAttributes
|
||||
attributes={flexAddressListState.entries}
|
||||
onAdd={flexAddressListState.add}
|
||||
onChange={flexAddressListState.update}
|
||||
onRemove={flexAddressListState.remove}
|
||||
subtitle="Enter the member addresses and corresponding mint counts"
|
||||
title="Members"
|
||||
/>
|
||||
<Conditional test={whitelistType === 'flex' && showFlexMemberList}>
|
||||
<Alert className="mt-8" type="info">
|
||||
You may optionally choose a .csv file of additional member addresses and mint counts.
|
||||
</Alert>
|
||||
<WhitelistFlexUpload onChange={setFlexMemberList} />
|
||||
</Conditional>
|
||||
</Conditional>
|
||||
</div>
|
||||
<div className="space-y-8">
|
||||
<div className="relative">
|
||||
|
@ -13,13 +13,14 @@ import { InputDateTime } from 'components/InputDateTime'
|
||||
import { JsonPreview } from 'components/JsonPreview'
|
||||
import { LinkTabs } from 'components/LinkTabs'
|
||||
import { whitelistLinkTabs } from 'components/LinkTabs.data'
|
||||
import { type WhitelistFlexMember, WhitelistFlexUpload } from 'components/WhitelistFlexUpload'
|
||||
import { WhitelistUpload } from 'components/WhitelistUpload'
|
||||
import { useContracts } from 'contexts/contracts'
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import type { InstantiateResponse } from 'contracts/sg721'
|
||||
import type { NextPage } from 'next'
|
||||
import { NextSeo } from 'next-seo'
|
||||
import { type FormEvent, useState } from 'react'
|
||||
import { type FormEvent, useEffect, useState } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { FaAsterisk } from 'react-icons/fa'
|
||||
import { useMutation } from 'react-query'
|
||||
@ -27,7 +28,7 @@ import { isValidAddress } from 'utils/isValidAddress'
|
||||
import { withMetadata } from 'utils/layout'
|
||||
import { links } from 'utils/links'
|
||||
|
||||
import { WHITELIST_CODE_ID } from '../../../utils/constants'
|
||||
import { WHITELIST_CODE_ID, WHITELIST_FLEX_CODE_ID } from '../../../utils/constants'
|
||||
|
||||
const WhitelistInstantiatePage: NextPage = () => {
|
||||
const wallet = useWallet()
|
||||
@ -36,8 +37,10 @@ const WhitelistInstantiatePage: NextPage = () => {
|
||||
const [startDate, setStartDate] = useState<Date | undefined>(undefined)
|
||||
const [endDate, setEndDate] = useState<Date | undefined>(undefined)
|
||||
const [adminsMutable, setAdminsMutable] = useState<boolean>(true)
|
||||
const [whitelistType, setWhitelistType] = useState<'standard' | 'flex'>('standard')
|
||||
|
||||
const [whitelistArray, setWhitelistArray] = useState<string[]>([])
|
||||
const [whitelistStandardArray, setWhitelistStandardArray] = useState<string[]>([])
|
||||
const [whitelistFlexArray, setWhitelistFlexArray] = useState<WhitelistFlexMember[]>([])
|
||||
|
||||
const unitPriceState = useNumberInputState({
|
||||
id: 'unit-price',
|
||||
@ -63,6 +66,13 @@ const WhitelistInstantiatePage: NextPage = () => {
|
||||
placeholder: '5',
|
||||
})
|
||||
|
||||
const whaleCapState = useNumberInputState({
|
||||
id: 'whale-cap',
|
||||
name: 'whaleCap',
|
||||
title: 'Whale Cap (optional)',
|
||||
subtitle: 'Maximum number of tokens a single address can mint',
|
||||
})
|
||||
|
||||
const addressListState = useAddressListState()
|
||||
|
||||
const { data, isLoading, mutate } = useMutation(
|
||||
@ -79,8 +89,8 @@ const WhitelistInstantiatePage: NextPage = () => {
|
||||
throw new Error('End date is required')
|
||||
}
|
||||
|
||||
const msg = {
|
||||
members: whitelistArray,
|
||||
const standardMsg = {
|
||||
members: whitelistStandardArray,
|
||||
start_time: (startDate.getTime() * 1_000_000).toString(),
|
||||
end_time: (endDate.getTime() * 1_000_000).toString(),
|
||||
mint_price: coin(String(Number(unitPriceState.value) * 1000000), 'ustars'),
|
||||
@ -95,8 +105,31 @@ const WhitelistInstantiatePage: NextPage = () => {
|
||||
] || [wallet.address],
|
||||
admins_mutable: adminsMutable,
|
||||
}
|
||||
|
||||
const flexMsg = {
|
||||
members: whitelistFlexArray,
|
||||
start_time: (startDate.getTime() * 1_000_000).toString(),
|
||||
end_time: (endDate.getTime() * 1_000_000).toString(),
|
||||
mint_price: coin(String(Number(unitPriceState.value) * 1000000), 'ustars'),
|
||||
whale_cap: whaleCapState.value || undefined,
|
||||
member_limit: memberLimitState.value,
|
||||
admins: [
|
||||
...new Set(
|
||||
addressListState.values
|
||||
.map((a) => a.address.trim())
|
||||
.filter((address) => address !== '' && isValidAddress(address.trim()) && address.startsWith('stars')),
|
||||
),
|
||||
] || [wallet.address],
|
||||
admins_mutable: adminsMutable,
|
||||
}
|
||||
|
||||
return toast.promise(
|
||||
contract.instantiate(WHITELIST_CODE_ID, msg, 'Stargaze Whitelist Contract', wallet.address),
|
||||
contract.instantiate(
|
||||
whitelistType === 'standard' ? WHITELIST_CODE_ID : WHITELIST_FLEX_CODE_ID,
|
||||
whitelistType === 'standard' ? standardMsg : flexMsg,
|
||||
whitelistType === 'standard' ? 'Stargaze Whitelist Contract' : 'Stargaze Whitelist Flex Contract',
|
||||
wallet.address,
|
||||
),
|
||||
{
|
||||
loading: 'Instantiating contract...',
|
||||
error: 'Instantiation failed!',
|
||||
@ -112,9 +145,18 @@ const WhitelistInstantiatePage: NextPage = () => {
|
||||
)
|
||||
|
||||
const whitelistFileOnChange = (whitelistData: string[]) => {
|
||||
setWhitelistArray(whitelistData)
|
||||
setWhitelistStandardArray(whitelistData)
|
||||
}
|
||||
|
||||
const whitelistFlexFileOnChange = (whitelistData: WhitelistFlexMember[]) => {
|
||||
setWhitelistFlexArray(whitelistData)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setWhitelistStandardArray([])
|
||||
setWhitelistFlexArray([])
|
||||
}, [whitelistType])
|
||||
|
||||
return (
|
||||
<form className="py-6 px-12 space-y-4" onSubmit={mutate}>
|
||||
<NextSeo title="Instantiate Whitelist Contract" />
|
||||
@ -125,6 +167,48 @@ const WhitelistInstantiatePage: NextPage = () => {
|
||||
/>
|
||||
<LinkTabs activeIndex={0} data={whitelistLinkTabs} />
|
||||
|
||||
<div className="flex justify-between mb-5 ml-6 max-w-[300px] text-lg font-bold">
|
||||
<div className="form-check form-check-inline">
|
||||
<input
|
||||
checked={whitelistType === 'standard'}
|
||||
className="peer sr-only"
|
||||
id="inlineRadio1"
|
||||
name="inlineRadioOptions3"
|
||||
onClick={() => {
|
||||
setWhitelistType('standard')
|
||||
}}
|
||||
type="radio"
|
||||
value="nft-storage"
|
||||
/>
|
||||
<label
|
||||
className="inline-block py-1 px-2 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="inlineRadio1"
|
||||
>
|
||||
Standard Whitelist
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="form-check form-check-inline">
|
||||
<input
|
||||
checked={whitelistType === 'flex'}
|
||||
className="peer sr-only"
|
||||
id="inlineRadio2"
|
||||
name="inlineRadioOptions2"
|
||||
onClick={() => {
|
||||
setWhitelistType('flex')
|
||||
}}
|
||||
type="radio"
|
||||
value="flex"
|
||||
/>
|
||||
<label
|
||||
className="inline-block py-1 px-2 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="inlineRadio2"
|
||||
>
|
||||
Whitelist Flex
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Conditional test={Boolean(data)}>
|
||||
<Alert type="info">
|
||||
<b>Instantiate success!</b> Here is the transaction result containing the contract address and the transaction
|
||||
@ -134,7 +218,7 @@ const WhitelistInstantiatePage: NextPage = () => {
|
||||
<br />
|
||||
</Conditional>
|
||||
|
||||
<div className="mt-2 ml-3 w-full form-control">
|
||||
<div className="mt-2 ml-3 w-1/3 form-control">
|
||||
<label className="justify-start cursor-pointer label">
|
||||
<span className="mr-4 font-bold">Mutable Administrator Addresses</span>
|
||||
<input
|
||||
@ -158,16 +242,29 @@ const WhitelistInstantiatePage: NextPage = () => {
|
||||
</div>
|
||||
|
||||
<FormGroup subtitle="Your whitelisted addresses" title="Whitelist File">
|
||||
<WhitelistUpload onChange={whitelistFileOnChange} />
|
||||
<Conditional test={whitelistArray.length > 0}>
|
||||
<JsonPreview content={whitelistArray} initialState={false} title="File Contents" />
|
||||
<Conditional test={whitelistType === 'standard'}>
|
||||
<WhitelistUpload onChange={whitelistFileOnChange} />
|
||||
<Conditional test={whitelistStandardArray.length > 0}>
|
||||
<JsonPreview content={whitelistStandardArray} initialState={false} title="File Contents" />
|
||||
</Conditional>
|
||||
</Conditional>
|
||||
<Conditional test={whitelistType === 'flex'}>
|
||||
<WhitelistFlexUpload onChange={whitelistFlexFileOnChange} />
|
||||
<Conditional test={whitelistFlexArray.length > 0}>
|
||||
<JsonPreview content={whitelistFlexArray} initialState={false} title="File Contents" />
|
||||
</Conditional>
|
||||
</Conditional>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup subtitle="Information about your minting settings" title="Minting Details">
|
||||
<NumberInput isRequired {...unitPriceState} />
|
||||
<NumberInput isRequired {...memberLimitState} />
|
||||
<NumberInput isRequired {...perAddressLimitState} />
|
||||
<Conditional test={whitelistType === 'standard'}>
|
||||
<NumberInput isRequired {...perAddressLimitState} />
|
||||
</Conditional>
|
||||
<Conditional test={whitelistType === 'flex'}>
|
||||
<NumberInput {...whaleCapState} />
|
||||
</Conditional>
|
||||
<FormControl htmlId="start-date" isRequired subtitle="Start time for the minting" title="Start Time">
|
||||
<InputDateTime minDate={new Date()} onChange={(date) => setStartDate(date)} value={startDate} />
|
||||
</FormControl>
|
||||
|
@ -1,9 +1,12 @@
|
||||
export const SG721_CODE_ID = parseInt(process.env.NEXT_PUBLIC_SG721_CODE_ID, 10)
|
||||
export const SG721_UPDATABLE_CODE_ID = parseInt(process.env.NEXT_PUBLIC_SG721_UPDATABLE_CODE_ID, 10)
|
||||
export const WHITELIST_CODE_ID = parseInt(process.env.NEXT_PUBLIC_WHITELIST_CODE_ID, 10)
|
||||
export const WHITELIST_FLEX_CODE_ID = parseInt(process.env.NEXT_PUBLIC_WHITELIST_FLEX_CODE_ID, 10)
|
||||
export const VENDING_MINTER_CODE_ID = parseInt(process.env.NEXT_PUBLIC_VENDING_MINTER_CODE_ID, 10)
|
||||
export const VENDING_MINTER_FLEX_CODE_ID = parseInt(process.env.NEXT_PUBLIC_VENDING_MINTER_FLEX_CODE_ID, 10)
|
||||
export const VENDING_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_VENDING_FACTORY_ADDRESS
|
||||
export const VENDING_FACTORY_UPDATABLE_ADDRESS = process.env.NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_ADDRESS
|
||||
export const VENDING_FACTORY_FLEX_ADDRESS = process.env.NEXT_PUBLIC_VENDING_FACTORY_FLEX_ADDRESS
|
||||
export const BASE_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_BASE_FACTORY_ADDRESS
|
||||
export const BASE_FACTORY_UPDATABLE_ADDRESS = process.env.NEXT_PUBLIC_BASE_FACTORY_UPDATABLE_ADDRESS
|
||||
export const SG721_NAME_ADDRESS = process.env.NEXT_PUBLIC_SG721_NAME_ADDRESS
|
||||
|
31
utils/csvToFlexList.ts
Normal file
31
utils/csvToFlexList.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import type { WhitelistFlexMember } from 'components/WhitelistFlexUpload'
|
||||
|
||||
export const csvToFlexList = (str: string, delimiter = ',') => {
|
||||
let newline = '\n'
|
||||
if (str.includes('\r')) newline = '\r'
|
||||
if (str.includes('\r\n')) newline = '\r\n'
|
||||
|
||||
const headers = str.slice(0, str.indexOf(newline)).split(delimiter)
|
||||
if (headers.length !== 2) {
|
||||
throw new Error('Invalid whitelist-flex file.')
|
||||
}
|
||||
if (headers[0] !== 'address' || headers[1] !== 'mint_count') {
|
||||
throw new Error('Invalid whitelist-flex file. Headers must be "address" and "mint_count".')
|
||||
}
|
||||
|
||||
const rows = str.slice(str.indexOf('\n') + 1).split(newline)
|
||||
|
||||
const arr = rows
|
||||
.filter((row) => row !== '')
|
||||
.map((row) => {
|
||||
const values = row.split(delimiter)
|
||||
const el = headers.reduce((object, header, index) => {
|
||||
// @ts-expect-error assume object as Record<string, unknown>
|
||||
object[header] = values[index]
|
||||
return object
|
||||
}, {})
|
||||
return el
|
||||
})
|
||||
|
||||
return arr as WhitelistFlexMember[]
|
||||
}
|
52
utils/isValidFlexListFile.ts
Normal file
52
utils/isValidFlexListFile.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import type { WhitelistFlexMember } from 'components/WhitelistFlexUpload'
|
||||
import { toast } from 'react-hot-toast'
|
||||
|
||||
import { isValidAddress } from './isValidAddress'
|
||||
|
||||
export const isValidFlexListFile = (file: WhitelistFlexMember[]) => {
|
||||
let sumOfAmounts = 0
|
||||
file.forEach((allocation) => {
|
||||
sumOfAmounts += Number(allocation.mint_count)
|
||||
})
|
||||
if (sumOfAmounts > 10000) {
|
||||
toast.error(`Total mint count should be less than 10000 tokens (current count: ${sumOfAmounts}))`)
|
||||
return false
|
||||
}
|
||||
|
||||
const checks = file.map((account) => {
|
||||
// Check if address is valid bech32 address
|
||||
if (account.address.trim().startsWith('stars')) {
|
||||
if (!isValidAddress(account.address.trim())) {
|
||||
return { address: false }
|
||||
}
|
||||
}
|
||||
// Check if address start with stars
|
||||
if (!account.address.trim().startsWith('stars') && !account.address.trim().endsWith('.stars')) {
|
||||
return { address: false }
|
||||
}
|
||||
// Check if amount is valid
|
||||
if (!Number.isInteger(Number(account.mint_count)) || !(Number(account.mint_count) > 0)) {
|
||||
return { mint_count: false }
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
const isStargazeAddresses = file.every(
|
||||
(account) => account.address.trim().startsWith('stars') || account.address.trim().endsWith('.stars'),
|
||||
)
|
||||
if (!isStargazeAddresses) {
|
||||
toast.error('All accounts must be on the Stargaze network')
|
||||
return false
|
||||
}
|
||||
|
||||
if (checks.filter((check) => check?.address === false).length > 0) {
|
||||
toast.error('Invalid address in file')
|
||||
return false
|
||||
}
|
||||
if (checks.filter((check) => check?.mint_count === false).length > 0) {
|
||||
toast.error('Invalid mint count in file. Mint count must be a positive integer.')
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
Loading…
Reference in New Issue
Block a user