Merge pull request #88 from public-awesome/additional-names-support
Additional Stargaze Names support
This commit is contained in:
commit
a01fc71755
@ -1,4 +1,4 @@
|
|||||||
APP_VERSION=0.3.7
|
APP_VERSION=0.3.8
|
||||||
|
|
||||||
NEXT_PUBLIC_PINATA_ENDPOINT_URL=https://api.pinata.cloud/pinning/pinFileToIPFS
|
NEXT_PUBLIC_PINATA_ENDPOINT_URL=https://api.pinata.cloud/pinning/pinFileToIPFS
|
||||||
NEXT_PUBLIC_SG721_CODE_ID=274
|
NEXT_PUBLIC_SG721_CODE_ID=274
|
||||||
|
@ -1,16 +1,60 @@
|
|||||||
|
import { toUtf8 } from '@cosmjs/encoding'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import React from 'react'
|
import { useWallet } from 'contexts/wallet'
|
||||||
|
import React, { useState } from 'react'
|
||||||
import { toast } from 'react-hot-toast'
|
import { toast } from 'react-hot-toast'
|
||||||
|
import { SG721_NAME_ADDRESS } from 'utils/constants'
|
||||||
import { csvToArray } from 'utils/csvToArray'
|
import { csvToArray } from 'utils/csvToArray'
|
||||||
import type { AirdropAllocation } from 'utils/isValidAccountsFile'
|
import type { AirdropAllocation } from 'utils/isValidAccountsFile'
|
||||||
import { isValidAccountsFile } from 'utils/isValidAccountsFile'
|
import { isValidAccountsFile } from 'utils/isValidAccountsFile'
|
||||||
|
import { isValidAddress } from 'utils/isValidAddress'
|
||||||
|
|
||||||
interface AirdropUploadProps {
|
interface AirdropUploadProps {
|
||||||
onChange: (data: AirdropAllocation[]) => void
|
onChange: (data: AirdropAllocation[]) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AirdropUpload = ({ onChange }: AirdropUploadProps) => {
|
export const AirdropUpload = ({ onChange }: AirdropUploadProps) => {
|
||||||
|
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) => {
|
||||||
|
if (!wallet.client) throw new Error('Wallet not connected')
|
||||||
|
await wallet.client
|
||||||
|
.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))
|
||||||
|
resolvedAllocationData.push({ address: tokenUri, amount: data.amount })
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
const onFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const onFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setResolvedAllocationData([])
|
||||||
if (!event.target.files) return toast.error('Error opening file')
|
if (!event.target.files) return toast.error('Error opening file')
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
if (!event.target.files[0]?.name.endsWith('.csv')) {
|
if (!event.target.files[0]?.name.endsWith('.csv')) {
|
||||||
@ -18,16 +62,29 @@ export const AirdropUpload = ({ onChange }: AirdropUploadProps) => {
|
|||||||
return onChange([])
|
return onChange([])
|
||||||
}
|
}
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
reader.onload = (e: ProgressEvent<FileReader>) => {
|
reader.onload = async (e: ProgressEvent<FileReader>) => {
|
||||||
try {
|
try {
|
||||||
if (!e.target?.result) return toast.error('Error parsing file.')
|
if (!e.target?.result) return toast.error('Error parsing file.')
|
||||||
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
||||||
const accountsData = csvToArray(e.target.result.toString())
|
const accountsData = csvToArray(e.target.result.toString())
|
||||||
|
console.log(accountsData)
|
||||||
if (!isValidAccountsFile(accountsData)) {
|
if (!isValidAccountsFile(accountsData)) {
|
||||||
event.target.value = ''
|
event.target.value = ''
|
||||||
return onChange([])
|
return onChange([])
|
||||||
}
|
}
|
||||||
return onChange(accountsData)
|
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,
|
||||||
|
}))
|
||||||
|
.concat(resolvedAllocationData),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
toast.error(error.message, { style: { maxWidth: 'none' } })
|
toast.error(error.message, { style: { maxWidth: 'none' } })
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import { toast } from 'react-hot-toast'
|
|||||||
import { FaArrowRight } from 'react-icons/fa'
|
import { FaArrowRight } from 'react-icons/fa'
|
||||||
import { useMutation } from 'react-query'
|
import { useMutation } from 'react-query'
|
||||||
import type { AirdropAllocation } from 'utils/isValidAccountsFile'
|
import type { AirdropAllocation } from 'utils/isValidAccountsFile'
|
||||||
|
import { resolveAddress } from 'utils/resolveAddress'
|
||||||
|
|
||||||
import type { CollectionInfo } from '../../../contracts/sg721/contract'
|
import type { CollectionInfo } from '../../../contracts/sg721/contract'
|
||||||
import { TextInput } from '../../forms/FormInput'
|
import { TextInput } from '../../forms/FormInput'
|
||||||
@ -54,6 +55,7 @@ export const CollectionActions = ({
|
|||||||
const [airdropArray, setAirdropArray] = useState<string[]>([])
|
const [airdropArray, setAirdropArray] = useState<string[]>([])
|
||||||
const [collectionInfo, setCollectionInfo] = useState<CollectionInfo>()
|
const [collectionInfo, setCollectionInfo] = useState<CollectionInfo>()
|
||||||
const [explicitContent, setExplicitContent] = useState<ExplicitContentType>(undefined)
|
const [explicitContent, setExplicitContent] = useState<ExplicitContentType>(undefined)
|
||||||
|
const [resolvedRecipientAddress, setResolvedRecipientAddress] = useState<string>('')
|
||||||
|
|
||||||
const actionComboboxState = useActionsComboboxState()
|
const actionComboboxState = useActionsComboboxState()
|
||||||
const type = actionComboboxState.value?.id
|
const type = actionComboboxState.value?.id
|
||||||
@ -188,13 +190,43 @@ export const CollectionActions = ({
|
|||||||
vendingMinterMessages,
|
vendingMinterMessages,
|
||||||
baseMinterMessages,
|
baseMinterMessages,
|
||||||
sg721Messages,
|
sg721Messages,
|
||||||
recipient: recipientState.value,
|
recipient: resolvedRecipientAddress,
|
||||||
recipients: airdropArray,
|
recipients: airdropArray,
|
||||||
txSigner: wallet.address,
|
txSigner: wallet.address,
|
||||||
type,
|
type,
|
||||||
price: priceState.value.toString(),
|
price: priceState.value.toString(),
|
||||||
collectionInfo,
|
collectionInfo,
|
||||||
}
|
}
|
||||||
|
const resolveRecipientAddress = async () => {
|
||||||
|
await resolveAddress(recipientState.value.trim(), wallet).then((resolvedAddress) => {
|
||||||
|
setResolvedRecipientAddress(resolvedAddress)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
useEffect(() => {
|
||||||
|
void resolveRecipientAddress()
|
||||||
|
}, [recipientState.value])
|
||||||
|
|
||||||
|
const resolveRoyaltyPaymentAddress = async () => {
|
||||||
|
await resolveAddress(royaltyPaymentAddressState.value.trim(), wallet).then((resolvedAddress) => {
|
||||||
|
setCollectionInfo({
|
||||||
|
description: descriptionState.value || undefined,
|
||||||
|
image: imageState.value || undefined,
|
||||||
|
explicit_content: explicitContent,
|
||||||
|
external_link: externalLinkState.value || undefined,
|
||||||
|
royalty_info:
|
||||||
|
royaltyPaymentAddressState.value && royaltyShareState.value
|
||||||
|
? {
|
||||||
|
payment_address: resolvedAddress,
|
||||||
|
share: (Number(royaltyShareState.value) / 100).toString(),
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void resolveRoyaltyPaymentAddress()
|
||||||
|
}, [royaltyPaymentAddressState.value])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCollectionInfo({
|
setCollectionInfo({
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { Conditional } from 'components/Conditional'
|
import { Conditional } from 'components/Conditional'
|
||||||
import { FormGroup } from 'components/FormGroup'
|
import { FormGroup } from 'components/FormGroup'
|
||||||
import { useInputState } from 'components/forms/FormInput.hooks'
|
import { useInputState } from 'components/forms/FormInput.hooks'
|
||||||
|
import { useWallet } from 'contexts/wallet'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
import { resolveAddress } from '../../../utils/resolveAddress'
|
||||||
import { NumberInput, TextInput } from '../../forms/FormInput'
|
import { NumberInput, TextInput } from '../../forms/FormInput'
|
||||||
|
|
||||||
interface RoyaltyDetailsProps {
|
interface RoyaltyDetailsProps {
|
||||||
@ -18,6 +20,7 @@ export interface RoyaltyDetailsDataProps {
|
|||||||
type RoyaltyState = 'none' | 'new'
|
type RoyaltyState = 'none' | 'new'
|
||||||
|
|
||||||
export const RoyaltyDetails = ({ onChange }: RoyaltyDetailsProps) => {
|
export const RoyaltyDetails = ({ onChange }: RoyaltyDetailsProps) => {
|
||||||
|
const wallet = useWallet()
|
||||||
const [royaltyState, setRoyaltyState] = useState<RoyaltyState>('none')
|
const [royaltyState, setRoyaltyState] = useState<RoyaltyState>('none')
|
||||||
|
|
||||||
const royaltyPaymentAddressState = useInputState({
|
const royaltyPaymentAddressState = useInputState({
|
||||||
@ -37,17 +40,23 @@ export const RoyaltyDetails = ({ onChange }: RoyaltyDetailsProps) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const data: RoyaltyDetailsDataProps = {
|
void resolveAddress(
|
||||||
royaltyType: royaltyState,
|
royaltyPaymentAddressState.value
|
||||||
paymentAddress: royaltyPaymentAddressState.value
|
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.replace(/,/g, '')
|
.replace(/,/g, '')
|
||||||
.replace(/"/g, '')
|
.replace(/"/g, '')
|
||||||
.replace(/'/g, '')
|
.replace(/'/g, '')
|
||||||
.replace(/ /g, ''),
|
.replace(/ /g, ''),
|
||||||
share: Number(royaltyShareState.value),
|
wallet,
|
||||||
}
|
).then((royaltyPaymentAddress) => {
|
||||||
onChange(data)
|
royaltyPaymentAddressState.onChange(royaltyPaymentAddress)
|
||||||
|
const data: RoyaltyDetailsDataProps = {
|
||||||
|
royaltyType: royaltyState,
|
||||||
|
paymentAddress: royaltyPaymentAddressState.value,
|
||||||
|
share: Number(royaltyShareState.value),
|
||||||
|
}
|
||||||
|
onChange(data)
|
||||||
|
})
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [royaltyState, royaltyPaymentAddressState.value, royaltyShareState.value])
|
}, [royaltyState, royaltyPaymentAddressState.value, royaltyShareState.value])
|
||||||
|
|
||||||
|
@ -11,6 +11,8 @@ import type { VendingMinterInstance } from 'contracts/vendingMinter'
|
|||||||
import { toast } from 'react-hot-toast'
|
import { toast } from 'react-hot-toast'
|
||||||
import { useQuery } from 'react-query'
|
import { useQuery } from 'react-query'
|
||||||
|
|
||||||
|
import { useWallet } from '../../../contexts/wallet'
|
||||||
|
import { resolveAddress } from '../../../utils/resolveAddress'
|
||||||
import type { MinterType } from '../actions/Combobox'
|
import type { MinterType } from '../actions/Combobox'
|
||||||
|
|
||||||
interface CollectionQueriesProps {
|
interface CollectionQueriesProps {
|
||||||
@ -29,6 +31,8 @@ export const CollectionQueries = ({
|
|||||||
baseMinterMessages,
|
baseMinterMessages,
|
||||||
minterType,
|
minterType,
|
||||||
}: CollectionQueriesProps) => {
|
}: CollectionQueriesProps) => {
|
||||||
|
const wallet = useWallet()
|
||||||
|
|
||||||
const comboboxState = useQueryComboboxState()
|
const comboboxState = useQueryComboboxState()
|
||||||
const type = comboboxState.value?.id
|
const type = comboboxState.value?.id
|
||||||
|
|
||||||
@ -57,20 +61,26 @@ export const CollectionQueries = ({
|
|||||||
async ({ queryKey }) => {
|
async ({ queryKey }) => {
|
||||||
const [_sg721Messages, _baseMinterMessages_, _vendingMinterMessages, _type, _tokenId, _address] = queryKey
|
const [_sg721Messages, _baseMinterMessages_, _vendingMinterMessages, _type, _tokenId, _address] = queryKey
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
const result = await dispatchQuery({
|
const res = await resolveAddress(_address, wallet).then(async (resolvedAddress) => {
|
||||||
tokenId: _tokenId,
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
vendingMinterMessages: _vendingMinterMessages,
|
const result = await dispatchQuery({
|
||||||
baseMinterMessages: _baseMinterMessages_,
|
tokenId: _tokenId,
|
||||||
sg721Messages: _sg721Messages,
|
vendingMinterMessages: _vendingMinterMessages,
|
||||||
address: _address,
|
baseMinterMessages: _baseMinterMessages_,
|
||||||
type: _type,
|
sg721Messages: _sg721Messages,
|
||||||
|
address: resolvedAddress,
|
||||||
|
type: _type,
|
||||||
|
})
|
||||||
|
return result
|
||||||
})
|
})
|
||||||
return result
|
return res
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
placeholderData: null,
|
placeholderData: null,
|
||||||
onError: (error: any) => {
|
onError: (error: any) => {
|
||||||
toast.error(error.message, { style: { maxWidth: 'none' } })
|
if (addressState.value.length > 12 && !addressState.value.includes('.')) {
|
||||||
|
toast.error(error.message, { style: { maxWidth: 'none' } })
|
||||||
|
}
|
||||||
},
|
},
|
||||||
enabled: Boolean(sg721ContractAddress && minterContractAddress && type),
|
enabled: Boolean(sg721ContractAddress && minterContractAddress && type),
|
||||||
retry: false,
|
retry: false,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "stargaze-studio",
|
"name": "stargaze-studio",
|
||||||
"version": "0.3.7",
|
"version": "0.3.8",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
|
@ -52,6 +52,7 @@ import type { MinterType } from '../../components/collections/actions/Combobox'
|
|||||||
import type { UploadMethod } from '../../components/collections/creation/UploadDetails'
|
import type { UploadMethod } from '../../components/collections/creation/UploadDetails'
|
||||||
import { ConfirmationModal } from '../../components/ConfirmationModal'
|
import { ConfirmationModal } from '../../components/ConfirmationModal'
|
||||||
import { getAssetType } from '../../utils/getAssetType'
|
import { getAssetType } from '../../utils/getAssetType'
|
||||||
|
import { isValidAddress } from '../../utils/isValidAddress'
|
||||||
|
|
||||||
const CollectionCreationPage: NextPage = () => {
|
const CollectionCreationPage: NextPage = () => {
|
||||||
const wallet = useWallet()
|
const wallet = useWallet()
|
||||||
@ -767,6 +768,12 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
if (royaltyDetails.share === 0) throw new Error('Royalty share percentage is required')
|
if (royaltyDetails.share === 0) throw new Error('Royalty share percentage is required')
|
||||||
if (royaltyDetails.share > 100 || royaltyDetails.share < 0) throw new Error('Invalid royalty share percentage')
|
if (royaltyDetails.share > 100 || royaltyDetails.share < 0) throw new Error('Invalid royalty share percentage')
|
||||||
if (royaltyDetails.paymentAddress === '') throw new Error('Royalty payment address is required')
|
if (royaltyDetails.paymentAddress === '') throw new Error('Royalty payment address is required')
|
||||||
|
if (!isValidAddress(royaltyDetails.paymentAddress)) {
|
||||||
|
if (royaltyDetails.paymentAddress.trim().endsWith('.stars')) {
|
||||||
|
throw new Error('Royalty payment address could not be resolved')
|
||||||
|
}
|
||||||
|
throw new Error('Invalid royalty payment address')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ import { useWallet } from 'contexts/wallet'
|
|||||||
import type { NextPage } from 'next'
|
import type { NextPage } from 'next'
|
||||||
import { NextSeo } from 'next-seo'
|
import { NextSeo } from 'next-seo'
|
||||||
import type { FormEvent } from 'react'
|
import type { FormEvent } from 'react'
|
||||||
import { useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { toast } from 'react-hot-toast'
|
import { toast } from 'react-hot-toast'
|
||||||
import { FaAsterisk } from 'react-icons/fa'
|
import { FaAsterisk } from 'react-icons/fa'
|
||||||
import { useMutation } from 'react-query'
|
import { useMutation } from 'react-query'
|
||||||
@ -27,6 +27,7 @@ import { links } from 'utils/links'
|
|||||||
|
|
||||||
import type { CreateBaseMinterResponse } from '../../../contracts/baseFactory/contract'
|
import type { CreateBaseMinterResponse } from '../../../contracts/baseFactory/contract'
|
||||||
import { SG721_CODE_ID } from '../../../utils/constants'
|
import { SG721_CODE_ID } from '../../../utils/constants'
|
||||||
|
import { resolveAddress } from '../../../utils/resolveAddress'
|
||||||
|
|
||||||
const BaseMinterInstantiatePage: NextPage = () => {
|
const BaseMinterInstantiatePage: NextPage = () => {
|
||||||
const wallet = useWallet()
|
const wallet = useWallet()
|
||||||
@ -161,6 +162,18 @@ const BaseMinterInstantiatePage: NextPage = () => {
|
|||||||
|
|
||||||
const txHash = data?.transactionHash
|
const txHash = data?.transactionHash
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void resolveAddress(creatorState.value, wallet).then((resolvedAddress) => {
|
||||||
|
creatorState.onChange(resolvedAddress)
|
||||||
|
})
|
||||||
|
}, [creatorState.value])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void resolveAddress(royaltyPaymentAddressState.value, wallet).then((resolvedAddress) => {
|
||||||
|
royaltyPaymentAddressState.onChange(resolvedAddress)
|
||||||
|
})
|
||||||
|
}, [royaltyPaymentAddressState.value])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="py-6 px-12 space-y-4" onSubmit={mutate}>
|
<form className="py-6 px-12 space-y-4" onSubmit={mutate}>
|
||||||
<NextSeo title="Instantiate Base Minter Contract" />
|
<NextSeo title="Instantiate Base Minter Contract" />
|
||||||
|
@ -25,12 +25,15 @@ import { useMutation } from 'react-query'
|
|||||||
import { parseJson } from 'utils/json'
|
import { parseJson } from 'utils/json'
|
||||||
import { withMetadata } from 'utils/layout'
|
import { withMetadata } from 'utils/layout'
|
||||||
import { links } from 'utils/links'
|
import { links } from 'utils/links'
|
||||||
|
import { resolveAddress } from 'utils/resolveAddress'
|
||||||
|
|
||||||
const Sg721ExecutePage: NextPage = () => {
|
const Sg721ExecutePage: NextPage = () => {
|
||||||
const { sg721: contract } = useContracts()
|
const { sg721: contract } = useContracts()
|
||||||
const wallet = useWallet()
|
const wallet = useWallet()
|
||||||
|
|
||||||
const [lastTx, setLastTx] = useState('')
|
const [lastTx, setLastTx] = useState('')
|
||||||
|
const [resolvedRecipientAddress, setResolvedRecipientAddress] = useState<string>('')
|
||||||
|
const [resolvedOperatorAddress, setResolvedOperatorAddress] = useState<string>('')
|
||||||
|
|
||||||
const comboboxState = useExecuteComboboxState()
|
const comboboxState = useExecuteComboboxState()
|
||||||
const type = comboboxState.value?.id
|
const type = comboboxState.value?.id
|
||||||
@ -93,8 +96,8 @@ const Sg721ExecutePage: NextPage = () => {
|
|||||||
contract: contractState.value,
|
contract: contractState.value,
|
||||||
tokenId: tokenIdState.value,
|
tokenId: tokenIdState.value,
|
||||||
messages,
|
messages,
|
||||||
recipient: recipientState.value,
|
recipient: resolvedRecipientAddress,
|
||||||
operator: operatorState.value,
|
operator: resolvedOperatorAddress,
|
||||||
type,
|
type,
|
||||||
tokenURI: tokenURIState.value,
|
tokenURI: tokenURIState.value,
|
||||||
msg: parseJson(messageState.value) || {},
|
msg: parseJson(messageState.value) || {},
|
||||||
@ -137,6 +140,24 @@ const Sg721ExecutePage: NextPage = () => {
|
|||||||
if (initial && initial.length > 0) contractState.onChange(initial)
|
if (initial && initial.length > 0) contractState.onChange(initial)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const resolveRecipientAddress = async () => {
|
||||||
|
await resolveAddress(recipientState.value.trim(), wallet).then((resolvedAddress) => {
|
||||||
|
setResolvedRecipientAddress(resolvedAddress)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
useEffect(() => {
|
||||||
|
void resolveRecipientAddress()
|
||||||
|
}, [recipientState.value])
|
||||||
|
|
||||||
|
const resolveOperatorAddress = async () => {
|
||||||
|
await resolveAddress(operatorState.value.trim(), wallet).then((resolvedAddress) => {
|
||||||
|
setResolvedOperatorAddress(resolvedAddress)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
useEffect(() => {
|
||||||
|
void resolveOperatorAddress()
|
||||||
|
}, [operatorState.value])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="py-6 px-12 space-y-4">
|
<section className="py-6 px-12 space-y-4">
|
||||||
<NextSeo title="Execute Sg721 Contract" />
|
<NextSeo title="Execute Sg721 Contract" />
|
||||||
|
@ -19,6 +19,7 @@ import { toast } from 'react-hot-toast'
|
|||||||
import { useQuery } from 'react-query'
|
import { useQuery } from 'react-query'
|
||||||
import { withMetadata } from 'utils/layout'
|
import { withMetadata } from 'utils/layout'
|
||||||
import { links } from 'utils/links'
|
import { links } from 'utils/links'
|
||||||
|
import { resolveAddress } from 'utils/resolveAddress'
|
||||||
|
|
||||||
const Sg721QueryPage: NextPage = () => {
|
const Sg721QueryPage: NextPage = () => {
|
||||||
const { sg721: contract } = useContracts()
|
const { sg721: contract } = useContracts()
|
||||||
@ -58,13 +59,16 @@ const Sg721QueryPage: NextPage = () => {
|
|||||||
async ({ queryKey }) => {
|
async ({ queryKey }) => {
|
||||||
const [_contractAddress, _type, _contract, _wallet, _tokenId, _address] = queryKey
|
const [_contractAddress, _type, _contract, _wallet, _tokenId, _address] = queryKey
|
||||||
const messages = contract?.use(contractAddress)
|
const messages = contract?.use(contractAddress)
|
||||||
const result = await dispatchQuery({
|
const res = await resolveAddress(_address, wallet).then(async (resolvedAddress) => {
|
||||||
messages,
|
const result = await dispatchQuery({
|
||||||
type,
|
messages,
|
||||||
tokenId: _tokenId,
|
type,
|
||||||
address: _address,
|
tokenId: _tokenId,
|
||||||
|
address: resolvedAddress,
|
||||||
|
})
|
||||||
|
return result
|
||||||
})
|
})
|
||||||
return result
|
return res
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
placeholderData: null,
|
placeholderData: null,
|
||||||
|
@ -25,6 +25,7 @@ import { FaArrowRight } from 'react-icons/fa'
|
|||||||
import { useMutation } from 'react-query'
|
import { useMutation } from 'react-query'
|
||||||
import { withMetadata } from 'utils/layout'
|
import { withMetadata } from 'utils/layout'
|
||||||
import { links } from 'utils/links'
|
import { links } from 'utils/links'
|
||||||
|
import { resolveAddress } from 'utils/resolveAddress'
|
||||||
|
|
||||||
const VendingMinterExecutePage: NextPage = () => {
|
const VendingMinterExecutePage: NextPage = () => {
|
||||||
const { vendingMinter: contract } = useContracts()
|
const { vendingMinter: contract } = useContracts()
|
||||||
@ -32,6 +33,7 @@ const VendingMinterExecutePage: NextPage = () => {
|
|||||||
const [lastTx, setLastTx] = useState('')
|
const [lastTx, setLastTx] = useState('')
|
||||||
|
|
||||||
const [timestamp, setTimestamp] = useState<Date | undefined>(undefined)
|
const [timestamp, setTimestamp] = useState<Date | undefined>(undefined)
|
||||||
|
const [resolvedRecipientAddress, setResolvedRecipientAddress] = useState<string>('')
|
||||||
|
|
||||||
const comboboxState = useExecuteComboboxState()
|
const comboboxState = useExecuteComboboxState()
|
||||||
const type = comboboxState.value?.id
|
const type = comboboxState.value?.id
|
||||||
@ -94,7 +96,7 @@ const VendingMinterExecutePage: NextPage = () => {
|
|||||||
contract: contractState.value,
|
contract: contractState.value,
|
||||||
tokenId: tokenIdState.value,
|
tokenId: tokenIdState.value,
|
||||||
messages,
|
messages,
|
||||||
recipient: recipientState.value,
|
recipient: resolvedRecipientAddress,
|
||||||
txSigner: wallet.address,
|
txSigner: wallet.address,
|
||||||
price: priceState.value ? priceState.value.toString() : '0',
|
price: priceState.value ? priceState.value.toString() : '0',
|
||||||
type,
|
type,
|
||||||
@ -183,6 +185,15 @@ const VendingMinterExecutePage: NextPage = () => {
|
|||||||
if (initial && initial.length > 0) contractState.onChange(initial)
|
if (initial && initial.length > 0) contractState.onChange(initial)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const resolveRecipientAddress = async () => {
|
||||||
|
await resolveAddress(recipientState.value.trim(), wallet).then((resolvedAddress) => {
|
||||||
|
setResolvedRecipientAddress(resolvedAddress)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
useEffect(() => {
|
||||||
|
void resolveRecipientAddress()
|
||||||
|
}, [recipientState.value])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="py-6 px-12 space-y-4">
|
<section className="py-6 px-12 space-y-4">
|
||||||
<NextSeo title="Execute Vending Minter Contract" />
|
<NextSeo title="Execute Vending Minter Contract" />
|
||||||
|
@ -17,13 +17,14 @@ import { useWallet } from 'contexts/wallet'
|
|||||||
import type { NextPage } from 'next'
|
import type { NextPage } from 'next'
|
||||||
import { NextSeo } from 'next-seo'
|
import { NextSeo } from 'next-seo'
|
||||||
import type { FormEvent } from 'react'
|
import type { FormEvent } from 'react'
|
||||||
import { useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { toast } from 'react-hot-toast'
|
import { toast } from 'react-hot-toast'
|
||||||
import { FaAsterisk } from 'react-icons/fa'
|
import { FaAsterisk } from 'react-icons/fa'
|
||||||
import { useMutation } from 'react-query'
|
import { useMutation } from 'react-query'
|
||||||
import { VENDING_FACTORY_ADDRESS } from 'utils/constants'
|
import { VENDING_FACTORY_ADDRESS } from 'utils/constants'
|
||||||
import { withMetadata } from 'utils/layout'
|
import { withMetadata } from 'utils/layout'
|
||||||
import { links } from 'utils/links'
|
import { links } from 'utils/links'
|
||||||
|
import { resolveAddress } from 'utils/resolveAddress'
|
||||||
|
|
||||||
import type { CreateVendingMinterResponse } from '../../../contracts/vendingFactory/contract'
|
import type { CreateVendingMinterResponse } from '../../../contracts/vendingFactory/contract'
|
||||||
|
|
||||||
@ -222,9 +223,20 @@ const VendingMinterInstantiatePage: NextPage = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
const txHash = data?.transactionHash
|
const txHash = data?.transactionHash
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void resolveAddress(creatorState.value, wallet).then((resolvedAddress) => {
|
||||||
|
creatorState.onChange(resolvedAddress)
|
||||||
|
})
|
||||||
|
}, [creatorState.value])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void resolveAddress(royaltyPaymentAddressState.value, wallet).then((resolvedAddress) => {
|
||||||
|
royaltyPaymentAddressState.onChange(resolvedAddress)
|
||||||
|
})
|
||||||
|
}, [royaltyPaymentAddressState.value])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="py-6 px-12 space-y-4" onSubmit={mutate}>
|
<form className="py-6 px-12 space-y-4" onSubmit={mutate}>
|
||||||
<NextSeo title="Instantiate Vending Minter Contract" />
|
<NextSeo title="Instantiate Vending Minter Contract" />
|
||||||
|
@ -19,6 +19,7 @@ import { toast } from 'react-hot-toast'
|
|||||||
import { useQuery } from 'react-query'
|
import { useQuery } from 'react-query'
|
||||||
import { withMetadata } from 'utils/layout'
|
import { withMetadata } from 'utils/layout'
|
||||||
import { links } from 'utils/links'
|
import { links } from 'utils/links'
|
||||||
|
import { resolveAddress } from 'utils/resolveAddress'
|
||||||
|
|
||||||
const VendingMinterQueryPage: NextPage = () => {
|
const VendingMinterQueryPage: NextPage = () => {
|
||||||
const { vendingMinter: contract } = useContracts()
|
const { vendingMinter: contract } = useContracts()
|
||||||
@ -47,12 +48,15 @@ const VendingMinterQueryPage: NextPage = () => {
|
|||||||
async ({ queryKey }) => {
|
async ({ queryKey }) => {
|
||||||
const [_contractAddress, _type, _contract, _wallet] = queryKey
|
const [_contractAddress, _type, _contract, _wallet] = queryKey
|
||||||
const messages = contract?.use(_contractAddress)
|
const messages = contract?.use(_contractAddress)
|
||||||
const result = await dispatchQuery({
|
const res = await resolveAddress(address, wallet).then(async (resolvedAddress) => {
|
||||||
address,
|
const result = await dispatchQuery({
|
||||||
messages,
|
address: resolvedAddress,
|
||||||
type,
|
messages,
|
||||||
|
type,
|
||||||
|
})
|
||||||
|
return result
|
||||||
})
|
})
|
||||||
return result
|
return res
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
placeholderData: null,
|
placeholderData: null,
|
||||||
|
@ -19,6 +19,7 @@ import { toast } from 'react-hot-toast'
|
|||||||
import { useQuery } from 'react-query'
|
import { useQuery } from 'react-query'
|
||||||
import { withMetadata } from 'utils/layout'
|
import { withMetadata } from 'utils/layout'
|
||||||
import { links } from 'utils/links'
|
import { links } from 'utils/links'
|
||||||
|
import { resolveAddress } from 'utils/resolveAddress'
|
||||||
|
|
||||||
const WhitelistQueryPage: NextPage = () => {
|
const WhitelistQueryPage: NextPage = () => {
|
||||||
const { whitelist: contract } = useContracts()
|
const { whitelist: contract } = useContracts()
|
||||||
@ -49,12 +50,15 @@ const WhitelistQueryPage: NextPage = () => {
|
|||||||
async ({ queryKey }) => {
|
async ({ queryKey }) => {
|
||||||
const [_contractAddress, _type, _contract, _wallet, _address] = queryKey
|
const [_contractAddress, _type, _contract, _wallet, _address] = queryKey
|
||||||
const messages = contract?.use(contractAddress)
|
const messages = contract?.use(contractAddress)
|
||||||
const result = await dispatchQuery({
|
const res = await resolveAddress(_address, wallet).then(async (resolvedAddress) => {
|
||||||
messages,
|
const result = await dispatchQuery({
|
||||||
type,
|
messages,
|
||||||
address: _address,
|
type,
|
||||||
|
address: resolvedAddress,
|
||||||
|
})
|
||||||
|
return result
|
||||||
})
|
})
|
||||||
return result
|
return res
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
placeholderData: null,
|
placeholderData: null,
|
||||||
|
@ -13,17 +13,19 @@ export const isValidAccountsFile = (file: AirdropAllocation[]) => {
|
|||||||
sumOfAmounts += Number(allocation.amount)
|
sumOfAmounts += Number(allocation.amount)
|
||||||
})
|
})
|
||||||
if (sumOfAmounts > 10000) {
|
if (sumOfAmounts > 10000) {
|
||||||
toast.error(`Accounts file must have less than 10000 accounts`)
|
toast.error(`Accounts file must cover less than 10000 tokens`)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const checks = file.map((account) => {
|
const checks = file.map((account) => {
|
||||||
// Check if address is valid bech32 address
|
// Check if address is valid bech32 address
|
||||||
if (!isValidAddress(account.address)) {
|
if (account.address.trim().startsWith('stars')) {
|
||||||
return { address: false }
|
if (!isValidAddress(account.address.trim())) {
|
||||||
|
return { address: false }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Check if address start with stars
|
// Check if address start with stars
|
||||||
if (!account.address.startsWith('stars')) {
|
if (!account.address.trim().startsWith('stars') && !account.address.trim().endsWith('.stars')) {
|
||||||
return { address: false }
|
return { address: false }
|
||||||
}
|
}
|
||||||
// Check if amount is valid
|
// Check if amount is valid
|
||||||
@ -33,7 +35,9 @@ export const isValidAccountsFile = (file: AirdropAllocation[]) => {
|
|||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
|
|
||||||
const isStargazeAddresses = file.every((account) => account.address.startsWith('stars'))
|
const isStargazeAddresses = file.every(
|
||||||
|
(account) => account.address.trim().startsWith('stars') || account.address.trim().endsWith('.stars'),
|
||||||
|
)
|
||||||
if (!isStargazeAddresses) {
|
if (!isStargazeAddresses) {
|
||||||
toast.error('All accounts must be on the same network')
|
toast.error('All accounts must be on the same network')
|
||||||
return false
|
return false
|
||||||
|
40
utils/resolveAddress.ts
Normal file
40
utils/resolveAddress.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { toUtf8 } from '@cosmjs/encoding'
|
||||||
|
import toast from 'react-hot-toast'
|
||||||
|
|
||||||
|
import type { KeplrWalletStore } from '../contexts/wallet'
|
||||||
|
import { SG721_NAME_ADDRESS } from './constants'
|
||||||
|
import { isValidAddress } from './isValidAddress'
|
||||||
|
|
||||||
|
export const resolveAddress = async (name: string, wallet: KeplrWalletStore): Promise<string> => {
|
||||||
|
if (!name.trim().endsWith('.stars')) return name.trim()
|
||||||
|
|
||||||
|
if (wallet.client) {
|
||||||
|
const tokenUri = await wallet.client
|
||||||
|
.queryContractRaw(
|
||||||
|
SG721_NAME_ADDRESS,
|
||||||
|
toUtf8(
|
||||||
|
Buffer.from(
|
||||||
|
`0006${Buffer.from('tokens').toString('hex')}${Buffer.from(
|
||||||
|
name.trim().substring(0, name.trim().lastIndexOf('.')),
|
||||||
|
).toString('hex')}`,
|
||||||
|
'hex',
|
||||||
|
).toString(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.then((res) => {
|
||||||
|
const parsedTokenUri = JSON.parse(new TextDecoder().decode(res as Uint8Array)).token_uri
|
||||||
|
console.log(parsedTokenUri)
|
||||||
|
if (parsedTokenUri && isValidAddress(parsedTokenUri)) return parsedTokenUri as string
|
||||||
|
toast.error(`Resolved address is empty or invalid for the name: ${name.trim()}`)
|
||||||
|
return name
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e)
|
||||||
|
toast.error(`Error resolving address for the name: ${name.trim()}`)
|
||||||
|
return name
|
||||||
|
})
|
||||||
|
return tokenUri
|
||||||
|
}
|
||||||
|
toast.error('Wallet is not connected. Unable to resolve address.')
|
||||||
|
return name
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user