Merge branch 'develop' into main

This commit is contained in:
Serkan Reis 2024-04-16 18:34:30 +03:00 committed by GitHub
commit 83835dfba2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 1023 additions and 115 deletions

View File

@ -59,6 +59,7 @@ NEXT_PUBLIC_BASE_FACTORY_ADDRESS="stars1a45hcxty3spnmm2f0papl8v4dk5ew29s4syhn4ef
NEXT_PUBLIC_BASE_FACTORY_UPDATABLE_ADDRESS="stars100xegx2syry4tclkmejjwxk4nfqahvcqhm9qxut5wxuzhj5d9qfsh5nmym"
NEXT_PUBLIC_OPEN_EDITION_FACTORY_ADDRESS="stars1sqweqcxlf2f7qhf27gn5naqusk5q52fkzewmy63c4sglvle3s7ls6k828e"
NEXT_PUBLIC_OPEN_EDITION_FACTORY_FLEX_ADDRESS="stars1nc59ddaa8xcx9mu8jladza82dznhxrta3njal3xylkqlsfqa7g4s9s5q02"
NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS="stars1fk5dkzcylam8mcpqrn8y9spauvc3d4navtaqurcc49dc3p9f8d3qdkvymx"
NEXT_PUBLIC_VENDING_IBC_KUJI_FACTORY_ADDRESS="stars1yyje87e0h9mqg34kp3x75yesa78ve4glc3dstdrn6nscw3zjfanqkj95f0"
@ -72,9 +73,11 @@ NEXT_PUBLIC_VENDING_IBC_CRBRUS_FACTORY_FLEX_ADDRESS="stars1halhp674yxwgn3p4gpkl8
# NEXT_PUBLIC_OPEN_EDITION_IBC_ATOM_FACTORY_ADDRESS=
# NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_ATOM_FACTORY_ADDRESS=
# NEXT_PUBLIC_OPEN_EDITION_IBC_USDC_FACTORY_ADDRESS=
# NEXT_PUBLIC_OPEN_EDITION_IBC_USDC_FACTORY_ADDRESS="stars152a40mmd3k2kk90add606vrqxcvzdp29qrjx4pjv33cjl6svksfscrrtuk"
# NEXT_PUBLIC_OPEN_EDITION_IBC_USDC_FACTORY_FLEX_ADDRESS="stars10sz9mup3a548l34k83q5w59nrklrnvv2gdsdkr2xref4zl5j3d4q0efamx"
# NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_USDC_FACTORY_ADDRESS=
# NEXT_PUBLIC_OPEN_EDITION_IBC_TIA_FACTORY_ADDRESS=
# NEXT_PUBLIC_OPEN_EDITION_IBC_TIA_FACTORY_ADDRESS="stars1vza7k890fkejxz3mqwau0u2m89k9y76w94vvxe4d42ya9862ryfq0damns"
# NEXT_PUBLIC_OPEN_EDITION_IBC_TIA_FACTORY_FLEX_ADDRESS="stars1jgn0ntt5tut93yn756rrqa60794qdsrn6dwhl8vhfx0yxgpr44qsfzhmrt"
# NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_TIA_FACTORY_ADDRESS=
NEXT_PUBLIC_OPEN_EDITION_IBC_FRNZ_FACTORY_ADDRESS="stars1vzffawsjhvspstu5lvtzz2x5n7zh07hnw09c9dfxcj78un05rcms5n3q3e"
NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_FRNZ_FACTORY_ADDRESS="stars1tc09vlgdg8rqyapcxwm9qdq8naj4gym9px4ntue9cs0kse5rvess0nee3a"

View File

