Merge pull request #128 from public-awesome/wl-related-changes

WL related changes
This commit is contained in:
Serkan Reis 2023-03-27 07:06:31 +03:00 committed by GitHub
commit 2e0d8e8ed9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 223 additions and 14 deletions

View File

@ -1,4 +1,4 @@
APP_VERSION=0.4.9
APP_VERSION=0.5.0
NEXT_PUBLIC_PINATA_ENDPOINT_URL=https://api.pinata.cloud/pinning/pinFileToIPFS
NEXT_PUBLIC_SG721_CODE_ID=1702

View File

@ -1,8 +1,11 @@
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 React, { useEffect, useState } from 'react'
import { isValidAddress } from 'utils/isValidAddress'
import { Conditional } from '../../Conditional'
import { AddressInput, NumberInput } from '../../forms/FormInput'
@ -22,6 +25,8 @@ export interface WhitelistDetailsDataProps {
endTime?: string
perAddressLimit?: number
memberLimit?: number
admins?: string[]
adminsMutable?: boolean
}
type WhitelistState = 'none' | 'existing' | 'new'
@ -31,6 +36,7 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
const [startDate, setStartDate] = useState<Date | undefined>(undefined)
const [endDate, setEndDate] = useState<Date | undefined>(undefined)
const [whitelistArray, setWhitelistArray] = useState<string[]>([])
const [adminsMutable, setAdminsMutable] = useState<boolean>(true)
const whitelistAddressState = useInputState({
id: 'whitelist-address',
@ -67,6 +73,8 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
setWhitelistArray(data)
}
const addressListState = useAddressListState()
useEffect(() => {
const data: WhitelistDetailsDataProps = {
whitelistType: whitelistState,
@ -82,6 +90,14 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
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
@ -94,6 +110,8 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
endDate,
whitelistArray,
whitelistState,
addressListState.values,
adminsMutable,
])
return (
@ -186,6 +204,28 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
</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}
isRequired
onAdd={addressListState.add}
onChange={addressListState.update}
onRemove={addressListState.remove}
subtitle="The list of administrator addresses"
title="Administrator Addresses"
/>
</div>
<FormGroup subtitle="TXT file that contains the whitelisted addresses" title="Whitelist File">
<WhitelistUpload onChange={whitelistFileOnChange} />
</FormGroup>

View File

@ -27,5 +27,9 @@ export function useAddressListState() {
})
}
return { entries, values, add, update, remove }
function reset() {
setRecord({})
}
return { entries, values, add, update, remove, reset }
}

View File

