diff --git a/components/badges/actions/Action.tsx b/components/badges/actions/Action.tsx index 1848f4c..0921f8e 100644 --- a/components/badges/actions/Action.tsx +++ b/components/badges/actions/Action.tsx @@ -1,4 +1,5 @@ // import { AirdropUpload } from 'components/AirdropUpload' +import { toUtf8 } from '@cosmjs/encoding' import type { DispatchExecuteArgs } from 'components/badges/actions/actions' import { dispatchExecute, isEitherType, previewExecutePayload } from 'components/badges/actions/actions' import { ActionsCombobox } from 'components/badges/actions/Combobox' @@ -18,6 +19,7 @@ import { useWallet } from 'contexts/wallet' import type { Badge, BadgeHubInstance } from 'contracts/badgeHub' import * as crypto from 'crypto' import { toPng } from 'html-to-image' +import sizeof from 'object-sizeof' import { QRCodeCanvas } from 'qrcode.react' import type { FormEvent } from 'react' import { useEffect, useRef, useState } from 'react' @@ -51,6 +53,8 @@ export const BadgeActions = ({ badgeHubContractAddress, badgeId, badgeHubMessage const [badge, setBadge] = useState() const [transferrable, setTransferrable] = useState(undefined) const [resolvedOwnerAddress, setResolvedOwnerAddress] = useState('') + const [editFee, setEditFee] = useState(undefined) + const [dispatch, setDispatch] = useState(false) const [createdBadgeId, setCreatedBadgeId] = useState(undefined) const [createdBadgeKey, setCreatedBadgeKey] = useState(undefined) @@ -229,6 +233,7 @@ export const BadgeActions = ({ badgeHubContractAddress, badgeId, badgeHubMessage youtube_url: youtubeUrlState.value || undefined, }, id: badgeId, + editFee, owner: resolvedOwnerAddress, pubkey: pubkeyState.value, signature: signatureState.value, @@ -241,6 +246,7 @@ export const BadgeActions = ({ badgeHubContractAddress, badgeId, badgeHubMessage txSigner: wallet.address, type, } + const resolveOwnerAddress = async () => { await resolveAddress(ownerState.value.trim(), wallet).then((resolvedAddress) => { setResolvedOwnerAddress(resolvedAddress) @@ -333,6 +339,20 @@ export const BadgeActions = ({ badgeHubContractAddress, badgeId, badgeHubMessage maxSupplyState.value, ]) + useEffect(() => { + if (attributesState.values.length === 0) + attributesState.add({ + trait_type: '', + value: '', + }) + }, []) + + useEffect(() => { + void dispatchEditBadge().catch((err) => { + toast.error(String(err), { style: { maxWidth: 'none' } }) + }) + }, [dispatch]) + useEffect(() => { const addresses: string[] = [] airdropAllocationArray.forEach((allocation) => { @@ -358,53 +378,56 @@ export const BadgeActions = ({ badgeHubContractAddress, badgeId, badgeHubMessage throw new Error('Please enter the Badge Hub contract addresses.') } - // if (wallet.client && type === 'update_mint_price') { - // const contractConfig = wallet.client.queryContractSmart(minterContractAddress, { - // config: {}, - // }) - // await toast - // .promise( - // wallet.client.queryContractSmart(minterContractAddress, { - // mint_price: {}, - // }), - // { - // error: `Querying mint price failed!`, - // loading: 'Querying current mint price...', - // success: (price) => { - // console.log('Current mint price: ', price) - // return `Current mint price is ${Number(price.public_price.amount) / 1000000} STARS` - // }, - // }, - // ) - // .then(async (price) => { - // if (Number(price.public_price.amount) / 1000000 <= priceState.value) { - // await contractConfig - // .then((config) => { - // console.log(config.start_time, Date.now() * 1000000) - // if (Number(config.start_time) < Date.now() * 1000000) { - // throw new Error( - // `Minting has already started on ${new Date( - // Number(config.start_time) / 1000000, - // ).toLocaleString()}. Updated mint price cannot be higher than the current price of ${ - // Number(price.public_price.amount) / 1000000 - // } STARS`, - // ) - // } - // }) - // .catch((error) => { - // throw new Error(String(error).substring(String(error).lastIndexOf('Error:') + 7)) - // }) - // } - // }) - // } + if (wallet.client && type === 'edit_badge') { + const feeRateRaw = await wallet.client.queryContractRaw( + badgeHubContractAddress, + toUtf8(Buffer.from(Buffer.from('fee_rate').toString('hex'), 'hex').toString()), + ) + const feeRate = JSON.parse(new TextDecoder().decode(feeRateRaw as Uint8Array)) - const txHash = await toast.promise(dispatchExecute(payload), { - error: `${type.charAt(0).toUpperCase() + type.slice(1)} execute failed!`, - loading: 'Executing message...', - success: (tx) => `Transaction ${tx} success!`, - }) - if (txHash) { - setLastTx(txHash) + await toast + .promise( + wallet.client.queryContractSmart(badgeHubContractAddress, { + badge: { id: badgeId }, + }), + { + error: `Edit Fee calculation failed!`, + loading: 'Calculating Edit Fee...', + success: (currentBadge) => { + console.log('Current badge: ', currentBadge) + return `Current metadata is ${ + Number(sizeof(currentBadge.metadata)) + Number(sizeof(currentBadge.metadata.attributes)) + } bytes in size.` + }, + }, + ) + .then((currentBadge) => { + const currentBadgeMetadataSize = + Number(sizeof(currentBadge.metadata)) + Number(sizeof(currentBadge.metadata.attributes)) + console.log('Current badge metadata size: ', currentBadgeMetadataSize) + const newBadgeMetadataSize = Number(sizeof(badge?.metadata)) + Number(sizeof(badge?.metadata.attributes)) + console.log('New badge metadata size: ', newBadgeMetadataSize) + if (newBadgeMetadataSize > currentBadgeMetadataSize) { + const calculatedFee = ((newBadgeMetadataSize - currentBadgeMetadataSize) * Number(feeRate.metadata)) / 2 + setEditFee(calculatedFee) + setDispatch(!dispatch) + } else { + setEditFee(undefined) + setDispatch(!dispatch) + } + }) + .catch((error) => { + throw new Error(String(error).substring(String(error).lastIndexOf('Error:') + 7)) + }) + } else { + const txHash = await toast.promise(dispatchExecute(payload), { + error: `${type.charAt(0).toUpperCase() + type.slice(1)} execute failed!`, + loading: 'Executing message...', + success: (tx) => `Transaction ${tx} success!`, + }) + if (txHash) { + setLastTx(txHash) + } } }, { @@ -414,6 +437,19 @@ export const BadgeActions = ({ badgeHubContractAddress, badgeId, badgeHubMessage }, ) + const dispatchEditBadge = async () => { + if (type) { + const txHash = await toast.promise(dispatchExecute(payload), { + error: `${type.charAt(0).toUpperCase() + type.slice(1)} execute failed!`, + loading: 'Executing message...', + success: (tx) => `Transaction ${tx} success!`, + }) + if (txHash) { + setLastTx(txHash) + } + } + } + const airdropFileOnChange = (data: AirdropAllocation[]) => { setAirdropAllocationArray(data) } diff --git a/components/badges/actions/actions.ts b/components/badges/actions/actions.ts index 9ad81b8..d2d78f8 100644 --- a/components/badges/actions/actions.ts +++ b/components/badges/actions/actions.ts @@ -100,7 +100,7 @@ export type DispatchExecuteArgs = { } & ( | { type: undefined } | { type: Select<'create_badge'>; badge: Badge } - | { type: Select<'edit_badge'>; id: number; metadata: Metadata } + | { type: Select<'edit_badge'>; id: number; metadata: Metadata; editFee?: number } | { type: Select<'add_keys'>; id: number; keys: string[] } | { type: Select<'purge_keys'>; id: number; limit?: number } | { type: Select<'purge_owners'>; id: number; limit?: number } @@ -120,7 +120,7 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => { return badgeHubMessages.createBadge(txSigner, args.badge) } case 'edit_badge': { - return badgeHubMessages.editBadge(txSigner, args.id, args.metadata) + return badgeHubMessages.editBadge(txSigner, args.id, args.metadata, args.editFee) } case 'add_keys': { return badgeHubMessages.addKeys(txSigner, args.id, args.keys) diff --git a/components/badges/creation/ImageUploadDetails.tsx b/components/badges/creation/ImageUploadDetails.tsx index 487bdd2..fb8d725 100644 --- a/components/badges/creation/ImageUploadDetails.tsx +++ b/components/badges/creation/ImageUploadDetails.tsx @@ -15,7 +15,7 @@ import { toast } from 'react-hot-toast' import type { UploadServiceType } from 'services/upload' export type UploadMethod = 'new' | 'existing' -export type MintRule = 'by_key' | 'by_minter' | 'by_keys' +export type MintRule = 'by_key' | 'by_minter' | 'by_keys' | 'not_resolved' interface ImageUploadDetailsProps { onChange: (value: ImageUploadDetailsDataProps) => void diff --git a/contracts/badgeHub/contract.ts b/contracts/badgeHub/contract.ts index cd840fb..aab992f 100644 --- a/contracts/badgeHub/contract.ts +++ b/contracts/badgeHub/contract.ts @@ -67,7 +67,7 @@ export interface BadgeHubInstance { //Execute createBadge: (senderAddress: string, badge: Badge) => Promise - editBadge: (senderAddress: string, id: number, metadata: Metadata) => Promise + editBadge: (senderAddress: string, id: number, metadata: Metadata, editFee?: number) => Promise addKeys: (senderAddress: string, id: number, keys: string[]) => Promise purgeKeys: (senderAddress: string, id: number, limit?: number) => Promise purgeOwners: (senderAddress: string, id: number, limit?: number) => Promise @@ -79,7 +79,7 @@ export interface BadgeHubInstance { export interface BadgeHubMessages { createBadge: (badge: Badge) => CreateBadgeMessage - editBadge: (id: number, metadata: Metadata) => EditBadgeMessage + editBadge: (id: number, metadata: Metadata, editFee?: number) => EditBadgeMessage addKeys: (id: number, keys: string[]) => AddKeysMessage purgeKeys: (id: number, limit?: number) => PurgeKeysMessage purgeOwners: (id: number, limit?: number) => PurgeOwnersMessage @@ -314,7 +314,12 @@ export const badgeHub = (client: SigningCosmWasmClient, txSigner: string): Badge return res.transactionHash.concat(`:${id}`) } - const editBadge = async (senderAddress: string, id: number, metadata: Metadata): Promise => { + const editBadge = async ( + senderAddress: string, + id: number, + metadata: Metadata, + editFee?: number, + ): Promise => { const res = await client.execute( senderAddress, contractAddress, @@ -326,6 +331,7 @@ export const badgeHub = (client: SigningCosmWasmClient, txSigner: string): Badge }, 'auto', '', + editFee ? [coin(editFee, 'ustars')] : [], ) return res.transactionHash @@ -524,7 +530,7 @@ export const badgeHub = (client: SigningCosmWasmClient, txSigner: string): Badge } } - const editBadge = (id: number, metadata: Metadata): EditBadgeMessage => { + const editBadge = (id: number, metadata: Metadata, editFee?: number): EditBadgeMessage => { return { sender: txSigner, contract: contractAddress, @@ -534,7 +540,7 @@ export const badgeHub = (client: SigningCosmWasmClient, txSigner: string): Badge metadata, }, }, - funds: [], + funds: editFee ? [coin(editFee, 'ustars')] : [], } } diff --git a/pages/badges/actions.tsx b/pages/badges/actions.tsx index 03a6b34..34c7749 100644 --- a/pages/badges/actions.tsx +++ b/pages/badges/actions.tsx @@ -113,7 +113,7 @@ const BadgeActionsPage: NextPage = () => { }) .catch((err) => { console.log(err) - setMintRule('by_key') + setMintRule('not_resolved') console.log('Unable to retrieve Mint Rule. Defaulting to "by_key".') }) }, [debouncedBadgeHubContractState, debouncedBadgeIdState, wallet.client]) @@ -132,14 +132,16 @@ const BadgeActionsPage: NextPage = () => {
- Mint Rule: - - {mintRule - .toString() - .split('_') - .map((s) => s.charAt(0).toUpperCase() + s.substring(1)) - .join(' ')} - +
+ Mint Rule: + + {mintRule + .toString() + .split('_') + .map((s) => s.charAt(0).toUpperCase() + s.substring(1)) + .join(' ')} + +