@ -17,7 +17,7 @@ import { useWallet } from 'utils/wallet'
import { NumberInput, TextInput } from '../forms/FormInput'
import type { UploadMethod } from './OffChainMetadataUploadDetails'
export type LimitType = 'count_limited' | 'time_limited'
export type LimitType = 'count_limited' | 'time_limited' | 'time_and_count_limited'
interface MintingDetailsProps {
onChange: (data: MintingDetailsDataProps) => void
@ -25,6 +25,8 @@ interface MintingDetailsProps {
minimumMintPrice: number
mintTokenFromFactory?: TokenInfo | undefined
importedMintingDetails?: MintingDetailsDataProps
isPresale: boolean
whitelistStartDate?: string
}
export interface MintingDetailsDataProps {
@ -44,6 +46,8 @@ export const MintingDetails = ({
minimumMintPrice,
mintTokenFromFactory,
importedMintingDetails,
isPresale,
whitelistStartDate,
}: MintingDetailsProps) => {
const wallet = useWallet()
@ -109,11 +113,19 @@ export const MintingDetails = ({
: '',
perAddressLimit: perAddressLimitState.value,
startTime: timestamp ? (timestamp.getTime() * 1_000_000).toString() : '',
endTime: endTimestamp ? (endTimestamp.getTime() * 1_000_000).toString() : '',
endTime:
limitType === 'time_limited' || limitType === 'time_and_count_limited'
? endTimestamp
? (endTimestamp.getTime() * 1_000_000).toString()
: ''
: undefined,
paymentAddress: paymentAddressState.value.trim(),
selectedMintToken,
limitType,
tokenCountLimit: limitType === 'count_limited' ? tokenCountLimitState.value : undefined,
tokenCountLimit:
limitType === 'count_limited' || limitType === 'time_and_count_limited'
? tokenCountLimitState.value
: undefined,
}
onChange(data)
// eslint-disable-next-line react-hooks/exhaustive-deps
@ -144,6 +156,12 @@ export const MintingDetails = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [importedMintingDetails])
useEffect(() => {
if (isPresale) {
setTimestamp(whitelistStartDate ? new Date(Number(whitelistStartDate) / 1_000_000) : undefined)
}
}, [whitelistStartDate, isPresale])
return (
<div className="border-l-[1px] border-gray-500 border-opacity-20">
<FormGroup subtitle="Information about your minting settings" title="Minting Details">
@ -172,6 +190,7 @@ export const MintingDetails = ({
title="Start Time"
>
<InputDateTime
disabled={isPresale}
minDate={
timezone === 'Local' ? new Date() : new Date(Date.now() + new Date().getTimezoneOffset() * 60 * 1000)
}
@ -197,10 +216,12 @@ export const MintingDetails = ({
<label className="justify-start ml-6 cursor-pointer label">
<span className="mr-2">Time</span>
<input
checked={limitType === 'time_limited'}
checked={limitType === 'time_limited' || limitType === 'time_and_count_limited'}
className={`${limitType === 'time_limited' ? `bg-stargaze` : `bg-gray-600`} checkbox`}
onClick={() => {
setLimitType('time_limited' as LimitType)
if (limitType === 'time_and_count_limited') setLimitType('count_limited' as LimitType)
else if (limitType === 'count_limited') setLimitType('time_and_count_limited' as LimitType)
else setLimitType('count_limited' as LimitType)
}}
type="checkbox"
/>
@ -208,16 +229,18 @@ export const MintingDetails = ({
<label className="justify-start ml-4 cursor-pointer label">
<span className="mr-2">Token Count</span>
<input
checked={limitType === 'count_limited'}
checked={limitType === 'count_limited' || limitType === 'time_and_count_limited'}
className={`${limitType === 'count_limited' ? `bg-stargaze` : `bg-gray-600`} checkbox`}
onClick={() => {
setLimitType('count_limited' as LimitType)
if (limitType === 'time_and_count_limited') setLimitType('time_limited' as LimitType)
else if (limitType === 'time_limited') setLimitType('time_and_count_limited' as LimitType)
else setLimitType('time_limited' as LimitType)
}}
type="checkbox"
/>
</label>
</div>
<Conditional test={limitType === 'time_limited'}>
<Conditional test={limitType === 'time_limited' || limitType === 'time_and_count_limited'}>
<FormControl
htmlId="endTimestamp"
isRequired
@ -247,7 +270,7 @@ export const MintingDetails = ({
/>
</FormControl>
</Conditional>
<Conditional test={limitType === 'count_limited'}>
<Conditional test={limitType === 'count_limited' || limitType === 'time_and_count_limited'}>
<NumberInput {...tokenCountLimitState} isRequired />
</Conditional>
</FormGroup>

View File

@ -6,13 +6,13 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { toUtf8 } from '@cosmjs/encoding'
import { coin } from '@cosmjs/proto-signing'
import axios from 'axios'
import clsx from 'clsx'
import { Button } from 'components/Button'
import type { MinterType } from 'components/collections/actions/Combobox'
import { Conditional } from 'components/Conditional'
import { ConfirmationModal } from 'components/ConfirmationModal'
import { LoadingModal } from 'components/LoadingModal'
import { openEditionMinterList } from 'config/minter'
import { type TokenInfo } from 'config/token'
import { useContracts } from 'contexts/contracts'
import { addLogItem } from 'contexts/log'
@ -22,12 +22,15 @@ import React, { useEffect, useMemo, useState } from 'react'
import { toast } from 'react-hot-toast'
import { upload } from 'services/upload'
import {
OPEN_EDITION_FACTORY_ADDRESS,
OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS,
SG721_OPEN_EDITION_CODE_ID,
SG721_OPEN_EDITION_UPDATABLE_CODE_ID,
STRDST_SG721_CODE_ID,
WHITELIST_CODE_ID,
WHITELIST_FLEX_CODE_ID,
WHITELIST_MERKLE_TREE_API_URL,
WHITELIST_MERKLE_TREE_CODE_ID,
} from 'utils/constants'
import { useDebounce } from 'utils/debounce'
import type { AssetType } from 'utils/getAssetType'
import { isValidAddress } from 'utils/isValidAddress'
import { checkTokenUri } from 'utils/isValidTokenUri'
@ -47,12 +50,14 @@ import {
import type { OnChainMetadataInputDetailsDataProps } from './OnChainMetadataInputDetails'
import { OnChainMetadataInputDetails } from './OnChainMetadataInputDetails'
import { type RoyaltyDetailsDataProps, RoyaltyDetails } from './RoyaltyDetails'
import { type WhitelistDetailsDataProps, WhitelistDetails } from './WhitelistDetails'
export type MetadataStorageMethod = 'off-chain' | 'on-chain'
export interface OpenEditionMinterDetailsDataProps {
imageUploadDetails?: ImageUploadDetailsDataProps
collectionDetails?: CollectionDetailsDataProps
whitelistDetails?: WhitelistDetailsDataProps
royaltyDetails?: RoyaltyDetailsDataProps
onChainMetadataInputDetails?: OnChainMetadataInputDetailsDataProps
offChainMetadataUploadDetails?: OffChainMetadataUploadDetailsDataProps
@ -62,24 +67,26 @@ export interface OpenEditionMinterDetailsDataProps {
coverImageUrl?: string | null
tokenUri?: string | null
tokenImageUri?: string | null
isRefreshed?: boolean
}
interface OpenEditionMinterCreatorProps {
onChange: (data: OpenEditionMinterCreatorDataProps) => void
onDetailsChange: (data: OpenEditionMinterDetailsDataProps) => void
openEditionMinterUpdatableCreationFee?: string
openEditionMinterCreationFee?: string
minimumMintPrice?: string
minimumUpdatableMintPrice?: string
minterType?: MinterType
mintTokenFromFactory?: TokenInfo | undefined
importedOpenEditionMinterDetails?: OpenEditionMinterDetailsDataProps
isMatchingFactoryPresent?: boolean
openEditionFactoryAddress?: string
}
export interface OpenEditionMinterCreatorDataProps {
metadataStorageMethod: MetadataStorageMethod
openEditionMinterContractAddress: string | null
sg721ContractAddress: string | null
whitelistContractAddress: string | null
transactionHash: string | null
}
@ -87,21 +94,27 @@ export const OpenEditionMinterCreator = ({
onChange,
onDetailsChange,
openEditionMinterCreationFee,
openEditionMinterUpdatableCreationFee,
minimumMintPrice,
minimumUpdatableMintPrice,
minterType,
mintTokenFromFactory,
importedOpenEditionMinterDetails,
isMatchingFactoryPresent,
openEditionFactoryAddress,
}: OpenEditionMinterCreatorProps) => {
const wallet = useWallet()
const { openEditionMinter: openEditionMinterContract, openEditionFactory: openEditionFactoryContract } =
useContracts()
const {
openEditionMinter: openEditionMinterContract,
openEditionFactory: openEditionFactoryContract,
whitelist: whitelistContract,
whitelistMerkleTree: whitelistMerkleTreeContract,
} = useContracts()
const [metadataStorageMethod, setMetadataStorageMethod] = useState<MetadataStorageMethod>('off-chain')
const [imageUploadDetails, setImageUploadDetails] = useState<ImageUploadDetailsDataProps | null>(null)
const [collectionDetails, setCollectionDetails] = useState<CollectionDetailsDataProps | null>(null)
const [whitelistDetails, setWhitelistDetails] = useState<WhitelistDetailsDataProps | null>(null)
const [royaltyDetails, setRoyaltyDetails] = useState<RoyaltyDetailsDataProps | null>(null)
const [isRefreshed, setIsRefreshed] = useState(false)
const [onChainMetadataInputDetails, setOnChainMetadataInputDetails] =
useState<OnChainMetadataInputDetailsDataProps | null>(null)
const [offChainMetadataUploadDetails, setOffChainMetadataUploadDetails] =
@ -116,29 +129,19 @@ export const OpenEditionMinterCreator = ({
const [coverImageUrl, setCoverImageUrl] = useState<string | null>(null)
const [openEditionMinterContractAddress, setOpenEditionMinterContractAddress] = useState<string | null>(null)
const [sg721ContractAddress, setSg721ContractAddress] = useState<string | null>(null)
const [whitelistContractAddress, setWhitelistContractAddress] = useState<string | null>(null)
const [transactionHash, setTransactionHash] = useState<string | null>(null)
const [thumbnailImageUri, setThumbnailImageUri] = useState<string | undefined>(undefined)
const thumbnailCompatibleAssetTypes: AssetType[] = ['video', 'audio', 'html']
const factoryAddressForSelectedDenom =
openEditionMinterList.find((minter) => minter.supportedToken === mintTokenFromFactory && minter.updatable === false)
?.factoryAddress || OPEN_EDITION_FACTORY_ADDRESS
const updatableFactoryAddressForSelectedDenom =
openEditionMinterList.find((minter) => minter.supportedToken === mintTokenFromFactory && minter.updatable === true)
?.factoryAddress || OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS
const openEditionFactoryMessages = useMemo(
() =>
openEditionFactoryContract?.use(
collectionDetails?.updatable ? updatableFactoryAddressForSelectedDenom : factoryAddressForSelectedDenom,
),
() => openEditionFactoryContract?.use(openEditionFactoryAddress as string),
[
openEditionFactoryContract,
wallet.address,
collectionDetails?.updatable,
factoryAddressForSelectedDenom,
updatableFactoryAddressForSelectedDenom,
openEditionFactoryAddress,
wallet.isWalletConnected,
],
)
@ -152,13 +155,26 @@ export const OpenEditionMinterCreator = ({
.then(() => {
void checkRoyaltyDetails()
.then(() => {
void checkwalletBalance()
checkWhitelistDetails()
.then(() => {
setReadyToCreate(true)
void checkwalletBalance()
.then(() => {
setReadyToCreate(true)
})
.catch((error: any) => {
toast.error(`Error in Wallet Balance: ${error.message}`, { style: { maxWidth: 'none' } })
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
setReadyToCreate(false)
})
})
.catch((error: any) => {
toast.error(`Error in Wallet Balance: ${error.message}`, { style: { maxWidth: 'none' } })
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
.catch((error) => {
if (String(error.message).includes('Insufficient wallet balance')) {
toast.error(`${error.message}`, { style: { maxWidth: 'none' } })
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
} else {
toast.error(`Error in Whitelist Configuration: ${error.message}`, { style: { maxWidth: 'none' } })
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
}
setReadyToCreate(false)
})
})
@ -299,9 +315,9 @@ export const OpenEditionMinterCreator = ({
if (!mintingDetails) throw new Error('Please fill out the minting details')
if (mintingDetails.unitPrice === '') throw new Error('Mint price is required')
if (collectionDetails?.updatable) {
if (Number(mintingDetails.unitPrice) < Number(minimumUpdatableMintPrice))
if (Number(mintingDetails.unitPrice) < Number(minimumMintPrice))
throw new Error(
`Invalid mint price: The minimum mint price is ${Number(minimumUpdatableMintPrice) / 1000000} ${
`Invalid mint price: The minimum mint price is ${Number(minimumMintPrice) / 1000000} ${
mintTokenFromFactory?.displayName
}`,
)
@ -342,6 +358,92 @@ export const OpenEditionMinterCreator = ({
(!isValidAddress(mintingDetails.paymentAddress) || !mintingDetails.paymentAddress.startsWith('stars1'))
)
throw new Error('Invalid payment address')
if (!isMatchingFactoryPresent)
throw new Error(
`No matching open edition factory contract found for the selected parameters (Mint Price Denom: ${mintingDetails.selectedMintToken?.displayName}, Whitelist Type: ${whitelistDetails?.whitelistType})`,
)
}
const checkWhitelistDetails = async () => {
if (!whitelistDetails) throw new Error('Please fill out the whitelist details')
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 if (!JSON.stringify(config).includes('member_limit') || config?.member_limit === 0) {
// whitelistDetails.whitelistType = 'merkletree'
throw new Error(
'Whitelist Merkle Tree is not supported yet. Please use a standard or flexible whitelist contract.',
)
} 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`)
}
if (mintingDetails?.tokenCountLimit && config?.per_address_limit) {
if (mintingDetails.tokenCountLimit >= 100 && Number(config.per_address_limit) > 50) {
throw Error(
`Invalid limit for tokens per address (${config.per_address_limit} tokens). Tokens per address limit cannot exceed 50 regardless of the total number of tokens.`,
)
} else if (
mintingDetails.tokenCountLimit >= 100 &&
Number(config.per_address_limit) > Math.ceil((mintingDetails.tokenCountLimit / 100) * 3)
) {
throw Error(
`Invalid limit for tokens per address (${config.per_address_limit} tokens). Tokens per address limit cannot exceed 3% of the total number of tokens in the collection.`,
)
} else if (mintingDetails.tokenCountLimit < 100 && Number(config.per_address_limit) > 3) {
throw Error(
`Invalid limit for tokens per address (${config.per_address_limit} tokens). Tokens per address limit cannot exceed 3 for collections with a token count limit smaller than 100 tokens.`,
)
}
}
}
} else if (whitelistDetails.whitelistState === 'new') {
if (whitelistDetails.members?.length === 0) throw new Error('Whitelist member list cannot be empty')
if (whitelistDetails.unitPrice === undefined) 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.whitelistType === 'standard' &&
(!whitelistDetails.perAddressLimit || whitelistDetails.perAddressLimit === 0)
)
throw new Error('Per address limit is required')
if (
whitelistDetails.whitelistType !== 'merkletree' &&
(!whitelistDetails.memberLimit || whitelistDetails.memberLimit === 0)
)
throw new Error('Member limit is required')
if (Number(whitelistDetails.startTime) >= Number(whitelistDetails.endTime))
throw new Error('Whitelist start time cannot be equal to or later than the whitelist end time')
if (Number(whitelistDetails.startTime) !== Number(mintingDetails?.startTime))
throw new Error('Whitelist start time must be the same as the minting start time')
if (whitelistDetails.perAddressLimit && mintingDetails?.tokenCountLimit) {
if (mintingDetails.tokenCountLimit >= 100 && whitelistDetails.perAddressLimit > 50) {
throw Error(
`Invalid limit for tokens per address. Tokens per address limit cannot exceed 50 regardless of the total number of tokens.`,
)
} else if (
mintingDetails.tokenCountLimit >= 100 &&
whitelistDetails.perAddressLimit > Math.ceil((mintingDetails.tokenCountLimit / 100) * 3)
) {
throw Error(
`Invalid limit for tokens per address. Tokens per address limit cannot exceed 3% of the total number of tokens in the collection.`,
)
} else if (mintingDetails.tokenCountLimit < 100 && whitelistDetails.perAddressLimit > 3) {
throw Error(
`Invalid limit for tokens per address. Tokens per address limit cannot exceed 3 for collections with a token count limit smaller than 100 tokens.`,
)
}
}
}
}
const checkRoyaltyDetails = async () => {
@ -378,9 +480,13 @@ export const OpenEditionMinterCreator = ({
const checkwalletBalance = async () => {
if (!wallet.isWalletConnected) throw new Error('Wallet not connected.')
const amountNeeded = collectionDetails?.updatable
? Number(openEditionMinterUpdatableCreationFee)
: Number(openEditionMinterCreationFee)
let amountNeeded = 0
if (whitelistDetails?.whitelistState === 'new' && whitelistDetails.memberLimit) {
amountNeeded =
Math.ceil(Number(whitelistDetails.memberLimit) / 1000) * 100000000 + Number(openEditionMinterCreationFee)
} else {
amountNeeded = openEditionMinterCreationFee ? Number(openEditionMinterCreationFee) : 0
}
await (await wallet.getCosmWasmClient()).getBalance(wallet.address || '', 'ustars').then((balance) => {
if (amountNeeded >= Number(balance.amount))
throw new Error(
@ -399,6 +505,7 @@ export const OpenEditionMinterCreator = ({
setTokenImageUri(null)
setOpenEditionMinterContractAddress(null)
setSg721ContractAddress(null)
setWhitelistContractAddress(null)
setTransactionHash(null)
if (metadataStorageMethod === 'off-chain') {
if (offChainMetadataUploadDetails?.uploadMethod === 'new') {
@ -423,13 +530,27 @@ export const OpenEditionMinterCreator = ({
setTokenUri(metadataUriWithBase)
setCoverImageUrl(coverImageUriWithBase)
setUploading(false)
await instantiateOpenEditionMinter(metadataUriWithBase, coverImageUriWithBase)
let whitelist: string | undefined
if (whitelistDetails?.whitelistState === 'existing') whitelist = whitelistDetails.contractAddress
else if (whitelistDetails?.whitelistState === 'new') whitelist = await instantiateWhitelist()
setWhitelistContractAddress(whitelist as string)
await instantiateOpenEditionMinter(metadataUriWithBase, coverImageUriWithBase, undefined, whitelist)
} else {
setTokenUri(offChainMetadataUploadDetails?.tokenURI as string)
setCoverImageUrl(offChainMetadataUploadDetails?.imageUrl as string)
let whitelist: string | undefined
if (whitelistDetails?.whitelistState === 'existing') whitelist = whitelistDetails.contractAddress
else if (whitelistDetails?.whitelistState === 'new') whitelist = await instantiateWhitelist()
setWhitelistContractAddress(whitelist as string)
await instantiateOpenEditionMinter(
offChainMetadataUploadDetails?.tokenURI as string,
offChainMetadataUploadDetails?.imageUrl as string,
undefined,
whitelist,
)
}
} else if (metadataStorageMethod === 'on-chain') {
@ -471,15 +592,27 @@ export const OpenEditionMinterCreator = ({
? `ipfs://${thumbnailUri}/${(imageUploadDetails.thumbnailFile as File).name}`
: undefined
setThumbnailImageUri(thumbnailUriWithBase)
setUploading(false)
await instantiateOpenEditionMinter(imageUriWithBase, coverImageUriWithBase, thumbnailUriWithBase)
let whitelist: string | undefined
if (whitelistDetails?.whitelistState === 'existing') whitelist = whitelistDetails.contractAddress
else if (whitelistDetails?.whitelistState === 'new') whitelist = await instantiateWhitelist()
setWhitelistContractAddress(whitelist as string)
await instantiateOpenEditionMinter(imageUriWithBase, coverImageUriWithBase, thumbnailUriWithBase, whitelist)
} else if (imageUploadDetails?.uploadMethod === 'existing') {
setTokenImageUri(imageUploadDetails.imageUrl as string)
setCoverImageUrl(imageUploadDetails.coverImageUrl as string)
let whitelist: string | undefined
if (whitelistDetails?.whitelistState === 'existing') whitelist = whitelistDetails.contractAddress
else if (whitelistDetails?.whitelistState === 'new') whitelist = await instantiateWhitelist()
setWhitelistContractAddress(whitelist as string)
await instantiateOpenEditionMinter(
imageUploadDetails.imageUrl as string,
imageUploadDetails.coverImageUrl as string,
whitelist,
)
}
}
@ -578,7 +711,104 @@ export const OpenEditionMinterCreator = ({
})
}
const instantiateOpenEditionMinter = async (uri: string, coverImageUri: string, thumbnailUri?: string) => {
const instantiateWhitelist = async () => {
if (!wallet.isWalletConnected) throw new Error('Wallet not connected')
if (!whitelistContract) throw new Error('Contract not found')
if (whitelistDetails?.whitelistType === 'standard' || whitelistDetails?.whitelistType === 'flex') {
const standardMsg = {
members: whitelistDetails.members,
start_time: whitelistDetails.startTime,
end_time: whitelistDetails.endTime,
mint_price: coin(
String(Number(whitelistDetails.unitPrice)),
mintTokenFromFactory ? mintTokenFromFactory.denom : 'ustars',
),
per_address_limit: whitelistDetails.perAddressLimit,
member_limit: whitelistDetails.memberLimit,
admins: whitelistDetails.admins || [wallet.address],
admins_mutable: whitelistDetails.adminsMutable,
}
const flexMsg = {
members: whitelistDetails.members,
start_time: whitelistDetails.startTime,
end_time: whitelistDetails.endTime,
mint_price: coin(
String(Number(whitelistDetails.unitPrice)),
mintTokenFromFactory ? mintTokenFromFactory.denom : 'ustars',
),
member_limit: whitelistDetails.memberLimit,
admins: whitelistDetails.admins || [wallet.address],
admins_mutable: whitelistDetails.adminsMutable,
}
const data = await whitelistContract.instantiate(
whitelistDetails.whitelistType === 'standard' ? WHITELIST_CODE_ID : WHITELIST_FLEX_CODE_ID,
whitelistDetails.whitelistType === 'standard' ? standardMsg : flexMsg,
'Stargaze Whitelist Contract',
wallet.address,
)
return data.contractAddress
} else if (whitelistDetails?.whitelistType === 'merkletree') {
const members = whitelistDetails.members as string[]
const membersCsv = members.join('\n')
const membersBlob = new Blob([membersCsv], { type: 'text/csv' })
const membersFile = new File([membersBlob], 'members.csv', { type: 'text/csv' })
const formData = new FormData()
formData.append('whitelist', membersFile)
const response = await toast
.promise(
axios.post(`${WHITELIST_MERKLE_TREE_API_URL}/create_whitelist`, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
}),
{
loading: 'Fetching merkle root hash...',
success: 'Merkle root fetched successfully.',
error: 'Error fetching root hash from Whitelist Merkle Tree API.',
},
)
.catch((error) => {
console.log('error', error)
throw new Error('Whitelist instantiation failed.')
})
const rootHash = response.data.root_hash
console.log('rootHash', rootHash)
const merkleTreeMsg = {
merkle_root: rootHash,
merkle_tree_uri: null,
start_time: whitelistDetails.startTime,
end_time: whitelistDetails.endTime,
mint_price: coin(
String(Number(whitelistDetails.unitPrice)),
mintTokenFromFactory ? mintTokenFromFactory.denom : 'ustars',
),
per_address_limit: whitelistDetails.perAddressLimit,
admins: whitelistDetails.admins || [wallet.address],
admins_mutable: whitelistDetails.adminsMutable,
}
const data = await whitelistMerkleTreeContract?.instantiate(
WHITELIST_MERKLE_TREE_CODE_ID,
merkleTreeMsg,
'Stargaze Whitelist Merkle Tree Contract',
wallet.address,
)
return data?.contractAddress
}
}
const instantiateOpenEditionMinter = async (
uri: string,
coverImageUri: string,
thumbnailUri?: string,
whitelist?: string,
) => {
if (!wallet.isWalletConnected) throw new Error('Wallet not connected')
if (!openEditionFactoryContract) throw new Error('Contract not found')
if (!openEditionMinterContract) throw new Error('Contract not found')
@ -619,16 +849,23 @@ export const OpenEditionMinterCreator = ({
: null,
},
start_time: mintingDetails?.startTime,
end_time: mintingDetails?.limitType === ('time_limited' as LimitType) ? mintingDetails.endTime : null,
end_time:
mintingDetails?.limitType === ('time_limited' as LimitType) ||
mintingDetails?.limitType === ('time_and_count_limited' as LimitType)
? mintingDetails.endTime
: null,
mint_price: {
amount: Number(mintingDetails?.unitPrice).toString(),
denom: (mintTokenFromFactory?.denom as string) || 'ustars',
},
per_address_limit: mintingDetails?.perAddressLimit,
num_tokens:
mintingDetails?.limitType === ('count_limited' as LimitType) ? mintingDetails.tokenCountLimit : null,
mintingDetails?.limitType === ('count_limited' as LimitType) ||
mintingDetails?.limitType === ('time_and_count_limited' as LimitType)
? mintingDetails.tokenCountLimit
: null,
payment_address: mintingDetails?.paymentAddress || null,
// whitelist: null,
whitelist,
},
collection_params: {
code_id: collectionDetails?.updatable
@ -658,18 +895,11 @@ export const OpenEditionMinterCreator = ({
}
const payload: OpenEditionFactoryDispatchExecuteArgs = {
contract: collectionDetails?.updatable ? updatableFactoryAddressForSelectedDenom : factoryAddressForSelectedDenom,
contract: openEditionFactoryAddress as string,
messages: openEditionFactoryMessages,
txSigner: wallet.address || '',
msg,
funds: [
coin(
collectionDetails?.updatable
? (openEditionMinterUpdatableCreationFee as string)
: (openEditionMinterCreationFee as string),
'ustars',
),
],
funds: [coin(openEditionMinterCreationFee as string, 'ustars')],
updatable: collectionDetails?.updatable,
}
await openEditionFactoryDispatchExecute(payload)
@ -700,16 +930,24 @@ export const OpenEditionMinterCreator = ({
metadataStorageMethod,
openEditionMinterContractAddress,
sg721ContractAddress,
whitelistContractAddress,
transactionHash,
}
onChange(data)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [metadataStorageMethod, openEditionMinterContractAddress, sg721ContractAddress, transactionHash])
}, [
metadataStorageMethod,
openEditionMinterContractAddress,
sg721ContractAddress,
whitelistContractAddress,
transactionHash,
])
useEffect(() => {
const data: OpenEditionMinterDetailsDataProps = {
imageUploadDetails: imageUploadDetails ? imageUploadDetails : undefined,
collectionDetails: collectionDetails ? collectionDetails : undefined,
whitelistDetails: whitelistDetails ? whitelistDetails : undefined,
royaltyDetails: royaltyDetails ? royaltyDetails : undefined,
onChainMetadataInputDetails: onChainMetadataInputDetails ? onChainMetadataInputDetails : undefined,
offChainMetadataUploadDetails: offChainMetadataUploadDetails ? offChainMetadataUploadDetails : undefined,
@ -719,12 +957,14 @@ export const OpenEditionMinterCreator = ({
coverImageUrl,
tokenUri,
tokenImageUri,
isRefreshed,
}
onDetailsChange(data)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
imageUploadDetails,
collectionDetails,
whitelistDetails,
royaltyDetails,
onChainMetadataInputDetails,
offChainMetadataUploadDetails,
@ -734,6 +974,7 @@ export const OpenEditionMinterCreator = ({
coverImageUrl,
tokenUri,
tokenImageUri,
isRefreshed,
])
useEffect(() => {
@ -742,6 +983,40 @@ export const OpenEditionMinterCreator = ({
}
}, [importedOpenEditionMinterDetails])
const fetchWhitelistConfig = async (contractAddress: string | undefined) => {
if (contractAddress === '' || !whitelistDetails) return
const contract = whitelistContract?.use(contractAddress)
await contract
?.config()
.then((config) => {
if (!config) {
whitelistDetails.whitelistType = 'standard'
return
}
if (JSON.stringify(config).includes('whale_cap')) whitelistDetails.whitelistType = 'flex'
else if (!JSON.stringify(config).includes('member_limit') || config.member_limit === 0) {
// whitelistDetails.whitelistType = 'merkletree'
toast.error(
'Whitelist Merkle Tree is not supported yet for open edition collections. Please use a standard or flexible whitelist contract.',
)
} else whitelistDetails.whitelistType = 'standard'
setIsRefreshed(!isRefreshed)
})
.catch((error) => {
console.log('error', error)
})
}
const debouncedWhitelistContractAddress = useDebounce(whitelistDetails?.contractAddress, 300)
useEffect(() => {
if (whitelistDetails?.whitelistState === 'existing' && debouncedWhitelistContractAddress !== '') {
void fetchWhitelistConfig(debouncedWhitelistContractAddress)
}
}, [whitelistDetails?.whitelistState, debouncedWhitelistContractAddress])
return (
<div>
{/* TODO: Cancel once we're able to index on-chain metadata */}
@ -830,16 +1105,22 @@ export const OpenEditionMinterCreator = ({
/>
<MintingDetails
importedMintingDetails={importedOpenEditionMinterDetails?.mintingDetails}
minimumMintPrice={
collectionDetails?.updatable
? Number(minimumUpdatableMintPrice) / 1000000
: Number(minimumMintPrice) / 1000000
}
isPresale={whitelistDetails?.whitelistState === 'new'}
minimumMintPrice={Number(minimumMintPrice) / 1000000}
mintTokenFromFactory={mintTokenFromFactory}
onChange={setMintingDetails}
uploadMethod={offChainMetadataUploadDetails?.uploadMethod as UploadMethod}
whitelistStartDate={whitelistDetails?.startTime}
/>
</div>
<div className="my-6 mx-10">
<WhitelistDetails
importedWhitelistDetails={importedOpenEditionMinterDetails?.whitelistDetails}
mintingTokenFromFactory={mintTokenFromFactory}
onChange={setWhitelistDetails}
/>
</div>
<div className="my-6">
<RoyaltyDetails
importedRoyaltyDetails={importedOpenEditionMinterDetails?.royaltyDetails}

View File

@ -0,0 +1,528 @@
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
/* eslint-disable no-nested-ternary */
import { Button } from 'components/Button'
import { FormControl } from 'components/FormControl'
import { FormGroup } from 'components/FormGroup'
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 type { TokenInfo } from 'config/token'
import { useGlobalSettings } from 'contexts/globalSettings'
import React, { useEffect, useState } from 'react'
import { isValidAddress } from 'utils/isValidAddress'
import { useWallet } from 'utils/wallet'
import { Conditional } from '../Conditional'
import { AddressInput, NumberInput } from '../forms/FormInput'
import { JsonPreview } from '../JsonPreview'
import { WhitelistUpload } from '../WhitelistUpload'
interface WhitelistDetailsProps {
onChange: (data: WhitelistDetailsDataProps) => void
mintingTokenFromFactory?: TokenInfo
importedWhitelistDetails?: WhitelistDetailsDataProps
}
export interface WhitelistDetailsDataProps {
whitelistState: WhitelistState
whitelistType: WhitelistType
contractAddress?: string
members?: string[] | WhitelistFlexMember[]
unitPrice?: string
startTime?: string
endTime?: string
perAddressLimit?: number
memberLimit?: number
admins?: string[]
adminsMutable?: boolean
}
type WhitelistState = 'none' | 'existing' | 'new'
export type WhitelistType = 'standard' | 'flex' | 'merkletree'
export const WhitelistDetails = ({
onChange,
mintingTokenFromFactory,
importedWhitelistDetails,
}: WhitelistDetailsProps) => {
const wallet = useWallet()
const { timezone } = useGlobalSettings()
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 [whitelistStandardArray, setWhitelistStandardArray] = useState<string[]>([])
const [whitelistFlexArray, setWhitelistFlexArray] = useState<WhitelistFlexMember[]>([])
const [whitelistMerkleTreeArray, setWhitelistMerkleTreeArray] = useState<string[]>([])
const [adminsMutable, setAdminsMutable] = useState<boolean>(true)
const whitelistAddressState = useInputState({
id: 'whitelist-address',
name: 'whitelistAddress',
title: 'Whitelist Address',
defaultValue: '',
})
const unitPriceState = useNumberInputState({
id: 'unit-price',
name: 'unitPrice',
title: 'Unit Price',
subtitle: `Token price for whitelisted addresses \n (min. 0 ${
mintingTokenFromFactory ? mintingTokenFromFactory.displayName : 'STARS'
})`,
placeholder: '25',
})
const memberLimitState = useNumberInputState({
id: 'member-limit',
name: 'memberLimit',
title: 'Member Limit',
subtitle: 'Maximum number of whitelisted addresses',
placeholder: '1000',
})
const perAddressLimitState = useNumberInputState({
id: 'per-address-limit',
name: 'perAddressLimit',
title: 'Per Address Limit',
subtitle: 'Maximum number of tokens per whitelisted address',
placeholder: '5',
})
const addressListState = useAddressListState()
const whitelistFileOnChange = (data: string[]) => {
if (whitelistType === 'standard') setWhitelistStandardArray(data)
if (whitelistType === 'merkletree') setWhitelistMerkleTreeArray(data)
}
const whitelistFlexFileOnChange = (whitelistData: WhitelistFlexMember[]) => {
setWhitelistFlexArray(whitelistData)
}
const downloadSampleWhitelistFlexFile = () => {
const csvData =
'address,mint_count\nstars153w5xhuqu3et29lgqk4dsynj6gjn96lr33wx4e,3\nstars1xkes5r2k8u3m3ayfpverlkcrq3k4jhdk8ws0uz,1\nstars1s8qx0zvz8yd6e4x0mqmqf7fr9vvfn622wtp3g3,2'
const blob = new Blob([csvData], { type: 'text/csv' })
const url = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.setAttribute('href', url)
a.setAttribute('download', 'sample_whitelist_flex.csv')
a.click()
}
const downloadSampleWhitelistFile = () => {
const txtData =
'stars153w5xhuqu3et29lgqk4dsynj6gjn96lr33wx4e\nstars1xkes5r2k8u3m3ayfpverlkcrq3k4jhdk8ws0uz\nstars1s8qx0zvz8yd6e4x0mqmqf7fr9vvfn622wtp3g3'
const blob = new Blob([txtData], { type: 'text/txt' })
const url = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.setAttribute('href', url)
a.setAttribute('download', 'sample_whitelist.txt')
a.click()
}
useEffect(() => {
if (!importedWhitelistDetails) {
setWhitelistStandardArray([])
setWhitelistFlexArray([])
setWhitelistMerkleTreeArray([])
}
}, [whitelistType])
useEffect(() => {
const data: WhitelistDetailsDataProps = {
whitelistState,
whitelistType,
contractAddress: whitelistAddressState.value
.toLowerCase()
.replace(/,/g, '')
.replace(/"/g, '')
.replace(/'/g, '')
.replace(/ /g, ''),
members:
whitelistType === 'standard'
? whitelistStandardArray
: whitelistType === 'merkletree'
? whitelistMerkleTreeArray
: whitelistFlexArray,
unitPrice: unitPriceState.value
? (Number(unitPriceState.value) * 1_000_000).toString()
: unitPriceState.value === 0
? '0'
: undefined,
startTime: startDate ? (startDate.getTime() * 1_000_000).toString() : '',
endTime: endDate ? (endDate.getTime() * 1_000_000).toString() : '',
perAddressLimit: perAddressLimitState.value,
memberLimit: memberLimitState.value,
admins: [
...new Set(
addressListState.values
.map((a) => a.address.trim())
.filter((address) => address !== '' && isValidAddress(address.trim()) && address.startsWith('stars')),
),
],
adminsMutable,
}
onChange(data)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
whitelistAddressState.value,
unitPriceState.value,
memberLimitState.value,
perAddressLimitState.value,
startDate,
endDate,
whitelistStandardArray,
whitelistFlexArray,
whitelistMerkleTreeArray,
whitelistState,
whitelistType,
addressListState.values,
adminsMutable,
])
// make the necessary changes with respect to imported whitelist details
useEffect(() => {
if (importedWhitelistDetails) {
setWhitelistState(importedWhitelistDetails.whitelistState)
setWhitelistType(importedWhitelistDetails.whitelistType)
whitelistAddressState.onChange(
importedWhitelistDetails.contractAddress ? importedWhitelistDetails.contractAddress : '',
)
unitPriceState.onChange(
importedWhitelistDetails.unitPrice ? Number(importedWhitelistDetails.unitPrice) / 1000000 : 0,
)
memberLimitState.onChange(importedWhitelistDetails.memberLimit ? importedWhitelistDetails.memberLimit : 0)
perAddressLimitState.onChange(
importedWhitelistDetails.perAddressLimit ? importedWhitelistDetails.perAddressLimit : 0,
)
setStartDate(
importedWhitelistDetails.startTime
? new Date(Number(importedWhitelistDetails.startTime) / 1_000_000)
: undefined,
)
setEndDate(
importedWhitelistDetails.endTime ? new Date(Number(importedWhitelistDetails.endTime) / 1_000_000) : undefined,
)
setAdminsMutable(importedWhitelistDetails.adminsMutable ? importedWhitelistDetails.adminsMutable : true)
importedWhitelistDetails.admins?.forEach((admin) => {
addressListState.reset()
addressListState.add({ address: admin })
})
if (importedWhitelistDetails.whitelistType === 'standard') {
setWhitelistStandardArray([])
importedWhitelistDetails.members?.forEach((member) => {
setWhitelistStandardArray((standardArray) => [...standardArray, member as string])
})
} else if (importedWhitelistDetails.whitelistType === 'merkletree') {
setWhitelistMerkleTreeArray([])
// importedWhitelistDetails.members?.forEach((member) => {
// setWhitelistMerkleTreeArray((merkleTreeArray) => [...merkleTreeArray, member as string])
// })
} else if (importedWhitelistDetails.whitelistType === 'flex') {
setWhitelistFlexArray([])
importedWhitelistDetails.members?.forEach((member) => {
setWhitelistFlexArray((flexArray) => [
...flexArray,
{
address: (member as WhitelistFlexMember).address,
mint_count: (member as WhitelistFlexMember).mint_count,
},
])
})
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [importedWhitelistDetails])
useEffect(() => {
if (whitelistState === 'new' && wallet.address) {
addressListState.reset()
addressListState.add({ address: wallet.address })
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [whitelistState, wallet.address])
return (
<div className="py-3 px-8 rounded border-2 border-white/20">
<div className="flex justify-center">
<div className="ml-4 font-bold form-check form-check-inline">
<input
checked={whitelistState === 'none'}
className="peer sr-only"
id="whitelistRadio1"
name="whitelistRadioOptions1"
onClick={() => {
setWhitelistState('none')
setWhitelistType('standard')
}}
type="radio"
value="None"
/>
<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="whitelistRadio1"
>
No whitelist
</label>
</div>
<div className="ml-4 font-bold form-check form-check-inline">
<input
checked={whitelistState === 'existing'}
className="peer sr-only"
id="whitelistRadio2"
name="whitelistRadioOptions2"
onClick={() => {
setWhitelistState('existing')
}}
type="radio"
value="Existing"
/>
<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="whitelistRadio2"
>
Existing whitelist
</label>
</div>
<div className="ml-4 font-bold form-check form-check-inline">
<input
checked={whitelistState === 'new'}
className="peer sr-only"
id="whitelistRadio3"
name="whitelistRadioOptions3"
onClick={() => {
setWhitelistState('new')
}}
type="radio"
value="New"
/>
<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="whitelistRadio3"
>
New whitelist
</label>
</div>
</div>
<Conditional test={whitelistState === 'existing'}>
<AddressInput {...whitelistAddressState} className="pb-5" isRequired />
</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="standard"
/>
<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 className="form-check form-check-inline">
<input
checked={whitelistType === 'merkletree'}
className="peer sr-only"
id="inlineRadio9"
name="inlineRadioOptions9"
onClick={() => {
setWhitelistType('merkletree')
}}
type="radio"
value="merkletree"
/>
<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="inlineRadio9"
>
Whitelist Merkle Tree
</label>
</div> */}
</div>
<div className="grid grid-cols-2">
<FormGroup subtitle="Information about your minting settings" title="Whitelist Minting Details">
<NumberInput isRequired {...unitPriceState} />
<Conditional test={whitelistType !== 'merkletree'}>
<NumberInput isRequired {...memberLimitState} />
</Conditional>
<Conditional test={whitelistType === 'standard' || whitelistType === 'merkletree'}>
<NumberInput isRequired {...perAddressLimitState} />
</Conditional>
<FormControl
htmlId="start-date"
isRequired
subtitle="Start time for minting tokens to whitelisted addresses"
title={`Whitelist Start Time ${timezone === 'Local' ? '(local)' : '(UTC)'}`}
>
<InputDateTime
minDate={
timezone === 'Local' ? new Date() : new Date(Date.now() + new Date().getTimezoneOffset() * 60 * 1000)
}
onChange={(date) =>
date
? setStartDate(
timezone === 'Local'
? date
: new Date(date.getTime() - new Date().getTimezoneOffset() * 60 * 1000),
)
: setStartDate(undefined)
}
value={
timezone === 'Local'
? startDate
: startDate
? new Date(startDate.getTime() + new Date().getTimezoneOffset() * 60 * 1000)
: undefined
}
/>
</FormControl>
<FormControl
htmlId="end-date"
isRequired
subtitle="Whitelist End Time dictates when public sales will start"
title={`Whitelist End Time ${timezone === 'Local' ? '(local)' : '(UTC)'}`}
>
<InputDateTime
minDate={
timezone === 'Local' ? new Date() : new Date(Date.now() + new Date().getTimezoneOffset() * 60 * 1000)
}
onChange={(date) =>
date
? setEndDate(
timezone === 'Local'
? date
: new Date(date.getTime() - new Date().getTimezoneOffset() * 60 * 1000),
)
: setEndDate(undefined)
}
value={
timezone === 'Local'
? endDate
: endDate
? new Date(endDate.getTime() + new Date().getTimezoneOffset() * 60 * 1000)
: undefined
}
/>
</FormControl>
</FormGroup>
<div>
<div className="mt-2 ml-3 w-[65%] form-control">
<label className="justify-start cursor-pointer label">
<span className="mr-4 font-bold">Mutable Administrator Addresses</span>
<input
checked={adminsMutable}
className={`toggle ${adminsMutable ? `bg-stargaze` : `bg-gray-600`}`}
onClick={() => setAdminsMutable(!adminsMutable)}
type="checkbox"
/>
</label>
</div>
<div className="my-4 ml-4">
<AddressList
entries={addressListState.entries}
onAdd={addressListState.add}
onChange={addressListState.update}
onRemove={addressListState.remove}
subtitle="The list of administrator addresses"
title="Administrator Addresses"
/>
</div>
<Conditional test={whitelistType === 'standard'}>
<FormGroup
subtitle={
<div>
<span>TXT file that contains the whitelisted addresses</span>
<Button className="mt-2 text-sm text-white" onClick={downloadSampleWhitelistFile}>
Download Sample File
</Button>
</div>
}
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={
<div>
<span>CSV file that contains the whitelisted addresses and corresponding mint counts</span>
<Button className="mt-2 text-sm text-white" onClick={downloadSampleWhitelistFlexFile}>
Download Sample File
</Button>
</div>
}
title="Whitelist File"
>
<WhitelistFlexUpload onChange={whitelistFlexFileOnChange} />
</FormGroup>
<Conditional test={whitelistFlexArray.length > 0}>
<JsonPreview content={whitelistFlexArray} initialState={false} title="File Contents" />
</Conditional>
</Conditional>
<Conditional test={whitelistType === 'merkletree'}>
<FormGroup
subtitle={
<div>
<span>TXT file that contains the whitelisted addresses</span>
<Button className="mt-2 text-sm text-white" onClick={downloadSampleWhitelistFile}>
Download Sample File
</Button>
</div>
}
title="Whitelist File"
>
<WhitelistUpload onChange={whitelistFileOnChange} />
</FormGroup>
<Conditional test={whitelistStandardArray.length > 0}>
<JsonPreview content={whitelistStandardArray} initialState title="File Contents" />
</Conditional>
</Conditional>
</div>
</div>
</Conditional>
</div>
)
}

View File

@ -8,13 +8,17 @@ import {
FEATURED_VENDING_IBC_TIA_FACTORY_MERKLE_TREE_ADDRESS,
FEATURED_VENDING_IBC_USDC_FACTORY_FLEX_ADDRESS,
OPEN_EDITION_FACTORY_ADDRESS,
OPEN_EDITION_FACTORY_FLEX_ADDRESS,
OPEN_EDITION_IBC_ATOM_FACTORY_ADDRESS,
OPEN_EDITION_IBC_ATOM_FACTORY_FLEX_ADDRESS,
OPEN_EDITION_IBC_CRBRUS_FACTORY_ADDRESS,
OPEN_EDITION_IBC_FRNZ_FACTORY_ADDRESS,
OPEN_EDITION_IBC_KUJI_FACTORY_ADDRESS,
OPEN_EDITION_IBC_NBTC_FACTORY_ADDRESS,
OPEN_EDITION_IBC_TIA_FACTORY_ADDRESS,
OPEN_EDITION_IBC_TIA_FACTORY_FLEX_ADDRESS,
OPEN_EDITION_IBC_USDC_FACTORY_ADDRESS,
OPEN_EDITION_IBC_USDC_FACTORY_FLEX_ADDRESS,
OPEN_EDITION_IBC_USK_FACTORY_ADDRESS,
OPEN_EDITION_NATIVE_BRNCH_FACTORY_ADDRESS,
OPEN_EDITION_NATIVE_STRDST_FACTORY_ADDRESS,
@ -95,6 +99,7 @@ export const openEditionStarsMinter: MinterInfo = {
supportedToken: stars,
updatable: false,
featured: false,
flexible: false,
}
export const openEditionUpdatableStarsMinter: MinterInfo = {
@ -103,6 +108,7 @@ export const openEditionUpdatableStarsMinter: MinterInfo = {
supportedToken: stars,
updatable: true,
featured: false,
flexible: false,
}
export const openEditionIbcAtomMinter: MinterInfo = {
@ -111,6 +117,7 @@ export const openEditionIbcAtomMinter: MinterInfo = {
supportedToken: ibcAtom,
updatable: false,
featured: false,
flexible: false,
}
export const openEditionUpdatableIbcAtomMinter: MinterInfo = {
@ -119,6 +126,7 @@ export const openEditionUpdatableIbcAtomMinter: MinterInfo = {
supportedToken: ibcAtom,
updatable: true,
featured: false,
flexible: false,
}
export const openEditionIbcUsdcMinter: MinterInfo = {
@ -127,6 +135,7 @@ export const openEditionIbcUsdcMinter: MinterInfo = {
supportedToken: ibcUsdc,
updatable: false,
featured: false,
flexible: false,
}
export const openEditionIbcTiaMinter: MinterInfo = {
@ -135,6 +144,7 @@ export const openEditionIbcTiaMinter: MinterInfo = {
supportedToken: ibcTia,
updatable: false,
featured: false,
flexible: false,
}
export const openEditionIbcNbtcMinter: MinterInfo = {
@ -143,6 +153,7 @@ export const openEditionIbcNbtcMinter: MinterInfo = {
supportedToken: ibcNbtc,
updatable: false,
featured: false,
flexible: false,
}
export const openEditionUpdatableIbcUsdcMinter: MinterInfo = {
@ -151,6 +162,7 @@ export const openEditionUpdatableIbcUsdcMinter: MinterInfo = {
supportedToken: ibcUsdc,
updatable: true,
featured: false,
flexible: false,
}
export const openEditionUpdatableIbcTiaMinter: MinterInfo = {
@ -159,6 +171,7 @@ export const openEditionUpdatableIbcTiaMinter: MinterInfo = {
supportedToken: ibcTia,
updatable: true,
featured: false,
flexible: false,
}
export const openEditionUpdatableIbcNbtcMinter: MinterInfo = {
@ -167,6 +180,7 @@ export const openEditionUpdatableIbcNbtcMinter: MinterInfo = {
supportedToken: ibcNbtc,
updatable: true,
featured: false,
flexible: false,
}
export const openEditionIbcFrnzMinter: MinterInfo = {
@ -175,6 +189,7 @@ export const openEditionIbcFrnzMinter: MinterInfo = {
supportedToken: ibcFrnz,
updatable: false,
featured: false,
flexible: false,
}
export const openEditionUpdatableIbcFrnzMinter: MinterInfo = {
@ -183,6 +198,7 @@ export const openEditionUpdatableIbcFrnzMinter: MinterInfo = {
supportedToken: ibcFrnz,
updatable: true,
featured: false,
flexible: false,
}
export const openEditionIbcUskMinter: MinterInfo = {
@ -191,6 +207,7 @@ export const openEditionIbcUskMinter: MinterInfo = {
supportedToken: ibcUsk,
updatable: false,
featured: false,
flexible: false,
}
export const openEditionUpdatableIbcUskMinter: MinterInfo = {
@ -199,6 +216,7 @@ export const openEditionUpdatableIbcUskMinter: MinterInfo = {
supportedToken: ibcUsk,
updatable: true,
featured: false,
flexible: false,
}
export const openEditionIbcKujiMinter: MinterInfo = {
@ -207,6 +225,7 @@ export const openEditionIbcKujiMinter: MinterInfo = {
supportedToken: ibcKuji,
updatable: false,
featured: false,
flexible: false,
}
// export const openEditionIbcHuahuaMinter: MinterInfo = {
@ -223,6 +242,7 @@ export const openEditionIbcCrbrusMinter: MinterInfo = {
supportedToken: ibcCrbrus,
updatable: false,
featured: false,
flexible: false,
}
export const openEditionNativeStrdstMinter: MinterInfo = {
@ -231,6 +251,7 @@ export const openEditionNativeStrdstMinter: MinterInfo = {
supportedToken: nativeStardust,
updatable: false,
featured: false,
flexible: false,
}
export const openEditionNativeBrnchMinter: MinterInfo = {
@ -239,6 +260,7 @@ export const openEditionNativeBrnchMinter: MinterInfo = {
supportedToken: nativeBrnch,
updatable: false,
featured: false,
flexible: false,
}
export const openEditionMinterList = [
@ -263,6 +285,49 @@ export const openEditionMinterList = [
openEditionNativeBrnchMinter,
]
export const flexibleOpenEditionStarsMinter: MinterInfo = {
id: 'flexible-open-edition-stars-minter',
factoryAddress: OPEN_EDITION_FACTORY_FLEX_ADDRESS,
supportedToken: stars,
updatable: false,
featured: false,
flexible: true,
}
export const flexibleOpenEditionIbcAtomMinter: MinterInfo = {
id: 'flexible-open-edition-ibc-atom-minter',
factoryAddress: OPEN_EDITION_IBC_ATOM_FACTORY_FLEX_ADDRESS,
supportedToken: ibcAtom,
updatable: false,
featured: false,
flexible: true,
}
export const flexibleOpenEditionIbcUsdcMinter: MinterInfo = {
id: 'flexible-open-edition-ibc-usdc-minter',
factoryAddress: OPEN_EDITION_IBC_USDC_FACTORY_FLEX_ADDRESS,
supportedToken: ibcUsdc,
updatable: false,
featured: false,
flexible: true,
}
export const flexibleOpenEditionIbcTiaMinter: MinterInfo = {
id: 'flexible-open-edition-ibc-tia-minter',
factoryAddress: OPEN_EDITION_IBC_TIA_FACTORY_FLEX_ADDRESS,
supportedToken: ibcTia,
updatable: false,
featured: false,
flexible: true,
}
export const flexibleOpenEditionMinterList = [
flexibleOpenEditionStarsMinter,
flexibleOpenEditionIbcAtomMinter,
flexibleOpenEditionIbcUsdcMinter,
flexibleOpenEditionIbcTiaMinter,
]
export const vendingStarsMinter: MinterInfo = {
id: 'vending-stars-minter',
factoryAddress: VENDING_FACTORY_ADDRESS,

4
env.d.ts vendored
View File

@ -78,12 +78,16 @@ declare namespace NodeJS {
readonly NEXT_PUBLIC_VENDING_NATIVE_BRNCH_UPDATABLE_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_NATIVE_BRNCH_FLEX_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_FACTORY_FLEX_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_ATOM_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_ATOM_FACTORY_FLEX_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_ATOM_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_USDC_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_USDC_FACTORY_FLEX_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_USDC_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_TIA_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_TIA_FACTORY_FLEX_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_TIA_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_NBTC_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_NBTC_FACTORY_ADDRESS: string

View File

@ -35,6 +35,7 @@ import { LoadingModal } from 'components/LoadingModal'
import type { OpenEditionMinterCreatorDataProps } from 'components/openEdition/OpenEditionMinterCreator'
import { OpenEditionMinterCreator } from 'components/openEdition/OpenEditionMinterCreator'
import {
flexibleOpenEditionMinterList,
flexibleVendingMinterList,
merkleTreeVendingMinterList,
openEditionMinterList,
@ -61,7 +62,6 @@ import {
BLOCK_EXPLORER_URL,
NETWORK,
OPEN_EDITION_FACTORY_ADDRESS,
OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS,
SG721_CODE_ID,
SG721_UPDATABLE_CODE_ID,
STARGAZE_URL,
@ -130,20 +130,19 @@ const CollectionCreationPage: NextPage = () => {
const [baseMinterCreationFee, setBaseMinterCreationFee] = useState<string | null>(null)
const [vendingMinterUpdatableCreationFee, setVendingMinterUpdatableCreationFee] = useState<string | null>(null)
const [openEditionMinterCreationFee, setOpenEditionMinterCreationFee] = useState<string | null>(null)
const [openEditionMinterUpdatableCreationFee, setOpenEditionMinterUpdatableCreationFee] = 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 [minimumOpenEditionMintPrice, setMinimumOpenEditionMintPrice] = useState<string | null>('0')
const [minimumOpenEditionUpdatableMintPrice, setMinimumOpenEditionUpdatableMintPrice] = useState<string | null>('0')
const [minimumFlexMintPrice, setMinimumFlexMintPrice] = useState<string | null>('0')
const [mintTokenFromOpenEditionFactory, setMintTokenFromOpenEditionFactory] = useState<TokenInfo | undefined>(stars)
const [mintTokenFromVendingFactory, setMintTokenFromVendingFactory] = useState<TokenInfo | undefined>(stars)
const [vendingFactoryAddress, setVendingFactoryAddress] = useState<string | null>(VENDING_FACTORY_ADDRESS)
const [openEditionFactoryAddress, setOpenEditionFactoryAddress] = useState<string | undefined>(
OPEN_EDITION_FACTORY_ADDRESS,
)
const vendingFactoryMessages = useMemo(
() => vendingFactoryContract?.use(vendingFactoryAddress as string),
@ -170,6 +169,7 @@ const CollectionCreationPage: NextPage = () => {
const [coverImageUrl, setCoverImageUrl] = useState<string | null>(null)
const [transactionHash, setTransactionHash] = useState<string | null>(null)
const [isMatchingVendingFactoryPresent, setIsMatchingVendingFactoryPresent] = useState<boolean>(true)
const [isMatchingOpenEditionFactoryPresent, setIsMatchingOpenEditionFactoryPresent] = useState<boolean>(true)
const performVendingMinterChecks = () => {
try {
@ -1273,32 +1273,27 @@ const CollectionCreationPage: NextPage = () => {
setOpenEditionMinterCreationFee(openEditionFactoryParameters?.params?.creation_fee?.amount)
setMinimumOpenEditionMintPrice(openEditionFactoryParameters?.params?.min_mint_price?.amount)
}
if (OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS) {
const openEditionUpdatableFactoryParameters = await client
.queryContractSmart(OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS, { params: {} })
.catch((error) => {
toast.error(`${error.message}`, { style: { maxWidth: 'none' } })
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
})
setOpenEditionMinterUpdatableCreationFee(openEditionUpdatableFactoryParameters?.params?.creation_fee?.amount)
setMinimumOpenEditionUpdatableMintPrice(openEditionUpdatableFactoryParameters?.params?.min_mint_price?.amount)
}
setInitialParametersFetched(true)
}
const fetchOpenEditionFactoryParameters = useCallback(async () => {
const client = await wallet.getCosmWasmClient()
const factoryForSelectedDenom = openEditionMinterList.find(
(minter) =>
minter.supportedToken === openEditionMinterDetails?.mintingDetails?.selectedMintToken &&
minter.updatable === false,
)
const updatableFactoryForSelectedDenom = openEditionMinterList.find(
(minter) =>
minter.supportedToken === openEditionMinterDetails?.mintingDetails?.selectedMintToken &&
minter.updatable === true,
)
const factoryForSelectedDenom = openEditionMinterList
.concat(flexibleOpenEditionMinterList)
.find(
(minter) =>
minter.supportedToken === openEditionMinterDetails?.mintingDetails?.selectedMintToken &&
minter.updatable === openEditionMinterDetails.collectionDetails?.updatable &&
minter.flexible ===
(openEditionMinterDetails.whitelistDetails?.whitelistState !== 'none' &&
openEditionMinterDetails.whitelistDetails?.whitelistType === 'flex'),
)
console.log('OE Factory: ', factoryForSelectedDenom?.factoryAddress)
if (factoryForSelectedDenom?.factoryAddress) {
setIsMatchingOpenEditionFactoryPresent(true)
setOpenEditionFactoryAddress(factoryForSelectedDenom.factoryAddress)
const openEditionFactoryParameters = await client
.queryContractSmart(factoryForSelectedDenom.factoryAddress, { params: {} })
.catch((error) => {
@ -1306,35 +1301,24 @@ const CollectionCreationPage: NextPage = () => {
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
})
setOpenEditionMinterCreationFee(openEditionFactoryParameters?.params?.creation_fee?.amount)
if (!openEditionMinterDetails?.collectionDetails?.updatable) {
setMinimumOpenEditionMintPrice(openEditionFactoryParameters?.params?.min_mint_price?.amount)
setMintTokenFromOpenEditionFactory(
tokensList.find((token) => token.denom === openEditionFactoryParameters?.params?.min_mint_price?.denom),
)
}
}
if (updatableFactoryForSelectedDenom?.factoryAddress) {
const openEditionUpdatableFactoryParameters = await client
.queryContractSmart(updatableFactoryForSelectedDenom.factoryAddress, { params: {} })
.catch((error) => {
toast.error(`${error.message}`, { style: { maxWidth: 'none' } })
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
})
setOpenEditionMinterUpdatableCreationFee(openEditionUpdatableFactoryParameters?.params?.creation_fee?.amount)
if (openEditionMinterDetails?.collectionDetails?.updatable) {
setMinimumOpenEditionUpdatableMintPrice(openEditionUpdatableFactoryParameters?.params?.min_mint_price?.amount)
setMintTokenFromOpenEditionFactory(
tokensList.find(
(token) => token.denom === openEditionUpdatableFactoryParameters?.params?.min_mint_price?.denom,
),
)
}
setMinimumOpenEditionMintPrice(openEditionFactoryParameters?.params?.min_mint_price?.amount)
setMintTokenFromOpenEditionFactory(
tokensList.find((token) => token.denom === openEditionFactoryParameters?.params?.min_mint_price?.denom),
)
} else if (
openEditionMinterDetails?.mintingDetails?.selectedMintToken &&
openEditionMinterDetails.whitelistDetails?.whitelistState
) {
setIsMatchingOpenEditionFactoryPresent(false)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
openEditionMinterDetails?.mintingDetails?.selectedMintToken,
openEditionMinterDetails?.collectionDetails?.updatable,
openEditionMinterDetails?.whitelistDetails?.whitelistType,
openEditionMinterDetails?.whitelistDetails?.whitelistState,
wallet.isWalletConnected,
openEditionMinterDetails?.isRefreshed,
])
const fetchVendingFactoryParameters = useCallback(async () => {
@ -1622,6 +1606,19 @@ const CollectionCreationPage: NextPage = () => {
>
{openEditionMinterCreatorData?.sg721ContractAddress as string}
</Anchor>
<Conditional test={openEditionMinterCreatorData?.whitelistContractAddress !== null}>
<br />
Whitelist Contract Address:{' '}
<Anchor
className="text-stargaze hover:underline"
external
href={`/contracts/whitelist/query/?contractAddress=${
openEditionMinterCreatorData?.whitelistContractAddress as string
}`}
>
{openEditionMinterCreatorData?.whitelistContractAddress as string}
</Anchor>
</Conditional>
<br />
Transaction Hash: {' '}
<Conditional test={NETWORK === 'testnet'}>
@ -1930,14 +1927,14 @@ const CollectionCreationPage: NextPage = () => {
<Conditional test={minterType === 'openEdition'}>
<OpenEditionMinterCreator
importedOpenEditionMinterDetails={importedDetails?.openEditionMinterDetails}
isMatchingFactoryPresent={isMatchingOpenEditionFactoryPresent}
minimumMintPrice={minimumOpenEditionMintPrice as string}
minimumUpdatableMintPrice={minimumOpenEditionUpdatableMintPrice as string}
mintTokenFromFactory={mintTokenFromOpenEditionFactory}
minterType={minterType}
onChange={setOpenEditionMinterCreatorData}
onDetailsChange={setOpenEditionMinterDetails}
openEditionFactoryAddress={openEditionFactoryAddress}
openEditionMinterCreationFee={openEditionMinterCreationFee as string}
openEditionMinterUpdatableCreationFee={openEditionMinterUpdatableCreationFee as string}
/>
</Conditional>
<div className="mx-10">

View File

@ -80,14 +80,21 @@ export const VENDING_NATIVE_BRNCH_FLEX_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 OPEN_EDITION_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_OPEN_EDITION_FACTORY_ADDRESS
export const OPEN_EDITION_FACTORY_FLEX_ADDRESS = process.env.NEXT_PUBLIC_OPEN_EDITION_FACTORY_FLEX_ADDRESS
export const OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS
export const OPEN_EDITION_IBC_ATOM_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_OPEN_EDITION_IBC_ATOM_FACTORY_ADDRESS
export const OPEN_EDITION_IBC_ATOM_FACTORY_FLEX_ADDRESS =
process.env.NEXT_PUBLIC_OPEN_EDITION_IBC_ATOM_FACTORY_FLEX_ADDRESS
export const OPEN_EDITION_UPDATABLE_IBC_ATOM_FACTORY_ADDRESS =
process.env.NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_ATOM_FACTORY_ADDRESS
export const OPEN_EDITION_IBC_USDC_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_OPEN_EDITION_IBC_USDC_FACTORY_ADDRESS
export const OPEN_EDITION_IBC_USDC_FACTORY_FLEX_ADDRESS =
process.env.NEXT_PUBLIC_OPEN_EDITION_IBC_USDC_FACTORY_FLEX_ADDRESS
export const OPEN_EDITION_UPDATABLE_IBC_USDC_FACTORY_ADDRESS =
process.env.NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_USDC_FACTORY_ADDRESS
export const OPEN_EDITION_IBC_TIA_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_OPEN_EDITION_IBC_TIA_FACTORY_ADDRESS
export const OPEN_EDITION_IBC_TIA_FACTORY_FLEX_ADDRESS =
process.env.NEXT_PUBLIC_OPEN_EDITION_IBC_TIA_FACTORY_FLEX_ADDRESS
export const OPEN_EDITION_UPDATABLE_IBC_TIA_FACTORY_ADDRESS =
process.env.NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_TIA_FACTORY_ADDRESS
export const OPEN_EDITION_IBC_NBTC_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_OPEN_EDITION_IBC_NBTC_FACTORY_ADDRESS