Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ export const MaxClaimablePerWalletInput: React.FC = () => {
<QuantityInputWithUnlimited
isRequired
decimals={tokenDecimals}
isDisabled={dropType === "specific" || formDisabled}
isDisabled={
dropType === "specific" || formDisabled || (isErc20 && !tokenDecimals)
}
value={field?.maxClaimablePerWallet?.toString() || "0"}
onChange={(value) =>
form.setValue(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const MaxClaimableSupplyInput: React.FC = () => {
>
<QuantityInputWithUnlimited
isRequired
isDisabled={formDisabled}
isDisabled={formDisabled || (isErc20 && !tokenDecimals)}
decimals={tokenDecimals}
value={field.maxClaimableSupply?.toString() || "0"}
onChange={(value) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
type BaseTransactionOptions,
type ThirdwebClient,
toTokens,
toUnits,
} from "thirdweb";
import type { OverrideEntry } from "thirdweb/dist/types/utils/extensions/drops/types";
import type { Prettify } from "thirdweb/dist/types/utils/type-utils";
Expand Down Expand Up @@ -42,6 +43,7 @@ type CombinedClaimCondition = Prettify<
type Options =
| {
type: "erc20";
decimals?: number;
}
| {
type: "erc721";
Expand Down Expand Up @@ -91,12 +93,24 @@ export async function getClaimPhasesInLegacyFormat(
startTime: new Date(Number(condition.startTimestamp * 1000n)),
currencyAddress: condition.currency,
price: condition.pricePerToken,
maxClaimableSupply: toUnlimited(condition.maxClaimableSupply),
maxClaimableSupply:
options.type === "erc20"
? convertERC20ValueToDisplayValue(
condition.maxClaimableSupply,
options.decimals,
)
: toUnlimited(condition.maxClaimableSupply),
currencyMetadata,
currentMintSupply: (
condition.maxClaimableSupply - condition.supplyClaimed
).toString(),
maxClaimablePerWallet: toUnlimited(condition.quantityLimitPerWallet),
maxClaimablePerWallet:
options.type === "erc20"
? convertERC20ValueToDisplayValue(
condition.quantityLimitPerWallet,
options.decimals,
)
: toUnlimited(condition.quantityLimitPerWallet),
merkleRootHash: condition.merkleRoot,
metadata,
snapshot,
Expand All @@ -114,8 +128,20 @@ export function setClaimPhasesTx(
const phases = rawPhases.map((phase) => {
return {
startTime: toDate(phase.startTime),
maxClaimableSupply: toBigInt(phase.maxClaimableSupply),
maxClaimablePerWallet: toBigInt(phase.maxClaimablePerWallet),
maxClaimableSupply:
baseOptions.type === "erc20"
? convertERC20ValueToWei(
phase.maxClaimableSupply,
baseOptions.decimals,
)
: toBigInt(phase.maxClaimableSupply),
maxClaimablePerWallet:
baseOptions.type === "erc20"
? convertERC20ValueToWei(
phase.maxClaimablePerWallet,
baseOptions.decimals,
)
: toBigInt(phase.maxClaimablePerWallet),
merkleRootHash: phase.merkleRootHash as string | undefined,
overrideList: phase.snapshot?.length
? snapshotToOverrides(phase.snapshot)
Expand Down Expand Up @@ -175,18 +201,56 @@ function toDate(timestamp: number | Date | undefined) {
}
return new Date(timestamp);
}
function toBigInt(value: string | number | undefined) {
function toBigInt(value: string | number | undefined): bigint | undefined {
if (value === undefined) {
return undefined;
}
if (value === "unlimited") {
return maxUint256;
}
}

// The input from client-side is non-wei, but the extension is expecting value in wei
// so we need to convert it using toUnits
function convertERC20ValueToWei(
value: string | number | undefined,
decimals?: number,
) {
if (value === undefined) {
return undefined;
}
if (value === "unlimited") {
return maxUint256;
}
// The ERC20Claim condition extension in v5 does not convert to wei for us
// so we have to, manually
if (decimals) {
return toUnits(value.toString(), decimals);
}
return BigInt(value);
}

// This value we get from ERC20Ext.getClaimConditions is in wei
// so we have to convert it using toTokens for readability, and for users to update
// (when user updates this value, we convert it back to wei - see `function setClaimPhasesTx`)
function convertERC20ValueToDisplayValue(
value: bigint,
decimals?: number,
): string {
if (value === maxUint256) {
return "unlimited";
}
if (decimals) {
return toTokens(value, decimals);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do you need this here? unlimited should be the same no matter the decimals no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is similar to toBigInt. While toBigInt converts the value to send it to the blockchain, toUnlimited converts the value from the blockchain to display it on the UI.

Without this new logic, a maxClaimableSupply of 100 tokens will be displayed on the frontend as: 100000000000000000000n

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the code pattern to make it similar to toBigInt

}
return value.toString();
}

function toUnlimited(value: bigint) {
return value === maxUint256 ? "unlimited" : value.toString();
if (value === maxUint256) {
return "unlimited";
}
return value.toString();
}

async function fetchSnapshot(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
useFieldArray,
useForm,
} from "react-hook-form";
import { toast } from "sonner";
import {
NATIVE_TOKEN_ADDRESS,
type ThirdwebContract,
Expand Down Expand Up @@ -152,7 +153,7 @@ interface ClaimsConditionFormContextData {
field: ControlledField;
phaseIndex: number;
formDisabled: boolean;
tokenDecimals: number;
tokenDecimals: number | undefined;
isMultiPhase: boolean;
isActive: boolean;
dropType: DropType;
Expand Down Expand Up @@ -210,7 +211,6 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
enabled: isErc20,
},
});
const tokenDecimalsData = tokenDecimals.data ?? 0;
const saveClaimPhaseNotification = useTxNotifications(
"Saved claim phases",
"Failed to save claim phases",
Expand All @@ -219,7 +219,7 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
const claimConditionsQuery = useReadContract(getClaimPhasesInLegacyFormat, {
contract,
...(isErc20
? { type: "erc20" }
? { type: "erc20", decimals: tokenDecimals.data }
: isErc721
? { type: "erc721" }
: { type: "erc1155", tokenId: BigInt(tokenId || 0) }),
Expand Down Expand Up @@ -259,7 +259,11 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
);
}, [claimConditionsQuery.data, isMultiPhase]);

const isFetchingData = claimConditionsQuery.isFetching || sendTx.isPending;
const isFetchingData =
claimConditionsQuery.isFetching ||
sendTx.isPending ||
// Need to make sure the tokenDecimals.data is present when interacting with ERC20 claim conditions
(isErc20 && tokenDecimals.isLoading);

const canEditForm = isAdmin && !isFetchingData;

Expand Down Expand Up @@ -353,13 +357,17 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
action: "set-claim-conditions",
label: "attempt",
});

if (isErc20 && !tokenDecimals.data) {
return toast.error(
`Could not fetch token decimals for contract ${contract.address}`,
);
}
try {
const tx = setClaimPhasesTx(
{
contract,
...(isErc20
? { type: "erc20" }
? { type: "erc20", decimals: tokenDecimals.data }
: isErc721
? { type: "erc721" }
: { type: "erc1155", tokenId: BigInt(tokenId || 0) }),
Expand Down Expand Up @@ -453,6 +461,15 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
);
}

// Do not proceed if fails to load the tokenDecimals.data - for ERC20 drop contracts specifically
if (isErc20 && tokenDecimals.data === undefined) {
return (
<div className="flex h-[400px] w-full items-center justify-center rounded-lg border border-border">
Failed to load token decimals
</div>
);
}

return (
<>
<Flex onSubmit={handleFormSubmit} direction="column" as="form" gap={10}>
Expand Down Expand Up @@ -508,7 +525,7 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
phaseIndex: index,
formDisabled: !canEditForm,
isErc20,
tokenDecimals: tokenDecimalsData,
tokenDecimals: tokenDecimals.data,
dropType,
setOpenSnapshotIndex,
isAdmin,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const QuantityInputWithUnlimited: React.FC<
<InputGroup {...restInputProps}>
<Input
isRequired={isRequired}
isDisabled={decimals === undefined || isDisabled}
isDisabled={isDisabled}
value={stringValue === "unlimited" ? "Unlimited" : stringValue}
onChange={(e) => updateValue(e.currentTarget.value)}
onBlur={() => {
Expand All @@ -69,7 +69,7 @@ export const QuantityInputWithUnlimited: React.FC<
{hideMaxButton ? null : (
<InputRightElement w="auto">
<Button
isDisabled={decimals === undefined || isDisabled}
isDisabled={isDisabled}
colorScheme="primary"
variant="ghost"
size="sm"
Expand Down
Loading