From 57e16dfa8b933ae8031e66aa9c7d409d9f4f27ef Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Mon, 27 Feb 2023 13:28:14 +0300 Subject: [PATCH 01/46] Enable badge creation using Mint Rule: By Minter --- pages/badges/create.tsx | 49 +++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/pages/badges/create.tsx b/pages/badges/create.tsx index 8cc7a95..3b8af69 100644 --- a/pages/badges/create.tsx +++ b/pages/badges/create.tsx @@ -1,4 +1,5 @@ /* eslint-disable eslint-comments/disable-enable-pair */ +/* eslint-disable no-nested-ternary */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ @@ -66,6 +67,14 @@ const BadgeCreationPage: NextPage = () => { subtitle: 'Part of the key pair to be utilized for post-creation access control', }) + const designatedMinterState = useInputState({ + id: 'designatedMinter', + name: 'designatedMinter', + title: 'Minter Address', + subtitle: 'The address of the designated minter for this badge', + defaultValue: wallet.address, + }) + const performBadgeCreationChecks = () => { try { setReadyToCreateBadge(false) @@ -133,9 +142,18 @@ const BadgeCreationPage: NextPage = () => { youtube_url: badgeDetails?.youtube_url || undefined, }, transferrable: badgeDetails?.transferrable as boolean, - rule: { - by_key: keyState.value, - }, + rule: + mintRule === 'by_key' + ? { + by_key: keyState.value, + } + : mintRule === 'by_minter' + ? { + by_minter: designatedMinterState.value, + } + : { + by_keys: [keyState.value], + }, expiry: badgeDetails?.expiry || undefined, max_supply: badgeDetails?.max_supply || undefined, } @@ -184,7 +202,8 @@ const BadgeCreationPage: NextPage = () => { const checkBadgeDetails = () => { if (!badgeDetails) throw new Error('Please fill out the required fields') - if (keyState.value === '' || !createdBadgeKey) throw new Error('Please generate a public key') + if (mintRule === 'by_key' && (keyState.value === '' || !createdBadgeKey)) + throw new Error('Please generate a public key') if (badgeDetails.external_url) { try { const url = new URL(badgeDetails.external_url) @@ -416,12 +435,11 @@ const BadgeCreationPage: NextPage = () => { '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', - mintRule !== 'by_minter' ? 'text-slate-500 bg-stargaze/5 hover:bg-gray/20' : 'hover:bg-white/5', + mintRule !== 'by_minter' ? 'bg-stargaze/5 hover:bg-stargaze/80' : 'hover:bg-white/5', )} > + + -
- - -
+ +
+ +
+
Date: Mon, 27 Feb 2023 18:33:57 +0300 Subject: [PATCH 02/46] Update Mint Rule: By Minter badge creation summary --- pages/badges/create.tsx | 151 +++++++++++++++++++++++++++------------- 1 file changed, 101 insertions(+), 50 deletions(-) diff --git a/pages/badges/create.tsx b/pages/badges/create.tsx index 3b8af69..e4707bf 100644 --- a/pages/badges/create.tsx +++ b/pages/badges/create.tsx @@ -149,7 +149,7 @@ const BadgeCreationPage: NextPage = () => { } : mintRule === 'by_minter' ? { - by_minter: designatedMinterState.value, + by_minter: designatedMinterState.value.trim(), } : { by_keys: [keyState.value], @@ -284,52 +284,106 @@ const BadgeCreationPage: NextPage = () => {
- -
-
-
- + + +
+
+
+ +
+
+ + +
-
- - +
+ Badge ID:{` ${badgeId as string}`} +
+ Private Key: + + + +
+ Transaction Hash: {' '} + + + {transactionHash} + + + + + {transactionHash} + + +
+
+
+ + You may click{' '} + + here + {' '} + or scan the QR code to claim a badge. + +
+
+ + You may download the QR code or copy the claim URL to share with others. + +
+
+ + + +
Badge ID:{` ${badgeId as string}`}
- Private Key: - - - + Designated Minter Address: {` ${designatedMinterState.value}`}
Transaction Hash: {' '} @@ -358,22 +412,19 @@ const BadgeCreationPage: NextPage = () => { here {' '} - or scan the QR code to claim a badge. + and select Actions {'>'} Mint By Minter to mint a badge.
-
- You may download the QR code or copy the claim URL to share with others.
-
-
- + +
From 68efd8d361538bfe9e76bb525d897cda005117fc Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Mon, 27 Feb 2023 19:01:38 +0300 Subject: [PATCH 03/46] Implement Mint by Minter for Badge Actions --- components/badges/actions/Action.tsx | 37 +++++++++++++++++++++++++++- components/badges/actions/actions.ts | 2 +- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/components/badges/actions/Action.tsx b/components/badges/actions/Action.tsx index 0d8b69f..9096a24 100644 --- a/components/badges/actions/Action.tsx +++ b/components/badges/actions/Action.tsx @@ -3,18 +3,23 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ // import { AirdropUpload } from 'components/AirdropUpload' import { toUtf8 } from '@cosmjs/encoding' +import { Alert } from 'components/Alert' import type { DispatchExecuteArgs } from 'components/badges/actions/actions' import { dispatchExecute, isEitherType, previewExecutePayload } from 'components/badges/actions/actions' import { ActionsCombobox } from 'components/badges/actions/Combobox' import { useActionsComboboxState } from 'components/badges/actions/Combobox.hooks' import { Button } from 'components/Button' +import { Conditional } from 'components/Conditional' import { FormControl } from 'components/FormControl' import { FormGroup } from 'components/FormGroup' +import { AddressList } from 'components/forms/AddressList' +import { useAddressListState } from 'components/forms/AddressList.hooks' import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks' import { MetadataAttributes } from 'components/forms/MetadataAttributes' import { useMetadataAttributesState } from 'components/forms/MetadataAttributes.hooks' import { JsonPreview } from 'components/JsonPreview' import { TransactionHash } from 'components/TransactionHash' +import { WhitelistUpload } from 'components/WhitelistUpload' import { useWallet } from 'contexts/wallet' import type { Badge, BadgeHubInstance } from 'contracts/badgeHub' import * as crypto from 'crypto' @@ -26,6 +31,7 @@ import { FaArrowRight } from 'react-icons/fa' import { useMutation } from 'react-query' import * as secp256k1 from 'secp256k1' import { sha256 } from 'utils/hash' +import { isValidAddress } from 'utils/isValidAddress' import { resolveAddress } from 'utils/resolveAddress' import { BadgeAirdropListUpload } from '../../BadgeAirdropListUpload' @@ -54,6 +60,7 @@ export const BadgeActions = ({ badgeHubContractAddress, badgeId, badgeHubMessage const [triggerDispatch, setTriggerDispatch] = useState(false) const [keyPairs, setKeyPairs] = useState([]) const [signature, setSignature] = useState('') + const [ownerList, setOwnerList] = useState([]) const actionComboboxState = useActionsComboboxState() const type = actionComboboxState.value?.id @@ -147,6 +154,8 @@ export const BadgeActions = ({ badgeHubContractAddress, badgeId, badgeHubMessage defaultValue: wallet.address, }) + const ownerListState = useAddressListState() + const pubkeyState = useInputState({ id: 'pubkey', name: 'pubkey', @@ -179,6 +188,7 @@ export const BadgeActions = ({ badgeHubContractAddress, badgeId, badgeHubMessage const showOwnerField = isEitherType(type, ['mint_by_key', 'mint_by_keys']) const showPrivateKeyField = isEitherType(type, ['mint_by_key', 'mint_by_keys', 'airdrop_by_key']) const showAirdropFileField = isEitherType(type, ['airdrop_by_key']) + const showOwnerList = isEitherType(type, ['mint_by_minter']) const payload: DispatchExecuteArgs = { badge: { @@ -235,7 +245,14 @@ export const BadgeActions = ({ badgeHubContractAddress, badgeId, badgeHubMessage signature, keys: [], limit: limitState.value, - owners: [], + owners: [ + ...new Set( + ownerListState.values + .map((a) => a.address.trim()) + .filter((address) => address !== '' && isValidAddress(address.trim()) && address.startsWith('stars')) + .concat(ownerList), + ), + ], recipients: airdropAllocationArray, privateKey: privateKeyState.value, nft: nftState.value, @@ -517,6 +534,24 @@ export const BadgeActions = ({ badgeHubContractAddress, badgeId, badgeHubMessage )} {showPrivateKeyField && } + +
+ + + You may optionally choose a text file of additional owner addresses. + + +
+
+ {showAirdropFileField && ( Date: Mon, 27 Feb 2023 21:43:57 +0300 Subject: [PATCH 04/46] Enable badge creation using Mint Rule: By Keys --- contracts/badgeHub/contract.ts | 17 +++++-- pages/badges/create.tsx | 87 +++++++++++++++++++++++++++------- utils/hash.ts | 19 ++++++++ 3 files changed, 104 insertions(+), 19 deletions(-) diff --git a/contracts/badgeHub/contract.ts b/contracts/badgeHub/contract.ts index dc086c0..3e497d8 100644 --- a/contracts/badgeHub/contract.ts +++ b/contracts/badgeHub/contract.ts @@ -28,7 +28,7 @@ export interface MigrateResponse { export interface Rule { by_key?: string by_minter?: string - by_keys?: string[] + by_keys?: string } export interface Trait { @@ -53,7 +53,7 @@ export interface Badge { manager: string metadata: Metadata transferrable: boolean - rule: Rule + rule: Rule | string expiry?: number max_supply?: number } @@ -102,7 +102,7 @@ export interface CreateBadgeMessage { manager: string metadata: Metadata transferrable: boolean - rule: Rule + rule: Rule | string expiry?: number max_supply?: number } @@ -349,6 +349,16 @@ export const badgeHub = (client: SigningCosmWasmClient, txSigner: string): Badge } const addKeys = async (senderAddress: string, id: number, keys: string[]): Promise => { + const feeRateRaw = await client.queryContractRaw( + contractAddress, + toUtf8(Buffer.from(Buffer.from('fee_rate').toString('hex'), 'hex').toString()), + ) + console.log('Fee Rate Raw: ', feeRateRaw) + const feeRate = JSON.parse(new TextDecoder().decode(feeRateRaw as Uint8Array)) + console.log('Fee Rate:', feeRate) + + console.log('keys size: ', sizeof(keys)) + console.log(keys) const res = await client.execute( senderAddress, contractAddress, @@ -360,6 +370,7 @@ export const badgeHub = (client: SigningCosmWasmClient, txSigner: string): Badge }, 'auto', '', + [coin(Math.ceil((Number(sizeof(keys)) * 1.1 * Number(feeRate.key)) / 2), 'ustars')], ) return res.transactionHash diff --git a/pages/badges/create.tsx b/pages/badges/create.tsx index e4707bf..8c3c7e4 100644 --- a/pages/badges/create.tsx +++ b/pages/badges/create.tsx @@ -1,4 +1,5 @@ /* eslint-disable eslint-comments/disable-enable-pair */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable no-nested-ternary */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ @@ -16,8 +17,8 @@ import type { ImageUploadDetailsDataProps, MintRule } from 'components/badges/cr import { ImageUploadDetails } from 'components/badges/creation/ImageUploadDetails' import { Button } from 'components/Button' import { Conditional } from 'components/Conditional' -import { TextInput } from 'components/forms/FormInput' -import { useInputState } from 'components/forms/FormInput.hooks' +import { NumberInput, TextInput } from 'components/forms/FormInput' +import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks' import { Tooltip } from 'components/Tooltip' import { useContracts } from 'contexts/contracts' import { useWallet } from 'contexts/wallet' @@ -39,6 +40,8 @@ import { withMetadata } from 'utils/layout' import { links } from 'utils/links' import { truncateMiddle } from 'utils/text' +import { generateKeyPairs } from '../../utils/hash' + const BadgeCreationPage: NextPage = () => { const wallet = useWallet() const { badgeHub: badgeHubContract } = useContracts() @@ -51,6 +54,7 @@ const BadgeCreationPage: NextPage = () => { const [uploading, setUploading] = useState(false) const [creatingBadge, setCreatingBadge] = useState(false) + const [isAddingKeysComplete, setIsAddingKeysComplete] = useState(false) const [readyToCreateBadge, setReadyToCreateBadge] = useState(false) const [mintRule, setMintRule] = useState('by_key') @@ -58,6 +62,7 @@ const BadgeCreationPage: NextPage = () => { const [imageUrl, setImageUrl] = useState(null) const [createdBadgeKey, setCreatedBadgeKey] = useState(undefined) const [transactionHash, setTransactionHash] = useState(null) + const [keyPairs, setKeyPairs] = useState<{ publicKey: string; privateKey: string }[]>([]) const qrRef = useRef(null) const keyState = useInputState({ @@ -67,6 +72,14 @@ const BadgeCreationPage: NextPage = () => { subtitle: 'Part of the key pair to be utilized for post-creation access control', }) + const numberOfKeysState = useNumberInputState({ + id: 'numberOfKeys', + name: 'numberOfKeys', + title: 'Number of Keys', + subtitle: 'The number of key pairs to be generated for post-creation access control', + defaultValue: 1, + }) + const designatedMinterState = useInputState({ id: 'designatedMinter', name: 'designatedMinter', @@ -151,9 +164,7 @@ const BadgeCreationPage: NextPage = () => { ? { by_minter: designatedMinterState.value.trim(), } - : { - by_keys: [keyState.value], - }, + : 'by_keys', expiry: badgeDetails?.expiry || undefined, max_supply: badgeDetails?.max_supply || undefined, } @@ -165,13 +176,46 @@ const BadgeCreationPage: NextPage = () => { badge, type: 'create_badge', } - const data = await badgeHubDispatchExecute(payload) - console.log(data) - setCreatingBadge(false) - setTransactionHash(data.split(':')[0]) - setBadgeId(data.split(':')[1]) - } catch (error: any) { - toast.error(error.message, { style: { maxWidth: 'none' } }) + if (mintRule !== 'by_keys') { + const data = await badgeHubDispatchExecute(payload) + console.log(data) + setCreatingBadge(false) + setTransactionHash(data.split(':')[0]) + setBadgeId(data.split(':')[1]) + } else { + setKeyPairs([]) + const generatedKeyPairs = generateKeyPairs(numberOfKeysState.value) + 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, + { + 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' } }) setCreatingBadge(false) setUploading(false) } @@ -263,6 +307,12 @@ const BadgeCreationPage: NextPage = () => { setReadyToCreateBadge(false) }, [imageUploadDetails?.uploadMethod]) + useEffect(() => { + if (keyPairs.length > 0) { + toast.success('Key pairs generated successfully.') + } + }, [keyPairs]) + return (
@@ -463,12 +513,11 @@ const BadgeCreationPage: NextPage = () => { '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', - mintRule !== 'by_keys' ? 'text-slate-500 bg-stargaze/5 hover:bg-gray/20' : 'hover:bg-white/5', + mintRule !== 'by_keys' ? 'bg-stargaze/5 hover:bg-stargaze/80' : 'hover:bg-white/5', )} > @@ -498,7 +547,7 @@ const BadgeCreationPage: NextPage = () => { type="button" >

Mint Rule: By Minter

- + Badges can be minted by a designated minter account. @@ -517,6 +566,12 @@ const BadgeCreationPage: NextPage = () => {
+ +
+ +
+
+
diff --git a/utils/hash.ts b/utils/hash.ts index c98cebf..ca64e60 100644 --- a/utils/hash.ts +++ b/utils/hash.ts @@ -1,5 +1,6 @@ /* eslint-disable eslint-comments/disable-enable-pair */ +import * as crypto from 'crypto' import { Word32Array } from 'jscrypto' import { SHA256 } from 'jscrypto/SHA256' import * as secp256k1 from 'secp256k1' @@ -23,3 +24,21 @@ export function generateSignature(id: number, owner: string, privateKey: string) return '' } } + +export function generateKeyPairs(amount: number) { + const keyPairs: { publicKey: string; privateKey: string }[] = [] + for (let i = 0; i < amount; i++) { + let privKey: Buffer + do { + privKey = crypto.randomBytes(32) + } while (!secp256k1.privateKeyVerify(privKey)) + + const privateKey = privKey.toString('hex') + const publicKey = Buffer.from(secp256k1.publicKeyCreate(privKey)).toString('hex') + keyPairs.push({ + publicKey, + privateKey, + }) + } + return keyPairs +} From 68c3d44d01e2472df5b2600bc14f67d20da610e4 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Mon, 27 Feb 2023 22:55:48 +0300 Subject: [PATCH 05/46] Mint Rule: By Keys summary with downloadable key pairs --- pages/badges/create.tsx | 107 +++++++++++++++++++++++++++++++++++----- 1 file changed, 94 insertions(+), 13 deletions(-) diff --git a/pages/badges/create.tsx b/pages/badges/create.tsx index 8c3c7e4..32c9be4 100644 --- a/pages/badges/create.tsx +++ b/pages/badges/create.tsx @@ -17,8 +17,8 @@ import type { ImageUploadDetailsDataProps, MintRule } from 'components/badges/cr import { ImageUploadDetails } from 'components/badges/creation/ImageUploadDetails' import { Button } from 'components/Button' import { Conditional } from 'components/Conditional' -import { NumberInput, TextInput } from 'components/forms/FormInput' -import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks' +import { TextInput } from 'components/forms/FormInput' +import { useInputState } from 'components/forms/FormInput.hooks' import { Tooltip } from 'components/Tooltip' import { useContracts } from 'contexts/contracts' import { useWallet } from 'contexts/wallet' @@ -63,6 +63,7 @@ const BadgeCreationPage: NextPage = () => { const [createdBadgeKey, setCreatedBadgeKey] = useState(undefined) const [transactionHash, setTransactionHash] = useState(null) const [keyPairs, setKeyPairs] = useState<{ publicKey: string; privateKey: string }[]>([]) + const [numberOfKeys, setNumberOfKeys] = useState(1) const qrRef = useRef(null) const keyState = useInputState({ @@ -72,14 +73,6 @@ const BadgeCreationPage: NextPage = () => { subtitle: 'Part of the key pair to be utilized for post-creation access control', }) - const numberOfKeysState = useNumberInputState({ - id: 'numberOfKeys', - name: 'numberOfKeys', - title: 'Number of Keys', - subtitle: 'The number of key pairs to be generated for post-creation access control', - defaultValue: 1, - }) - const designatedMinterState = useInputState({ id: 'designatedMinter', name: 'designatedMinter', @@ -177,14 +170,18 @@ const BadgeCreationPage: NextPage = () => { type: 'create_badge', } if (mintRule !== 'by_keys') { + setBadgeId(null) + setIsAddingKeysComplete(false) const data = await badgeHubDispatchExecute(payload) console.log(data) setCreatingBadge(false) setTransactionHash(data.split(':')[0]) setBadgeId(data.split(':')[1]) } else { + setBadgeId(null) + setIsAddingKeysComplete(false) setKeyPairs([]) - const generatedKeyPairs = generateKeyPairs(numberOfKeysState.value) + const generatedKeyPairs = generateKeyPairs(numberOfKeys) setKeyPairs(generatedKeyPairs) await badgeHubDispatchExecute(payload) .then(async (data) => { @@ -282,7 +279,15 @@ const BadgeCreationPage: NextPage = () => { }) } - // copy claim url to clipboard + 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() + } + 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}` @@ -428,6 +433,65 @@ const BadgeCreationPage: NextPage = () => {
+ + + +
+ Badge ID:{` ${badgeId as string}`} +
+ Transaction Hash: {' '} + + + {transactionHash} + + + + + {transactionHash} + + +
+
+
+ + You may click{' '} + + here + {' '} + 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. + +
+ +
+ + Make sure to download the whitelisted keys added during badge creation. + + +
+
+
+
+
+
+
+
@@ -499,6 +563,7 @@ const BadgeCreationPage: NextPage = () => { onClick={() => { setMintRule('by_key') setReadyToCreateBadge(false) + setBadgeId(null) }} type="button" > @@ -521,6 +586,7 @@ const BadgeCreationPage: NextPage = () => { onClick={() => { setMintRule('by_keys') setReadyToCreateBadge(false) + setBadgeId(null) }} type="button" > @@ -543,6 +609,7 @@ const BadgeCreationPage: NextPage = () => { onClick={() => { setMintRule('by_minter') setReadyToCreateBadge(false) + setBadgeId(null) }} type="button" > @@ -568,7 +635,21 @@ const BadgeCreationPage: NextPage = () => {
- +
+
+ Number of Keys + + The number of key pairs to be whitelisted for post-creation access control + +
+ setNumberOfKeys(Number(e.target.value))} + required + type="number" + value={numberOfKeys} + /> +
From 1396445f1953c67d9986596d1d23ee7e9dd6ffd4 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Tue, 28 Feb 2023 10:05:06 +0300 Subject: [PATCH 06/46] Update Mint Rule: By Keys summary UI --- pages/badges/create.tsx | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/pages/badges/create.tsx b/pages/badges/create.tsx index 32c9be4..9898a81 100644 --- a/pages/badges/create.tsx +++ b/pages/badges/create.tsx @@ -459,6 +459,16 @@ const BadgeCreationPage: NextPage = () => {
+ +
+ + Make sure to download the whitelisted keys added during badge creation. + + +
+
@@ -475,17 +485,6 @@ const BadgeCreationPage: NextPage = () => { 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. -
- -
- - Make sure to download the whitelisted keys added during badge creation. - - -
-
@@ -643,7 +642,7 @@ const BadgeCreationPage: NextPage = () => {
setNumberOfKeys(Number(e.target.value))} required type="number" From 0696e83bfaa26d72bb40ae1fa2e2d60e83b223c9 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Tue, 28 Feb 2023 11:15:33 +0300 Subject: [PATCH 07/46] Implement Mint by Keys for Badge Actions > Actions --- components/badges/actions/Action.tsx | 38 ++++++++++++---------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/components/badges/actions/Action.tsx b/components/badges/actions/Action.tsx index 9096a24..f92afd4 100644 --- a/components/badges/actions/Action.tsx +++ b/components/badges/actions/Action.tsx @@ -22,7 +22,6 @@ import { TransactionHash } from 'components/TransactionHash' import { WhitelistUpload } from 'components/WhitelistUpload' import { useWallet } from 'contexts/wallet' import type { Badge, BadgeHubInstance } from 'contracts/badgeHub' -import * as crypto from 'crypto' import sizeof from 'object-sizeof' import type { FormEvent } from 'react' import { useEffect, useState } from 'react' @@ -156,18 +155,24 @@ export const BadgeActions = ({ badgeHubContractAddress, badgeId, badgeHubMessage const ownerListState = useAddressListState() - const pubkeyState = useInputState({ - id: 'pubkey', - name: 'pubkey', - title: 'Pubkey', - subtitle: 'The public key to check whether it can be used to mint a badge', + const pubKeyState = useInputState({ + id: 'pubKey', + name: 'pubKey', + title: 'Public Key', + subtitle: + type === 'mint_by_keys' + ? 'The whitelisted public key authorized to mint a badge' + : 'The public key to check whether it can be used to mint a badge', }) const privateKeyState = useInputState({ id: 'privateKey', name: 'privateKey', title: 'Private Key', - subtitle: 'The private key that was generated during badge creation', + subtitle: + type === 'mint_by_keys' + ? 'The corresponding private key for the whitelisted public key' + : 'The private key that was generated during badge creation', }) const nftState = useInputState({ @@ -185,10 +190,11 @@ export const BadgeActions = ({ badgeHubContractAddress, badgeId, badgeHubMessage }) const showMetadataField = isEitherType(type, ['edit_badge']) - const showOwnerField = isEitherType(type, ['mint_by_key', 'mint_by_keys']) + const showOwnerField = isEitherType(type, ['mint_by_key', 'mint_by_keys', 'mint_by_minter']) const showPrivateKeyField = isEitherType(type, ['mint_by_key', 'mint_by_keys', 'airdrop_by_key']) const showAirdropFileField = isEitherType(type, ['airdrop_by_key']) const showOwnerList = isEitherType(type, ['mint_by_minter']) + const showPubKeyField = isEitherType(type, ['mint_by_keys']) const payload: DispatchExecuteArgs = { badge: { @@ -241,7 +247,7 @@ export const BadgeActions = ({ badgeHubContractAddress, badgeId, badgeHubMessage id: badgeId, editFee, owner: resolvedOwnerAddress, - pubkey: pubkeyState.value, + pubkey: pubKeyState.value, signature, keys: [], limit: limitState.value, @@ -484,19 +490,6 @@ export const BadgeActions = ({ badgeHubContractAddress, badgeId, badgeHubMessage } } - const handleGenerateKeys = (amount: number) => { - for (let i = 0; i < amount; i++) { - let privKey: Buffer - do { - privKey = crypto.randomBytes(32) - } while (!secp256k1.privateKeyVerify(privKey)) - - const privateKey = privKey.toString('hex') - const publicKey = Buffer.from(secp256k1.publicKeyCreate(privKey)).toString('hex') - keyPairs.push(publicKey.concat(',', privateKey)) - } - } - return (
@@ -532,6 +525,7 @@ export const BadgeActions = ({ badgeHubContractAddress, badgeId, badgeHubMessage title="Owner" /> )} + {showPubKeyField && } {showPrivateKeyField && } From 3a5dd27dff7a944a2ca638d9158b58f411868bfb Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Tue, 28 Feb 2023 11:33:28 +0300 Subject: [PATCH 08/46] Enable Purge Keys/Owners on Badge Actions > Actions --- components/badges/actions/Action.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/components/badges/actions/Action.tsx b/components/badges/actions/Action.tsx index f92afd4..3726a93 100644 --- a/components/badges/actions/Action.tsx +++ b/components/badges/actions/Action.tsx @@ -34,7 +34,7 @@ import { isValidAddress } from 'utils/isValidAddress' import { resolveAddress } from 'utils/resolveAddress' import { BadgeAirdropListUpload } from '../../BadgeAirdropListUpload' -import { AddressInput, TextInput } from '../../forms/FormInput' +import { AddressInput, NumberInput, TextInput } from '../../forms/FormInput' import type { MintRule } from '../creation/ImageUploadDetails' interface BadgeActionsProps { @@ -186,7 +186,7 @@ export const BadgeActions = ({ badgeHubContractAddress, badgeId, badgeHubMessage id: 'limit', name: 'limit', title: 'Limit', - subtitle: 'Number of keys/owners to execute the action for', + subtitle: 'Number of keys/owners to execute the action for (0 for all)', }) const showMetadataField = isEitherType(type, ['edit_badge']) @@ -195,6 +195,7 @@ export const BadgeActions = ({ badgeHubContractAddress, badgeId, badgeHubMessage const showAirdropFileField = isEitherType(type, ['airdrop_by_key']) const showOwnerList = isEitherType(type, ['mint_by_minter']) const showPubKeyField = isEitherType(type, ['mint_by_keys']) + const showLimitState = isEitherType(type, ['purge_keys', 'purge_owners']) const payload: DispatchExecuteArgs = { badge: { @@ -250,7 +251,7 @@ export const BadgeActions = ({ badgeHubContractAddress, badgeId, badgeHubMessage pubkey: pubKeyState.value, signature, keys: [], - limit: limitState.value, + limit: limitState.value || undefined, owners: [ ...new Set( ownerListState.values @@ -527,6 +528,7 @@ export const BadgeActions = ({ badgeHubContractAddress, badgeId, badgeHubMessage )} {showPubKeyField && } {showPrivateKeyField && } + {showLimitState && }
From 8323fc908b93e9f3318ba4fc75cbda5dc347310c Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Tue, 28 Feb 2023 12:10:39 +0300 Subject: [PATCH 09/46] Implement Add Keys for Badge Actions > Actions --- components/badges/actions/Action.tsx | 60 ++++++++++++++++++++++++++-- components/badges/actions/actions.ts | 30 +++++++------- 2 files changed, 72 insertions(+), 18 deletions(-) diff --git a/components/badges/actions/Action.tsx b/components/badges/actions/Action.tsx index 3726a93..cd211a3 100644 --- a/components/badges/actions/Action.tsx +++ b/components/badges/actions/Action.tsx @@ -29,7 +29,7 @@ import { toast } from 'react-hot-toast' import { FaArrowRight } from 'react-icons/fa' import { useMutation } from 'react-query' import * as secp256k1 from 'secp256k1' -import { sha256 } from 'utils/hash' +import { generateKeyPairs, sha256 } from 'utils/hash' import { isValidAddress } from 'utils/isValidAddress' import { resolveAddress } from 'utils/resolveAddress' @@ -57,9 +57,10 @@ export const BadgeActions = ({ badgeHubContractAddress, badgeId, badgeHubMessage const [resolvedOwnerAddress, setResolvedOwnerAddress] = useState('') const [editFee, setEditFee] = useState(undefined) const [triggerDispatch, setTriggerDispatch] = useState(false) - const [keyPairs, setKeyPairs] = useState([]) + const [keyPairs, setKeyPairs] = useState<{ publicKey: string; privateKey: string }[]>([]) const [signature, setSignature] = useState('') const [ownerList, setOwnerList] = useState([]) + const [numberOfKeys, setNumberOfKeys] = useState(0) const actionComboboxState = useActionsComboboxState() const type = actionComboboxState.value?.id @@ -250,7 +251,7 @@ export const BadgeActions = ({ badgeHubContractAddress, badgeId, badgeHubMessage owner: resolvedOwnerAddress, pubkey: pubKeyState.value, signature, - keys: [], + keys: keyPairs.map((keyPair) => keyPair.publicKey), limit: limitState.value || undefined, owners: [ ...new Set( @@ -379,6 +380,21 @@ export const BadgeActions = ({ badgeHubContractAddress, badgeId, badgeHubMessage handleGenerateSignature(badgeId, resolvedOwnerAddress, privateKeyState.value) }, [privateKeyState.value, resolvedOwnerAddress]) + useEffect(() => { + if (numberOfKeys > 0) { + setKeyPairs(generateKeyPairs(numberOfKeys)) + } + }, [numberOfKeys]) + + 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.toString()}-keys.json` + document.body.appendChild(element) + element.click() + } + const { isLoading, mutate } = useMutation( async (event: FormEvent) => { if (!wallet.client) { @@ -529,6 +545,44 @@ export const BadgeActions = ({ badgeHubContractAddress, badgeId, badgeHubMessage {showPubKeyField && } {showPrivateKeyField && } {showLimitState && } + + + This action is only available if the badge with the specified id is either minted out or expired. + + + + +
+
+
+ Number of Keys + + The number of public keys to be whitelisted for minting badges + +
+ setNumberOfKeys(Number(e.target.value))} + required + type="number" + value={numberOfKeys} + /> +
+
+
+ + 0 && type === 'add_keys'}> + +
+ + Make sure to download the whitelisted public keys together with their private key counterparts. + + +
+
+
diff --git a/components/badges/actions/actions.ts b/components/badges/actions/actions.ts index 6afa5b1..004e876 100644 --- a/components/badges/actions/actions.ts +++ b/components/badges/actions/actions.ts @@ -28,11 +28,6 @@ export const BY_KEY_ACTION_LIST: ActionListItem[] = [ name: 'Edit Badge', description: `Edit badge metadata for the badge with the specified ID`, }, - { - id: 'purge_owners', - name: 'Purge Owners', - description: `Purge owners from the badge with the specified ID`, - }, { id: 'mint_by_key', name: 'Mint by Key', @@ -43,6 +38,11 @@ export const BY_KEY_ACTION_LIST: ActionListItem[] = [ name: 'Airdrop by Key', description: `Airdrop badges to a list of specified addresses`, }, + { + id: 'purge_owners', + name: 'Purge Owners', + description: `Purge owners from the badge with the specified ID`, + }, ] export const BY_KEYS_ACTION_LIST: ActionListItem[] = [ @@ -51,6 +51,11 @@ export const BY_KEYS_ACTION_LIST: ActionListItem[] = [ name: 'Edit Badge', description: `Edit badge metadata for the badge with the specified ID`, }, + { + id: 'mint_by_keys', + name: 'Mint by Keys', + description: `Mint a new badge with a whitelisted private key`, + }, { id: 'add_keys', name: 'Add Keys', @@ -66,11 +71,6 @@ export const BY_KEYS_ACTION_LIST: ActionListItem[] = [ name: 'Purge Owners', description: `Purge owners from the badge with the specified ID`, }, - { - id: 'mint_by_keys', - name: 'Mint by Keys', - description: `Mint a new badge with a whitelisted private key`, - }, ] export const BY_MINTER_ACTION_LIST: ActionListItem[] = [ @@ -79,16 +79,16 @@ export const BY_MINTER_ACTION_LIST: ActionListItem[] = [ name: 'Edit Badge', description: `Edit badge metadata for the badge with the specified ID`, }, - { - id: 'purge_owners', - name: 'Purge Owners', - description: `Purge owners from the badge with the specified ID`, - }, { id: 'mint_by_minter', name: 'Mint by Minter', description: `Mint a new badge to specified owner addresses`, }, + { + id: 'purge_owners', + name: 'Purge Owners', + description: `Purge owners from the badge with the specified ID`, + }, ] export interface DispatchExecuteProps { From 765cce575ea27b4ac9602458cb022d3ba01ecf07 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Tue, 28 Feb 2023 12:17:43 +0300 Subject: [PATCH 10/46] Enable Mint Rule: By Minter & By Keys related actions on Badge Hub Dashboard --- contracts/badgeHub/messages/execute.ts | 40 +++++++++++++------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/contracts/badgeHub/messages/execute.ts b/contracts/badgeHub/messages/execute.ts index 9c9f595..5d15b56 100644 --- a/contracts/badgeHub/messages/execute.ts +++ b/contracts/badgeHub/messages/execute.ts @@ -32,36 +32,36 @@ export const EXECUTE_LIST: ExecuteListItem[] = [ name: 'Edit Badge', description: ` Edit badge metadata for the badge with the specified ID`, }, - // { - // id: 'add_keys', - // name: 'Add Keys', - // description: `Add keys to the badge with the specified ID`, - // }, - // { - // id: 'purge_keys', - // name: 'Purge Keys', - // description: `Purge keys from the badge with the specified ID`, - // }, + { + id: 'add_keys', + name: 'Add Keys', + description: `Add keys to the badge with the specified ID`, + }, + { + id: 'purge_keys', + name: 'Purge Keys', + description: `Purge keys from the badge with the specified ID`, + }, { id: 'purge_owners', name: 'Purge Owners', description: `Purge owners from the badge with the specified ID`, }, - // { - // id: 'mint_by_minter', - // name: 'Mint by Minter', - // description: `Mint a new token by the minter with the specified ID`, - // }, + { + id: 'mint_by_minter', + name: 'Mint by Minter', + description: `Mint a new token by the minter with the specified ID`, + }, { id: 'mint_by_key', name: 'Mint by Key', description: `Mint a new token by the key with the specified ID`, }, - // { - // id: 'mint_by_keys', - // name: 'Mint by Keys', - // description: `Mint a new token by the keys with the specified ID`, - // }, + { + id: 'mint_by_keys', + name: 'Mint by Keys', + description: `Mint a new token by the keys with the specified ID`, + }, { id: 'set_nft', name: 'Set NFT', From 38dc24e0b8ea1d4b344fb1440974092a193ff1e6 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Tue, 28 Feb 2023 12:38:42 +0300 Subject: [PATCH 11/46] Implement Add Keys for Badge Hub Dashboard > Execute --- pages/contracts/badgeHub/execute.tsx | 62 ++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/pages/contracts/badgeHub/execute.tsx b/pages/contracts/badgeHub/execute.tsx index 4980e61..50ccbcd 100644 --- a/pages/contracts/badgeHub/execute.tsx +++ b/pages/contracts/badgeHub/execute.tsx @@ -40,7 +40,7 @@ import { useMutation } from 'react-query' import * as secp256k1 from 'secp256k1' import { copy } from 'utils/clipboard' import { NETWORK } from 'utils/constants' -import { sha256 } from 'utils/hash' +import { generateKeyPairs, sha256 } from 'utils/hash' import { withMetadata } from 'utils/layout' import { links } from 'utils/links' import { resolveAddress } from 'utils/resolveAddress' @@ -66,6 +66,8 @@ const BadgeHubExecutePage: NextPage = () => { const [editFee, setEditFee] = useState(undefined) const [triggerDispatch, setTriggerDispatch] = useState(false) const qrRef = useRef(null) + const [numberOfKeys, setNumberOfKeys] = useState(0) + const [keyPairs, setKeyPairs] = useState<{ publicKey: string; privateKey: string }[]>([]) const comboboxState = useExecuteComboboxState() const type = comboboxState.value?.id @@ -205,7 +207,15 @@ const BadgeHubExecutePage: NextPage = () => { const showBadgeField = type === 'create_badge' const showMetadataField = isEitherType(type, ['create_badge', 'edit_badge']) - const showIdField = isEitherType(type, ['edit_badge', 'mint_by_key']) + const showIdField = isEitherType(type, [ + 'edit_badge', + 'add_keys', + 'purge_keys', + 'purge_owners', + 'mint_by_key', + 'mint_by_keys', + 'mint_by_minter', + ]) const showNFTField = type === 'set_nft' const showOwnerField = type === 'mint_by_key' const showPrivateKeyField = type === 'mint_by_key' @@ -263,7 +273,7 @@ const BadgeHubExecutePage: NextPage = () => { owner: ownerState.value, pubkey: pubkeyState.value, signature, - keys: [], + keys: keyPairs.map((keyPair) => keyPair.publicKey), limit: limitState.value, owners: [], nft: nftState.value, @@ -388,6 +398,14 @@ const BadgeHubExecutePage: NextPage = () => { link.click() }) } + 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-${badgeIdState.value}-keys.json` + document.body.appendChild(element) + element.click() + } const copyClaimURL = async () => { const baseURL = NETWORK === 'testnet' ? 'https://badges.publicawesome.dev' : 'https://badges.stargaze.zone' @@ -409,6 +427,12 @@ const BadgeHubExecutePage: NextPage = () => { } } + useEffect(() => { + if (numberOfKeys > 0) { + setKeyPairs(generateKeyPairs(numberOfKeys)) + } + }, [numberOfKeys]) + useEffect(() => { if (privateKeyState.value.length === 64 && resolvedOwnerAddress) handleGenerateSignature(badgeIdState.value, resolvedOwnerAddress, privateKeyState.value) @@ -636,6 +660,38 @@ const BadgeHubExecutePage: NextPage = () => { title="Owner" /> )} + +
+
+
+ Number of Keys + + The number of public keys to be whitelisted for minting badges + +
+ setNumberOfKeys(Number(e.target.value))} + required + type="number" + value={numberOfKeys} + /> +
+
+
+ + 0 && type === 'add_keys'}> + +
+ + Make sure to download the whitelisted public keys together with their private key counterparts. + + +
+
+
{showPrivateKeyField && } {showNFTField && }
From cfbec6baeb0bd88348ce5498cec4cc6fe6f89df0 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Tue, 28 Feb 2023 12:42:07 +0300 Subject: [PATCH 12/46] Implement Purge Keys & Owners for Badge Hub Dashboard > Execute --- pages/contracts/badgeHub/execute.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pages/contracts/badgeHub/execute.tsx b/pages/contracts/badgeHub/execute.tsx index 50ccbcd..a9fb3e7 100644 --- a/pages/contracts/badgeHub/execute.tsx +++ b/pages/contracts/badgeHub/execute.tsx @@ -202,7 +202,7 @@ const BadgeHubExecutePage: NextPage = () => { id: 'limit', name: 'limit', title: 'Limit', - subtitle: 'Number of keys/owners to execute the action for', + subtitle: 'Number of keys/owners to execute the action for (0 for all)', }) const showBadgeField = type === 'create_badge' @@ -216,6 +216,7 @@ const BadgeHubExecutePage: NextPage = () => { 'mint_by_keys', 'mint_by_minter', ]) + const showLimitField = isEitherType(type, ['purge_keys', 'purge_owners']) const showNFTField = type === 'set_nft' const showOwnerField = type === 'mint_by_key' const showPrivateKeyField = type === 'mint_by_key' @@ -274,7 +275,7 @@ const BadgeHubExecutePage: NextPage = () => { pubkey: pubkeyState.value, signature, keys: keyPairs.map((keyPair) => keyPair.publicKey), - limit: limitState.value, + limit: limitState.value || undefined, owners: [], nft: nftState.value, editFee, @@ -692,6 +693,7 @@ const BadgeHubExecutePage: NextPage = () => {
+ {showLimitField && } {showPrivateKeyField && } {showNFTField && }
From f9f0946b4129dc4bc4c149c5b08bb865bc87ced5 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Tue, 28 Feb 2023 12:59:31 +0300 Subject: [PATCH 13/46] Implement Mint by Keys for Badge Hub Dashboard > Execute --- pages/contracts/badgeHub/execute.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pages/contracts/badgeHub/execute.tsx b/pages/contracts/badgeHub/execute.tsx index a9fb3e7..9849e9e 100644 --- a/pages/contracts/badgeHub/execute.tsx +++ b/pages/contracts/badgeHub/execute.tsx @@ -175,20 +175,24 @@ const BadgeHubExecutePage: NextPage = () => { name: 'owner', title: 'Owner', subtitle: 'The owner of the badge', + defaultValue: wallet.address, }) const pubkeyState = useInputState({ id: 'pubkey', name: 'pubkey', title: 'Pubkey', - subtitle: 'The public key for the badge', + subtitle: 'The whitelisted public key authorized to mint a badge', }) const privateKeyState = useInputState({ id: 'privateKey', name: 'privateKey', title: 'Private Key', - subtitle: 'The private key generated during badge creation', + subtitle: + type === 'mint_by_keys' + ? 'The corresponding private key for the whitelisted public key' + : 'The private key that was generated during badge creation', }) const nftState = useInputState({ @@ -218,8 +222,9 @@ const BadgeHubExecutePage: NextPage = () => { ]) const showLimitField = isEitherType(type, ['purge_keys', 'purge_owners']) const showNFTField = type === 'set_nft' - const showOwnerField = type === 'mint_by_key' - const showPrivateKeyField = type === 'mint_by_key' + const showOwnerField = isEitherType(type, ['mint_by_key', 'mint_by_keys', 'mint_by_minter']) + const showPubkeyField = isEitherType(type, ['mint_by_keys']) + const showPrivateKeyField = isEitherType(type, ['mint_by_key', 'mint_by_keys']) const messages = useMemo(() => contract?.use(contractState.value), [contract, wallet.address, contractState.value]) const payload: DispatchExecuteArgs = { @@ -694,6 +699,7 @@ const BadgeHubExecutePage: NextPage = () => { {showLimitField && } + {showPubkeyField && } {showPrivateKeyField && } {showNFTField && } From 4bf6e6ee5f8792cf8faf994ce0c13c10e23b05ca Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Tue, 28 Feb 2023 13:56:04 +0300 Subject: [PATCH 14/46] Implement Mint by Minter for Badge Hub Dashboard > Execute --- pages/contracts/badgeHub/execute.tsx | 36 ++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/pages/contracts/badgeHub/execute.tsx b/pages/contracts/badgeHub/execute.tsx index 9849e9e..e4d6780 100644 --- a/pages/contracts/badgeHub/execute.tsx +++ b/pages/contracts/badgeHub/execute.tsx @@ -12,6 +12,8 @@ import { ContractPageHeader } from 'components/ContractPageHeader' import { ExecuteCombobox } from 'components/contracts/badgeHub/ExecuteCombobox' import { useExecuteComboboxState } from 'components/contracts/badgeHub/ExecuteCombobox.hooks' import { FormControl } from 'components/FormControl' +import { AddressList } from 'components/forms/AddressList' +import { useAddressListState } from 'components/forms/AddressList.hooks' import { AddressInput, NumberInput } from 'components/forms/FormInput' import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks' import { InputDateTime } from 'components/InputDateTime' @@ -20,6 +22,7 @@ import { LinkTabs } from 'components/LinkTabs' import { badgeHubLinkTabs } from 'components/LinkTabs.data' import { Tooltip } from 'components/Tooltip' import { TransactionHash } from 'components/TransactionHash' +import { WhitelistUpload } from 'components/WhitelistUpload' import { useContracts } from 'contexts/contracts' import { useWallet } from 'contexts/wallet' import type { Badge } from 'contracts/badgeHub' @@ -41,6 +44,7 @@ import * as secp256k1 from 'secp256k1' import { copy } from 'utils/clipboard' import { NETWORK } from 'utils/constants' import { generateKeyPairs, sha256 } from 'utils/hash' +import { isValidAddress } from 'utils/isValidAddress' import { withMetadata } from 'utils/layout' import { links } from 'utils/links' import { resolveAddress } from 'utils/resolveAddress' @@ -63,6 +67,7 @@ const BadgeHubExecutePage: NextPage = () => { const [createdBadgeKey, setCreatedBadgeKey] = useState(undefined) const [resolvedOwnerAddress, setResolvedOwnerAddress] = useState('') const [signature, setSignature] = useState('') + const [ownerList, setOwnerList] = useState([]) const [editFee, setEditFee] = useState(undefined) const [triggerDispatch, setTriggerDispatch] = useState(false) const qrRef = useRef(null) @@ -178,6 +183,8 @@ const BadgeHubExecutePage: NextPage = () => { defaultValue: wallet.address, }) + const ownerListState = useAddressListState() + const pubkeyState = useInputState({ id: 'pubkey', name: 'pubkey', @@ -222,7 +229,8 @@ const BadgeHubExecutePage: NextPage = () => { ]) const showLimitField = isEitherType(type, ['purge_keys', 'purge_owners']) const showNFTField = type === 'set_nft' - const showOwnerField = isEitherType(type, ['mint_by_key', 'mint_by_keys', 'mint_by_minter']) + const showOwnerField = isEitherType(type, ['mint_by_key', 'mint_by_keys']) + const showOwnerListField = isEitherType(type, ['mint_by_minter']) const showPubkeyField = isEitherType(type, ['mint_by_keys']) const showPrivateKeyField = isEitherType(type, ['mint_by_key', 'mint_by_keys']) @@ -281,7 +289,14 @@ const BadgeHubExecutePage: NextPage = () => { signature, keys: keyPairs.map((keyPair) => keyPair.publicKey), limit: limitState.value || undefined, - owners: [], + owners: [ + ...new Set( + ownerListState.values + .map((a) => a.address.trim()) + .filter((address) => address !== '' && isValidAddress(address.trim()) && address.startsWith('stars')) + .concat(ownerList), + ), + ], nft: nftState.value, editFee, contract: contractState.value, @@ -666,6 +681,23 @@ const BadgeHubExecutePage: NextPage = () => { title="Owner" /> )} + +
+ + + You may optionally choose a text file of additional owner addresses. + + +
+
From 3c2478cd38fb09ef444ded7fdad86a7811b3fbae Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Tue, 28 Feb 2023 17:00:44 +0300 Subject: [PATCH 15/46] Enable Mint Rule: By Keys & By Minter for badge creation using the dashboard --- pages/contracts/badgeHub/execute.tsx | 172 +++++++++++++++++++++++++-- 1 file changed, 159 insertions(+), 13 deletions(-) diff --git a/pages/contracts/badgeHub/execute.tsx b/pages/contracts/badgeHub/execute.tsx index e4d6780..e078be8 100644 --- a/pages/contracts/badgeHub/execute.tsx +++ b/pages/contracts/badgeHub/execute.tsx @@ -1,11 +1,14 @@ /* eslint-disable eslint-comments/disable-enable-pair */ +/* eslint-disable no-nested-ternary */ /* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */ /* 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 { Alert } from 'components/Alert' +import type { MintRule } from 'components/badges/creation/ImageUploadDetails' import { Button } from 'components/Button' import { Conditional } from 'components/Conditional' import { ContractPageHeader } from 'components/ContractPageHeader' @@ -66,6 +69,7 @@ const BadgeHubExecutePage: NextPage = () => { const [createdBadgeId, setCreatedBadgeId] = useState(null) const [createdBadgeKey, setCreatedBadgeKey] = useState(undefined) const [resolvedOwnerAddress, setResolvedOwnerAddress] = useState('') + const [resolvedMinterAddress, setResolvedMinterAddress] = useState('') const [signature, setSignature] = useState('') const [ownerList, setOwnerList] = useState([]) const [editFee, setEditFee] = useState(undefined) @@ -73,6 +77,7 @@ const BadgeHubExecutePage: NextPage = () => { const qrRef = useRef(null) const [numberOfKeys, setNumberOfKeys] = useState(0) const [keyPairs, setKeyPairs] = useState<{ publicKey: string; privateKey: string }[]>([]) + const [mintRule, setMintRule] = useState('by_key') const comboboxState = useExecuteComboboxState() const type = comboboxState.value?.id @@ -216,6 +221,14 @@ const BadgeHubExecutePage: NextPage = () => { subtitle: 'Number of keys/owners to execute the action for (0 for all)', }) + const designatedMinterState = useInputState({ + id: 'designatedMinter', + name: 'designatedMinter', + title: 'Minter Address', + subtitle: 'The address of the designated minter for this badge', + defaultValue: wallet.address, + }) + const showBadgeField = type === 'create_badge' const showMetadataField = isEitherType(type, ['create_badge', 'edit_badge']) const showIdField = isEitherType(type, [ @@ -258,9 +271,16 @@ const BadgeHubExecutePage: NextPage = () => { youtube_url: youtubeUrlState.value || undefined, }, transferrable, - rule: { - by_key: keyState.value, - }, + rule: + mintRule === 'by_key' + ? { + by_key: keyState.value, + } + : mintRule === 'by_minter' + ? { + by_minter: resolvedMinterAddress, + } + : 'by_keys', expiry: timestamp ? timestamp.getTime() * 1000000 : undefined, max_supply: maxSupplyState.value || undefined, }, @@ -284,7 +304,7 @@ const BadgeHubExecutePage: NextPage = () => { youtube_url: youtubeUrlState.value || undefined, }, id: badgeIdState.value, - owner: ownerState.value, + owner: resolvedOwnerAddress, pubkey: pubkeyState.value, signature, keys: keyPairs.map((keyPair) => keyPair.publicKey), @@ -368,6 +388,7 @@ const BadgeHubExecutePage: NextPage = () => { if (txHash) { setLastTx(txHash.split(':')[0]) setCreatedBadgeId(txHash.split(':')[1]) + badgeIdState.onChange(!isNaN(Number(txHash.split(':')[1])) ? Number(txHash.split(':')[1]) : 1) } } }, @@ -493,6 +514,15 @@ const BadgeHubExecutePage: NextPage = () => { void resolveOwnerAddress() }, [ownerState.value]) + const resolveMinterAddress = async () => { + await resolveAddress(designatedMinterState.value.trim(), wallet).then((resolvedAddress) => { + setResolvedMinterAddress(resolvedAddress) + }) + } + useEffect(() => { + void resolveMinterAddress() + }, [designatedMinterState.value]) + const resolveManagerAddress = async () => { await resolveAddress(managerState.value.trim(), wallet).then((resolvedAddress) => { setBadge({ @@ -517,9 +547,16 @@ const BadgeHubExecutePage: NextPage = () => { youtube_url: youtubeUrlState.value || undefined, }, transferrable, - rule: { - by_key: keyState.value, - }, + rule: + mintRule === 'by_key' + ? { + by_key: keyState.value, + } + : mintRule === 'by_minter' + ? { + by_minter: resolvedMinterAddress, + } + : 'by_keys', expiry: timestamp ? timestamp.getTime() * 1000000 : undefined, max_supply: maxSupplyState.value || undefined, }) @@ -553,9 +590,16 @@ const BadgeHubExecutePage: NextPage = () => { youtube_url: youtubeUrlState.value || undefined, }, transferrable, - rule: { - by_key: keyState.value, - }, + rule: + mintRule === 'by_key' + ? { + by_key: keyState.value, + } + : mintRule === 'by_minter' + ? { + by_minter: resolvedMinterAddress, + } + : 'by_keys', expiry: timestamp ? timestamp.getTime() * 1000000 : undefined, max_supply: maxSupplyState.value || undefined, }) @@ -586,7 +630,7 @@ const BadgeHubExecutePage: NextPage = () => { /> - {showBadgeField && createdBadgeId && createdBadgeKey && ( + {showBadgeField && createdBadgeId && createdBadgeKey && mintRule === 'by_key' && (
@@ -643,14 +687,116 @@ const BadgeHubExecutePage: NextPage = () => {
)} + + {showBadgeField && createdBadgeId && mintRule === 'by_keys' && ( + +
+ Badge ID:{` ${createdBadgeId as string}`} +
+
+
+ + You may select Message Type {'>'} Add Keys to add whitelisted keys authorized to mint a badge. + +
+
+
+
+ )} + + {showBadgeField && createdBadgeId && mintRule === 'by_minter' && ( + +
+ Badge successfully created with ID:{` ${createdBadgeId as string}`} +
+ Designated Minter Address: {` ${resolvedMinterAddress}`} +
+
+
+ + You may select Message Type {'>'} Mint by Minter to mint badges using the designated minter wallet. + +
+
+
+
+ )} +
+ +
+
+
+ Mint Rule: +
+ { + setMintRule('by_key') + setCreatedBadgeId(null) + }} + type="radio" + /> + +
+
+ { + setMintRule('by_keys') + setCreatedBadgeId(null) + }} + type="radio" + /> + +
+
+ { + setMintRule('by_minter') + setCreatedBadgeId(null) + }} + type="radio" + /> + +
+
+
+
+
{showIdField && } {showBadgeField && } - {showBadgeField && } - {showBadgeField && } + {showBadgeField && mintRule === 'by_key' && } + {showBadgeField && mintRule === 'by_key' && } + {showBadgeField && mintRule === 'by_minter' && } {showMetadataField && (
Metadata From f486e4c39e92d70be9213bfc23128dfcae719f7d Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Tue, 28 Feb 2023 17:09:06 +0300 Subject: [PATCH 16/46] Name resolution support for designated minter address during badge creation --- pages/badges/create.tsx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/pages/badges/create.tsx b/pages/badges/create.tsx index 9898a81..d28f56e 100644 --- a/pages/badges/create.tsx +++ b/pages/badges/create.tsx @@ -38,6 +38,7 @@ import { copy } from 'utils/clipboard' import { BADGE_HUB_ADDRESS, BLOCK_EXPLORER_URL, NETWORK } from 'utils/constants' import { withMetadata } from 'utils/layout' import { links } from 'utils/links' +import { resolveAddress } from 'utils/resolveAddress' import { truncateMiddle } from 'utils/text' import { generateKeyPairs } from '../../utils/hash' @@ -57,6 +58,7 @@ const BadgeCreationPage: NextPage = () => { const [isAddingKeysComplete, setIsAddingKeysComplete] = useState(false) const [readyToCreateBadge, setReadyToCreateBadge] = useState(false) const [mintRule, setMintRule] = useState('by_key') + const [resolvedMinterAddress, setResolvedMinterAddress] = useState('') const [badgeId, setBadgeId] = useState(null) const [imageUrl, setImageUrl] = useState(null) @@ -127,6 +129,15 @@ const BadgeCreationPage: NextPage = () => { } } + const resolveMinterAddress = async () => { + await resolveAddress(designatedMinterState.value.trim(), wallet).then((resolvedAddress) => { + setResolvedMinterAddress(resolvedAddress) + }) + } + useEffect(() => { + void resolveMinterAddress() + }, [designatedMinterState.value]) + const createNewBadge = async () => { try { if (!wallet.initialized) throw new Error('Wallet not connected') @@ -155,7 +166,7 @@ const BadgeCreationPage: NextPage = () => { } : mintRule === 'by_minter' ? { - by_minter: designatedMinterState.value.trim(), + by_minter: resolvedMinterAddress, } : 'by_keys', expiry: badgeDetails?.expiry || undefined, @@ -496,7 +507,7 @@ const BadgeCreationPage: NextPage = () => {
Badge ID:{` ${badgeId as string}`}
- Designated Minter Address: {` ${designatedMinterState.value}`} + Designated Minter Address: {` ${resolvedMinterAddress}`}
Transaction Hash: {' '} @@ -654,7 +665,7 @@ const BadgeCreationPage: NextPage = () => {
- +
From f1b12ad56d6330b163242c73a10be9a41b07f7c0 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Tue, 28 Feb 2023 17:11:02 +0300 Subject: [PATCH 17/46] Bump Studio version --- .env.example | 5 ++++- package.json | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index dfc15fa..b13b26f 100644 --- a/.env.example +++ b/.env.example @@ -1,10 +1,13 @@ -APP_VERSION=0.4.5 +APP_VERSION=0.4.6 NEXT_PUBLIC_PINATA_ENDPOINT_URL=https://api.pinata.cloud/pinning/pinFileToIPFS NEXT_PUBLIC_SG721_CODE_ID=793 +NEXT_PUBLIC_SG721_UPDATABLE_CODE_ID=1659 NEXT_PUBLIC_VENDING_MINTER_CODE_ID=275 NEXT_PUBLIC_VENDING_FACTORY_ADDRESS="stars1s48apjumprma0d64ge88dmu7lr8exu02tlw90xxupgds0s25gfasx447dn" +NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_ADDRESS="stars1csq2m3gpca9syyq386v6rsfq5r3cp8llee9eyx5uj4wcmxcmg98sqx5xzg" NEXT_PUBLIC_BASE_FACTORY_ADDRESS="stars1c6juqgd7cm80afpmuszun66rl9zdc4kgfht8fk34tfq3zk87l78sdxngzv" +NEXT_PUBLIC_BASE_FACTORY_UPDATABLE_ADDRESS="stars1c0zth6uw9y7d4deqxae9ucexec49l5n26yls4p83wzj9r069pn4svk904y" NEXT_PUBLIC_SG721_NAME_ADDRESS="stars1fx74nkqkw2748av8j7ew7r3xt9cgjqduwn8m0ur5lhe49uhlsasszc5fhr" NEXT_PUBLIC_BASE_MINTER_CODE_ID=613 NEXT_PUBLIC_WHITELIST_CODE_ID=277 diff --git a/package.json b/package.json index 29e79d9..74fead5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "stargaze-studio", - "version": "0.4.5", + "version": "0.4.6", "workspaces": [ "packages/*" ], From 12a95097f666b0a633bc145692039747c8b1ee81 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Tue, 28 Feb 2023 17:17:18 +0300 Subject: [PATCH 18/46] Enable queryKey & queryKeys on Badge Hub dashboard > Query --- contracts/badgeHub/messages/query.ts | 4 ++-- pages/contracts/badgeHub/query.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/badgeHub/messages/query.ts b/contracts/badgeHub/messages/query.ts index 39538a7..cbfb4bc 100644 --- a/contracts/badgeHub/messages/query.ts +++ b/contracts/badgeHub/messages/query.ts @@ -14,8 +14,8 @@ export const QUERY_LIST: QueryListItem[] = [ { id: 'config', name: 'Config', description: 'View current config' }, { id: 'getBadge', name: 'Query Badge', description: 'Query a badge by ID' }, { id: 'getBadges', name: 'Query Badges', description: 'Query a list of badges' }, - // { id: 'getKey', name: 'Query Key', description: 'Query a key by ID to see if it's whitelisted' }, - // { id: 'getKeys', name: 'Query Keys', description: 'Query the list of whitelisted keys' }, + { id: 'getKey', name: 'Query Key', description: 'Query a key by ID to see if it's whitelisted' }, + { id: 'getKeys', name: 'Query Keys', description: 'Query the list of whitelisted keys' }, ] export interface DispatchQueryProps { diff --git a/pages/contracts/badgeHub/query.tsx b/pages/contracts/badgeHub/query.tsx index df7b56a..466b889 100644 --- a/pages/contracts/badgeHub/query.tsx +++ b/pages/contracts/badgeHub/query.tsx @@ -154,7 +154,7 @@ const BadgeHubQueryPage: NextPage = () => { ))} - + From 9a3282a0c3da2fe0d5e4d8e429e1d78d3731315a Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Tue, 28 Feb 2023 18:23:55 +0300 Subject: [PATCH 19/46] Update .env.example --- .env.example | 3 --- 1 file changed, 3 deletions(-) diff --git a/.env.example b/.env.example index b13b26f..86d57a2 100644 --- a/.env.example +++ b/.env.example @@ -2,12 +2,9 @@ APP_VERSION=0.4.6 NEXT_PUBLIC_PINATA_ENDPOINT_URL=https://api.pinata.cloud/pinning/pinFileToIPFS NEXT_PUBLIC_SG721_CODE_ID=793 -NEXT_PUBLIC_SG721_UPDATABLE_CODE_ID=1659 NEXT_PUBLIC_VENDING_MINTER_CODE_ID=275 NEXT_PUBLIC_VENDING_FACTORY_ADDRESS="stars1s48apjumprma0d64ge88dmu7lr8exu02tlw90xxupgds0s25gfasx447dn" -NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_ADDRESS="stars1csq2m3gpca9syyq386v6rsfq5r3cp8llee9eyx5uj4wcmxcmg98sqx5xzg" NEXT_PUBLIC_BASE_FACTORY_ADDRESS="stars1c6juqgd7cm80afpmuszun66rl9zdc4kgfht8fk34tfq3zk87l78sdxngzv" -NEXT_PUBLIC_BASE_FACTORY_UPDATABLE_ADDRESS="stars1c0zth6uw9y7d4deqxae9ucexec49l5n26yls4p83wzj9r069pn4svk904y" NEXT_PUBLIC_SG721_NAME_ADDRESS="stars1fx74nkqkw2748av8j7ew7r3xt9cgjqduwn8m0ur5lhe49uhlsasszc5fhr" NEXT_PUBLIC_BASE_MINTER_CODE_ID=613 NEXT_PUBLIC_WHITELIST_CODE_ID=277 From 4f76a73fb6547d5bbbd3c5feb5966b8cc26433d2 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Fri, 3 Mar 2023 11:05:16 +0300 Subject: [PATCH 20/46] Update per_address_limit checks performed during Vending Minter creation --- pages/collections/create.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pages/collections/create.tsx b/pages/collections/create.tsx index 1116be5..25dd566 100644 --- a/pages/collections/create.tsx +++ b/pages/collections/create.tsx @@ -748,12 +748,17 @@ const CollectionCreationPage: NextPage = () => { mintingDetails.perAddressLimit > mintingDetails.numTokens ) throw new Error('Invalid limit for tokens per address') + if (mintingDetails.numTokens < 100 && mintingDetails.perAddressLimit > 3) + throw new Error( + 'Invalid limit for tokens per address. Tokens per address limit cannot exceed 3 for collections with less than 100 tokens in total.', + ) if ( - mintingDetails.numTokens > 100 && - mintingDetails.numTokens < 100 * mintingDetails.perAddressLimit && - mintingDetails.perAddressLimit > mintingDetails.numTokens / 100 + mintingDetails.numTokens >= 100 && + mintingDetails.perAddressLimit > Math.ceil((mintingDetails.numTokens / 100) * 3) ) - throw new Error('Invalid limit for tokens per address. The limit cannot exceed 1% of the total number of tokens.') + throw new Error( + 'Invalid limit for tokens per address. Tokens per address limit cannot exceed 3% of the total number of tokens in the collection.', + ) if (mintingDetails.startTime === '') throw new Error('Start time is required') if (Number(mintingDetails.startTime) < new Date().getTime() * 1000000) throw new Error('Invalid start time') } From e1dd8dadc27f771308130b5f30ad7df7e145cb65 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Fri, 3 Mar 2023 11:07:41 +0300 Subject: [PATCH 21/46] Fix typo in Studio description --- config/favicons.json | 2 +- pages/index.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/favicons.json b/config/favicons.json index 4cea67d..b7943a0 100644 --- a/config/favicons.json +++ b/config/favicons.json @@ -2,7 +2,7 @@ "path": "/assets/", "appName": "StargazeStudio", "appShortName": "StargazeStudio", - "appDescription": "Stargaze Studio is built to provide useful smart contract interfaces that helps you build and deploy your own NFT collection in no time.", + "appDescription": "Stargaze Studio is built to provide useful smart contract interfaces that help you build and deploy your own NFT collection in no time.", "developerName": "StargazeStudio", "developerURL": "https://", "background": "#FFC27D", diff --git a/pages/index.tsx b/pages/index.tsx index d208893..22c0ac6 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -15,8 +15,8 @@ const HomePage: NextPage = () => { Looking for a fast and efficient way to build an NFT collection? Stargaze Studio is the solution.

- Stargaze Studio is built to provide useful smart contract interfaces that helps you build and deploy your own - NFT collections in no time. + Stargaze Studio is built to provide useful smart contract interfaces that help you build and deploy your own NFT + collections in no time.


From 58a186e2abd20351c53c722b92b03602d4525917 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Fri, 3 Mar 2023 12:17:16 +0300 Subject: [PATCH 22/46] Update Vending Minter helpers --- contracts/vendingMinter/contract.ts | 82 +++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/contracts/vendingMinter/contract.ts b/contracts/vendingMinter/contract.ts index f159f44..8483fa3 100644 --- a/contracts/vendingMinter/contract.ts +++ b/contracts/vendingMinter/contract.ts @@ -48,6 +48,8 @@ export interface VendingMinterInstance { withdraw: (senderAddress: string) => Promise airdrop: (senderAddress: string, recipients: string[]) => Promise burnRemaining: (senderAddress: string) => Promise + updateDiscountPrice: (senderAddress: string, price: string) => Promise + removeDiscountPrice: (senderAddress: string) => Promise } export interface VendingMinterMessages { @@ -66,6 +68,8 @@ export interface VendingMinterMessages { withdraw: () => WithdrawMessage airdrop: (recipients: string[]) => CustomMessage burnRemaining: () => BurnRemainingMessage + updateDiscountPrice: (price: string) => UpdateDiscountPriceMessage + removeDiscountPrice: () => RemoveDiscountPriceMessage } export interface MintMessage { @@ -97,6 +101,26 @@ export interface UpdateMintPriceMessage { funds: Coin[] } +export interface UpdateDiscountPriceMessage { + sender: string + contract: string + msg: { + update_discount_price: { + price: string + } + } + funds: Coin[] +} + +export interface RemoveDiscountPriceMessage { + sender: string + contract: string + msg: { + remove_discount_price: Record + } + funds: Coin[] +} + export interface SetWhitelistMessage { sender: string contract: string @@ -326,6 +350,36 @@ export const vendingMinter = (client: SigningCosmWasmClient, txSigner: string): return res.transactionHash } + const updateDiscountPrice = async (senderAddress: string, price: string): Promise => { + const res = await client.execute( + senderAddress, + contractAddress, + { + update_discount_price: { + price: (Number(price) * 1000000).toString(), + }, + }, + 'auto', + '', + ) + + return res.transactionHash + } + + const removeDiscountPrice = async (senderAddress: string): Promise => { + const res = await client.execute( + senderAddress, + contractAddress, + { + remove_discount_price: {}, + }, + 'auto', + '', + ) + + return res.transactionHash + } + const setWhitelist = async (senderAddress: string, whitelist: string): Promise => { const res = await client.execute( senderAddress, @@ -552,6 +606,8 @@ export const vendingMinter = (client: SigningCosmWasmClient, txSigner: string): mint, purge, updateMintPrice, + updateDiscountPrice, + removeDiscountPrice, setWhitelist, updateStartTime, updateStartTradingTime, @@ -633,6 +689,30 @@ export const vendingMinter = (client: SigningCosmWasmClient, txSigner: string): } } + const updateDiscountPrice = (price: string): UpdateDiscountPriceMessage => { + return { + sender: txSigner, + contract: contractAddress, + msg: { + update_discount_price: { + price: (Number(price) * 1000000).toString(), + }, + }, + funds: [], + } + } + + const removeDiscountPrice = (): RemoveDiscountPriceMessage => { + return { + sender: txSigner, + contract: contractAddress, + msg: { + remove_discount_price: {}, + }, + funds: [], + } + } + const setWhitelist = (whitelist: string): SetWhitelistMessage => { return { sender: txSigner, @@ -795,6 +875,8 @@ export const vendingMinter = (client: SigningCosmWasmClient, txSigner: string): mint, purge, updateMintPrice, + updateDiscountPrice, + removeDiscountPrice, setWhitelist, updateStartTime, updateStartTradingTime, From a09d59887cfaed7d40ed1d25b84bc83fe426e46c Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Fri, 3 Mar 2023 12:40:36 +0300 Subject: [PATCH 23/46] Update dispatcher logic for Vending Minter actions --- components/collections/actions/actions.ts | 36 +++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/components/collections/actions/actions.ts b/components/collections/actions/actions.ts index 98bff3f..021e83c 100644 --- a/components/collections/actions/actions.ts +++ b/components/collections/actions/actions.ts @@ -13,6 +13,8 @@ export const ACTION_TYPES = [ 'mint_token_uri', 'purge', 'update_mint_price', + 'update_discount_price', + 'remove_discount_price', 'mint_to', 'mint_for', 'batch_mint', @@ -87,16 +89,21 @@ export const VENDING_ACTION_LIST: ActionListItem[] = [ name: 'Mint', description: `Mint a token`, }, - { - id: 'purge', - name: 'Purge', - description: `Purge`, - }, { id: 'update_mint_price', name: 'Update Mint Price', description: `Update mint price`, }, + { + id: 'update_discount_price', + name: 'Update Discount Price', + description: `Update discount price`, + }, + { + id: 'remove_discount_price', + name: 'Remove Discount Price', + description: `Remove discount price`, + }, { id: 'mint_to', name: 'Mint To', @@ -182,6 +189,11 @@ export const VENDING_ACTION_LIST: ActionListItem[] = [ name: 'Burn Remaining Tokens', description: 'Burn remaining tokens', }, + { + id: 'purge', + name: 'Purge', + description: `Purge`, + }, ] export interface DispatchExecuteProps { @@ -205,6 +217,8 @@ export type DispatchExecuteArgs = { | { type: Select<'mint_token_uri'>; tokenUri: string } | { type: Select<'purge'> } | { type: Select<'update_mint_price'>; price: string } + | { type: Select<'update_discount_price'>; price: string } + | { type: Select<'remove_discount_price'> } | { type: Select<'mint_to'>; recipient: string } | { type: Select<'mint_for'>; recipient: string; tokenId: number } | { type: Select<'batch_mint'>; recipient: string; batchNumber: number } @@ -242,6 +256,12 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => { case 'update_mint_price': { return vendingMinterMessages.updateMintPrice(txSigner, args.price) } + case 'update_discount_price': { + return vendingMinterMessages.updateDiscountPrice(txSigner, args.price) + } + case 'remove_discount_price': { + return vendingMinterMessages.removeDiscountPrice(txSigner) + } case 'mint_to': { return vendingMinterMessages.mintTo(txSigner, args.recipient) } @@ -320,6 +340,12 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => { case 'update_mint_price': { return vendingMinterMessages(minterContract)?.updateMintPrice(args.price) } + case 'update_discount_price': { + return vendingMinterMessages(minterContract)?.updateDiscountPrice(args.price) + } + case 'remove_discount_price': { + return vendingMinterMessages(minterContract)?.removeDiscountPrice() + } case 'mint_to': { return vendingMinterMessages(minterContract)?.mintTo(args.recipient) } From ad4aa1b274cdbf7b1fd049dbc498f319b05314f0 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Fri, 3 Mar 2023 12:41:27 +0300 Subject: [PATCH 24/46] Update Collection Actions > Actions UI --- components/collections/actions/Action.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/components/collections/actions/Action.tsx b/components/collections/actions/Action.tsx index 68b0750..98cb7c3 100644 --- a/components/collections/actions/Action.tsx +++ b/components/collections/actions/Action.tsx @@ -114,8 +114,8 @@ export const CollectionActions = ({ const priceState = useNumberInputState({ id: 'update-mint-price', name: 'updateMintPrice', - title: 'Update Mint Price', - subtitle: 'New minting price in STARS', + title: type === 'update_discount_price' ? 'Discount Price' : 'Update Mint Price', + subtitle: type === 'update_discount_price' ? 'New discount price in STARS' : 'New minting price in STARS', }) const descriptionState = useInputState({ @@ -170,7 +170,7 @@ export const CollectionActions = ({ 'batch_mint_for', ]) const showAirdropFileField = type === 'airdrop' - const showPriceField = type === 'update_mint_price' + const showPriceField = isEitherType(type, ['update_mint_price', 'update_discount_price']) const showDescriptionField = type === 'update_collection_info' const showImageField = type === 'update_collection_info' const showExternalLinkField = type === 'update_collection_info' @@ -357,8 +357,8 @@ export const CollectionActions = ({ {showLimitField && } {showTokenIdField && } {showTokenIdListField && } - {showNumberOfTokensField && } - {showPriceField && } + {showNumberOfTokensField && } + {showPriceField && } {showDescriptionField && } {showImageField && } {showExternalLinkField && } From 93534b7c4b9b2d0b0014baf507865117f09bb10b Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Fri, 3 Mar 2023 12:45:41 +0300 Subject: [PATCH 25/46] Update dispatcher logic for Vending Minter dashboard > Execute --- contracts/vendingMinter/messages/execute.ts | 26 +++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/contracts/vendingMinter/messages/execute.ts b/contracts/vendingMinter/messages/execute.ts index 391178f..d75f4e6 100644 --- a/contracts/vendingMinter/messages/execute.ts +++ b/contracts/vendingMinter/messages/execute.ts @@ -7,6 +7,8 @@ export const EXECUTE_TYPES = [ 'mint', 'purge', 'update_mint_price', + 'update_discount_price', + 'remove_discount_price', 'set_whitelist', 'update_start_time', 'update_start_trading_time', @@ -39,6 +41,16 @@ export const EXECUTE_LIST: ExecuteListItem[] = [ name: 'Update Mint Price', description: `Update mint price`, }, + { + id: 'update_discount_price', + name: 'Update Discount Price', + description: `Update discount price`, + }, + { + id: 'remove_discount_price', + name: 'Remove Discount Price', + description: `Remove discount price`, + }, { id: 'set_whitelist', name: 'Set Whitelist', @@ -98,6 +110,8 @@ export type DispatchExecuteArgs = { | { type: Select<'mint'> } | { type: Select<'purge'> } | { type: Select<'update_mint_price'>; price: string } + | { type: Select<'update_discount_price'>; price: string } + | { type: Select<'remove_discount_price'> } | { type: Select<'set_whitelist'>; whitelist: string } | { type: Select<'update_start_time'>; startTime: string } | { type: Select<'update_start_trading_time'>; startTime?: string } @@ -123,6 +137,12 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => { case 'update_mint_price': { return messages.updateMintPrice(txSigner, args.price) } + case 'update_discount_price': { + return messages.updateDiscountPrice(txSigner, args.price) + } + case 'remove_discount_price': { + return messages.removeDiscountPrice(txSigner) + } case 'set_whitelist': { return messages.setWhitelist(txSigner, args.whitelist) } @@ -167,6 +187,12 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => { case 'update_mint_price': { return messages(contract)?.updateMintPrice(args.price) } + case 'update_discount_price': { + return messages(contract)?.updateDiscountPrice(args.price) + } + case 'remove_discount_price': { + return messages(contract)?.removeDiscountPrice() + } case 'set_whitelist': { return messages(contract)?.setWhitelist(args.whitelist) } From 816eda23240c1655d29f52d8c733b39dc117b224 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Fri, 3 Mar 2023 12:50:37 +0300 Subject: [PATCH 26/46] Update Vending Minter Contract dashboard > Execute UI --- pages/contracts/vendingMinter/execute.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pages/contracts/vendingMinter/execute.tsx b/pages/contracts/vendingMinter/execute.tsx index de73e62..17787e2 100644 --- a/pages/contracts/vendingMinter/execute.tsx +++ b/pages/contracts/vendingMinter/execute.tsx @@ -55,8 +55,8 @@ const VendingMinterExecutePage: NextPage = () => { const priceState = useNumberInputState({ id: 'price', name: 'price', - title: 'Price', - subtitle: 'Enter the token price', + title: type === 'update_discount_price' ? 'Discount Price' : 'Price', + subtitle: type === 'update_discount_price' ? 'New discount price in STARS' : 'Enter the token price', }) const contractState = useInputState({ @@ -86,7 +86,7 @@ const VendingMinterExecutePage: NextPage = () => { const showLimitField = type === 'update_per_address_limit' const showTokenIdField = type === 'mint_for' const showRecipientField = isEitherType(type, ['mint_to', 'mint_for']) - const showPriceField = type === 'update_mint_price' + const showPriceField = isEitherType(type, ['update_mint_price', 'update_discount_price']) const messages = useMemo(() => contract?.use(contractState.value), [contract, wallet.address, contractState.value]) const payload: DispatchExecuteArgs = { From f9504c622a743b6d4a6615c1d84ee01ffa63c832 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Fri, 3 Mar 2023 12:53:40 +0300 Subject: [PATCH 27/46] Bump Studio version --- .env.example | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 86d57a2..e98f503 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,4 @@ -APP_VERSION=0.4.6 +APP_VERSION=0.4.7 NEXT_PUBLIC_PINATA_ENDPOINT_URL=https://api.pinata.cloud/pinning/pinFileToIPFS NEXT_PUBLIC_SG721_CODE_ID=793 diff --git a/package.json b/package.json index 74fead5..213d0ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "stargaze-studio", - "version": "0.4.6", + "version": "0.4.7", "workspaces": [ "packages/*" ], From 8f75c24553d382da9fb6304079889e5bab388667 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Sat, 4 Mar 2023 12:43:04 +0300 Subject: [PATCH 28/46] Update WL per_address_limit checks on Vending Minter creation --- pages/collections/create.tsx | 55 ++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/pages/collections/create.tsx b/pages/collections/create.tsx index 25dd566..5455bdc 100644 --- a/pages/collections/create.tsx +++ b/pages/collections/create.tsx @@ -775,15 +775,25 @@ const CollectionCreationPage: NextPage = () => { const whitelistStartDate = new Date(Number(config?.start_time) / 1000000) throw Error(`Whitelist start time (${whitelistStartDate.toLocaleString()}) does not match minting start time`) } - if ( - mintingDetails?.numTokens && - config?.per_address_limit && - mintingDetails.numTokens > 100 && - Number(config.per_address_limit) > mintingDetails.numTokens / 100 - ) - throw Error( - `Invalid limit for tokens per address (${config.per_address_limit} tokens). The limit cannot exceed 1% of the total number of tokens.`, - ) + + if (mintingDetails?.numTokens && config?.per_address_limit) { + if (mintingDetails.numTokens >= 100 && Number(config.per_address_limit) > 50) { + throw Error( + `Invalid limit for tokens per address (${config.per_address_limit} tokens). Tokens per address limit cannot exceed 50 regardless of the total number of tokens.`, + ) + } else if ( + mintingDetails.numTokens >= 100 && + Number(config.per_address_limit) > Math.ceil((mintingDetails.numTokens / 100) * 3) + ) { + throw Error( + `Invalid limit for tokens per address (${config.per_address_limit} tokens). Tokens per address limit cannot exceed 3% of the total number of tokens in the collection.`, + ) + } else if (mintingDetails.numTokens < 100 && Number(config.per_address_limit) > 3) { + throw Error( + `Invalid limit for tokens per address (${config.per_address_limit} tokens). Tokens per address limit cannot exceed 3 for collections with less than 100 tokens in total.`, + ) + } + } } } else if (whitelistDetails.whitelistType === 'new') { if (whitelistDetails.members?.length === 0) throw new Error('Whitelist member list cannot be empty') @@ -800,15 +810,24 @@ const CollectionCreationPage: NextPage = () => { throw new Error('Whitelist start time cannot be later than whitelist end time') if (Number(whitelistDetails.startTime) !== Number(mintingDetails?.startTime)) throw new Error('Whitelist start time must be the same as the minting start time') - if ( - mintingDetails?.numTokens && - whitelistDetails.perAddressLimit && - mintingDetails.numTokens > 100 && - whitelistDetails.perAddressLimit > mintingDetails.numTokens / 100 - ) - throw Error( - `Invalid limit for tokens per address (${whitelistDetails.perAddressLimit} tokens). The limit cannot exceed 1% of the total number of tokens.`, - ) + if (whitelistDetails.perAddressLimit && mintingDetails?.numTokens) { + if (mintingDetails.numTokens >= 100 && whitelistDetails.perAddressLimit > 50) { + throw Error( + `Invalid limit for tokens per address. Tokens per address limit cannot exceed 50 regardless of the total number of tokens.`, + ) + } else if ( + mintingDetails.numTokens >= 100 && + whitelistDetails.perAddressLimit > Math.ceil((mintingDetails.numTokens / 100) * 3) + ) { + throw Error( + `Invalid limit for tokens per address. Tokens per address limit cannot exceed 3% of the total number of tokens in the collection.`, + ) + } else if (mintingDetails.numTokens < 100 && whitelistDetails.perAddressLimit > 3) { + throw Error( + `Invalid limit for tokens per address. Tokens per address limit cannot exceed 3 for collections with less than 100 tokens in total.`, + ) + } + } } } From 1ca3c1fb7b675e7b6a25f136e7d69361d3b26886 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Sat, 4 Mar 2023 12:49:01 +0300 Subject: [PATCH 29/46] Minor UI update on Confirmation Modal --- components/ConfirmationModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/ConfirmationModal.tsx b/components/ConfirmationModal.tsx index edaf792..8fc41fa 100644 --- a/components/ConfirmationModal.tsx +++ b/components/ConfirmationModal.tsx @@ -45,7 +45,7 @@ export const ConfirmationModal = (props: ConfirmationModalProps) => {
-
-
- -
- -
-
-
Date: Mon, 6 Mar 2023 21:49:05 +0300 Subject: [PATCH 32/46] Display badge creation fee estimate --- components/badges/creation/BadgeDetails.tsx | 55 ++++++++++++++++----- pages/badges/create.tsx | 39 +++++++++++++++ 2 files changed, 83 insertions(+), 11 deletions(-) diff --git a/components/badges/creation/BadgeDetails.tsx b/components/badges/creation/BadgeDetails.tsx index babf6c4..46a25b3 100644 --- a/components/badges/creation/BadgeDetails.tsx +++ b/components/badges/creation/BadgeDetails.tsx @@ -3,7 +3,9 @@ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ +import { toUtf8 } from '@cosmjs/encoding' import clsx from 'clsx' +import { Conditional } from 'components/Conditional' import { FormControl } from 'components/FormControl' import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks' import { useMetadataAttributesState } from 'components/forms/MetadataAttributes.hooks' @@ -13,6 +15,7 @@ import type { Trait } from 'contracts/badgeHub' import type { ChangeEvent } from 'react' import { useEffect, useRef, useState } from 'react' import { toast } from 'react-hot-toast' +import { BADGE_HUB_ADDRESS } from 'utils/constants' import { AddressInput, NumberInput, TextInput } from '../../forms/FormInput' import { MetadataAttributes } from '../../forms/MetadataAttributes' @@ -22,6 +25,7 @@ interface BadgeDetailsProps { onChange: (data: BadgeDetailsDataProps) => void uploadMethod: UploadMethod | undefined mintRule: MintRule + metadataSize: number } export interface BadgeDetailsDataProps { @@ -39,11 +43,12 @@ export interface BadgeDetailsDataProps { youtube_url?: string } -export const BadgeDetails = ({ onChange }: BadgeDetailsProps) => { +export const BadgeDetails = ({ metadataSize, onChange }: BadgeDetailsProps) => { const wallet = useWallet() const [timestamp, setTimestamp] = useState(undefined) const [transferrable, setTransferrable] = useState(false) const [metadataFile, setMetadataFile] = useState() + const [metadataFeeRate, setMetadataFeeRate] = useState(0) const metadataFileRef = useRef(null) @@ -259,6 +264,26 @@ export const BadgeDetails = ({ onChange }: BadgeDetailsProps) => { youtubeUrlState.value, ]) + useEffect(() => { + const retrieveFeeRate = async () => { + try { + if (wallet.client) { + const feeRateRaw = await wallet.client.queryContractRaw( + BADGE_HUB_ADDRESS, + toUtf8(Buffer.from(Buffer.from('fee_rate').toString('hex'), 'hex').toString()), + ) + console.log('Fee Rate Raw: ', feeRateRaw) + const feeRate = JSON.parse(new TextDecoder().decode(feeRateRaw as Uint8Array)) + setMetadataFeeRate(Number(feeRate.metadata)) + } + } catch (error) { + toast.error('Error retrieving metadata fee rate.') + console.log('Error retrieving fee rate: ', error) + } + } + void retrieveFeeRate() + }, [wallet.client]) + return (
@@ -272,16 +297,24 @@ export const BadgeDetails = ({ onChange }: BadgeDetailsProps) => { setTimestamp(date)} value={timestamp} /> -
- +
+
+ +
+ +
+
Fee Estimate:
+ {(metadataSize * Number(metadataFeeRate)) / 1000000} stars +
+
diff --git a/pages/badges/create.tsx b/pages/badges/create.tsx index 3f510b3..3763984 100644 --- a/pages/badges/create.tsx +++ b/pages/badges/create.tsx @@ -23,12 +23,14 @@ import { useInputState } from 'components/forms/FormInput.hooks' import { Tooltip } from 'components/Tooltip' import { useContracts } from 'contexts/contracts' import { useWallet } from 'contexts/wallet' +import type { Badge } from 'contracts/badgeHub' import type { DispatchExecuteArgs as BadgeHubDispatchExecuteArgs } from 'contracts/badgeHub/messages/execute' import { dispatchExecute as badgeHubDispatchExecute } from 'contracts/badgeHub/messages/execute' import * as crypto from 'crypto' import { toPng } from 'html-to-image' import type { NextPage } from 'next' import { NextSeo } from 'next-seo' +import sizeof from 'object-sizeof' import { QRCodeSVG } from 'qrcode.react' import { useEffect, useMemo, useRef, useState } from 'react' import { toast } from 'react-hot-toast' @@ -60,6 +62,7 @@ const BadgeCreationPage: NextPage = () => { const [readyToCreateBadge, setReadyToCreateBadge] = useState(false) const [mintRule, setMintRule] = useState('by_key') const [resolvedMinterAddress, setResolvedMinterAddress] = useState('') + const [tempBadge, setTempBadge] = useState() const [badgeId, setBadgeId] = useState(null) const [imageUrl, setImageUrl] = useState(null) @@ -139,6 +142,37 @@ const BadgeCreationPage: NextPage = () => { void resolveMinterAddress() }, [designatedMinterState.value]) + useEffect(() => { + const badge = { + manager: badgeDetails?.manager as string, + metadata: { + name: badgeDetails?.name || undefined, + description: badgeDetails?.description || undefined, + image: imageUrl || 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, + rule: + mintRule === 'by_key' + ? { + by_key: keyState.value, + } + : mintRule === 'by_minter' + ? { + by_minter: resolvedMinterAddress, + } + : 'by_keys', + expiry: badgeDetails?.expiry || undefined, + max_supply: badgeDetails?.max_supply || undefined, + } + setTempBadge(badge) + }, [badgeDetails, keyState.value, mintRule, resolvedMinterAddress, imageUrl]) + const createNewBadge = async () => { try { if (!wallet.initialized) throw new Error('Wallet not connected') @@ -672,6 +706,11 @@ const BadgeCreationPage: NextPage = () => {
Date: Mon, 6 Mar 2023 22:14:24 +0300 Subject: [PATCH 33/46] Add image preview for existing image URL --- components/badges/creation/BadgeDetails.tsx | 1 + components/badges/creation/ImageUploadDetails.tsx | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/components/badges/creation/BadgeDetails.tsx b/components/badges/creation/BadgeDetails.tsx index 46a25b3..7805dcc 100644 --- a/components/badges/creation/BadgeDetails.tsx +++ b/components/badges/creation/BadgeDetails.tsx @@ -278,6 +278,7 @@ export const BadgeDetails = ({ metadataSize, onChange }: BadgeDetailsProps) => { } } catch (error) { toast.error('Error retrieving metadata fee rate.') + setMetadataFeeRate(0) console.log('Error retrieving fee rate: ', error) } } diff --git a/components/badges/creation/ImageUploadDetails.tsx b/components/badges/creation/ImageUploadDetails.tsx index 0bd58b4..397f541 100644 --- a/components/badges/creation/ImageUploadDetails.tsx +++ b/components/badges/creation/ImageUploadDetails.tsx @@ -172,7 +172,7 @@ export const ImageUploadDetails = ({ onChange, mintRule }: ImageUploadDetailsPro
-
+

@@ -187,8 +187,17 @@ export const ImageUploadDetails = ({ onChange, mintRule }: ImageUploadDetailsPro {' '} and upload your image manually to get an image URL for your badge.

-
- +
+ + +
+ badge-preview +
+
From 278c9897032d25d1403c528660ccdff0b5e50532 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Mon, 6 Mar 2023 22:29:08 +0300 Subject: [PATCH 34/46] Update My Badges layout & add placeholders --- .env.example | 6 +++--- pages/badges/myBadges.tsx | 12 +++++++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.env.example b/.env.example index e98f503..29bdb31 100644 --- a/.env.example +++ b/.env.example @@ -1,9 +1,9 @@ APP_VERSION=0.4.7 NEXT_PUBLIC_PINATA_ENDPOINT_URL=https://api.pinata.cloud/pinning/pinFileToIPFS -NEXT_PUBLIC_SG721_CODE_ID=793 -NEXT_PUBLIC_VENDING_MINTER_CODE_ID=275 -NEXT_PUBLIC_VENDING_FACTORY_ADDRESS="stars1s48apjumprma0d64ge88dmu7lr8exu02tlw90xxupgds0s25gfasx447dn" +NEXT_PUBLIC_SG721_CODE_ID=1702 +NEXT_PUBLIC_VENDING_MINTER_CODE_ID=1701 +NEXT_PUBLIC_VENDING_FACTORY_ADDRESS="stars1xz4d6wzxqn3udgsm5qnr78y032xng4r2ycv7aw6mjtsuw59s2n9s93ec0v" NEXT_PUBLIC_BASE_FACTORY_ADDRESS="stars1c6juqgd7cm80afpmuszun66rl9zdc4kgfht8fk34tfq3zk87l78sdxngzv" NEXT_PUBLIC_SG721_NAME_ADDRESS="stars1fx74nkqkw2748av8j7ew7r3xt9cgjqduwn8m0ur5lhe49uhlsasszc5fhr" NEXT_PUBLIC_BASE_MINTER_CODE_ID=613 diff --git a/pages/badges/myBadges.tsx b/pages/badges/myBadges.tsx index 93bf831..7f388a1 100644 --- a/pages/badges/myBadges.tsx +++ b/pages/badges/myBadges.tsx @@ -49,12 +49,12 @@ const BadgeList: NextPage = () => { {myBadges.map((badge: any, index: any) => { return ( - +
Cover {
-

{badge.name}

+

+ {badge.name ? badge.name : 'No name provided.'} +

Badge ID: {badge.tokenId}

- - {badge.description} + + {badge.description ? badge.description : 'No description provided.'} {/*
*/} {/* */} From cf7f00f2eeecd1595427411665c561a8903d7631 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Tue, 7 Mar 2023 10:33:59 +0300 Subject: [PATCH 35/46] Add Tooltips for Mint Rule selection --- components/Tooltip.tsx | 3 +- pages/badges/create.tsx | 67 ++++++++++++++++++++++++----------------- 2 files changed, 41 insertions(+), 29 deletions(-) diff --git a/components/Tooltip.tsx b/components/Tooltip.tsx index fa43855..2b2e552 100644 --- a/components/Tooltip.tsx +++ b/components/Tooltip.tsx @@ -6,6 +6,7 @@ import { usePopper } from 'react-popper' export interface TooltipProps extends ComponentProps<'div'> { label: ReactNode children: ReactElement + placement?: 'top' | 'bottom' | 'left' | 'right' } export const Tooltip = ({ label, children, ...props }: TooltipProps) => { @@ -14,7 +15,7 @@ export const Tooltip = ({ label, children, ...props }: TooltipProps) => { const [show, setShow] = useState(false) const { styles, attributes } = usePopper(referenceElement, popperElement, { - placement: 'top', + placement: props.placement ? props.placement : 'top', }) return ( diff --git a/pages/badges/create.tsx b/pages/badges/create.tsx index 3763984..e92747f 100644 --- a/pages/badges/create.tsx +++ b/pages/badges/create.tsx @@ -603,20 +603,25 @@ const BadgeCreationPage: NextPage = () => { mintRule !== 'by_key' ? 'bg-stargaze/5 hover:bg-stargaze/80' : 'hover:bg-white/5', )} > - + +
{ mintRule !== 'by_keys' ? 'bg-stargaze/5 hover:bg-stargaze/80' : 'hover:bg-white/5', )} > - + +
{ type="button" >

Mint Rule: By Minter

- - Badges can be minted by a designated minter account. + + No key designation. Multiple badges can be minted to different addresses by a pre-determined minter + address.
From 43ae5eb03c3fe444550424ef8b370d7aabc7928a Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Tue, 7 Mar 2023 11:02:56 +0300 Subject: [PATCH 36/46] Improved responsiveness to changes in screen resolution --- components/forms/MetadataAttributes.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/forms/MetadataAttributes.tsx b/components/forms/MetadataAttributes.tsx index acfac6e..d911d23 100644 --- a/components/forms/MetadataAttributes.tsx +++ b/components/forms/MetadataAttributes.tsx @@ -73,9 +73,10 @@ export function MetadataAttribute({ id, isLast, onAdd, onChange, onRemove, defau }, [traitTypeState.value, traitValueState.value, id]) return ( -
+
+
+ +
From cf07cc3cf5828128a48676ac57a7d6d707cfb3d1 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Tue, 7 Mar 2023 12:27:46 +0300 Subject: [PATCH 40/46] Add fee estimation warning --- components/Tooltip.tsx | 7 ++++++- components/badges/creation/BadgeDetails.tsx | 16 ++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/components/Tooltip.tsx b/components/Tooltip.tsx index 5e6b874..b243e18 100644 --- a/components/Tooltip.tsx +++ b/components/Tooltip.tsx @@ -7,6 +7,7 @@ export interface TooltipProps extends ComponentProps<'div'> { label: ReactNode children: ReactElement placement?: 'top' | 'bottom' | 'left' | 'right' + backgroundColor?: string } export const Tooltip = ({ label, children, ...props }: TooltipProps) => { @@ -33,7 +34,11 @@ export const Tooltip = ({ label, children, ...props }: TooltipProps) => {
diff --git a/components/badges/creation/BadgeDetails.tsx b/components/badges/creation/BadgeDetails.tsx index 7805dcc..0fac8b4 100644 --- a/components/badges/creation/BadgeDetails.tsx +++ b/components/badges/creation/BadgeDetails.tsx @@ -19,6 +19,7 @@ import { BADGE_HUB_ADDRESS } from 'utils/constants' import { AddressInput, NumberInput, TextInput } from '../../forms/FormInput' import { MetadataAttributes } from '../../forms/MetadataAttributes' +import { Tooltip } from '../../Tooltip' import type { MintRule, UploadMethod } from './ImageUploadDetails' interface BadgeDetailsProps { @@ -311,10 +312,17 @@ export const BadgeDetails = ({ metadataSize, onChange }: BadgeDetailsProps) => {
-
-
Fee Estimate:
- {(metadataSize * Number(metadataFeeRate)) / 1000000} stars -
+ +
+
Fee Estimate:
+ {(metadataSize * Number(metadataFeeRate)) / 1000000} stars +
+
From cf4378bbadcfb2f70eea6ec225fb3628e220a1f6 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Tue, 7 Mar 2023 12:52:20 +0300 Subject: [PATCH 41/46] Clean up parseMetadata() --- components/badges/creation/BadgeDetails.tsx | 42 ++++----------------- 1 file changed, 7 insertions(+), 35 deletions(-) diff --git a/components/badges/creation/BadgeDetails.tsx b/components/badges/creation/BadgeDetails.tsx index 0fac8b4..58b02e1 100644 --- a/components/badges/creation/BadgeDetails.tsx +++ b/components/badges/creation/BadgeDetails.tsx @@ -139,41 +139,13 @@ export const BadgeDetails = ({ metadataSize, onChange }: BadgeDetailsProps) => { }) } } - if (!parsedMetadata.name) { - nameState.onChange('') - } else { - nameState.onChange(parsedMetadata.name) - } - if (!parsedMetadata.description) { - descriptionState.onChange('') - } else { - descriptionState.onChange(parsedMetadata.description) - } - if (!parsedMetadata.external_url) { - externalUrlState.onChange('') - } else { - externalUrlState.onChange(parsedMetadata.external_url) - } - if (!parsedMetadata.youtube_url) { - youtubeUrlState.onChange('') - } else { - youtubeUrlState.onChange(parsedMetadata.youtube_url) - } - if (!parsedMetadata.animation_url) { - animationUrlState.onChange('') - } else { - animationUrlState.onChange(parsedMetadata.animation_url) - } - if (!parsedMetadata.background_color) { - backgroundColorState.onChange('') - } else { - backgroundColorState.onChange(parsedMetadata.background_color) - } - if (!parsedMetadata.image_data) { - imageDataState.onChange('') - } else { - imageDataState.onChange(parsedMetadata.image_data) - } + nameState.onChange(parsedMetadata.name) + descriptionState.onChange(parsedMetadata.description) + externalUrlState.onChange(parsedMetadata.external_url) + youtubeUrlState.onChange(parsedMetadata.youtube_url) + animationUrlState.onChange(parsedMetadata.animation_url) + backgroundColorState.onChange(parsedMetadata.background_color) + imageDataState.onChange(parsedMetadata.image_data) } else { attributesState.reset() nameState.onChange('') From 8e0f1d3b518d67325f0e19a4fb8cd827dad0e78d Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Tue, 7 Mar 2023 14:59:54 +0300 Subject: [PATCH 42/46] Edit tooltip label for Mint Rule: By Minter --- pages/badges/create.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/badges/create.tsx b/pages/badges/create.tsx index 0bf4532..b089dc6 100644 --- a/pages/badges/create.tsx +++ b/pages/badges/create.tsx @@ -660,7 +660,7 @@ const BadgeCreationPage: NextPage = () => { )} >
-
- -
- +
+ +
+ > + +
-
+
From c224654c4c4494cf94417a0679cce1f816e15c9a Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Tue, 7 Mar 2023 16:13:08 +0300 Subject: [PATCH 45/46] Tooltip color update --- pages/badges/create.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pages/badges/create.tsx b/pages/badges/create.tsx index b089dc6..314f634 100644 --- a/pages/badges/create.tsx +++ b/pages/badges/create.tsx @@ -604,6 +604,7 @@ const BadgeCreationPage: NextPage = () => { )} > @@ -632,6 +633,7 @@ const BadgeCreationPage: NextPage = () => { )} > @@ -660,6 +662,7 @@ const BadgeCreationPage: NextPage = () => { )} > From 065d7b3e822fe593f64f8587cbab1d92c446bbea Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Tue, 7 Mar 2023 17:53:05 +0300 Subject: [PATCH 46/46] Update badge creation tooltips --- pages/badges/create.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pages/badges/create.tsx b/pages/badges/create.tsx index 314f634..8abd492 100644 --- a/pages/badges/create.tsx +++ b/pages/badges/create.tsx @@ -605,7 +605,8 @@ const BadgeCreationPage: NextPage = () => { >