Add whitelist-merkletree contract helpers

This commit is contained in:
Serkan Reis 2024-04-02 13:04:25 +03:00
parent fd65316d1f
commit f5f14fb330
6 changed files with 694 additions and 1 deletions

View File

@ -16,6 +16,7 @@ import type { UseVendingMinterContractProps } from 'contracts/vendingMinter'
import { useVendingMinterContract } from 'contracts/vendingMinter'
import type { UseWhiteListContractProps } from 'contracts/whitelist'
import { useWhiteListContract } from 'contracts/whitelist'
import { type UseWhiteListMerkleTreeContractProps, useWhiteListMerkleTreeContract } from 'contracts/whitelistMerkleTree'
import type { ReactNode, VFC } from 'react'
import { Fragment, useEffect } from 'react'
import { create } from 'zustand'
@ -32,6 +33,7 @@ export interface ContractsStore {
baseMinter: UseBaseMinterContractProps | null
openEditionMinter: UseOpenEditionMinterContractProps | null
whitelist: UseWhiteListContractProps | null
whitelistMerkleTree: UseWhiteListMerkleTreeContractProps | null
vendingFactory: UseVendingFactoryContractProps | null
baseFactory: UseBaseFactoryContractProps | null
openEditionFactory: UseOpenEditionFactoryContractProps | null
@ -49,6 +51,7 @@ export const defaultValues: ContractsStore = {
baseMinter: null,
openEditionMinter: null,
whitelist: null,
whitelistMerkleTree: null,
vendingFactory: null,
baseFactory: null,
openEditionFactory: null,
@ -83,6 +86,7 @@ const ContractsSubscription: VFC = () => {
const baseMinter = useBaseMinterContract()
const openEditionMinter = useOpenEditionMinterContract()
const whitelist = useWhiteListContract()
const whitelistMerkleTree = useWhiteListMerkleTreeContract()
const vendingFactory = useVendingFactoryContract()
const baseFactory = useBaseFactoryContract()
const openEditionFactory = useOpenEditionFactoryContract()
@ -97,6 +101,7 @@ const ContractsSubscription: VFC = () => {
baseMinter,
openEditionMinter,
whitelist,
whitelistMerkleTree,
vendingFactory,
baseFactory,
openEditionFactory,
@ -104,7 +109,20 @@ const ContractsSubscription: VFC = () => {
splits,
royaltyRegistry,
})
}, [sg721, vendingMinter, baseMinter, whitelist, vendingFactory, baseFactory, badgeHub, splits, royaltyRegistry])
}, [
sg721,
vendingMinter,
baseMinter,
whitelist,
whitelistMerkleTree,
vendingFactory,
baseFactory,
badgeHub,
splits,
royaltyRegistry,
openEditionMinter,
openEditionFactory,
])
return null
}

View File

