2023-02-11 17:16:36 +00:00
|
|
|
/* eslint-disable eslint-comments/disable-enable-pair */
|
2023-02-27 18:43:57 +00:00
|
|
|
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
2023-02-27 10:28:14 +00:00
|
|
|
/* eslint-disable no-nested-ternary */
|
2023-02-23 10:07:30 +00:00
|
|
|
|
2023-02-11 17:16:36 +00:00
|
|
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
|
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
|
|
|
2023-02-12 17:06:16 +00:00
|
|
|
//import { coin } from '@cosmjs/proto-signing'
|
2023-02-11 17:16:36 +00:00
|
|
|
import clsx from 'clsx'
|
|
|
|
import { Alert } from 'components/Alert'
|
|
|
|
import { Anchor } from 'components/Anchor'
|
2023-02-13 16:59:16 +00:00
|
|
|
import { BadgeConfirmationModal } from 'components/BadgeConfirmationModal'
|
|
|
|
import { BadgeLoadingModal } from 'components/BadgeLoadingModal'
|
2023-02-12 17:06:16 +00:00
|
|
|
import type { BadgeDetailsDataProps } from 'components/badges/creation/BadgeDetails'
|
|
|
|
import { BadgeDetails } from 'components/badges/creation/BadgeDetails'
|
|
|
|
import type { ImageUploadDetailsDataProps, MintRule } from 'components/badges/creation/ImageUploadDetails'
|
|
|
|
import { ImageUploadDetails } from 'components/badges/creation/ImageUploadDetails'
|
2023-02-11 17:16:36 +00:00
|
|
|
import { Button } from 'components/Button'
|
|
|
|
import { Conditional } from 'components/Conditional'
|
2023-02-27 19:55:48 +00:00
|
|
|
import { TextInput } from 'components/forms/FormInput'
|
|
|
|
import { useInputState } from 'components/forms/FormInput.hooks'
|
2023-02-22 07:17:24 +00:00
|
|
|
import { Tooltip } from 'components/Tooltip'
|
2023-02-11 17:16:36 +00:00
|
|
|
import { useContracts } from 'contexts/contracts'
|
|
|
|
import { useWallet } from 'contexts/wallet'
|
|
|
|
import type { DispatchExecuteArgs as BadgeHubDispatchExecuteArgs } from 'contracts/badgeHub/messages/execute'
|
|
|
|
import { dispatchExecute as badgeHubDispatchExecute } from 'contracts/badgeHub/messages/execute'
|
2023-02-13 10:33:36 +00:00
|
|
|
import * as crypto from 'crypto'
|
2023-02-13 14:58:28 +00:00
|
|
|
import { toPng } from 'html-to-image'
|
2023-02-11 17:16:36 +00:00
|
|
|
import type { NextPage } from 'next'
|
|
|
|
import { NextSeo } from 'next-seo'
|
|
|
|
import { QRCodeSVG } from 'qrcode.react'
|
|
|
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
|
|
import { toast } from 'react-hot-toast'
|
2023-02-13 14:58:28 +00:00
|
|
|
import { FaCopy, FaSave } from 'react-icons/fa'
|
2023-02-13 10:33:36 +00:00
|
|
|
import * as secp256k1 from 'secp256k1'
|
2023-02-11 17:16:36 +00:00
|
|
|
import { upload } from 'services/upload'
|
2023-02-22 07:17:24 +00:00
|
|
|
import { copy } from 'utils/clipboard'
|
2023-02-13 10:33:36 +00:00
|
|
|
import { BADGE_HUB_ADDRESS, BLOCK_EXPLORER_URL, NETWORK } from 'utils/constants'
|
2023-02-11 17:16:36 +00:00
|
|
|
import { withMetadata } from 'utils/layout'
|
|
|
|
import { links } from 'utils/links'
|
2023-02-22 07:17:24 +00:00
|
|
|
import { truncateMiddle } from 'utils/text'
|
2023-02-11 17:16:36 +00:00
|
|
|
|
2023-02-27 18:43:57 +00:00
|
|
|
import { generateKeyPairs } from '../../utils/hash'
|
|
|
|
|
2023-02-11 17:16:36 +00:00
|
|
|
const BadgeCreationPage: NextPage = () => {
|
|
|
|
const wallet = useWallet()
|
|
|
|
const { badgeHub: badgeHubContract } = useContracts()
|
|
|
|
const scrollRef = useRef<HTMLDivElement>(null)
|
|
|
|
|
|
|
|
const badgeHubMessages = useMemo(() => badgeHubContract?.use(BADGE_HUB_ADDRESS), [badgeHubContract, wallet.address])
|
|
|
|
|
|
|
|
const [imageUploadDetails, setImageUploadDetails] = useState<ImageUploadDetailsDataProps | null>(null)
|
2023-02-12 17:06:16 +00:00
|
|
|
const [badgeDetails, setBadgeDetails] = useState<BadgeDetailsDataProps | null>(null)
|
2023-02-11 17:16:36 +00:00
|
|
|
|
|
|
|
const [uploading, setUploading] = useState(false)
|
|
|
|
const [creatingBadge, setCreatingBadge] = useState(false)
|
2023-02-27 18:43:57 +00:00
|
|
|
const [isAddingKeysComplete, setIsAddingKeysComplete] = useState(false)
|
2023-02-11 17:16:36 +00:00
|
|
|
const [readyToCreateBadge, setReadyToCreateBadge] = useState(false)
|
|
|
|
const [mintRule, setMintRule] = useState<MintRule>('by_key')
|
|
|
|
|
|
|
|
const [badgeId, setBadgeId] = useState<string | null>(null)
|
|
|
|
const [imageUrl, setImageUrl] = useState<string | null>(null)
|
2023-02-13 10:33:36 +00:00
|
|
|
const [createdBadgeKey, setCreatedBadgeKey] = useState<string | undefined>(undefined)
|
2023-02-11 17:16:36 +00:00
|
|
|
const [transactionHash, setTransactionHash] = useState<string | null>(null)
|
2023-02-27 18:43:57 +00:00
|
|
|
const [keyPairs, setKeyPairs] = useState<{ publicKey: string; privateKey: string }[]>([])
|
2023-02-27 19:55:48 +00:00
|
|
|
const [numberOfKeys, setNumberOfKeys] = useState(1)
|
2023-02-13 14:58:28 +00:00
|
|
|
const qrRef = useRef<HTMLDivElement>(null)
|
2023-02-11 17:16:36 +00:00
|
|
|
|
2023-02-13 10:33:36 +00:00
|
|
|
const keyState = useInputState({
|
|
|
|
id: 'key',
|
|
|
|
name: 'key',
|
2023-02-22 07:17:24 +00:00
|
|
|
title: 'Public Key',
|
2023-02-23 12:26:42 +00:00
|
|
|
subtitle: 'Part of the key pair to be utilized for post-creation access control',
|
2023-02-13 10:33:36 +00:00
|
|
|
})
|
|
|
|
|
2023-02-27 10:28:14 +00:00
|
|
|
const designatedMinterState = useInputState({
|
|
|
|
id: 'designatedMinter',
|
|
|
|
name: 'designatedMinter',
|
|
|
|
title: 'Minter Address',
|
|
|
|
subtitle: 'The address of the designated minter for this badge',
|
|
|
|
defaultValue: wallet.address,
|
|
|
|
})
|
|
|
|
|
2023-02-11 17:16:36 +00:00
|
|
|
const performBadgeCreationChecks = () => {
|
|
|
|
try {
|
|
|
|
setReadyToCreateBadge(false)
|
|
|
|
checkImageUploadDetails()
|
2023-02-13 16:59:16 +00:00
|
|
|
checkBadgeDetails()
|
|
|
|
setTimeout(() => {
|
|
|
|
setReadyToCreateBadge(true)
|
|
|
|
}, 100)
|
2023-02-11 17:16:36 +00:00
|
|
|
} catch (error: any) {
|
|
|
|
toast.error(error.message, { style: { maxWidth: 'none' } })
|
|
|
|
setUploading(false)
|
|
|
|
setReadyToCreateBadge(false)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const handleImageUrl = async () => {
|
|
|
|
try {
|
|
|
|
setImageUrl(null)
|
|
|
|
setBadgeId(null)
|
|
|
|
setTransactionHash(null)
|
|
|
|
if (imageUploadDetails?.uploadMethod === 'new') {
|
|
|
|
setUploading(true)
|
2023-02-13 09:32:14 +00:00
|
|
|
const coverUrl = await upload(
|
2023-02-11 17:16:36 +00:00
|
|
|
[imageUploadDetails.assetFile] as File[],
|
|
|
|
imageUploadDetails.uploadService,
|
|
|
|
'cover',
|
|
|
|
imageUploadDetails.nftStorageApiKey as string,
|
|
|
|
imageUploadDetails.pinataApiKey as string,
|
|
|
|
imageUploadDetails.pinataSecretKey as string,
|
2023-02-13 09:32:14 +00:00
|
|
|
).then((imageBaseUrl) => {
|
|
|
|
setUploading(false)
|
|
|
|
return `ipfs://${imageBaseUrl}/${imageUploadDetails.assetFile?.name as string}`
|
|
|
|
})
|
|
|
|
setImageUrl(coverUrl)
|
|
|
|
return coverUrl
|
2023-02-11 17:16:36 +00:00
|
|
|
}
|
2023-02-13 09:32:14 +00:00
|
|
|
setImageUrl(imageUploadDetails?.imageUrl as string)
|
|
|
|
return imageUploadDetails?.imageUrl as string
|
2023-02-11 17:16:36 +00:00
|
|
|
} catch (error: any) {
|
|
|
|
toast.error(error.message, { style: { maxWidth: 'none' } })
|
|
|
|
setCreatingBadge(false)
|
|
|
|
setUploading(false)
|
2023-02-13 09:32:14 +00:00
|
|
|
throw new Error("Couldn't upload the image.")
|
2023-02-11 17:16:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-12 17:06:16 +00:00
|
|
|
const createNewBadge = async () => {
|
2023-02-13 09:32:14 +00:00
|
|
|
try {
|
|
|
|
if (!wallet.initialized) throw new Error('Wallet not connected')
|
|
|
|
if (!badgeHubContract) throw new Error('Contract not found')
|
2023-02-13 10:33:36 +00:00
|
|
|
setCreatingBadge(true)
|
2023-02-13 09:32:14 +00:00
|
|
|
const coverUrl = await handleImageUrl()
|
2023-02-12 17:06:16 +00:00
|
|
|
|
2023-02-13 09:32:14 +00:00
|
|
|
const badge = {
|
|
|
|
manager: badgeDetails?.manager as string,
|
|
|
|
metadata: {
|
|
|
|
name: badgeDetails?.name || undefined,
|
|
|
|
description: badgeDetails?.description || undefined,
|
|
|
|
image: coverUrl || undefined,
|
|
|
|
image_data: badgeDetails?.image_data || undefined,
|
|
|
|
external_url: badgeDetails?.external_url || undefined,
|
|
|
|
attributes: badgeDetails?.attributes || undefined,
|
|
|
|
background_color: badgeDetails?.background_color || undefined,
|
|
|
|
animation_url: badgeDetails?.animation_url || undefined,
|
|
|
|
youtube_url: badgeDetails?.youtube_url || undefined,
|
|
|
|
},
|
|
|
|
transferrable: badgeDetails?.transferrable as boolean,
|
2023-02-27 10:28:14 +00:00
|
|
|
rule:
|
|
|
|
mintRule === 'by_key'
|
|
|
|
? {
|
|
|
|
by_key: keyState.value,
|
|
|
|
}
|
|
|
|
: mintRule === 'by_minter'
|
|
|
|
? {
|
2023-02-27 15:33:57 +00:00
|
|
|
by_minter: designatedMinterState.value.trim(),
|
2023-02-27 10:28:14 +00:00
|
|
|
}
|
2023-02-27 18:43:57 +00:00
|
|
|
: 'by_keys',
|
2023-02-13 09:32:14 +00:00
|
|
|
expiry: badgeDetails?.expiry || undefined,
|
|
|
|
max_supply: badgeDetails?.max_supply || undefined,
|
|
|
|
}
|
2023-02-11 17:16:36 +00:00
|
|
|
|
2023-02-13 09:32:14 +00:00
|
|
|
const payload: BadgeHubDispatchExecuteArgs = {
|
|
|
|
contract: BADGE_HUB_ADDRESS,
|
|
|
|
messages: badgeHubMessages,
|
|
|
|
txSigner: wallet.address,
|
|
|
|
badge,
|
|
|
|
type: 'create_badge',
|
|
|
|
}
|
2023-02-27 18:43:57 +00:00
|
|
|
if (mintRule !== 'by_keys') {
|
2023-02-27 19:55:48 +00:00
|
|
|
setBadgeId(null)
|
|
|
|
setIsAddingKeysComplete(false)
|
2023-02-27 18:43:57 +00:00
|
|
|
const data = await badgeHubDispatchExecute(payload)
|
|
|
|
console.log(data)
|
|
|
|
setCreatingBadge(false)
|
|
|
|
setTransactionHash(data.split(':')[0])
|
|
|
|
setBadgeId(data.split(':')[1])
|
|
|
|
} else {
|
2023-02-27 19:55:48 +00:00
|
|
|
setBadgeId(null)
|
|
|
|
setIsAddingKeysComplete(false)
|
2023-02-27 18:43:57 +00:00
|
|
|
setKeyPairs([])
|
2023-02-27 19:55:48 +00:00
|
|
|
const generatedKeyPairs = generateKeyPairs(numberOfKeys)
|
2023-02-27 18:43:57 +00:00
|
|
|
setKeyPairs(generatedKeyPairs)
|
|
|
|
await badgeHubDispatchExecute(payload)
|
|
|
|
.then(async (data) => {
|
|
|
|
setCreatingBadge(false)
|
|
|
|
setTransactionHash(data.split(':')[0])
|
|
|
|
setBadgeId(data.split(':')[1])
|
|
|
|
const res = await toast.promise(
|
|
|
|
badgeHubContract.use(BADGE_HUB_ADDRESS)?.addKeys(
|
|
|
|
wallet.address,
|
|
|
|
Number(data.split(':')[1]),
|
|
|
|
generatedKeyPairs.map((key) => key.publicKey),
|
|
|
|
) as Promise<string>,
|
|
|
|
{
|
|
|
|
loading: 'Adding keys...',
|
|
|
|
success: (result) => {
|
|
|
|
setIsAddingKeysComplete(true)
|
|
|
|
return `Keys added successfully! Tx Hash: ${result}`
|
|
|
|
},
|
|
|
|
error: (error: { message: any }) => `Failed to add keys: ${error.message}`,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
})
|
|
|
|
.catch((error: { message: any }) => {
|
|
|
|
toast.error(error.message, { style: { maxWidth: 'none' } })
|
|
|
|
setUploading(false)
|
|
|
|
setIsAddingKeysComplete(false)
|
|
|
|
setCreatingBadge(false)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
} catch (err: any) {
|
|
|
|
toast.error(err.message, { style: { maxWidth: 'none' } })
|
2023-02-13 09:32:14 +00:00
|
|
|
setCreatingBadge(false)
|
|
|
|
setUploading(false)
|
2023-02-11 17:16:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const checkImageUploadDetails = () => {
|
|
|
|
if (!wallet.initialized) throw new Error('Wallet not connected.')
|
|
|
|
if (!imageUploadDetails) {
|
2023-02-13 09:32:14 +00:00
|
|
|
throw new Error('Please specify the image related details.')
|
2023-02-11 17:16:36 +00:00
|
|
|
}
|
2023-02-13 09:32:14 +00:00
|
|
|
|
2023-02-11 17:16:36 +00:00
|
|
|
if (imageUploadDetails.uploadMethod === 'new' && imageUploadDetails.assetFile === undefined) {
|
|
|
|
throw new Error('Please select the image file')
|
|
|
|
}
|
|
|
|
if (imageUploadDetails.uploadMethod === 'new') {
|
|
|
|
if (imageUploadDetails.uploadService === 'nft-storage') {
|
|
|
|
if (imageUploadDetails.nftStorageApiKey === '') {
|
|
|
|
throw new Error('Please enter a valid NFT.Storage API key')
|
|
|
|
}
|
|
|
|
} else if (imageUploadDetails.pinataApiKey === '' || imageUploadDetails.pinataSecretKey === '') {
|
|
|
|
throw new Error('Please enter Pinata API and secret keys')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (imageUploadDetails.uploadMethod === 'existing' && !imageUploadDetails.imageUrl?.includes('ipfs://')) {
|
|
|
|
throw new Error('Please specify a valid image URL')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-13 16:59:16 +00:00
|
|
|
const checkBadgeDetails = () => {
|
|
|
|
if (!badgeDetails) throw new Error('Please fill out the required fields')
|
2023-02-27 10:28:14 +00:00
|
|
|
if (mintRule === 'by_key' && (keyState.value === '' || !createdBadgeKey))
|
|
|
|
throw new Error('Please generate a public key')
|
2023-02-13 16:59:16 +00:00
|
|
|
if (badgeDetails.external_url) {
|
|
|
|
try {
|
|
|
|
const url = new URL(badgeDetails.external_url)
|
|
|
|
} catch (e: any) {
|
|
|
|
throw new Error(`Invalid external url: Make sure to include the protocol (e.g. https://)`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-02-11 17:16:36 +00:00
|
|
|
|
2023-02-13 10:33:36 +00:00
|
|
|
const handleGenerateKey = () => {
|
|
|
|
let privKey: Buffer
|
|
|
|
do {
|
|
|
|
privKey = crypto.randomBytes(32)
|
|
|
|
} while (!secp256k1.privateKeyVerify(privKey))
|
|
|
|
|
|
|
|
const privateKey = privKey.toString('hex')
|
|
|
|
setCreatedBadgeKey(privateKey)
|
|
|
|
console.log('Private Key: ', privateKey)
|
|
|
|
|
|
|
|
const publicKey = Buffer.from(secp256k1.publicKeyCreate(privKey)).toString('hex')
|
2023-02-13 16:59:16 +00:00
|
|
|
setBadgeId(null)
|
2023-02-13 10:33:36 +00:00
|
|
|
keyState.onChange(publicKey)
|
|
|
|
}
|
|
|
|
|
2023-02-13 14:58:28 +00:00
|
|
|
const handleDownloadQr = async () => {
|
|
|
|
const qrElement = qrRef.current
|
|
|
|
await toPng(qrElement as HTMLElement).then((dataUrl) => {
|
|
|
|
const link = document.createElement('a')
|
|
|
|
link.download = `badge-${badgeId as string}.png`
|
|
|
|
link.href = dataUrl
|
|
|
|
link.click()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-02-27 19:55:48 +00:00
|
|
|
const handleDownloadKeys = () => {
|
|
|
|
const element = document.createElement('a')
|
|
|
|
const file = new Blob([JSON.stringify(keyPairs)], { type: 'text/plain' })
|
|
|
|
element.href = URL.createObjectURL(file)
|
|
|
|
element.download = `badge-${badgeId as string}-keys.json`
|
|
|
|
document.body.appendChild(element)
|
|
|
|
element.click()
|
|
|
|
}
|
|
|
|
|
2023-02-13 14:58:28 +00:00
|
|
|
const copyClaimURL = async () => {
|
|
|
|
const baseURL = NETWORK === 'testnet' ? 'https://badges.publicawesome.dev' : 'https://badges.stargaze.zone'
|
|
|
|
const claimURL = `${baseURL}/?id=${badgeId as string}&key=${createdBadgeKey as string}`
|
|
|
|
await navigator.clipboard.writeText(claimURL)
|
|
|
|
toast.success('Copied claim URL to clipboard')
|
|
|
|
}
|
|
|
|
|
2023-02-11 17:16:36 +00:00
|
|
|
const checkwalletBalance = () => {
|
|
|
|
if (!wallet.initialized) throw new Error('Wallet not connected.')
|
|
|
|
// TODO: estimate creation cost and check wallet balance
|
|
|
|
}
|
|
|
|
useEffect(() => {
|
|
|
|
if (badgeId !== null) scrollRef.current?.scrollIntoView({ behavior: 'smooth' })
|
|
|
|
}, [badgeId])
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
setImageUrl(imageUploadDetails?.imageUrl as string)
|
|
|
|
}, [imageUploadDetails?.imageUrl])
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
setBadgeId(null)
|
|
|
|
setReadyToCreateBadge(false)
|
|
|
|
}, [imageUploadDetails?.uploadMethod])
|
|
|
|
|
2023-02-27 18:43:57 +00:00
|
|
|
useEffect(() => {
|
|
|
|
if (keyPairs.length > 0) {
|
|
|
|
toast.success('Key pairs generated successfully.')
|
|
|
|
}
|
|
|
|
}, [keyPairs])
|
|
|
|
|
2023-02-11 17:16:36 +00:00
|
|
|
return (
|
|
|
|
<div>
|
|
|
|
<NextSeo title="Create Badge" />
|
|
|
|
|
|
|
|
<div className="mt-5 space-y-5 text-center">
|
|
|
|
<h1 className="font-heading text-4xl font-bold">Create Badge</h1>
|
|
|
|
|
|
|
|
<Conditional test={uploading}>
|
2023-02-13 16:59:16 +00:00
|
|
|
<BadgeLoadingModal />
|
2023-02-11 17:16:36 +00:00
|
|
|
</Conditional>
|
|
|
|
|
|
|
|
<p>
|
|
|
|
Make sure you check our{' '}
|
|
|
|
<Anchor className="font-bold text-plumbus hover:underline" external href={links['Docs']}>
|
|
|
|
documentation
|
|
|
|
</Anchor>{' '}
|
|
|
|
on how to create a new badge.
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
<div className="mx-10" ref={scrollRef}>
|
|
|
|
<Conditional test={badgeId !== null}>
|
2023-02-27 15:33:57 +00:00
|
|
|
<Conditional test={mintRule === 'by_key'}>
|
|
|
|
<Alert className="mt-5" type="info">
|
|
|
|
<div className="flex flex-row">
|
|
|
|
<div>
|
|
|
|
<div className="w-[384px] h-[384px]" ref={qrRef}>
|
|
|
|
<QRCodeSVG
|
|
|
|
className="mx-auto"
|
|
|
|
level="H"
|
|
|
|
size={384}
|
|
|
|
value={`${
|
|
|
|
NETWORK === 'testnet' ? 'https://badges.publicawesome.dev' : 'https://badges.stargaze.zone'
|
|
|
|
}/?id=${badgeId as string}&key=${createdBadgeKey as string}`}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-2 mt-2 w-[384px]">
|
|
|
|
<Button
|
|
|
|
className="items-center w-full text-sm text-center rounded"
|
|
|
|
leftIcon={<FaSave />}
|
|
|
|
onClick={() => void handleDownloadQr()}
|
|
|
|
>
|
|
|
|
Download QR Code
|
|
|
|
</Button>
|
|
|
|
<Button
|
|
|
|
className="w-full text-sm text-center rounded"
|
|
|
|
isWide
|
|
|
|
leftIcon={<FaCopy />}
|
|
|
|
onClick={() => void copyClaimURL()}
|
|
|
|
variant="solid"
|
|
|
|
>
|
|
|
|
Copy Claim URL
|
|
|
|
</Button>
|
|
|
|
</div>
|
2023-02-13 14:58:28 +00:00
|
|
|
</div>
|
2023-02-27 15:33:57 +00:00
|
|
|
<div className="ml-4 text-lg">
|
|
|
|
Badge ID:{` ${badgeId as string}`}
|
|
|
|
<br />
|
|
|
|
Private Key:
|
|
|
|
<Tooltip label="Click to copy the private key">
|
|
|
|
<button
|
|
|
|
className="group flex space-x-2 font-mono text-base text-white/50 hover:underline"
|
|
|
|
onClick={() => void copy(createdBadgeKey as string)}
|
|
|
|
type="button"
|
|
|
|
>
|
|
|
|
<span>{truncateMiddle(createdBadgeKey ? createdBadgeKey : '', 32)}</span>
|
|
|
|
<FaCopy className="opacity-50 group-hover:opacity-100" />
|
|
|
|
</button>
|
|
|
|
</Tooltip>
|
|
|
|
<br />
|
|
|
|
Transaction Hash: {' '}
|
|
|
|
<Conditional test={NETWORK === 'testnet'}>
|
|
|
|
<Anchor
|
|
|
|
className="text-stargaze hover:underline"
|
|
|
|
external
|
|
|
|
href={`${BLOCK_EXPLORER_URL}/tx/${transactionHash as string}`}
|
|
|
|
>
|
|
|
|
{transactionHash}
|
|
|
|
</Anchor>
|
|
|
|
</Conditional>
|
|
|
|
<Conditional test={NETWORK === 'mainnet'}>
|
|
|
|
<Anchor
|
|
|
|
className="text-stargaze hover:underline"
|
|
|
|
external
|
|
|
|
href={`${BLOCK_EXPLORER_URL}/txs/${transactionHash as string}`}
|
|
|
|
>
|
|
|
|
{transactionHash}
|
|
|
|
</Anchor>
|
|
|
|
</Conditional>
|
|
|
|
<br />
|
|
|
|
<div className="text-base">
|
|
|
|
<div className="flex-row pt-4 mt-4 border-t-2">
|
|
|
|
<span>
|
|
|
|
You may click{' '}
|
|
|
|
<Anchor
|
|
|
|
className="text-stargaze hover:underline"
|
|
|
|
external
|
|
|
|
href={`${
|
|
|
|
NETWORK === 'testnet' ? 'https://badges.publicawesome.dev' : 'https://badges.stargaze.zone'
|
|
|
|
}/?id=${badgeId as string}&key=${createdBadgeKey as string}`}
|
|
|
|
>
|
|
|
|
here
|
|
|
|
</Anchor>{' '}
|
|
|
|
or scan the QR code to claim a badge.
|
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
<br />
|
|
|
|
<span className="mt-4">
|
|
|
|
You may download the QR code or copy the claim URL to share with others.
|
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
<br />
|
2023-02-13 14:58:28 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
2023-02-27 15:33:57 +00:00
|
|
|
</Alert>
|
|
|
|
</Conditional>
|
2023-02-27 19:55:48 +00:00
|
|
|
|
|
|
|
<Conditional test={mintRule === 'by_keys'}>
|
|
|
|
<Alert className="mt-5" type="info">
|
|
|
|
<div className="ml-4 text-lg">
|
|
|
|
Badge ID:{` ${badgeId as string}`}
|
|
|
|
<br />
|
|
|
|
Transaction Hash: {' '}
|
|
|
|
<Conditional test={NETWORK === 'testnet'}>
|
|
|
|
<Anchor
|
|
|
|
className="text-stargaze hover:underline"
|
|
|
|
external
|
|
|
|
href={`${BLOCK_EXPLORER_URL}/tx/${transactionHash as string}`}
|
|
|
|
>
|
|
|
|
{transactionHash}
|
|
|
|
</Anchor>
|
|
|
|
</Conditional>
|
|
|
|
<Conditional test={NETWORK === 'mainnet'}>
|
|
|
|
<Anchor
|
|
|
|
className="text-stargaze hover:underline"
|
|
|
|
external
|
|
|
|
href={`${BLOCK_EXPLORER_URL}/txs/${transactionHash as string}`}
|
|
|
|
>
|
|
|
|
{transactionHash}
|
|
|
|
</Anchor>
|
|
|
|
</Conditional>
|
|
|
|
<br />
|
|
|
|
<div className="text-base">
|
|
|
|
<div className="flex-row pt-4 mt-4 border-t-2">
|
|
|
|
<span>
|
|
|
|
You may click{' '}
|
|
|
|
<Anchor
|
|
|
|
className="text-stargaze hover:underline"
|
|
|
|
external
|
|
|
|
href={`/badges/actions/?badgeHubContractAddress=${BADGE_HUB_ADDRESS}&badgeId=${
|
|
|
|
badgeId as string
|
|
|
|
}`}
|
|
|
|
>
|
|
|
|
here
|
|
|
|
</Anchor>{' '}
|
|
|
|
and select Actions {'>'} Add Keys to add (additional) whitelisted keys or select Actions {'>'}{' '}
|
|
|
|
Mint by Keys to use one of the keys to mint a badge.
|
|
|
|
</span>
|
|
|
|
<br />
|
|
|
|
<Conditional test={isAddingKeysComplete}>
|
|
|
|
<div className="pt-2 mt-4 border-t-2">
|
|
|
|
<span className="mt-2">
|
|
|
|
Make sure to download the whitelisted keys added during badge creation.
|
|
|
|
</span>
|
|
|
|
<Button className="mt-2" onClick={() => handleDownloadKeys()}>
|
|
|
|
Download Keys
|
|
|
|
</Button>
|
|
|
|
</div>
|
|
|
|
</Conditional>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</Alert>
|
|
|
|
</Conditional>
|
|
|
|
|
2023-02-27 15:33:57 +00:00
|
|
|
<Conditional test={mintRule === 'by_minter'}>
|
|
|
|
<Alert className="mt-5" type="info">
|
2023-02-13 14:58:28 +00:00
|
|
|
<div className="ml-4 text-lg">
|
|
|
|
Badge ID:{` ${badgeId as string}`}
|
|
|
|
<br />
|
2023-02-27 15:33:57 +00:00
|
|
|
Designated Minter Address: {` ${designatedMinterState.value}`}
|
2023-02-22 07:17:24 +00:00
|
|
|
<br />
|
2023-02-13 14:58:28 +00:00
|
|
|
Transaction Hash: {' '}
|
|
|
|
<Conditional test={NETWORK === 'testnet'}>
|
|
|
|
<Anchor
|
|
|
|
className="text-stargaze hover:underline"
|
|
|
|
external
|
|
|
|
href={`${BLOCK_EXPLORER_URL}/tx/${transactionHash as string}`}
|
|
|
|
>
|
|
|
|
{transactionHash}
|
|
|
|
</Anchor>
|
|
|
|
</Conditional>
|
|
|
|
<Conditional test={NETWORK === 'mainnet'}>
|
|
|
|
<Anchor
|
|
|
|
className="text-stargaze hover:underline"
|
|
|
|
external
|
|
|
|
href={`${BLOCK_EXPLORER_URL}/txs/${transactionHash as string}`}
|
|
|
|
>
|
|
|
|
{transactionHash}
|
|
|
|
</Anchor>
|
|
|
|
</Conditional>
|
|
|
|
<br />
|
|
|
|
<div className="text-base">
|
|
|
|
<div className="flex-row pt-4 mt-4 border-t-2">
|
|
|
|
<span>
|
|
|
|
You may click{' '}
|
|
|
|
<Anchor
|
|
|
|
className="text-stargaze hover:underline"
|
|
|
|
external
|
2023-02-27 15:33:57 +00:00
|
|
|
href={`/badges/actions/?badgeHubContractAddress=${BADGE_HUB_ADDRESS}&badgeId=${
|
|
|
|
badgeId as string
|
|
|
|
}`}
|
2023-02-13 14:58:28 +00:00
|
|
|
>
|
|
|
|
here
|
|
|
|
</Anchor>{' '}
|
2023-02-27 15:33:57 +00:00
|
|
|
and select Actions {'>'} Mint By Minter to mint a badge.
|
2023-02-13 14:58:28 +00:00
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
2023-02-27 15:33:57 +00:00
|
|
|
</Alert>
|
|
|
|
</Conditional>
|
2023-02-11 17:16:36 +00:00
|
|
|
</Conditional>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div>
|
|
|
|
<div
|
|
|
|
className={clsx(
|
|
|
|
'mx-10 mt-5',
|
|
|
|
'grid before:absolute relative grid-cols-3 grid-flow-col items-stretch rounded',
|
|
|
|
'before:inset-x-0 before:bottom-0 before:border-white/25',
|
|
|
|
)}
|
|
|
|
>
|
|
|
|
<div
|
|
|
|
className={clsx(
|
|
|
|
'isolate space-y-1 border-2',
|
|
|
|
'first-of-type:rounded-tl-md last-of-type:rounded-tr-md',
|
|
|
|
mintRule === 'by_key' ? 'border-stargaze' : 'border-transparent',
|
|
|
|
mintRule !== 'by_key' ? 'bg-stargaze/5 hover:bg-stargaze/80' : 'hover:bg-white/5',
|
|
|
|
)}
|
|
|
|
>
|
|
|
|
<button
|
|
|
|
className="p-4 w-full h-full text-left bg-transparent"
|
|
|
|
onClick={() => {
|
|
|
|
setMintRule('by_key')
|
|
|
|
setReadyToCreateBadge(false)
|
2023-02-27 19:55:48 +00:00
|
|
|
setBadgeId(null)
|
2023-02-11 17:16:36 +00:00
|
|
|
}}
|
|
|
|
type="button"
|
|
|
|
>
|
|
|
|
<h4 className="font-bold">Mint Rule: By Key</h4>
|
2023-02-13 09:32:14 +00:00
|
|
|
<span className="text-sm text-white/80 line-clamp-2">
|
|
|
|
Badges can be minted more than once with a badge specific message signed by a designated private key.
|
|
|
|
</span>
|
2023-02-11 17:16:36 +00:00
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
<div
|
|
|
|
className={clsx(
|
|
|
|
'isolate space-y-1 border-2',
|
|
|
|
'first-of-type:rounded-tl-md last-of-type:rounded-tr-md',
|
|
|
|
mintRule === 'by_keys' ? 'border-stargaze' : 'border-transparent',
|
2023-02-27 18:43:57 +00:00
|
|
|
mintRule !== 'by_keys' ? 'bg-stargaze/5 hover:bg-stargaze/80' : 'hover:bg-white/5',
|
2023-02-11 17:16:36 +00:00
|
|
|
)}
|
|
|
|
>
|
|
|
|
<button
|
|
|
|
className="p-4 w-full h-full text-left bg-transparent"
|
|
|
|
onClick={() => {
|
|
|
|
setMintRule('by_keys')
|
|
|
|
setReadyToCreateBadge(false)
|
2023-02-27 19:55:48 +00:00
|
|
|
setBadgeId(null)
|
2023-02-11 17:16:36 +00:00
|
|
|
}}
|
|
|
|
type="button"
|
|
|
|
>
|
|
|
|
<h4 className="font-bold">Mint Rule: By Keys</h4>
|
2023-02-27 18:43:57 +00:00
|
|
|
<span className="text-sm text-white/80 line-clamp-2">
|
2023-02-13 09:32:14 +00:00
|
|
|
Similar to the By Key rule, however each designated private key can only be used once to mint a badge.
|
|
|
|
</span>
|
2023-02-11 17:16:36 +00:00
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
<div
|
|
|
|
className={clsx(
|
|
|
|
'isolate space-y-1 border-2',
|
|
|
|
'first-of-type:rounded-tl-md last-of-type:rounded-tr-md',
|
|
|
|
mintRule === 'by_minter' ? 'border-stargaze' : 'border-transparent',
|
2023-02-27 10:28:14 +00:00
|
|
|
mintRule !== 'by_minter' ? 'bg-stargaze/5 hover:bg-stargaze/80' : 'hover:bg-white/5',
|
2023-02-11 17:16:36 +00:00
|
|
|
)}
|
|
|
|
>
|
|
|
|
<button
|
|
|
|
className="p-4 w-full h-full text-left bg-transparent"
|
|
|
|
onClick={() => {
|
|
|
|
setMintRule('by_minter')
|
|
|
|
setReadyToCreateBadge(false)
|
2023-02-27 19:55:48 +00:00
|
|
|
setBadgeId(null)
|
2023-02-11 17:16:36 +00:00
|
|
|
}}
|
|
|
|
type="button"
|
|
|
|
>
|
|
|
|
<h4 className="font-bold">Mint Rule: By Minter</h4>
|
2023-02-27 18:43:57 +00:00
|
|
|
<span className="text-sm text-white/80 line-clamp-2">
|
2023-02-13 09:32:14 +00:00
|
|
|
Badges can be minted by a designated minter account.
|
|
|
|
</span>
|
2023-02-11 17:16:36 +00:00
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className="mx-10">
|
|
|
|
<ImageUploadDetails mintRule={mintRule} onChange={setImageUploadDetails} />
|
2023-02-27 10:28:14 +00:00
|
|
|
<Conditional test={mintRule === 'by_key'}>
|
|
|
|
<div className="flex flex-row justify-start py-3 px-8 mb-3 w-full rounded border-2 border-white/20">
|
|
|
|
<TextInput className="ml-4 w-full max-w-2xl" {...keyState} disabled required />
|
|
|
|
<Button className="mt-14 ml-4" isDisabled={creatingBadge} onClick={handleGenerateKey}>
|
|
|
|
Generate Key
|
|
|
|
</Button>
|
|
|
|
</div>
|
|
|
|
</Conditional>
|
2023-02-11 17:16:36 +00:00
|
|
|
|
2023-02-27 18:43:57 +00:00
|
|
|
<Conditional test={mintRule === 'by_keys'}>
|
|
|
|
<div className="flex flex-row justify-start py-3 px-8 mb-3 w-full rounded border-2 border-white/20">
|
2023-02-27 19:55:48 +00:00
|
|
|
<div className="grid grid-cols-2 gap-24">
|
|
|
|
<div className="flex flex-col ml-4">
|
|
|
|
<span className="font-bold">Number of Keys</span>
|
|
|
|
<span className="text-sm text-white/80">
|
|
|
|
The number of key pairs to be whitelisted for post-creation access control
|
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
<input
|
|
|
|
className="p-2 ml-1 w-1/4 max-w-2xl bg-white/10 rounded border-2 border-white/20"
|
|
|
|
onChange={(e) => setNumberOfKeys(Number(e.target.value))}
|
|
|
|
required
|
|
|
|
type="number"
|
|
|
|
value={numberOfKeys}
|
|
|
|
/>
|
|
|
|
</div>
|
2023-02-27 18:43:57 +00:00
|
|
|
</div>
|
|
|
|
</Conditional>
|
|
|
|
|
2023-02-27 10:28:14 +00:00
|
|
|
<Conditional test={mintRule === 'by_minter'}>
|
|
|
|
<div className="flex flex-row justify-start py-3 px-8 mb-3 w-full rounded border-2 border-white/20">
|
|
|
|
<TextInput className="ml-4 w-full max-w-2xl" {...designatedMinterState} required />
|
|
|
|
</div>
|
|
|
|
</Conditional>
|
2023-02-13 10:33:36 +00:00
|
|
|
|
2023-02-11 17:16:36 +00:00
|
|
|
<div className="flex justify-between py-3 px-8 rounded border-2 border-white/20 grid-col-2">
|
2023-02-12 17:06:16 +00:00
|
|
|
<BadgeDetails
|
|
|
|
mintRule={mintRule}
|
|
|
|
onChange={setBadgeDetails}
|
|
|
|
uploadMethod={imageUploadDetails?.uploadMethod ? imageUploadDetails.uploadMethod : 'new'}
|
2023-02-11 17:16:36 +00:00
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
|
2023-02-13 16:59:16 +00:00
|
|
|
<Conditional test={readyToCreateBadge}>
|
|
|
|
<BadgeConfirmationModal confirm={createNewBadge} />
|
|
|
|
</Conditional>
|
2023-02-11 17:16:36 +00:00
|
|
|
|
|
|
|
<div className="flex justify-end w-full">
|
|
|
|
<Button
|
2023-02-13 14:58:28 +00:00
|
|
|
className="relative justify-center p-2 mt-2 mb-6 max-h-12 text-white bg-plumbus hover:bg-plumbus-light border-0"
|
2023-02-11 17:16:36 +00:00
|
|
|
isLoading={creatingBadge}
|
2023-02-13 16:59:16 +00:00
|
|
|
onClick={() => performBadgeCreationChecks()}
|
2023-02-11 17:16:36 +00:00
|
|
|
variant="solid"
|
|
|
|
>
|
|
|
|
Create Badge
|
|
|
|
</Button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
export default withMetadata(BadgeCreationPage, { center: false })
|