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_PINATA_ENDPOINT_URL=https://api.pinata.cloud/pinning/pinFileToIPFS
|
||||||
NEXT_PUBLIC_SG721_CODE_ID=1911
|
NEXT_PUBLIC_SG721_CODE_ID=1911
|
||||||
NEXT_PUBLIC_SG721_UPDATABLE_CODE_ID=1912
|
NEXT_PUBLIC_SG721_UPDATABLE_CODE_ID=1912
|
||||||
NEXT_PUBLIC_VENDING_MINTER_CODE_ID=1909
|
NEXT_PUBLIC_VENDING_MINTER_CODE_ID=1909
|
||||||
|
NEXT_PUBLIC_VENDING_MINTER_FLEX_CODE_ID=2080
|
||||||
NEXT_PUBLIC_BASE_MINTER_CODE_ID=1910
|
NEXT_PUBLIC_BASE_MINTER_CODE_ID=1910
|
||||||
NEXT_PUBLIC_VENDING_FACTORY_ADDRESS="stars1ynec878x5phexq3hj4zdgvp6r5ayfmxks38kvunwyjugqn3hqeqq3cgtuw"
|
NEXT_PUBLIC_VENDING_FACTORY_ADDRESS="stars1ynec878x5phexq3hj4zdgvp6r5ayfmxks38kvunwyjugqn3hqeqq3cgtuw"
|
||||||
NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_ADDRESS="stars1fnfywcnzzwledr93at65qm8gf953tjxgh6u2u4r8n9vsdv7u75eqe7ecn3"
|
NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_ADDRESS="stars1fnfywcnzzwledr93at65qm8gf953tjxgh6u2u4r8n9vsdv7u75eqe7ecn3"
|
||||||
|
NEXT_PUBLIC_VENDING_FACTORY_FLEX_ADDRESS="stars1l5v0vgly8r9jw4exeajnftymr29kn70n23gpl2g5fylaww2pzkhq0rks7c"
|
||||||
NEXT_PUBLIC_BASE_FACTORY_ADDRESS="stars10rmaxgnjvskuumgv7e2awqkhdqdcygkwrz8a8vvt88szj7fc7xlq5jcs3f"
|
NEXT_PUBLIC_BASE_FACTORY_ADDRESS="stars10rmaxgnjvskuumgv7e2awqkhdqdcygkwrz8a8vvt88szj7fc7xlq5jcs3f"
|
||||||
NEXT_PUBLIC_BASE_FACTORY_UPDATABLE_ADDRESS="stars13pw8r33dsnghlxfj2upaywf38z2fc6npuw9maq9e5cpet4v285sscgzjp2"
|
NEXT_PUBLIC_BASE_FACTORY_UPDATABLE_ADDRESS="stars13pw8r33dsnghlxfj2upaywf38z2fc6npuw9maq9e5cpet4v285sscgzjp2"
|
||||||
NEXT_PUBLIC_SG721_NAME_ADDRESS="stars1fx74nkqkw2748av8j7ew7r3xt9cgjqduwn8m0ur5lhe49uhlsasszc5fhr"
|
NEXT_PUBLIC_SG721_NAME_ADDRESS="stars1fx74nkqkw2748av8j7ew7r3xt9cgjqduwn8m0ur5lhe49uhlsasszc5fhr"
|
||||||
NEXT_PUBLIC_WHITELIST_CODE_ID=1913
|
NEXT_PUBLIC_WHITELIST_CODE_ID=1913
|
||||||
|
NEXT_PUBLIC_WHITELIST_FLEX_CODE_ID=2005
|
||||||
NEXT_PUBLIC_BADGE_HUB_CODE_ID=1336
|
NEXT_PUBLIC_BADGE_HUB_CODE_ID=1336
|
||||||
NEXT_PUBLIC_BADGE_HUB_ADDRESS="stars1dacun0xn7z73qzdcmq27q3xn6xuprg8e2ugj364784al2v27tklqynhuqa"
|
NEXT_PUBLIC_BADGE_HUB_ADDRESS="stars1dacun0xn7z73qzdcmq27q3xn6xuprg8e2ugj364784al2v27tklqynhuqa"
|
||||||
NEXT_PUBLIC_BADGE_NFT_CODE_ID=1337
|
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 { useAddressListState } from 'components/forms/AddressList.hooks'
|
||||||
import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks'
|
import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks'
|
||||||
import { InputDateTime } from 'components/InputDateTime'
|
import { InputDateTime } from 'components/InputDateTime'
|
||||||
|
import type { WhitelistFlexMember } from 'components/WhitelistFlexUpload'
|
||||||
|
import { WhitelistFlexUpload } from 'components/WhitelistFlexUpload'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { isValidAddress } from 'utils/isValidAddress'
|
import { isValidAddress } from 'utils/isValidAddress'
|
||||||
|
|
||||||
@ -17,9 +19,10 @@ interface WhitelistDetailsProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface WhitelistDetailsDataProps {
|
export interface WhitelistDetailsDataProps {
|
||||||
whitelistType: WhitelistState
|
whitelistState: WhitelistState
|
||||||
|
whitelistType: WhitelistType
|
||||||
contractAddress?: string
|
contractAddress?: string
|
||||||
members?: string[]
|
members?: string[] | WhitelistFlexMember[]
|
||||||
unitPrice?: string
|
unitPrice?: string
|
||||||
startTime?: string
|
startTime?: string
|
||||||
endTime?: string
|
endTime?: string
|
||||||
@ -31,11 +34,15 @@ export interface WhitelistDetailsDataProps {
|
|||||||
|
|
||||||
type WhitelistState = 'none' | 'existing' | 'new'
|
type WhitelistState = 'none' | 'existing' | 'new'
|
||||||
|
|
||||||
|
type WhitelistType = 'standard' | 'flex'
|
||||||
|
|
||||||
export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
|
export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
|
||||||
const [whitelistState, setWhitelistState] = useState<WhitelistState>('none')
|
const [whitelistState, setWhitelistState] = useState<WhitelistState>('none')
|
||||||
|
const [whitelistType, setWhitelistType] = useState<WhitelistType>('standard')
|
||||||
const [startDate, setStartDate] = useState<Date | undefined>(undefined)
|
const [startDate, setStartDate] = useState<Date | undefined>(undefined)
|
||||||
const [endDate, setEndDate] = 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 [adminsMutable, setAdminsMutable] = useState<boolean>(true)
|
||||||
|
|
||||||
const whitelistAddressState = useInputState({
|
const whitelistAddressState = useInputState({
|
||||||
@ -72,19 +79,29 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
|
|||||||
const addressListState = useAddressListState()
|
const addressListState = useAddressListState()
|
||||||
|
|
||||||
const whitelistFileOnChange = (data: string[]) => {
|
const whitelistFileOnChange = (data: string[]) => {
|
||||||
setWhitelistArray(data)
|
setWhitelistStandardArray(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
const whitelistFlexFileOnChange = (whitelistData: WhitelistFlexMember[]) => {
|
||||||
|
setWhitelistFlexArray(whitelistData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setWhitelistStandardArray([])
|
||||||
|
setWhitelistFlexArray([])
|
||||||
|
}, [whitelistType])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const data: WhitelistDetailsDataProps = {
|
const data: WhitelistDetailsDataProps = {
|
||||||
whitelistType: whitelistState,
|
whitelistState,
|
||||||
|
whitelistType,
|
||||||
contractAddress: whitelistAddressState.value
|
contractAddress: whitelistAddressState.value
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.replace(/,/g, '')
|
.replace(/,/g, '')
|
||||||
.replace(/"/g, '')
|
.replace(/"/g, '')
|
||||||
.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() : '',
|
unitPrice: unitPriceState.value ? (Number(unitPriceState.value) * 1_000_000).toString() : '',
|
||||||
startTime: startDate ? (startDate.getTime() * 1_000_000).toString() : '',
|
startTime: startDate ? (startDate.getTime() * 1_000_000).toString() : '',
|
||||||
endTime: endDate ? (endDate.getTime() * 1_000_000).toString() : '',
|
endTime: endDate ? (endDate.getTime() * 1_000_000).toString() : '',
|
||||||
@ -108,7 +125,8 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
|
|||||||
perAddressLimitState.value,
|
perAddressLimitState.value,
|
||||||
startDate,
|
startDate,
|
||||||
endDate,
|
endDate,
|
||||||
whitelistArray,
|
whitelistStandardArray,
|
||||||
|
whitelistFlexArray,
|
||||||
whitelistState,
|
whitelistState,
|
||||||
addressListState.values,
|
addressListState.values,
|
||||||
adminsMutable,
|
adminsMutable,
|
||||||
@ -125,6 +143,7 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
|
|||||||
name="whitelistRadioOptions1"
|
name="whitelistRadioOptions1"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setWhitelistState('none')
|
setWhitelistState('none')
|
||||||
|
setWhitelistType('standard')
|
||||||
}}
|
}}
|
||||||
type="radio"
|
type="radio"
|
||||||
value="None"
|
value="None"
|
||||||
@ -181,11 +200,54 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
|
|||||||
</Conditional>
|
</Conditional>
|
||||||
|
|
||||||
<Conditional test={whitelistState === 'new'}>
|
<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">
|
<div className="grid grid-cols-2">
|
||||||
<FormGroup subtitle="Information about your minting settings" title="Whitelist Minting Details">
|
<FormGroup subtitle="Information about your minting settings" title="Whitelist Minting Details">
|
||||||
<NumberInput isRequired {...unitPriceState} />
|
<NumberInput isRequired {...unitPriceState} />
|
||||||
<NumberInput isRequired {...memberLimitState} />
|
<NumberInput isRequired {...memberLimitState} />
|
||||||
<NumberInput isRequired {...perAddressLimitState} />
|
<Conditional test={whitelistType === 'standard'}>
|
||||||
|
<NumberInput isRequired {...perAddressLimitState} />
|
||||||
|
</Conditional>
|
||||||
<FormControl
|
<FormControl
|
||||||
htmlId="start-date"
|
htmlId="start-date"
|
||||||
isRequired
|
isRequired
|
||||||
@ -226,11 +288,24 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
|
|||||||
title="Administrator Addresses"
|
title="Administrator Addresses"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<FormGroup subtitle="TXT file that contains the whitelisted addresses" title="Whitelist File">
|
<Conditional test={whitelistType === 'standard'}>
|
||||||
<WhitelistUpload onChange={whitelistFileOnChange} />
|
<FormGroup subtitle="TXT file that contains the whitelisted addresses" title="Whitelist File">
|
||||||
</FormGroup>
|
<WhitelistUpload onChange={whitelistFileOnChange} />
|
||||||
<Conditional test={whitelistArray.length > 0}>
|
</FormGroup>
|
||||||
<JsonPreview content={whitelistArray} initialState title="File Contents" />
|
<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>
|
</Conditional>
|
||||||
</div>
|
</div>
|
||||||
</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 { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
||||||
import type { Coin } from '@cosmjs/proto-signing'
|
import type { Coin } from '@cosmjs/proto-signing'
|
||||||
import type { logs } from '@cosmjs/stargate'
|
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'
|
import { VENDING_FACTORY_UPDATABLE_ADDRESS } from '../../utils/constants'
|
||||||
|
|
||||||
@ -23,11 +25,17 @@ export interface VendingFactoryInstance {
|
|||||||
msg: Record<string, unknown>,
|
msg: Record<string, unknown>,
|
||||||
funds: Coin[],
|
funds: Coin[],
|
||||||
updatable?: boolean,
|
updatable?: boolean,
|
||||||
|
flex?: boolean,
|
||||||
) => Promise<CreateVendingMinterResponse>
|
) => Promise<CreateVendingMinterResponse>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VendingFactoryMessages {
|
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 {
|
export interface CreateVendingMinterMessage {
|
||||||
@ -53,10 +61,11 @@ export const vendingFactory = (client: SigningCosmWasmClient, txSigner: string):
|
|||||||
msg: Record<string, unknown>,
|
msg: Record<string, unknown>,
|
||||||
funds: Coin[],
|
funds: Coin[],
|
||||||
updatable?: boolean,
|
updatable?: boolean,
|
||||||
|
flex?: boolean,
|
||||||
): Promise<CreateVendingMinterResponse> => {
|
): Promise<CreateVendingMinterResponse> => {
|
||||||
const result = await client.execute(
|
const result = await client.execute(
|
||||||
senderAddress,
|
senderAddress,
|
||||||
updatable ? VENDING_FACTORY_UPDATABLE_ADDRESS : VENDING_FACTORY_ADDRESS,
|
flex ? VENDING_FACTORY_FLEX_ADDRESS : updatable ? VENDING_FACTORY_UPDATABLE_ADDRESS : VENDING_FACTORY_ADDRESS,
|
||||||
msg,
|
msg,
|
||||||
'auto',
|
'auto',
|
||||||
'',
|
'',
|
||||||
@ -82,6 +91,7 @@ export const vendingFactory = (client: SigningCosmWasmClient, txSigner: string):
|
|||||||
msg: Record<string, unknown>,
|
msg: Record<string, unknown>,
|
||||||
funds: Coin[],
|
funds: Coin[],
|
||||||
updatable?: boolean,
|
updatable?: boolean,
|
||||||
|
flex?: boolean,
|
||||||
): CreateVendingMinterMessage => {
|
): CreateVendingMinterMessage => {
|
||||||
return {
|
return {
|
||||||
sender: txSigner,
|
sender: txSigner,
|
||||||
|
@ -11,6 +11,7 @@ export interface DispatchExecuteArgs {
|
|||||||
msg: Record<string, unknown>
|
msg: Record<string, unknown>
|
||||||
funds: Coin[]
|
funds: Coin[]
|
||||||
updatable?: boolean
|
updatable?: boolean
|
||||||
|
flex?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||||
@ -18,12 +19,12 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
|||||||
if (!messages) {
|
if (!messages) {
|
||||||
throw new Error('cannot dispatch execute, messages is not defined')
|
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) => {
|
export const previewExecutePayload = (args: DispatchExecuteArgs) => {
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
const { messages } = useVendingFactoryContract()
|
const { messages } = useVendingFactoryContract()
|
||||||
const { contract } = args
|
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 { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
||||||
import type { Coin } from '@cosmjs/proto-signing'
|
import type { Coin } from '@cosmjs/proto-signing'
|
||||||
import { coin } from '@cosmjs/proto-signing'
|
import { coin } from '@cosmjs/proto-signing'
|
||||||
|
import type { WhitelistFlexMember } from 'components/WhitelistFlexUpload'
|
||||||
|
|
||||||
export interface InstantiateResponse {
|
export interface InstantiateResponse {
|
||||||
readonly contractAddress: string
|
readonly contractAddress: string
|
||||||
@ -30,7 +31,7 @@ export interface WhiteListInstance {
|
|||||||
//Execute
|
//Execute
|
||||||
updateStartTime: (startTime: string) => Promise<string>
|
updateStartTime: (startTime: string) => Promise<string>
|
||||||
updateEndTime: (endTime: string) => Promise<string>
|
updateEndTime: (endTime: string) => Promise<string>
|
||||||
addMembers: (memberList: string[]) => Promise<string>
|
addMembers: (memberList: string[] | WhitelistFlexMember[]) => Promise<string>
|
||||||
removeMembers: (memberList: string[]) => Promise<string>
|
removeMembers: (memberList: string[]) => Promise<string>
|
||||||
updatePerAddressLimit: (limit: number) => Promise<string>
|
updatePerAddressLimit: (limit: number) => Promise<string>
|
||||||
increaseMemberLimit: (limit: number) => Promise<string>
|
increaseMemberLimit: (limit: number) => Promise<string>
|
||||||
@ -41,7 +42,7 @@ export interface WhiteListInstance {
|
|||||||
export interface WhitelistMessages {
|
export interface WhitelistMessages {
|
||||||
updateStartTime: (startTime: string) => UpdateStartTimeMessage
|
updateStartTime: (startTime: string) => UpdateStartTimeMessage
|
||||||
updateEndTime: (endTime: string) => UpdateEndTimeMessage
|
updateEndTime: (endTime: string) => UpdateEndTimeMessage
|
||||||
addMembers: (memberList: string[]) => AddMembersMessage
|
addMembers: (memberList: string[] | WhitelistFlexMember[]) => AddMembersMessage
|
||||||
removeMembers: (memberList: string[]) => RemoveMembersMessage
|
removeMembers: (memberList: string[]) => RemoveMembersMessage
|
||||||
updatePerAddressLimit: (limit: number) => UpdatePerAddressLimitMessage
|
updatePerAddressLimit: (limit: number) => UpdatePerAddressLimitMessage
|
||||||
increaseMemberLimit: (limit: number) => IncreaseMemberLimitMessage
|
increaseMemberLimit: (limit: number) => IncreaseMemberLimitMessage
|
||||||
@ -86,7 +87,7 @@ export interface AddMembersMessage {
|
|||||||
sender: string
|
sender: string
|
||||||
contract: string
|
contract: string
|
||||||
msg: {
|
msg: {
|
||||||
add_members: { to_add: string[] }
|
add_members: { to_add: string[] | WhitelistFlexMember[] }
|
||||||
}
|
}
|
||||||
funds: Coin[]
|
funds: Coin[]
|
||||||
}
|
}
|
||||||
@ -182,7 +183,7 @@ export const WhiteList = (client: SigningCosmWasmClient, txSigner: string): Whit
|
|||||||
return res.transactionHash
|
return res.transactionHash
|
||||||
}
|
}
|
||||||
|
|
||||||
const addMembers = async (memberList: string[]): Promise<string> => {
|
const addMembers = async (memberList: string[] | WhitelistFlexMember[]): Promise<string> => {
|
||||||
const res = await client.execute(
|
const res = await client.execute(
|
||||||
txSigner,
|
txSigner,
|
||||||
contractAddress,
|
contractAddress,
|
||||||
@ -307,7 +308,7 @@ export const WhiteList = (client: SigningCosmWasmClient, txSigner: string): Whit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const addMembers = (memberList: string[]) => {
|
const addMembers = (memberList: string[] | WhitelistFlexMember[]) => {
|
||||||
return {
|
return {
|
||||||
sender: txSigner,
|
sender: txSigner,
|
||||||
contract: contractAddress,
|
contract: contractAddress,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import type { WhitelistFlexMember } from '../../../components/WhitelistFlexUpload'
|
||||||
import type { WhiteListInstance } from '../index'
|
import type { WhiteListInstance } from '../index'
|
||||||
import { useWhiteListContract } from '../index'
|
import { useWhiteListContract } from '../index'
|
||||||
|
|
||||||
@ -68,23 +69,16 @@ export interface DispatchExecuteProps {
|
|||||||
[k: string]: unknown
|
[k: string]: unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
type Select<T extends ExecuteType> = T
|
|
||||||
|
|
||||||
/** @see {@link WhiteListInstance} */
|
/** @see {@link WhiteListInstance} */
|
||||||
export type DispatchExecuteArgs = {
|
export interface DispatchExecuteArgs {
|
||||||
contract: string
|
contract: string
|
||||||
messages?: WhiteListInstance
|
messages?: WhiteListInstance
|
||||||
} & (
|
type: string | undefined
|
||||||
| { type: undefined }
|
timestamp: string
|
||||||
| { type: Select<'update_start_time'>; timestamp: string }
|
members: string[] | WhitelistFlexMember[]
|
||||||
| { type: Select<'update_end_time'>; timestamp: string }
|
limit: number
|
||||||
| { type: Select<'add_members'>; members: string[] }
|
admins: 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'> }
|
|
||||||
)
|
|
||||||
|
|
||||||
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||||
const { messages } = args
|
const { messages } = args
|
||||||
@ -105,7 +99,7 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
|||||||
return messages.addMembers(args.members)
|
return messages.addMembers(args.members)
|
||||||
}
|
}
|
||||||
case 'remove_members': {
|
case 'remove_members': {
|
||||||
return messages.removeMembers(args.members)
|
return messages.removeMembers(args.members as string[])
|
||||||
}
|
}
|
||||||
case 'update_per_address_limit': {
|
case 'update_per_address_limit': {
|
||||||
return messages.updatePerAddressLimit(args.limit)
|
return messages.updatePerAddressLimit(args.limit)
|
||||||
@ -140,7 +134,7 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => {
|
|||||||
return messages(contract)?.addMembers(args.members)
|
return messages(contract)?.addMembers(args.members)
|
||||||
}
|
}
|
||||||
case 'remove_members': {
|
case 'remove_members': {
|
||||||
return messages(contract)?.removeMembers(args.members)
|
return messages(contract)?.removeMembers(args.members as string[])
|
||||||
}
|
}
|
||||||
case 'update_per_address_limit': {
|
case 'update_per_address_limit': {
|
||||||
return messages(contract)?.updatePerAddressLimit(args.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_CODE_ID: string
|
||||||
readonly NEXT_PUBLIC_SG721_UPDATABLE_CODE_ID: string
|
readonly NEXT_PUBLIC_SG721_UPDATABLE_CODE_ID: string
|
||||||
readonly NEXT_PUBLIC_WHITELIST_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_CODE_ID: string
|
||||||
|
readonly NEXT_PUBLIC_VENDING_MINTER_FLEX_CODE_ID: string
|
||||||
readonly NEXT_PUBLIC_VENDING_FACTORY_ADDRESS: string
|
readonly NEXT_PUBLIC_VENDING_FACTORY_ADDRESS: string
|
||||||
readonly NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_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_ADDRESS: string
|
||||||
readonly NEXT_PUBLIC_BASE_FACTORY_UPDATABLE_ADDRESS: string
|
readonly NEXT_PUBLIC_BASE_FACTORY_UPDATABLE_ADDRESS: string
|
||||||
readonly NEXT_PUBLIC_SG721_NAME_ADDRESS: string
|
readonly NEXT_PUBLIC_SG721_NAME_ADDRESS: string
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "stargaze-studio",
|
"name": "stargaze-studio",
|
||||||
"version": "0.5.8",
|
"version": "0.5.9",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
|
@ -145,7 +145,7 @@ const CollectionActionsPage: NextPage = () => {
|
|||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
setMinterType('base')
|
setSg721Type('base')
|
||||||
console.log('Unable to retrieve contract type. Defaulting to "base".')
|
console.log('Unable to retrieve contract type. Defaulting to "base".')
|
||||||
})
|
})
|
||||||
}, [debouncedSg721ContractState, wallet.client])
|
}, [debouncedSg721ContractState, wallet.client])
|
||||||
|
@ -49,8 +49,10 @@ import {
|
|||||||
SG721_UPDATABLE_CODE_ID,
|
SG721_UPDATABLE_CODE_ID,
|
||||||
STARGAZE_URL,
|
STARGAZE_URL,
|
||||||
VENDING_FACTORY_ADDRESS,
|
VENDING_FACTORY_ADDRESS,
|
||||||
|
VENDING_FACTORY_FLEX_ADDRESS,
|
||||||
VENDING_FACTORY_UPDATABLE_ADDRESS,
|
VENDING_FACTORY_UPDATABLE_ADDRESS,
|
||||||
WHITELIST_CODE_ID,
|
WHITELIST_CODE_ID,
|
||||||
|
WHITELIST_FLEX_CODE_ID,
|
||||||
} from 'utils/constants'
|
} from 'utils/constants'
|
||||||
import { withMetadata } from 'utils/layout'
|
import { withMetadata } from 'utils/layout'
|
||||||
import { links } from 'utils/links'
|
import { links } from 'utils/links'
|
||||||
@ -93,9 +95,11 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
const [vendingMinterCreationFee, setVendingMinterCreationFee] = useState<string | null>(null)
|
const [vendingMinterCreationFee, setVendingMinterCreationFee] = useState<string | null>(null)
|
||||||
const [baseMinterCreationFee, setBaseMinterCreationFee] = useState<string | null>(null)
|
const [baseMinterCreationFee, setBaseMinterCreationFee] = useState<string | null>(null)
|
||||||
const [vendingMinterUpdatableCreationFee, setVendingMinterUpdatableCreationFee] = 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 [baseMinterUpdatableCreationFee, setBaseMinterUpdatableCreationFee] = useState<string | null>(null)
|
||||||
const [minimumMintPrice, setMinimumMintPrice] = useState<string | null>('0')
|
const [minimumMintPrice, setMinimumMintPrice] = useState<string | null>('0')
|
||||||
const [minimumUpdatableMintPrice, setMinimumUpdatableMintPrice] = useState<string | null>('0')
|
const [minimumUpdatableMintPrice, setMinimumUpdatableMintPrice] = useState<string | null>('0')
|
||||||
|
const [minimumFlexMintPrice, setMinimumFlexMintPrice] = useState<string | null>('0')
|
||||||
|
|
||||||
const [uploading, setUploading] = useState(false)
|
const [uploading, setUploading] = useState(false)
|
||||||
const [isMintingComplete, setIsMintingComplete] = useState(false)
|
const [isMintingComplete, setIsMintingComplete] = useState(false)
|
||||||
@ -221,8 +225,8 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
setCoverImageUrl(coverImageUri)
|
setCoverImageUrl(coverImageUri)
|
||||||
|
|
||||||
let whitelist: string | undefined
|
let whitelist: string | undefined
|
||||||
if (whitelistDetails?.whitelistType === 'existing') whitelist = whitelistDetails.contractAddress
|
if (whitelistDetails?.whitelistState === 'existing') whitelist = whitelistDetails.contractAddress
|
||||||
else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist()
|
else if (whitelistDetails?.whitelistState === 'new') whitelist = await instantiateWhitelist()
|
||||||
setWhitelistContractAddress(whitelist as string)
|
setWhitelistContractAddress(whitelist as string)
|
||||||
|
|
||||||
await instantiateVendingMinter(baseUri, coverImageUri, whitelist)
|
await instantiateVendingMinter(baseUri, coverImageUri, whitelist)
|
||||||
@ -231,8 +235,8 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
setCoverImageUrl(uploadDetails?.imageUrl as string)
|
setCoverImageUrl(uploadDetails?.imageUrl as string)
|
||||||
|
|
||||||
let whitelist: string | undefined
|
let whitelist: string | undefined
|
||||||
if (whitelistDetails?.whitelistType === 'existing') whitelist = whitelistDetails.contractAddress
|
if (whitelistDetails?.whitelistState === 'existing') whitelist = whitelistDetails.contractAddress
|
||||||
else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist()
|
else if (whitelistDetails?.whitelistState === 'new') whitelist = await instantiateWhitelist()
|
||||||
setWhitelistContractAddress(whitelist as string)
|
setWhitelistContractAddress(whitelist as string)
|
||||||
|
|
||||||
await instantiateVendingMinter(baseTokenUri as string, coverImageUrl as string, whitelist)
|
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 (!wallet.initialized) throw new Error('Wallet not connected')
|
||||||
if (!whitelistContract) throw new Error('Contract not found')
|
if (!whitelistContract) throw new Error('Contract not found')
|
||||||
|
|
||||||
const msg = {
|
const standardMsg = {
|
||||||
members: whitelistDetails?.members,
|
members: whitelistDetails?.members,
|
||||||
start_time: whitelistDetails?.startTime,
|
start_time: whitelistDetails?.startTime,
|
||||||
end_time: whitelistDetails?.endTime,
|
end_time: whitelistDetails?.endTime,
|
||||||
@ -412,9 +416,19 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
admins_mutable: whitelistDetails?.adminsMutable,
|
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(
|
const data = await whitelistContract.instantiate(
|
||||||
WHITELIST_CODE_ID,
|
whitelistDetails?.whitelistType === 'standard' ? WHITELIST_CODE_ID : WHITELIST_FLEX_CODE_ID,
|
||||||
msg,
|
whitelistDetails?.whitelistType === 'standard' ? standardMsg : flexMsg,
|
||||||
'Stargaze Whitelist Contract',
|
'Stargaze Whitelist Contract',
|
||||||
wallet.address,
|
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 = {
|
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,
|
messages: vendingFactoryMessages,
|
||||||
txSigner: wallet.address,
|
txSigner: wallet.address,
|
||||||
msg,
|
msg,
|
||||||
funds: [
|
funds: [
|
||||||
coin(
|
coin(
|
||||||
collectionDetails?.updatable
|
whitelistDetails?.whitelistState !== 'none' && whitelistDetails?.whitelistType === 'flex'
|
||||||
|
? (vendingMinterFlexCreationFee as string)
|
||||||
|
: collectionDetails?.updatable
|
||||||
? (vendingMinterUpdatableCreationFee as string)
|
? (vendingMinterUpdatableCreationFee as string)
|
||||||
: (vendingMinterCreationFee as string),
|
: (vendingMinterCreationFee as string),
|
||||||
'ustars',
|
'ustars',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
updatable: collectionDetails?.updatable,
|
updatable: collectionDetails?.updatable,
|
||||||
|
flex: whitelistDetails?.whitelistState !== 'none' && whitelistDetails?.whitelistType === 'flex',
|
||||||
}
|
}
|
||||||
const data = await vendingFactoryDispatchExecute(payload)
|
const data = await vendingFactoryDispatchExecute(payload)
|
||||||
setTransactionHash(data.transactionHash)
|
setTransactionHash(data.transactionHash)
|
||||||
@ -786,7 +820,10 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
const checkMintingDetails = () => {
|
const checkMintingDetails = () => {
|
||||||
if (!mintingDetails) throw new Error('Please fill out the minting details')
|
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 (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))
|
if (Number(mintingDetails.unitPrice) < Number(minimumUpdatableMintPrice))
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Invalid unit price: The minimum unit price is ${Number(minimumUpdatableMintPrice) / 1000000} STARS`,
|
`Invalid unit price: The minimum unit price is ${Number(minimumUpdatableMintPrice) / 1000000} STARS`,
|
||||||
@ -822,12 +859,14 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
|
|
||||||
const checkWhitelistDetails = async () => {
|
const checkWhitelistDetails = async () => {
|
||||||
if (!whitelistDetails) throw new Error('Please fill out the whitelist details')
|
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')
|
if (whitelistDetails.contractAddress === '') throw new Error('Whitelist contract address is required')
|
||||||
else {
|
else {
|
||||||
const contract = whitelistContract?.use(whitelistDetails.contractAddress)
|
const contract = whitelistContract?.use(whitelistDetails.contractAddress)
|
||||||
//check if the address belongs to a whitelist contract (see performChecks())
|
//check if the address belongs to a whitelist contract (see performChecks())
|
||||||
const config = await contract?.config()
|
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)) {
|
if (Number(config?.start_time) !== Number(mintingDetails?.startTime)) {
|
||||||
const whitelistStartDate = new Date(Number(config?.start_time) / 1000000)
|
const whitelistStartDate = new Date(Number(config?.start_time) / 1000000)
|
||||||
throw Error(`Whitelist start time (${whitelistStartDate.toLocaleString()}) does not match minting start time`)
|
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.members?.length === 0) throw new Error('Whitelist member list cannot be empty')
|
||||||
if (whitelistDetails.unitPrice === '') throw new Error('Whitelist unit price is required')
|
if (whitelistDetails.unitPrice === '') throw new Error('Whitelist unit price is required')
|
||||||
if (Number(whitelistDetails.unitPrice) < 0)
|
if (Number(whitelistDetails.unitPrice) < 0)
|
||||||
throw new Error('Invalid unit price: The unit price cannot be negative')
|
throw new Error('Invalid unit price: The unit price cannot be negative')
|
||||||
if (whitelistDetails.startTime === '') throw new Error('Start time is required')
|
if (whitelistDetails.startTime === '') throw new Error('Start time is required')
|
||||||
if (whitelistDetails.endTime === '') throw new Error('End 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')
|
throw new Error('Per address limit is required')
|
||||||
if (!whitelistDetails.memberLimit || whitelistDetails.memberLimit === 0)
|
if (!whitelistDetails.memberLimit || whitelistDetails.memberLimit === 0)
|
||||||
throw new Error('Member limit is required')
|
throw new Error('Member limit is required')
|
||||||
@ -945,14 +987,25 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
setVendingMinterUpdatableCreationFee(vendingFactoryUpdatableParameters?.params?.creation_fee?.amount)
|
setVendingMinterUpdatableCreationFee(vendingFactoryUpdatableParameters?.params?.creation_fee?.amount)
|
||||||
setMinimumUpdatableMintPrice(vendingFactoryUpdatableParameters?.params?.min_mint_price?.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 = () => {
|
const checkwalletBalance = () => {
|
||||||
if (!wallet.initialized) throw new Error('Wallet not connected.')
|
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 =
|
const amountNeeded =
|
||||||
Math.ceil(Number(whitelistDetails.memberLimit) / 1000) * 100000000 +
|
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))
|
if (amountNeeded >= Number(wallet.balance[0].amount))
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Insufficient wallet balance to instantiate the required contracts. Needed amount: ${(
|
`Insufficient wallet balance to instantiate the required contracts. Needed amount: ${(
|
||||||
@ -962,7 +1015,9 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
} else {
|
} else {
|
||||||
const amountNeeded =
|
const amountNeeded =
|
||||||
minterType === 'vending'
|
minterType === 'vending'
|
||||||
? collectionDetails?.updatable
|
? whitelistDetails?.whitelistState === 'existing' && whitelistDetails.whitelistType === 'flex'
|
||||||
|
? Number(vendingMinterFlexCreationFee)
|
||||||
|
: collectionDetails?.updatable
|
||||||
? Number(vendingMinterUpdatableCreationFee)
|
? Number(vendingMinterUpdatableCreationFee)
|
||||||
: Number(vendingMinterCreationFee)
|
: Number(vendingMinterCreationFee)
|
||||||
: collectionDetails?.updatable
|
: 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 { Alert } from 'components/Alert'
|
||||||
import { Button } from 'components/Button'
|
import { Button } from 'components/Button'
|
||||||
import { Conditional } from 'components/Conditional'
|
import { Conditional } from 'components/Conditional'
|
||||||
@ -7,6 +10,8 @@ import { useExecuteComboboxState } from 'components/contracts/whitelist/ExecuteC
|
|||||||
import { FormControl } from 'components/FormControl'
|
import { FormControl } from 'components/FormControl'
|
||||||
import { AddressList } from 'components/forms/AddressList'
|
import { AddressList } from 'components/forms/AddressList'
|
||||||
import { useAddressListState } from 'components/forms/AddressList.hooks'
|
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 { AddressInput, NumberInput } from 'components/forms/FormInput'
|
||||||
import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks'
|
import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks'
|
||||||
import { InputDateTime } from 'components/InputDateTime'
|
import { InputDateTime } from 'components/InputDateTime'
|
||||||
@ -14,6 +19,8 @@ import { JsonPreview } from 'components/JsonPreview'
|
|||||||
import { LinkTabs } from 'components/LinkTabs'
|
import { LinkTabs } from 'components/LinkTabs'
|
||||||
import { whitelistLinkTabs } from 'components/LinkTabs.data'
|
import { whitelistLinkTabs } from 'components/LinkTabs.data'
|
||||||
import { TransactionHash } from 'components/TransactionHash'
|
import { TransactionHash } from 'components/TransactionHash'
|
||||||
|
import type { WhitelistFlexMember } from 'components/WhitelistFlexUpload'
|
||||||
|
import { WhitelistFlexUpload } from 'components/WhitelistFlexUpload'
|
||||||
import { WhitelistUpload } from 'components/WhitelistUpload'
|
import { WhitelistUpload } from 'components/WhitelistUpload'
|
||||||
import { useContracts } from 'contexts/contracts'
|
import { useContracts } from 'contexts/contracts'
|
||||||
import { useWallet } from 'contexts/wallet'
|
import { useWallet } from 'contexts/wallet'
|
||||||
@ -27,6 +34,7 @@ import { useEffect, useMemo, useState } from 'react'
|
|||||||
import { toast } from 'react-hot-toast'
|
import { toast } from 'react-hot-toast'
|
||||||
import { FaArrowRight } from 'react-icons/fa'
|
import { FaArrowRight } from 'react-icons/fa'
|
||||||
import { useMutation } from 'react-query'
|
import { useMutation } from 'react-query'
|
||||||
|
import { useDebounce } from 'utils/debounce'
|
||||||
import { isValidAddress } from 'utils/isValidAddress'
|
import { isValidAddress } from 'utils/isValidAddress'
|
||||||
import { withMetadata } from 'utils/layout'
|
import { withMetadata } from 'utils/layout'
|
||||||
import { links } from 'utils/links'
|
import { links } from 'utils/links'
|
||||||
@ -37,6 +45,8 @@ const WhitelistExecutePage: NextPage = () => {
|
|||||||
|
|
||||||
const [lastTx, setLastTx] = useState('')
|
const [lastTx, setLastTx] = useState('')
|
||||||
const [memberList, setMemberList] = useState<string[]>([])
|
const [memberList, setMemberList] = useState<string[]>([])
|
||||||
|
const [flexMemberList, setFlexMemberList] = useState<WhitelistFlexMember[]>([])
|
||||||
|
const [whitelistType, setWhitelistType] = useState<'standard' | 'flex'>('standard')
|
||||||
|
|
||||||
const comboboxState = useExecuteComboboxState()
|
const comboboxState = useExecuteComboboxState()
|
||||||
const type = comboboxState.value?.id
|
const type = comboboxState.value?.id
|
||||||
@ -45,6 +55,8 @@ const WhitelistExecutePage: NextPage = () => {
|
|||||||
|
|
||||||
const addressListState = useAddressListState()
|
const addressListState = useAddressListState()
|
||||||
|
|
||||||
|
const flexAddressListState = useFlexMemberAttributesState()
|
||||||
|
|
||||||
const contractState = useInputState({
|
const contractState = useInputState({
|
||||||
id: 'contract-address',
|
id: 'contract-address',
|
||||||
name: 'contract-address',
|
name: 'contract-address',
|
||||||
@ -53,6 +65,8 @@ const WhitelistExecutePage: NextPage = () => {
|
|||||||
})
|
})
|
||||||
const contractAddress = contractState.value
|
const contractAddress = contractState.value
|
||||||
|
|
||||||
|
const debouncedWhitelistContractState = useDebounce(contractState.value, 300)
|
||||||
|
|
||||||
const limitState = useNumberInputState({
|
const limitState = useNumberInputState({
|
||||||
id: 'limit',
|
id: 'limit',
|
||||||
name: 'limit',
|
name: 'limit',
|
||||||
@ -63,7 +77,9 @@ const WhitelistExecutePage: NextPage = () => {
|
|||||||
|
|
||||||
const showLimitState = isEitherType(type, ['update_per_address_limit', 'increase_member_limit'])
|
const showLimitState = isEitherType(type, ['update_per_address_limit', 'increase_member_limit'])
|
||||||
const showTimestamp = isEitherType(type, ['update_start_time', 'update_end_time'])
|
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 showAdminList = isEitherType(type, ['update_admins'])
|
||||||
|
|
||||||
const messages = useMemo(() => contract?.use(contractState.value), [contract, contractState.value])
|
const messages = useMemo(() => contract?.use(contractState.value), [contract, contractState.value])
|
||||||
@ -73,14 +89,44 @@ const WhitelistExecutePage: NextPage = () => {
|
|||||||
type,
|
type,
|
||||||
limit: limitState.value,
|
limit: limitState.value,
|
||||||
timestamp: timestamp ? (timestamp.getTime() * 1_000_000).toString() : '',
|
timestamp: timestamp ? (timestamp.getTime() * 1_000_000).toString() : '',
|
||||||
members: [
|
members:
|
||||||
...new Set(
|
whitelistType === 'standard'
|
||||||
addressListState.values
|
? [
|
||||||
.map((a) => a.address.trim())
|
...new Set(
|
||||||
.filter((address) => address !== '' && isValidAddress(address.trim()) && address.startsWith('stars'))
|
addressListState.values
|
||||||
.concat(memberList),
|
.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: [
|
admins: [
|
||||||
...new Set(
|
...new Set(
|
||||||
addressListState.values
|
addressListState.values
|
||||||
@ -122,11 +168,55 @@ const WhitelistExecutePage: NextPage = () => {
|
|||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [contractAddress])
|
}, [contractAddress])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initial = new URL(document.URL).searchParams.get('contractAddress')
|
const initial = new URL(document.URL).searchParams.get('contractAddress')
|
||||||
if (initial && initial.length > 0) contractState.onChange(initial)
|
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 (
|
return (
|
||||||
<section className="py-6 px-12 space-y-4">
|
<section className="py-6 px-12 space-y-4">
|
||||||
<NextSeo title="Execute Whitelist Contract" />
|
<NextSeo title="Execute Whitelist Contract" />
|
||||||
@ -154,7 +244,7 @@ const WhitelistExecutePage: NextPage = () => {
|
|||||||
<InputDateTime minDate={new Date()} onChange={(date) => setTimestamp(date)} value={timestamp} />
|
<InputDateTime minDate={new Date()} onChange={(date) => setTimestamp(date)} value={timestamp} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Conditional>
|
</Conditional>
|
||||||
<Conditional test={showMemberList || showAdminList}>
|
<Conditional test={(whitelistType === 'standard' && showMemberList) || showAdminList || showRemoveMemberList}>
|
||||||
<AddressList
|
<AddressList
|
||||||
entries={addressListState.entries}
|
entries={addressListState.entries}
|
||||||
isRequired
|
isRequired
|
||||||
@ -164,13 +254,30 @@ const WhitelistExecutePage: NextPage = () => {
|
|||||||
subtitle={type === 'update_admins' ? 'Enter the admin addresses' : 'Enter the member addresses'}
|
subtitle={type === 'update_admins' ? 'Enter the admin addresses' : 'Enter the member addresses'}
|
||||||
title="Addresses"
|
title="Addresses"
|
||||||
/>
|
/>
|
||||||
<Conditional test={showMemberList}>
|
<Conditional test={whitelistType === 'standard' && showMemberList}>
|
||||||
<Alert className="mt-8" type="info">
|
<Alert className="mt-8" type="info">
|
||||||
You may optionally choose a text file of additional member addresses.
|
You may optionally choose a text file of additional member addresses.
|
||||||
</Alert>
|
</Alert>
|
||||||
<WhitelistUpload onChange={setMemberList} />
|
<WhitelistUpload onChange={setMemberList} />
|
||||||
</Conditional>
|
</Conditional>
|
||||||
</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>
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
|
@ -13,13 +13,14 @@ import { InputDateTime } from 'components/InputDateTime'
|
|||||||
import { JsonPreview } from 'components/JsonPreview'
|
import { JsonPreview } from 'components/JsonPreview'
|
||||||
import { LinkTabs } from 'components/LinkTabs'
|
import { LinkTabs } from 'components/LinkTabs'
|
||||||
import { whitelistLinkTabs } from 'components/LinkTabs.data'
|
import { whitelistLinkTabs } from 'components/LinkTabs.data'
|
||||||
|
import { type WhitelistFlexMember, WhitelistFlexUpload } from 'components/WhitelistFlexUpload'
|
||||||
import { WhitelistUpload } from 'components/WhitelistUpload'
|
import { WhitelistUpload } from 'components/WhitelistUpload'
|
||||||
import { useContracts } from 'contexts/contracts'
|
import { useContracts } from 'contexts/contracts'
|
||||||
import { useWallet } from 'contexts/wallet'
|
import { useWallet } from 'contexts/wallet'
|
||||||
import type { InstantiateResponse } from 'contracts/sg721'
|
import type { InstantiateResponse } from 'contracts/sg721'
|
||||||
import type { NextPage } from 'next'
|
import type { NextPage } from 'next'
|
||||||
import { NextSeo } from 'next-seo'
|
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 { toast } from 'react-hot-toast'
|
||||||
import { FaAsterisk } from 'react-icons/fa'
|
import { FaAsterisk } from 'react-icons/fa'
|
||||||
import { useMutation } from 'react-query'
|
import { useMutation } from 'react-query'
|
||||||
@ -27,7 +28,7 @@ import { isValidAddress } from 'utils/isValidAddress'
|
|||||||
import { withMetadata } from 'utils/layout'
|
import { withMetadata } from 'utils/layout'
|
||||||
import { links } from 'utils/links'
|
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 WhitelistInstantiatePage: NextPage = () => {
|
||||||
const wallet = useWallet()
|
const wallet = useWallet()
|
||||||
@ -36,8 +37,10 @@ const WhitelistInstantiatePage: NextPage = () => {
|
|||||||
const [startDate, setStartDate] = useState<Date | undefined>(undefined)
|
const [startDate, setStartDate] = useState<Date | undefined>(undefined)
|
||||||
const [endDate, setEndDate] = useState<Date | undefined>(undefined)
|
const [endDate, setEndDate] = useState<Date | undefined>(undefined)
|
||||||
const [adminsMutable, setAdminsMutable] = useState<boolean>(true)
|
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({
|
const unitPriceState = useNumberInputState({
|
||||||
id: 'unit-price',
|
id: 'unit-price',
|
||||||
@ -63,6 +66,13 @@ const WhitelistInstantiatePage: NextPage = () => {
|
|||||||
placeholder: '5',
|
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 addressListState = useAddressListState()
|
||||||
|
|
||||||
const { data, isLoading, mutate } = useMutation(
|
const { data, isLoading, mutate } = useMutation(
|
||||||
@ -79,8 +89,8 @@ const WhitelistInstantiatePage: NextPage = () => {
|
|||||||
throw new Error('End date is required')
|
throw new Error('End date is required')
|
||||||
}
|
}
|
||||||
|
|
||||||
const msg = {
|
const standardMsg = {
|
||||||
members: whitelistArray,
|
members: whitelistStandardArray,
|
||||||
start_time: (startDate.getTime() * 1_000_000).toString(),
|
start_time: (startDate.getTime() * 1_000_000).toString(),
|
||||||
end_time: (endDate.getTime() * 1_000_000).toString(),
|
end_time: (endDate.getTime() * 1_000_000).toString(),
|
||||||
mint_price: coin(String(Number(unitPriceState.value) * 1000000), 'ustars'),
|
mint_price: coin(String(Number(unitPriceState.value) * 1000000), 'ustars'),
|
||||||
@ -95,8 +105,31 @@ const WhitelistInstantiatePage: NextPage = () => {
|
|||||||
] || [wallet.address],
|
] || [wallet.address],
|
||||||
admins_mutable: adminsMutable,
|
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(
|
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...',
|
loading: 'Instantiating contract...',
|
||||||
error: 'Instantiation failed!',
|
error: 'Instantiation failed!',
|
||||||
@ -112,9 +145,18 @@ const WhitelistInstantiatePage: NextPage = () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const whitelistFileOnChange = (whitelistData: string[]) => {
|
const whitelistFileOnChange = (whitelistData: string[]) => {
|
||||||
setWhitelistArray(whitelistData)
|
setWhitelistStandardArray(whitelistData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const whitelistFlexFileOnChange = (whitelistData: WhitelistFlexMember[]) => {
|
||||||
|
setWhitelistFlexArray(whitelistData)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setWhitelistStandardArray([])
|
||||||
|
setWhitelistFlexArray([])
|
||||||
|
}, [whitelistType])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="py-6 px-12 space-y-4" onSubmit={mutate}>
|
<form className="py-6 px-12 space-y-4" onSubmit={mutate}>
|
||||||
<NextSeo title="Instantiate Whitelist Contract" />
|
<NextSeo title="Instantiate Whitelist Contract" />
|
||||||
@ -125,6 +167,48 @@ const WhitelistInstantiatePage: NextPage = () => {
|
|||||||
/>
|
/>
|
||||||
<LinkTabs activeIndex={0} data={whitelistLinkTabs} />
|
<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)}>
|
<Conditional test={Boolean(data)}>
|
||||||
<Alert type="info">
|
<Alert type="info">
|
||||||
<b>Instantiate success!</b> Here is the transaction result containing the contract address and the transaction
|
<b>Instantiate success!</b> Here is the transaction result containing the contract address and the transaction
|
||||||
@ -134,7 +218,7 @@ const WhitelistInstantiatePage: NextPage = () => {
|
|||||||
<br />
|
<br />
|
||||||
</Conditional>
|
</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">
|
<label className="justify-start cursor-pointer label">
|
||||||
<span className="mr-4 font-bold">Mutable Administrator Addresses</span>
|
<span className="mr-4 font-bold">Mutable Administrator Addresses</span>
|
||||||
<input
|
<input
|
||||||
@ -158,16 +242,29 @@ const WhitelistInstantiatePage: NextPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FormGroup subtitle="Your whitelisted addresses" title="Whitelist File">
|
<FormGroup subtitle="Your whitelisted addresses" title="Whitelist File">
|
||||||
<WhitelistUpload onChange={whitelistFileOnChange} />
|
<Conditional test={whitelistType === 'standard'}>
|
||||||
<Conditional test={whitelistArray.length > 0}>
|
<WhitelistUpload onChange={whitelistFileOnChange} />
|
||||||
<JsonPreview content={whitelistArray} initialState={false} title="File Contents" />
|
<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>
|
</Conditional>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
<FormGroup subtitle="Information about your minting settings" title="Minting Details">
|
<FormGroup subtitle="Information about your minting settings" title="Minting Details">
|
||||||
<NumberInput isRequired {...unitPriceState} />
|
<NumberInput isRequired {...unitPriceState} />
|
||||||
<NumberInput isRequired {...memberLimitState} />
|
<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">
|
<FormControl htmlId="start-date" isRequired subtitle="Start time for the minting" title="Start Time">
|
||||||
<InputDateTime minDate={new Date()} onChange={(date) => setStartDate(date)} value={startDate} />
|
<InputDateTime minDate={new Date()} onChange={(date) => setStartDate(date)} value={startDate} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
export const SG721_CODE_ID = parseInt(process.env.NEXT_PUBLIC_SG721_CODE_ID, 10)
|
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 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_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_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_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_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_ADDRESS = process.env.NEXT_PUBLIC_BASE_FACTORY_ADDRESS
|
||||||
export const BASE_FACTORY_UPDATABLE_ADDRESS = process.env.NEXT_PUBLIC_BASE_FACTORY_UPDATABLE_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
|
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