diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.ts b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.ts index 80c02dd8cfa..688e26b84c7 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.ts +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.ts @@ -175,7 +175,7 @@ function toDate(timestamp: number | Date | undefined) { } return new Date(timestamp); } -function toBigInt(value: string | number | undefined) { +export function toBigInt(value: string | number | undefined) { if (value === undefined) { return undefined; } diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/index.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/index.tsx index 791fc990776..23421cc6508 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/index.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/index.tsx @@ -26,10 +26,12 @@ import { useFieldArray, useForm, } from "react-hook-form"; +import { toast } from "sonner"; import { NATIVE_TOKEN_ADDRESS, type ThirdwebContract, ZERO_ADDRESS, + toUnits, } from "thirdweb"; import { decimals } from "thirdweb/extensions/erc20"; import { @@ -48,7 +50,11 @@ import { } from "../legacy-zod-schema"; import { ResetClaimEligibility } from "../reset-claim-eligibility"; import { SnapshotUpload } from "../snapshot-upload"; -import { getClaimPhasesInLegacyFormat, setClaimPhasesTx } from "./hooks"; +import { + getClaimPhasesInLegacyFormat, + setClaimPhasesTx, + toBigInt, +} from "./hooks"; import { ClaimConditionsPhase } from "./phase"; type ClaimConditionDashboardInput = ClaimConditionInput & { @@ -355,6 +361,31 @@ export const ClaimConditionsForm: React.FC = ({ }); try { + if (isErc20 && !tokenDecimalsData) { + return toast.error("Could not fetch token metadata"); + } + + // For ERC20 claim condition, we need to convert `maxClaimableSupply` and `maxClaimbablePerWallet` to wei + const phases = isErc20 + ? d.phases.map((item) => { + item.maxClaimableSupply = + item.maxClaimableSupply !== undefined + ? toUnits( + String(toBigInt(item.maxClaimableSupply)).toString(), + tokenDecimalsData, + ).toString() + : undefined; + item.maxClaimablePerWallet = + item.maxClaimablePerWallet !== undefined + ? toUnits( + String(toBigInt(item.maxClaimablePerWallet)), + tokenDecimalsData, + ).toString() + : undefined; + return item; + }) + : d.phases; + const tx = setClaimPhasesTx( { contract, @@ -364,7 +395,7 @@ export const ClaimConditionsForm: React.FC = ({ ? { type: "erc721" } : { type: "erc1155", tokenId: BigInt(tokenId || 0) }), }, - d.phases, + phases, ); await sendTx.mutateAsync(tx); trackEvent({ @@ -630,7 +661,10 @@ export const ClaimConditionsForm: React.FC = ({ colorScheme="primary" txChainID={contract.chain.id} transactionCount={1} - isDisabled={claimConditionsQuery.isPending} + isDisabled={ + claimConditionsQuery.isPending || + (isErc20 && tokenDecimals.isPending) + } type="submit" isLoading={sendTx.isPending} loadingText="Saving..." diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/phase.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/phase.tsx index 7f08a7953a3..d8c9dd87259 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/phase.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/phase.tsx @@ -1,7 +1,8 @@ import { AdminOnly } from "@3rdweb-sdk/react/components/roles/admin-only"; import { Flex, SimpleGrid } from "@chakra-ui/react"; import { ChevronDownIcon, ChevronUpIcon, XIcon } from "lucide-react"; -import type { ThirdwebContract } from "thirdweb"; +import { type ThirdwebContract, toTokens } from "thirdweb"; +import { maxUint256 } from "thirdweb/utils"; import { Badge, Button, Card, Heading, Text } from "tw-components"; import { ClaimConditionTypeData, useClaimConditionsFormContext } from "."; import { PricePreview } from "../price-preview"; @@ -34,6 +35,7 @@ export const ClaimConditionsPhase: React.FC = ({ isActive, isMultiPhase, phaseIndex, + tokenDecimals, } = useClaimConditionsFormContext(); const toggleEditing = () => { @@ -102,7 +104,18 @@ export const ClaimConditionsPhase: React.FC = ({ {isErc20 ? "Tokens" : "NFTs"} to drop - {field.maxClaimableSupply} + + {field.maxClaimableSupply === "unlimited" + ? "Unlimited" + : isErc20 && field.maxClaimableSupply + ? BigInt(field.maxClaimableSupply) === maxUint256 + ? "Unlimited" + : toTokens( + BigInt(field.maxClaimableSupply), + tokenDecimals, + ) + : field.maxClaimableSupply} + = ({ Unlimited ) : ( - {field.maxClaimablePerWallet} + {field.maxClaimablePerWallet === "unlimited" + ? "Unlimited" + : isErc20 && field.maxClaimablePerWallet + ? BigInt(field.maxClaimablePerWallet) === maxUint256 + ? "Unlimited" + : toTokens( + BigInt(field.maxClaimablePerWallet), + tokenDecimals, + ) + : field.maxClaimablePerWallet} )} diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/quantity-input-with-unlimited.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/quantity-input-with-unlimited.tsx index 96888dc6966..7b14d13032f 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/quantity-input-with-unlimited.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/quantity-input-with-unlimited.tsx @@ -5,6 +5,7 @@ import { InputRightElement, } from "@chakra-ui/react"; import { useEffect, useState } from "react"; +import { toTokens } from "thirdweb"; import { Button } from "tw-components"; interface QuantityInputWithUnlimitedProps @@ -60,7 +61,13 @@ export const QuantityInputWithUnlimited: React.FC< if (value === "unlimited") { setStringValue("unlimited"); } else if (!Number.isNaN(Number(value))) { - setStringValue(Number(Number(value).toFixed(decimals)).toString()); + if (decimals) { + setStringValue(toTokens(BigInt(value), decimals)); + } else { + setStringValue( + Number(Number(value).toFixed(decimals)).toString(), + ); + } } else { setStringValue("0"); }