Merge pull request #273 from public-awesome/develop

Sync dev > main
This commit is contained in:
Serkan Reis 2023-11-19 15:37:06 +03:00 committed by GitHub
commit 8a27afe29a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 144 additions and 8 deletions

View File

@ -1,4 +1,4 @@
APP_VERSION=0.8.0
APP_VERSION=0.8.1
NEXT_PUBLIC_PINATA_ENDPOINT_URL=https://api.pinata.cloud/pinning/pinFileToIPFS
NEXT_PUBLIC_SG721_CODE_ID=2595

View File

@ -32,10 +32,12 @@ export interface DispatchQueryProps {
messages: WhiteListInstance | undefined
type: QueryType
address: string
startAfter?: string
limit?: number
}
export const dispatchQuery = (props: DispatchQueryProps) => {
const { messages, type, address } = props
const { messages, type, address, startAfter, limit } = props
switch (type) {
case 'has_started':
return messages?.hasStarted()
@ -44,7 +46,7 @@ export const dispatchQuery = (props: DispatchQueryProps) => {
case 'is_active':
return messages?.isActive()
case 'members':
return messages?.members()
return messages?.members(startAfter, limit)
case 'admin_list':
return messages?.adminList()
case 'has_member':

View File

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

View File

@ -1,9 +1,18 @@
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable no-await-in-loop */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { toUtf8 } from '@cosmjs/encoding'
import clsx from 'clsx'
import { Button } from 'components/Button'
import { Conditional } from 'components/Conditional'
import { ContractPageHeader } from 'components/ContractPageHeader'
import { FormControl } from 'components/FormControl'
import { AddressInput } from 'components/forms/FormInput'
import { useInputState } from 'components/forms/FormInput.hooks'
import { AddressInput, NumberInput, TextInput } from 'components/forms/FormInput'
import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks'
import { JsonPreview } from 'components/JsonPreview'
import { LinkTabs } from 'components/LinkTabs'
import { whitelistLinkTabs } from 'components/LinkTabs.data'
@ -16,6 +25,7 @@ import { NextSeo } from 'next-seo'
import { useEffect, useState } from 'react'
import { toast } from 'react-hot-toast'
import { useQuery } from 'react-query'
import { useDebounce } from 'utils/debounce'
import { withMetadata } from 'utils/layout'
import { links } from 'utils/links'
import { resolveAddress } from 'utils/resolveAddress'
@ -24,6 +34,7 @@ import { useWallet } from 'utils/wallet'
const WhitelistQueryPage: NextPage = () => {
const { whitelist: contract } = useContracts()
const wallet = useWallet()
const [exporting, setExporting] = useState(false)
const contractState = useInputState({
id: 'contract-address',
@ -41,20 +52,49 @@ const WhitelistQueryPage: NextPage = () => {
})
const address = addressState.value
const limit = useNumberInputState({
id: 'limit',
name: 'limit',
title: 'Limit',
subtitle: 'Maximum number of addresses to return',
defaultValue: 20,
})
const debouncedLimit = useDebounce(limit.value, 500)
const startAfter = useInputState({
id: 'start-after',
name: 'start-after',
title: 'Start After',
subtitle: 'Address to start after',
})
useEffect(() => {
if (debouncedLimit > 100) {
toast.success('Only 100 addresses can be returned at a time even if the limit is higher.', {
style: { maxWidth: 'none' },
icon: '📝',
duration: 5000,
})
}
}, [debouncedLimit])
const [type, setType] = useState<QueryType>('config')
const addressVisible = type === 'has_member'
const { data: response } = useQuery(
[contractAddress, type, contract, wallet.address, address] as const,
[contractAddress, type, contract, wallet.address, address, startAfter.value, limit.value] as const,
async ({ queryKey }) => {
const [_contractAddress, _type, _contract, _wallet, _address] = queryKey
const [_contractAddress, _type, _contract, _wallet, _address, _startAfter, _limit] = queryKey
const messages = contract?.use(contractAddress)
const res = await resolveAddress(_address, wallet).then(async (resolvedAddress) => {
const result = await dispatchQuery({
messages,
type,
address: resolvedAddress,
startAfter: _startAfter || undefined,
limit: _limit,
})
return result
})
@ -82,6 +122,88 @@ const WhitelistQueryPage: NextPage = () => {
if (initial && initial.length > 0) contractState.onChange(initial)
}, [])
const exportAllMembers = async () => {
if (wallet.isWalletDisconnected) {
toast.error('Please connect your wallet first.', { style: { maxWidth: 'none' } })
setExporting(false)
return
}
try {
const messages = contract?.use(contractAddress)
setExporting(true)
const contractInfoResponse = await (await wallet.getCosmWasmClient())
.queryContractRaw(
contractAddress.trim(),
toUtf8(Buffer.from(Buffer.from('contract_info').toString('hex'), 'hex').toString()),
)
.catch((e) => {
if (e.message.includes('bech32')) throw new Error('Invalid whitelist contract address.')
console.log(e.message)
})
const contractInfo = JSON.parse(new TextDecoder().decode(contractInfoResponse as Uint8Array))
console.log('Contract Info: ', contractInfo.contract)
if (contractInfo.contract.includes('flex')) {
let membersResponse = (await dispatchQuery({ messages, address, type: 'members', limit: 100 })) as any
let membersArray = [...membersResponse.members]
let lastMember = membersResponse.members[membersResponse.members.length - 1]
while (membersResponse.members.length === 100) {
membersResponse = (await dispatchQuery({
messages,
address,
type: 'members',
limit: 100,
startAfter: lastMember.address,
})) as any
lastMember = membersResponse.members[membersResponse.members.length - 1]
membersArray = [...membersArray, ...membersResponse.members]
}
membersArray.unshift({ address: 'address', mint_count: 'mint_count' })
const csv = membersArray.map((row) => Object.values(row).join(',')).join('\n')
const csvData = new Blob([csv], { type: 'text/csv;charset=utf-8;' })
const csvURL = window.URL.createObjectURL(csvData)
const tempLink = document.createElement('a')
tempLink.href = csvURL
tempLink.setAttribute('download', 'whitelist_flex_members.csv')
tempLink.click()
} else if (contractInfo.contract.includes('whitelist') && !contractInfo.contract.includes('flex')) {
let membersResponse = (await dispatchQuery({ messages, address, type: 'members', limit: 100 })) as any
let membersArray = [...membersResponse.members]
let lastMember = membersResponse.members[membersResponse.members.length - 1]
while (membersResponse.members.length === 100) {
membersResponse = (await dispatchQuery({
messages,
address,
type: 'members',
limit: 100,
startAfter: lastMember,
})) as any
lastMember = membersResponse.members[membersResponse.members.length - 1]
membersArray = [...membersArray, ...membersResponse.members]
}
const txt = membersArray.map((member) => member).join('\n')
const txtData = new Blob([txt], { type: 'text/txt;charset=utf-8;' })
const txtURL = window.URL.createObjectURL(txtData)
const tempLink = document.createElement('a')
tempLink.href = txtURL
tempLink.setAttribute('download', 'whitelist_members.txt')
tempLink.click()
} else {
toast.error('Invalid whitelist contract address.', { style: { maxWidth: 'none' } })
}
setExporting(false)
} catch (e: any) {
console.log(e)
toast.error(e.message, { style: { maxWidth: 'none' } })
setExporting(false)
}
}
return (
<section className="py-6 px-12 space-y-4">
<NextSeo title="Query Whitelist Contract" />
@ -117,6 +239,18 @@ const WhitelistQueryPage: NextPage = () => {
<Conditional test={addressVisible}>
<AddressInput {...addressState} />
</Conditional>
<Conditional test={type === 'members'}>
<TextInput {...startAfter} />
<NumberInput {...limit} />
<Button
className="py-2 px-4 font-bold text-white/90 bg-stargaze hover:bg-stargaze-80 rounded"
isLoading={exporting}
onClick={exportAllMembers}
type="button"
>
Export All Members
</Button>
</Conditional>
</div>
<JsonPreview content={contractAddress ? { type, response } : null} title="Query Response" />
</div>