@ -0,0 +1,374 @@
import type { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
import type { Coin } from '@cosmjs/proto-signing'
import type { WhitelistFlexMember } from 'components/WhitelistFlexUpload'
export interface InstantiateResponse {
readonly contractAddress: string
readonly transactionHash: string
}
export interface ConfigResponse {
readonly per_address_limit: number
readonly start_time: string
readonly end_time: string
readonly mint_price: Coin
readonly is_active: boolean
}
export interface WhiteListMerkleTreeInstance {
readonly contractAddress: string
//Query
hasStarted: () => Promise<boolean>
hasEnded: () => Promise<boolean>
isActive: () => Promise<boolean>
hasMember: (member: string, proof_hashes: string[]) => Promise<boolean>
adminList: () => Promise<string[]>
config: () => Promise<ConfigResponse>
canExecute: (sender: string, msg: string) => Promise<boolean>
merkleRoot: () => Promise<string>
merkleTreeUri: () => Promise<string>
//Execute
updateStartTime: (startTime: string) => Promise<string>
updateEndTime: (endTime: string) => Promise<string>
addMembers: (memberList: string[] | WhitelistFlexMember[]) => Promise<string>
removeMembers: (memberList: string[]) => Promise<string>
// updatePerAddressLimit: (limit: number) => Promise<string>
updateAdmins: (admins: string[]) => Promise<string>
freeze: () => Promise<string>
}
export interface WhiteListMerkleTreeMessages {
updateStartTime: (startTime: string) => UpdateStartTimeMessage
updateEndTime: (endTime: string) => UpdateEndTimeMessage
addMembers: (memberList: string[] | WhitelistFlexMember[]) => AddMembersMessage
removeMembers: (memberList: string[]) => RemoveMembersMessage
// updatePerAddressLimit: (limit: number) => UpdatePerAddressLimitMessage
updateAdmins: (admins: string[]) => UpdateAdminsMessage
freeze: () => FreezeMessage
}
export interface UpdateStartTimeMessage {
sender: string
contract: string
msg: {
update_start_time: string
}
funds: Coin[]
}
export interface UpdateEndTimeMessage {
sender: string
contract: string
msg: {
update_end_time: string
}
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
msg: {
add_members: { to_add: string[] | WhitelistFlexMember[] }
}
funds: Coin[]
}
export interface RemoveMembersMessage {
sender: string
contract: string
msg: {
remove_members: { to_remove: string[] }
}
funds: Coin[]
}
// export interface UpdatePerAddressLimitMessage {
// sender: string
// contract: string
// msg: {
// update_per_address_limit: number
// }
// funds: Coin[]
// }
export interface WhiteListMerkleTreeContract {
instantiate: (
codeId: number,
initMsg: Record<string, unknown>,
label: string,
admin?: string,
) => Promise<InstantiateResponse>
use: (contractAddress: string) => WhiteListMerkleTreeInstance
messages: (contractAddress: string) => WhiteListMerkleTreeMessages
}
export const WhiteListMerkleTree = (client: SigningCosmWasmClient, txSigner: string): WhiteListMerkleTreeContract => {
const use = (contractAddress: string): WhiteListMerkleTreeInstance => {
///QUERY START
const hasStarted = async (): Promise<boolean> => {
return client.queryContractSmart(contractAddress, { has_started: {} })
}
const hasEnded = async (): Promise<boolean> => {
return client.queryContractSmart(contractAddress, { has_ended: {} })
}
const isActive = async (): Promise<boolean> => {
return client.queryContractSmart(contractAddress, { is_active: {} })
}
const hasMember = async (member: string, proofHashes: string[]): Promise<boolean> => {
return client.queryContractSmart(contractAddress, {
has_member: { member, proof_hashes: proofHashes },
})
}
const adminList = async (): Promise<string[]> => {
return client.queryContractSmart(contractAddress, {
admin_list: {},
})
}
const config = async (): Promise<ConfigResponse> => {
return client.queryContractSmart(contractAddress, {
config: {},
})
}
const merkleRoot = async (): Promise<string> => {
return client.queryContractSmart(contractAddress, {
merkle_root: {},
})
}
const merkleTreeUri = async (): Promise<string> => {
return client.queryContractSmart(contractAddress, {
merkle_tree_uri: {},
})
}
const canExecute = async (sender: string, msg: string): Promise<boolean> => {
return client.queryContractSmart(contractAddress, {
can_execute: { sender, msg },
})
}
/// QUERY END
/// EXECUTE START
const updateStartTime = async (startTime: string): Promise<string> => {
const res = await client.execute(txSigner, contractAddress, { update_start_time: startTime }, 'auto')
return res.transactionHash
}
const updateEndTime = async (endTime: string): Promise<string> => {
const res = await client.execute(txSigner, contractAddress, { update_end_time: endTime }, 'auto')
return res.transactionHash
}
const addMembers = async (memberList: string[] | WhitelistFlexMember[]): Promise<string> => {
const res = await client.execute(
txSigner,
contractAddress,
{
add_members: {
to_add: memberList,
},
},
'auto',
)
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,
contractAddress,
{
remove_members: {
to_remove: memberList,
},
},
'auto',
)
return res.transactionHash
}
// const updatePerAddressLimit = async (limit: number): Promise<string> => {
// const res = await client.execute(txSigner, contractAddress, { update_per_address_limit: limit }, 'auto')
// return res.transactionHash
// }
/// EXECUTE END
return {
contractAddress,
updateStartTime,
updateEndTime,
updateAdmins,
freeze,
addMembers,
removeMembers,
// updatePerAddressLimit,
hasStarted,
hasEnded,
isActive,
hasMember,
adminList,
config,
merkleRoot,
merkleTreeUri,
canExecute,
}
}
const instantiate = async (
codeId: number,
initMsg: Record<string, unknown>,
label: string,
admin?: string,
): Promise<InstantiateResponse> => {
const result = await client.instantiate(txSigner, codeId, initMsg, label, 'auto', {
admin,
})
return {
contractAddress: result.contractAddress,
transactionHash: result.transactionHash,
}
}
const messages = (contractAddress: string) => {
const updateStartTime = (startTime: string) => {
return {
sender: txSigner,
contract: contractAddress,
msg: {
update_start_time: startTime,
},
funds: [],
}
}
const updateEndTime = (endTime: string) => {
return {
sender: txSigner,
contract: contractAddress,
msg: {
update_end_time: endTime,
},
funds: [],
}
}
const addMembers = (memberList: string[] | WhitelistFlexMember[]) => {
return {
sender: txSigner,
contract: contractAddress,
msg: {
add_members: { to_add: memberList },
},
funds: [],
}
}
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,
contract: contractAddress,
msg: {
remove_members: { to_remove: memberList },
},
funds: [],
}
}
// const updatePerAddressLimit = (limit: number) => {
// return {
// sender: txSigner,
// contract: contractAddress,
// msg: {
// update_per_address_limit: limit,
// },
// funds: [],
// }
// }
return {
updateStartTime,
updateEndTime,
updateAdmins,
addMembers,
removeMembers,
// updatePerAddressLimit,
freeze,
}
}
return { use, instantiate, messages }
}

