Instantiate new WL Flex during collection creation
This commit is contained in:
parent
0945bcf927
commit
ccf1b13f5b
@ -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'
|
||||||
|
|
||||||
@ -20,7 +22,7 @@ export interface WhitelistDetailsDataProps {
|
|||||||
whitelistState: WhitelistState
|
whitelistState: WhitelistState
|
||||||
whitelistType: WhitelistType
|
whitelistType: WhitelistType
|
||||||
contractAddress?: string
|
contractAddress?: string
|
||||||
members?: string[]
|
members?: string[] | WhitelistFlexMember[]
|
||||||
unitPrice?: string
|
unitPrice?: string
|
||||||
startTime?: string
|
startTime?: string
|
||||||
endTime?: string
|
endTime?: string
|
||||||
@ -39,7 +41,8 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
|
|||||||
const [whitelistType, setWhitelistType] = useState<WhitelistType>('standard')
|
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({
|
||||||
@ -76,9 +79,18 @@ 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 = {
|
||||||
whitelistState,
|
whitelistState,
|
||||||
@ -89,7 +101,7 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
|
|||||||
.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() : '',
|
||||||
@ -113,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,
|
||||||
@ -186,11 +199,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
|
||||||
@ -231,11 +287,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>
|
||||||
|
@ -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)
|
||||||
@ -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 === 'new' && 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 === 'new' && 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 === 'new' && 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 === 'new' && 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 === 'new' && 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`,
|
||||||
@ -859,7 +896,10 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
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,6 +985,13 @@ 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 = () => {
|
||||||
@ -952,7 +999,11 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
if (minterType === 'vending' && whitelistDetails?.whitelistState === '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: ${(
|
||||||
|
Loading…
Reference in New Issue
Block a user