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 +}