View File

@ -0,0 +1,2 @@
export * from './contract'
export * from './useContract'

View File

@ -0,0 +1,144 @@
import type { WhitelistFlexMember } from '../../../components/WhitelistFlexUpload'
import type { WhiteListMerkleTreeInstance } from '../index'
import { useWhiteListMerkleTreeContract } from '../index'
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',
'freeze',
] as const
export interface ExecuteListItem {
id: ExecuteType
name: string
description?: string
}
export const EXECUTE_LIST: ExecuteListItem[] = [
{
id: 'update_start_time',
name: 'Update Start Time',
description: `Update the start time of the whitelist`,
},
{
id: 'update_end_time',
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',
description: `Add members to the whitelist`,
},
{
id: 'remove_members',
name: 'Remove Members',
description: `Remove members from the whitelist`,
},
// {
// id: 'update_per_address_limit',
// name: 'Update Per Address Limit',
// description: `Update tokens per address limit`,
// },
{
id: 'freeze',
name: 'Freeze',
description: `Freeze the current state of the contract admin list`,
},
]
export interface DispatchExecuteProps {
type: ExecuteType
[k: string]: unknown
}
/** @see {@link WhiteListMerkleTreeInstance} */
export interface DispatchExecuteArgs {
contract: string
messages?: WhiteListMerkleTreeInstance
type: string | undefined
timestamp: string
members: string[] | WhitelistFlexMember[]
limit: number
admins: string[]
}
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
const { messages } = args
if (!messages) {
throw new Error('cannot dispatch execute, messages is not defined')
}
switch (args.type) {
case 'update_start_time': {
return messages.updateStartTime(args.timestamp)
}
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)
}
case 'remove_members': {
return messages.removeMembers(args.members as string[])
}
// case 'update_per_address_limit': {
// return messages.updatePerAddressLimit(args.limit)
// }
case 'freeze': {
return messages.freeze()
}
default: {
throw new Error('unknown execute type')
}
}
}
export const previewExecutePayload = (args: DispatchExecuteArgs) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const { messages } = useWhiteListMerkleTreeContract()
const { contract } = args
switch (args.type) {
case 'update_start_time': {
return messages(contract)?.updateStartTime(args.timestamp)
}
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)
}
case 'remove_members': {
return messages(contract)?.removeMembers(args.members as string[])
}
// case 'update_per_address_limit': {
// return messages(contract)?.updatePerAddressLimit(args.limit)
// }
case 'freeze': {
return messages(contract)?.freeze()
}
default: {
return {}
}
}
}
export const isEitherType = <T extends ExecuteType>(type: unknown, arr: T[]): type is T => {
return arr.some((val) => type === val)
}

