2023-01-12 07:18:10 +00:00
|
|
|
import { toUtf8 } from '@cosmjs/encoding'
|
2022-10-10 09:37:20 +00:00
|
|
|
import clsx from 'clsx'
|
2023-01-12 07:18:10 +00:00
|
|
|
import React, { useState } from 'react'
|
2022-10-10 09:37:20 +00:00
|
|
|
import { toast } from 'react-hot-toast'
|
2023-01-12 07:18:10 +00:00
|
|
|
import { SG721_NAME_ADDRESS } from 'utils/constants'
|
2022-10-10 09:37:20 +00:00
|
|
|
import { csvToArray } from 'utils/csvToArray'
|
|
|
|
import type { AirdropAllocation } from 'utils/isValidAccountsFile'
|
|
|
|
import { isValidAccountsFile } from 'utils/isValidAccountsFile'
|
2023-01-12 07:18:10 +00:00
|
|
|
import { isValidAddress } from 'utils/isValidAddress'
|
2023-10-11 23:36:14 +00:00
|
|
|
import { useWallet } from 'utils/wallet'
|
2022-10-10 09:37:20 +00:00
|
|
|
|
|
|
|
interface AirdropUploadProps {
|
|
|
|
onChange: (data: AirdropAllocation[]) => void
|
|
|
|
}
|
|
|
|
|
|
|
|
export const AirdropUpload = ({ onChange }: AirdropUploadProps) => {
|
2023-01-12 07:18:10 +00:00
|
|
|
const wallet = useWallet()
|
|
|
|
const [resolvedAllocationData, setResolvedAllocationData] = useState<AirdropAllocation[]>([])
|
|
|
|
|
|
|
|
const resolveAllocationData = async (allocationData: AirdropAllocation[]) => {
|
|
|
|
if (!allocationData.length) return []
|
|
|
|
await new Promise((resolve) => {
|
|
|
|
let i = 0
|
|
|
|
allocationData.map(async (data) => {
|
2023-10-11 23:36:14 +00:00
|
|
|
if (!wallet.isWalletConnected) throw new Error('Wallet not connected')
|
|
|
|
await (
|
|
|
|
await wallet.getCosmWasmClient()
|
|
|
|
)
|
2023-01-12 07:18:10 +00:00
|
|
|
.queryContractRaw(
|
|
|
|
SG721_NAME_ADDRESS,
|
|
|
|
toUtf8(
|
|
|
|
Buffer.from(
|
|
|
|
`0006${Buffer.from('tokens').toString('hex')}${Buffer.from(
|
|
|
|
data.address.trim().substring(0, data.address.lastIndexOf('.stars')),
|
|
|
|
).toString('hex')}`,
|
|
|
|
'hex',
|
|
|
|
).toString(),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
.then((res) => {
|
|
|
|
const tokenUri = JSON.parse(new TextDecoder().decode(res as Uint8Array)).token_uri
|
|
|
|
if (tokenUri && isValidAddress(tokenUri))
|
2023-06-08 07:06:29 +00:00
|
|
|
resolvedAllocationData.push({ address: tokenUri, amount: data.amount, tokenId: data.tokenId })
|
2023-01-12 07:18:10 +00:00
|
|
|
else toast.error(`Resolved address is empty or invalid for the name: ${data.address}`)
|
|
|
|
})
|
|
|
|
.catch((e) => {
|
|
|
|
console.log(e)
|
|
|
|
toast.error(`Error resolving address for the name: ${data.address}`)
|
|
|
|
})
|
|
|
|
|
|
|
|
i++
|
|
|
|
if (i === allocationData.length) resolve(resolvedAllocationData)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
return resolvedAllocationData
|
|
|
|
}
|
|
|
|
|
2022-10-10 09:37:20 +00:00
|
|
|
const onFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
2023-01-12 07:18:10 +00:00
|
|
|
setResolvedAllocationData([])
|
2022-10-10 09:37:20 +00:00
|
|
|
if (!event.target.files) return toast.error('Error opening file')
|
2022-10-10 10:40:20 +00:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
|
|
if (!event.target.files[0]?.name.endsWith('.csv')) {
|
2022-10-10 09:37:20 +00:00
|
|
|
toast.error('Please select a .csv file!')
|
|
|
|
return onChange([])
|
|
|
|
}
|
|
|
|
const reader = new FileReader()
|
2023-01-12 07:18:10 +00:00
|
|
|
reader.onload = async (e: ProgressEvent<FileReader>) => {
|
2022-10-10 09:37:20 +00:00
|
|
|
try {
|
|
|
|
if (!e.target?.result) return toast.error('Error parsing file.')
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
|
|
const accountsData = csvToArray(e.target.result.toString())
|
2023-01-12 07:18:10 +00:00
|
|
|
console.log(accountsData)
|
2022-10-10 09:37:20 +00:00
|
|
|
if (!isValidAccountsFile(accountsData)) {
|
|
|
|
event.target.value = ''
|
|
|
|
return onChange([])
|
|
|
|
}
|
2023-01-12 07:18:10 +00:00
|
|
|
await resolveAllocationData(accountsData.filter((data) => data.address.trim().endsWith('.stars'))).finally(
|
|
|
|
() => {
|
|
|
|
return onChange(
|
|
|
|
accountsData
|
|
|
|
.filter((data) => data.address.startsWith('stars') && !data.address.endsWith('.stars'))
|
|
|
|
.map((data) => ({
|
|
|
|
address: data.address.trim(),
|
|
|
|
amount: data.amount,
|
2023-06-08 07:06:29 +00:00
|
|
|
tokenId: data.tokenId,
|
2023-01-12 07:18:10 +00:00
|
|
|
}))
|
2023-06-08 07:06:29 +00:00
|
|
|
.concat(
|
|
|
|
resolvedAllocationData.map((data) => ({
|
|
|
|
address: data.address,
|
|
|
|
amount: data.amount,
|
|
|
|
tokenId: data.tokenId,
|
|
|
|
})),
|
|
|
|
),
|
2023-01-12 07:18:10 +00:00
|
|
|
)
|
|
|
|
},
|
|
|
|
)
|
2022-10-10 09:37:20 +00:00
|
|
|
} catch (error: any) {
|
2022-11-02 07:53:17 +00:00
|
|
|
toast.error(error.message, { style: { maxWidth: 'none' } })
|
2022-10-10 09:37:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
reader.readAsText(event.target.files[0])
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div
|
|
|
|
className={clsx(
|
|
|
|
'flex relative justify-center items-center mt-2 space-y-4 w-full h-32',
|
|
|
|
'rounded border-2 border-white/20 border-dashed',
|
|
|
|
)}
|
|
|
|
>
|
|
|
|
<input
|
|
|
|
accept=".csv"
|
|
|
|
className={clsx(
|
|
|
|
'file:py-2 file:px-4 file:mr-4 file:bg-plumbus-light file:rounded file:border-0 cursor-pointer',
|
|
|
|
'before:absolute before:inset-0 before:hover:bg-white/5 before:transition',
|
|
|
|
)}
|
|
|
|
id="airdrop-file"
|
|
|
|
multiple
|
|
|
|
onChange={onFileChange}
|
|
|
|
type="file"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|