@ -24,6 +24,7 @@ export interface WhiteListInstance {
isActive: () => Promise<boolean>
members: (startAfter?: string, limit?: number) => Promise<string[]>
hasMember: (member: string) => Promise<boolean>
adminList: () => Promise<string[]>
config: () => Promise<ConfigResponse>
//Execute
@ -33,6 +34,8 @@ export interface WhiteListInstance {
removeMembers: (memberList: string[]) => Promise<string>
updatePerAddressLimit: (limit: number) => Promise<string>
increaseMemberLimit: (limit: number) => Promise<string>
updateAdmins: (admins: string[]) => Promise<string>
freeze: () => Promise<string>
}
export interface WhitelistMessages {
@ -42,6 +45,8 @@ export interface WhitelistMessages {
removeMembers: (memberList: string[]) => RemoveMembersMessage
updatePerAddressLimit: (limit: number) => UpdatePerAddressLimitMessage
increaseMemberLimit: (limit: number) => IncreaseMemberLimitMessage
updateAdmins: (admins: string[]) => UpdateAdminsMessage
freeze: () => FreezeMessage
}
export interface UpdateStartTimeMessage {
@ -62,6 +67,22 @@ export interface UpdateEndTimeMessage {
funds: Coin[]
}
export interface UpdateAdminsMessage {
sender: string
contract: string
msg: {
update_admins: { admins: string[] }
}
funds: Coin[]
}
export interface FreezeMessage {
sender: string
contract: string
msg: { freeze: Record<string, never> }
funds: Coin[]
}
export interface AddMembersMessage {
sender: string
contract: string
@ -139,6 +160,12 @@ export const WhiteList = (client: SigningCosmWasmClient, txSigner: string): Whit
})
}
const adminList = async (): Promise<string[]> => {
return client.queryContractSmart(contractAddress, {
admin_list: {},
})
}
const config = async (): Promise<ConfigResponse> => {
return client.queryContractSmart(contractAddress, {
config: {},
@ -170,6 +197,32 @@ export const WhiteList = (client: SigningCosmWasmClient, txSigner: string): Whit
return res.transactionHash
}
const updateAdmins = async (admins: string[]): Promise<string> => {
const res = await client.execute(
txSigner,
contractAddress,
{
update_admins: {
admins,
},
},
'auto',
)
return res.transactionHash
}
const freeze = async (): Promise<string> => {
const res = await client.execute(
txSigner,
contractAddress,
{
freeze: {},
},
'auto',
)
return res.transactionHash
}
const removeMembers = async (memberList: string[]): Promise<string> => {
const res = await client.execute(
txSigner,
@ -199,6 +252,8 @@ export const WhiteList = (client: SigningCosmWasmClient, txSigner: string): Whit
contractAddress,
updateStartTime,
updateEndTime,
updateAdmins,
freeze,
addMembers,
removeMembers,
updatePerAddressLimit,
@ -208,6 +263,7 @@ export const WhiteList = (client: SigningCosmWasmClient, txSigner: string): Whit
isActive,
members,
hasMember,
adminList,
config,
}
}
@ -263,6 +319,28 @@ export const WhiteList = (client: SigningCosmWasmClient, txSigner: string): Whit
}
}
const updateAdmins = (admins: string[]) => {
return {
sender: txSigner,
contract: contractAddress,
msg: {
update_admins: { admins },
},
funds: [],
}
}
const freeze = () => {
return {
sender: txSigner,
contract: contractAddress,
msg: {
freeze: {},
},
funds: [],
}
}
const removeMembers = (memberList: string[]) => {
return {
sender: txSigner,
@ -299,10 +377,12 @@ export const WhiteList = (client: SigningCosmWasmClient, txSigner: string): Whit
return {
updateStartTime,
updateEndTime,
updateAdmins,
addMembers,
removeMembers,
updatePerAddressLimit,
increaseMemberLimit,
freeze,
}
}

View File

@ -6,10 +6,12 @@ export type ExecuteType = typeof EXECUTE_TYPES[number]
export const EXECUTE_TYPES = [
'update_start_time',
'update_end_time',
'update_admins',
'add_members',
'remove_members',
'update_per_address_limit',
'increase_member_limit',
'freeze',
] as const
export interface ExecuteListItem {
@ -29,6 +31,11 @@ export const EXECUTE_LIST: ExecuteListItem[] = [
name: 'Update End Time',
description: `Update the end time of the whitelist`,
},
{
id: 'update_admins',
name: 'Update Admins',
description: `Update the list of administrators for the whitelist`,
},
{
id: 'add_members',
name: 'Add Members',
@ -49,6 +56,11 @@ export const EXECUTE_LIST: ExecuteListItem[] = [
name: 'Increase Member Limit',
description: `Increase the member limit of the whitelist`,
},
{
id: 'freeze',
name: 'Freeze',
description: `Freeze the current state of the contract admin list`,
},
]
export interface DispatchExecuteProps {
@ -70,6 +82,8 @@ export type DispatchExecuteArgs = {
| { 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) => {
@ -84,6 +98,9 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
case 'update_end_time': {
return messages.updateEndTime(args.timestamp)
}
case 'update_admins': {
return messages.updateAdmins(args.admins)
}
case 'add_members': {
return messages.addMembers(args.members)
}
@ -96,6 +113,9 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
case 'increase_member_limit': {
return messages.increaseMemberLimit(args.limit)
}
case 'freeze': {
return messages.freeze()
}
default: {
throw new Error('unknown execute type')
}
@ -113,6 +133,9 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => {
case 'update_end_time': {
return messages(contract)?.updateEndTime(args.timestamp)
}
case 'update_admins': {
return messages(contract)?.updateAdmins(args.admins)
}
case 'add_members': {
return messages(contract)?.addMembers(args.members)
}
@ -125,6 +148,9 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => {
case 'increase_member_limit': {
return messages(contract)?.increaseMemberLimit(args.limit)
}
case 'freeze': {
return messages(contract)?.freeze()
}
default: {
return {}
}

View File

@ -2,8 +2,15 @@ import type { WhiteListInstance } from '../contract'
export type QueryType = typeof QUERY_TYPES[number]
export const QUERY_TYPES = ['has_started', 'has_ended', 'is_active', 'members', 'has_member', 'config'] as const
export const QUERY_TYPES = [
'has_started',
'has_ended',
'is_active',
'members',
'admin_list',
'has_member',
'config',
] as const
export interface QueryListItem {
id: QueryType
name: string
@ -15,6 +22,7 @@ export const QUERY_LIST: QueryListItem[] = [
{ id: 'has_ended', name: 'Has Ended', description: 'Check if the whitelist minting has ended' },
{ id: 'is_active', name: 'Is Active', description: 'Check if the whitelist minting is active' },
{ id: 'members', name: 'Members', description: 'View the whitelist members' },
{ id: 'admin_list', name: 'Admin List', description: 'View the whitelist admin list' },
{ id: 'has_member', name: 'Has Member', description: 'Check if a member is in the whitelist' },
{ id: 'config', name: 'Config', description: 'View the whitelist configuration' },
]
@ -36,6 +44,8 @@ export const dispatchQuery = (props: DispatchQueryProps) => {
return messages?.isActive()
case 'members':
return messages?.members()
case 'admin_list':
return messages?.adminList()
case 'has_member':
return messages?.hasMember(address)
case 'config':

View File

@ -1,6 +1,6 @@
{
"name": "stargaze-studio",
"version": "0.4.9",
"version": "0.5.0",
"workspaces": [
"packages/*"
],

View File

@ -378,6 +378,8 @@ const CollectionCreationPage: NextPage = () => {
mint_price: coin(String(Number(whitelistDetails?.unitPrice)), 'ustars'),
per_address_limit: whitelistDetails?.perAddressLimit,
member_limit: whitelistDetails?.memberLimit,
admins: whitelistDetails?.admins || [wallet.address],
admins_mutable: whitelistDetails?.adminsMutable,
}
const data = await whitelistContract.instantiate(
@ -813,8 +815,8 @@ const CollectionCreationPage: NextPage = () => {
throw new Error('Per address limit is required')
if (!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 later than whitelist end time')
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?.numTokens) {

View File

@ -64,6 +64,7 @@ const WhitelistExecutePage: NextPage = () => {
const showLimitState = isEitherType(type, ['update_per_address_limit', 'increase_member_limit'])
const showTimestamp = isEitherType(type, ['update_start_time', 'update_end_time'])
const showMemberList = isEitherType(type, ['add_members', 'remove_members'])
const showAdminList = isEitherType(type, ['update_admins'])
const messages = useMemo(() => contract?.use(contractState.value), [contract, contractState.value])
const payload: DispatchExecuteArgs = {
@ -80,6 +81,13 @@ const WhitelistExecutePage: NextPage = () => {
.concat(memberList),
),
],
admins: [
...new Set(
addressListState.values
.map((a) => a.address.trim())
.filter((address) => address !== '' && isValidAddress(address.trim()) && address.startsWith('stars')),
),
] || [wallet.address],
}
const { isLoading, mutate } = useMutation(
async (event: FormEvent) => {
@ -146,20 +154,22 @@ const WhitelistExecutePage: NextPage = () => {
<InputDateTime minDate={new Date()} onChange={(date) => setTimestamp(date)} value={timestamp} />
</FormControl>
</Conditional>
<Conditional test={showMemberList}>
<Conditional test={showMemberList || showAdminList}>
<AddressList
entries={addressListState.entries}
isRequired
onAdd={addressListState.add}
onChange={addressListState.update}
onRemove={addressListState.remove}
subtitle="Enter the member addresses"
subtitle={type === 'update_admins' ? 'Enter the admin addresses' : 'Enter the member addresses'}
title="Addresses"
/>
<Alert className="mt-8" type="info">
You may optionally choose a text file of additional member addresses.
</Alert>
<WhitelistUpload onChange={setMemberList} />
<Conditional test={showMemberList}>
<Alert className="mt-8" type="info">
You may optionally choose a text file of additional member addresses.
</Alert>
<WhitelistUpload onChange={setMemberList} />
</Conditional>
</Conditional>
</div>
<div className="space-y-8">

View File

@ -5,6 +5,8 @@ import { Conditional } from 'components/Conditional'
import { ContractPageHeader } from 'components/ContractPageHeader'
import { FormControl } from 'components/FormControl'
import { FormGroup } from 'components/FormGroup'
import { AddressList } from 'components/forms/AddressList'
import { useAddressListState } from 'components/forms/AddressList.hooks'
import { NumberInput } from 'components/forms/FormInput'
import { useNumberInputState } from 'components/forms/FormInput.hooks'
import { InputDateTime } from 'components/InputDateTime'
@ -22,6 +24,7 @@ import { toast } from 'react-hot-toast'
import { FaAsterisk } from 'react-icons/fa'
import { useMutation } from 'react-query'
import { WHITELIST_CODE_ID } from 'utils/constants'
import { isValidAddress } from 'utils/isValidAddress'
import { withMetadata } from 'utils/layout'
import { links } from 'utils/links'
@ -31,6 +34,7 @@ const WhitelistInstantiatePage: NextPage = () => {
const [startDate, setStartDate] = useState<Date | undefined>(undefined)
const [endDate, setEndDate] = useState<Date | undefined>(undefined)
const [adminsMutable, setAdminsMutable] = useState<boolean>(true)
const [whitelistArray, setWhitelistArray] = useState<string[]>([])
@ -58,6 +62,8 @@ const WhitelistInstantiatePage: NextPage = () => {
placeholder: '5',
})
const addressListState = useAddressListState()
const { data, isLoading, mutate } = useMutation(
async (event: FormEvent): Promise<InstantiateResponse | null> => {
event.preventDefault()
@ -79,6 +85,14 @@ const WhitelistInstantiatePage: NextPage = () => {
mint_price: coin(String(Number(unitPriceState.value) * 1000000), 'ustars'),
per_address_limit: perAddressLimitState.value,
member_limit: memberLimitState.value,
admins: [
...new Set(
addressListState.values
.map((a) => a.address.trim())
.filter((address) => address !== '' && isValidAddress(address.trim()) && address.startsWith('stars')),
),
] || [wallet.address],
admins_mutable: adminsMutable,
}
return toast.promise(
contract.instantiate(WHITELIST_CODE_ID, msg, 'Stargaze Whitelist Contract', wallet.address),
@ -119,6 +133,29 @@ const WhitelistInstantiatePage: NextPage = () => {
<br />
</Conditional>
<div className="mt-2 ml-3 w-full 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 w-1/2">
<AddressList
entries={addressListState.entries}
isRequired
onAdd={addressListState.add}
onChange={addressListState.update}
onRemove={addressListState.remove}
subtitle="The list of administrator addresses"
title="Administrator Addresses"
/>
</div>
<FormGroup subtitle="Your whitelisted addresses" title="Whitelist File">
<WhitelistUpload onChange={whitelistFileOnChange} />
<Conditional test={whitelistArray.length > 0}>

View File

@ -108,7 +108,7 @@ const WhitelistQueryPage: NextPage = () => {
onChange={(e) => setType(e.target.value as QueryType)}
>
{QUERY_LIST.map(({ id, name }) => (
<option key={`query-${id}`} value={id}>
<option key={`query-${id}`} className="mt-2 text-lg bg-[#1A1A1A]" value={id}>
{name}
</option>
))}