View File

@ -0,0 +1,66 @@
import type { WhiteListMerkleTreeInstance } from '../contract'
export type QueryType = typeof QUERY_TYPES[number]
export const QUERY_TYPES = [
'has_started',
'has_ended',
'is_active',
'admin_list',
'has_member',
'config',
'merkle_root',
'merkle_tree_uri',
] as const
export interface QueryListItem {
id: QueryType
name: string
description?: string
}
export const QUERY_LIST: QueryListItem[] = [
{ id: 'has_started', name: 'Has Started', description: 'Check if the whitelist minting has started' },
{ 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: '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' },
{ id: 'merkle_root', name: 'Merkle Root', description: 'View the whitelist merkle root' },
{ id: 'merkle_tree_uri', name: 'Merkle Tree URI', description: 'View the whitelist merkle tree URI' },
]
export interface DispatchQueryProps {
messages: WhiteListMerkleTreeInstance | undefined
type: QueryType
address: string
startAfter?: string
limit?: number
proofHashes?: string[]
}
export const dispatchQuery = (props: DispatchQueryProps) => {
const { messages, type, address, proofHashes } = props
switch (type) {
case 'has_started':
return messages?.hasStarted()
case 'has_ended':
return messages?.hasEnded()
case 'is_active':
return messages?.isActive()
case 'admin_list':
return messages?.adminList()
case 'has_member':
return messages?.hasMember(address, proofHashes || [])
case 'config':
return messages?.config()
case 'merkle_root':
return messages?.merkleRoot()
case 'merkle_tree_uri':
return messages?.merkleTreeUri()
default: {
throw new Error('unknown query type')
}
}
}

View File

@ -0,0 +1,89 @@
import { useCallback, useEffect, useState } from 'react'
import { useWallet } from 'utils/wallet'
import type {
InstantiateResponse,
WhiteListMerkleTreeContract,
WhiteListMerkleTreeInstance,
WhiteListMerkleTreeMessages,
} from './contract'
import { WhiteListMerkleTree as initContract } from './contract'
export interface UseWhiteListMerkleTreeContractProps {
instantiate: (
codeId: number,
initMsg: Record<string, unknown>,
label: string,
admin?: string,
) => Promise<InstantiateResponse>
use: (customAddress?: string) => WhiteListMerkleTreeInstance | undefined
updateContractAddress: (contractAddress: string) => void
messages: (contractAddress: string) => WhiteListMerkleTreeMessages | undefined
}
export function useWhiteListMerkleTreeContract(): UseWhiteListMerkleTreeContractProps {
const wallet = useWallet()
const [address, setAddress] = useState<string>('')
const [whiteListMerkleTree, setWhiteListMerkleTree] = useState<WhiteListMerkleTreeContract>()
useEffect(() => {
setAddress(localStorage.getItem('contract_address') || '')
}, [])
useEffect(() => {
if (!wallet.isWalletConnected) {
return
}
const load = async () => {
const client = await wallet.getSigningCosmWasmClient()
const contract = initContract(client, wallet.address || '')
setWhiteListMerkleTree(contract)
}
load().catch(console.error)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [wallet.isWalletConnected, wallet.address])
const updateContractAddress = (contractAddress: string) => {
setAddress(contractAddress)
}
const instantiate = useCallback(
(codeId: number, initMsg: Record<string, unknown>, label: string, admin?: string): Promise<InstantiateResponse> => {
return new Promise((resolve, reject) => {
if (!whiteListMerkleTree) {
reject(new Error('Contract is not initialized.'))
return
}
whiteListMerkleTree.instantiate(codeId, initMsg, label, admin).then(resolve).catch(reject)
})
},
[whiteListMerkleTree],
)
const use = useCallback(
(customAddress = ''): WhiteListMerkleTreeInstance | undefined => {
return whiteListMerkleTree?.use(address || customAddress)
},
[whiteListMerkleTree, address],
)
const messages = useCallback(
(customAddress = ''): WhiteListMerkleTreeMessages | undefined => {
return whiteListMerkleTree?.messages(address || customAddress)
},
[whiteListMerkleTree, address],
)
return {
instantiate,
use,
updateContractAddress,
messages,
}
}