remove claimconditions legacy (#4590)
## Problem solved
Short description of the bug fixed or feature added
<!-- start pr-codex -->
---
## PR-Codex overview
The focus of this PR is to refactor claim conditions components and improve error handling.
### Detailed summary
- Refactored components related to claim conditions
- Improved error handling in `errorParser.tsx`
- Updated transaction button component
- Enhanced ERC20 amount conversion logic
- Streamlined wallet confirmation logic
> The following files were skipped due to too many changes: `apps/dashboard/src/contract-ui/tabs/claim-conditions/components/snapshot-upload.tsx`, `apps/dashboard/src/contract-ui/tabs/claim-conditions/components/claim-conditions-form/hooks.ts`, `apps/dashboard/src/contract-ui/tabs/claim-conditions/components/claim-conditions-form/index.tsx`
> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`
<!-- end pr-codex -->
This commit is contained in:
@@ -35,7 +35,6 @@ interface TransactionButtonProps extends Omit<ButtonProps, "leftIcon"> {
|
||||
isLoading: boolean;
|
||||
isGasless?: boolean;
|
||||
upsellTestnet?: boolean;
|
||||
onChainSelect?: (chainId: number) => void;
|
||||
}
|
||||
|
||||
function useWalletRequiresExternalConfirmation() {
|
||||
@@ -55,7 +54,7 @@ export const TransactionButton: React.FC<TransactionButtonProps> = ({
|
||||
colorScheme,
|
||||
variant,
|
||||
isGasless,
|
||||
onChainSelect,
|
||||
|
||||
...restButtonProps
|
||||
}) => {
|
||||
const activeWallet = useActiveWallet();
|
||||
@@ -93,7 +92,6 @@ export const TransactionButton: React.FC<TransactionButtonProps> = ({
|
||||
>
|
||||
<PopoverTrigger>
|
||||
<ButtonComponent
|
||||
onChainSelect={onChainSelect}
|
||||
borderRadius="md"
|
||||
position="relative"
|
||||
role="group"
|
||||
|
||||
+1
-2
@@ -17,10 +17,9 @@ export const ClaimPriceInput = () => {
|
||||
field,
|
||||
isColumn,
|
||||
claimConditionType,
|
||||
isClaimPhaseV1,
|
||||
} = useClaimConditionsFormContext();
|
||||
|
||||
if (!isClaimPhaseV1 && claimConditionType === "creator") {
|
||||
if (claimConditionType === "creator") {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
+25
-40
@@ -18,7 +18,6 @@ export const ClaimerSelection = () => {
|
||||
field,
|
||||
dropType,
|
||||
isErc20,
|
||||
isClaimPhaseV1,
|
||||
setOpenSnapshotIndex: setOpenIndex,
|
||||
isAdmin,
|
||||
isColumn,
|
||||
@@ -31,14 +30,10 @@ export const ClaimerSelection = () => {
|
||||
if (val === "any") {
|
||||
form.setValue(`phases.${phaseIndex}.snapshot`, undefined);
|
||||
} else {
|
||||
if (val === "specific" && !isClaimPhaseV1) {
|
||||
if (val === "specific") {
|
||||
form.setValue(`phases.${phaseIndex}.maxClaimablePerWallet`, 0);
|
||||
}
|
||||
if (
|
||||
val === "overrides" &&
|
||||
!isClaimPhaseV1 &&
|
||||
field.maxClaimablePerWallet !== 1
|
||||
) {
|
||||
if (val === "overrides" && field.maxClaimablePerWallet !== 1) {
|
||||
form.setValue(`phases.${phaseIndex}.maxClaimablePerWallet`, 1);
|
||||
}
|
||||
form.setValue(`phases.${phaseIndex}.snapshot`, []);
|
||||
@@ -50,39 +45,31 @@ export const ClaimerSelection = () => {
|
||||
|
||||
const disabledSnapshotButton = isAdmin && formDisabled;
|
||||
|
||||
if (isClaimPhaseV1) {
|
||||
helperText =
|
||||
"Snapshot spots are one-time-use! Once a wallet has claimed the drop, it cannot claim again, even if it did not claim the entire amount assigned to it in the snapshot.";
|
||||
if (dropType === "specific") {
|
||||
helperText = (
|
||||
<>
|
||||
<b>Only</b> wallets on the <b>allowlist</b> can claim.
|
||||
</>
|
||||
);
|
||||
} else if (dropType === "any") {
|
||||
helperText = (
|
||||
<>
|
||||
<b>Anyone</b> can claim based on the rules defined in this phase.
|
||||
("Public Mint")
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
if (dropType === "specific") {
|
||||
helperText = (
|
||||
<>
|
||||
<b>Only</b> wallets on the <b>allowlist</b> can claim.
|
||||
</>
|
||||
);
|
||||
} else if (dropType === "any") {
|
||||
helperText = (
|
||||
<>
|
||||
<b>Anyone</b> can claim based on the rules defined in this phase.
|
||||
("Public Mint")
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
helperText = (
|
||||
<>
|
||||
<b>Anyone</b> can claim based on the rules defined in this phase.
|
||||
<br />
|
||||
<b>Wallets in the snapshot</b> can claim with special rules defined in
|
||||
the snapshot.
|
||||
</>
|
||||
);
|
||||
}
|
||||
helperText = (
|
||||
<>
|
||||
<b>Anyone</b> can claim based on the rules defined in this phase.
|
||||
<br />
|
||||
<b>Wallets in the snapshot</b> can claim with special rules defined in
|
||||
the snapshot.
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
!isClaimPhaseV1 &&
|
||||
(claimConditionType === "public" || claimConditionType === "creator")
|
||||
) {
|
||||
if (claimConditionType === "public" || claimConditionType === "creator") {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -113,9 +100,7 @@ export const ClaimerSelection = () => {
|
||||
onChange={handleClaimerChange}
|
||||
>
|
||||
<option value="any">Any wallet</option>
|
||||
{!isClaimPhaseV1 ? (
|
||||
<option value="overrides">Any wallet (with overrides)</option>
|
||||
) : null}
|
||||
<option value="overrides">Any wallet (with overrides)</option>
|
||||
<option value="specific">Only specific wallets</option>
|
||||
</Select>
|
||||
)}
|
||||
|
||||
+6
-15
@@ -10,7 +10,6 @@ export const MaxClaimablePerWalletInput: React.FC = () => {
|
||||
const {
|
||||
formDisabled,
|
||||
isErc20,
|
||||
isClaimPhaseV1,
|
||||
form,
|
||||
tokenDecimals,
|
||||
field,
|
||||
@@ -26,9 +25,7 @@ export const MaxClaimablePerWalletInput: React.FC = () => {
|
||||
return (
|
||||
<CustomFormControl
|
||||
disabled={formDisabled}
|
||||
label={`How many ${isErc20 ? "tokens" : "NFTs"} can be claimed per ${
|
||||
isClaimPhaseV1 ? "transaction" : "wallet"
|
||||
}?`}
|
||||
label={`How many ${isErc20 ? "tokens" : "NFTs"} can be claimed per wallet?`}
|
||||
error={
|
||||
form.getFieldState(
|
||||
`phases.${phaseIndex}.maxClaimablePerWallet`,
|
||||
@@ -37,14 +34,10 @@ export const MaxClaimablePerWalletInput: React.FC = () => {
|
||||
}
|
||||
helperText={
|
||||
<>
|
||||
{!isClaimPhaseV1 ? (
|
||||
<>
|
||||
This value applies for <strong>all</strong> wallets
|
||||
{claimConditionType !== "public"
|
||||
? ", and can be overridden for specific wallets in the snapshot. "
|
||||
: ". "}
|
||||
</>
|
||||
) : null}
|
||||
This value applies for <strong>all</strong> wallets
|
||||
{claimConditionType !== "public"
|
||||
? ", and can be overridden for specific wallets in the snapshot. "
|
||||
: ". "}
|
||||
Limits are set per wallets and not per user, sophisticated actors
|
||||
could get around wallet restrictions.{" "}
|
||||
<Link
|
||||
@@ -61,9 +54,7 @@ export const MaxClaimablePerWalletInput: React.FC = () => {
|
||||
<QuantityInputWithUnlimited
|
||||
isRequired
|
||||
decimals={tokenDecimals}
|
||||
isDisabled={
|
||||
(!isClaimPhaseV1 && dropType === "specific") || formDisabled
|
||||
}
|
||||
isDisabled={dropType === "specific" || formDisabled}
|
||||
value={field?.maxClaimablePerWallet?.toString() || "0"}
|
||||
onChange={(value) =>
|
||||
form.setValue(
|
||||
|
||||
+1
-2
@@ -14,10 +14,9 @@ export const MaxClaimableSupplyInput: React.FC = () => {
|
||||
tokenDecimals,
|
||||
field,
|
||||
claimConditionType,
|
||||
isClaimPhaseV1,
|
||||
} = useClaimConditionsFormContext();
|
||||
|
||||
if (!isClaimPhaseV1 && claimConditionType === "creator") {
|
||||
if (claimConditionType === "creator") {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
+1
-5
@@ -6,15 +6,11 @@ import { useClaimConditionsFormContext } from "../index";
|
||||
* Allows users to edit the name for the claim phase.
|
||||
*/
|
||||
export const PhaseNameInput: React.FC = () => {
|
||||
const { phaseIndex, formDisabled, isClaimPhaseV1, form, field } =
|
||||
const { phaseIndex, formDisabled, form, field } =
|
||||
useClaimConditionsFormContext();
|
||||
const inputPlaceholder = `Phase ${phaseIndex + 1}`;
|
||||
const inputValue = field.metadata?.name;
|
||||
|
||||
if (isClaimPhaseV1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<CustomFormControl
|
||||
disabled={formDisabled}
|
||||
|
||||
+237
@@ -0,0 +1,237 @@
|
||||
import type {
|
||||
ClaimCondition as LegacyClaimCondition,
|
||||
ClaimConditionInputSchema as LegacyClaimConditionInputSchema,
|
||||
} from "@thirdweb-dev/sdk";
|
||||
import {
|
||||
type BaseTransactionOptions,
|
||||
type ThirdwebClient,
|
||||
toTokens,
|
||||
} from "thirdweb";
|
||||
import type { OverrideEntry } from "thirdweb/dist/types/utils/extensions/drops/types";
|
||||
import type { Prettify } from "thirdweb/dist/types/utils/type-utils";
|
||||
import { getContractMetadata } from "thirdweb/extensions/common";
|
||||
import * as ERC20Ext from "thirdweb/extensions/erc20";
|
||||
import * as ERC721Ext from "thirdweb/extensions/erc721";
|
||||
import * as ERC1155Ext from "thirdweb/extensions/erc1155";
|
||||
import { download } from "thirdweb/storage";
|
||||
import { maxUint256 } from "thirdweb/utils";
|
||||
import type { z } from "zod";
|
||||
|
||||
type SnapshotEntry = {
|
||||
address: string;
|
||||
maxClaimable: string;
|
||||
price?: string | undefined;
|
||||
currencyAddress?: string | undefined;
|
||||
};
|
||||
|
||||
export type CombinedClaimCondition = Prettify<
|
||||
Omit<
|
||||
LegacyClaimCondition,
|
||||
"price" | "waitInSeconds" | "availableSupply" | "currencyMetadata"
|
||||
> & {
|
||||
price: bigint;
|
||||
currencyMetadata: Omit<
|
||||
LegacyClaimCondition["currencyMetadata"],
|
||||
"value"
|
||||
> & {
|
||||
value: bigint;
|
||||
};
|
||||
}
|
||||
>;
|
||||
|
||||
type Options =
|
||||
| {
|
||||
type: "erc20";
|
||||
}
|
||||
| {
|
||||
type: "erc721";
|
||||
}
|
||||
| {
|
||||
type: "erc1155";
|
||||
tokenId: bigint;
|
||||
};
|
||||
|
||||
export async function getClaimPhasesInLegacyFormat(
|
||||
options: BaseTransactionOptions<Options>,
|
||||
): Promise<CombinedClaimCondition[]> {
|
||||
const conditions = await (async () => {
|
||||
switch (options.type) {
|
||||
case "erc20":
|
||||
return ERC20Ext.getClaimConditions(options);
|
||||
case "erc721":
|
||||
return ERC721Ext.getClaimConditions(options);
|
||||
case "erc1155":
|
||||
return ERC1155Ext.getClaimConditions(options);
|
||||
}
|
||||
})();
|
||||
|
||||
return Promise.all(
|
||||
conditions.map(async (condition) => {
|
||||
const [currencyMetadata, metadata, contractMetadata] = await Promise.all([
|
||||
await ERC20Ext.getCurrencyMetadata({
|
||||
contract: { ...options.contract, address: condition.currency },
|
||||
}).then((m) => ({
|
||||
...m,
|
||||
displayValue: toTokens(condition.pricePerToken, m.decimals),
|
||||
value: condition.pricePerToken,
|
||||
})),
|
||||
download({
|
||||
uri: condition.metadata,
|
||||
client: options.contract.client,
|
||||
}).then((r) => r.json()),
|
||||
getContractMetadata(options),
|
||||
]);
|
||||
const snapshot = await fetchSnapshot(
|
||||
condition.merkleRoot,
|
||||
contractMetadata.merkle,
|
||||
options.contract.client,
|
||||
);
|
||||
return {
|
||||
...condition,
|
||||
startTime: new Date(Number(condition.startTimestamp * 1000n)),
|
||||
currencyAddress: condition.currency,
|
||||
price: condition.pricePerToken,
|
||||
maxClaimableSupply: toUnlimited(condition.maxClaimableSupply),
|
||||
currencyMetadata,
|
||||
currentMintSupply: (
|
||||
condition.maxClaimableSupply - condition.supplyClaimed
|
||||
).toString(),
|
||||
maxClaimablePerWallet: toUnlimited(condition.quantityLimitPerWallet),
|
||||
merkleRootHash: condition.merkleRoot,
|
||||
metadata,
|
||||
snapshot,
|
||||
} satisfies CombinedClaimCondition;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
type PhaseInput = z.input<typeof LegacyClaimConditionInputSchema>;
|
||||
|
||||
export function setClaimPhasesTx(
|
||||
baseOptions: BaseTransactionOptions<Options>,
|
||||
rawPhases: PhaseInput[],
|
||||
) {
|
||||
const phases = rawPhases.map((phase) => {
|
||||
return {
|
||||
startTime: toDate(phase.startTime),
|
||||
maxClaimableSupply: toBigInt(phase.maxClaimableSupply),
|
||||
maxClaimablePerWallet: toBigInt(phase.maxClaimablePerWallet),
|
||||
merkleRootHash: phase.merkleRootHash as string | undefined,
|
||||
overrideList: phase.snapshot?.length
|
||||
? snapshotToOverrides(phase.snapshot)
|
||||
: undefined,
|
||||
price: phase.price?.toString(),
|
||||
currencyAddress: phase.currencyAddress,
|
||||
metadata: phase.metadata,
|
||||
} satisfies ERC20Ext.SetClaimConditionsParams["phases"][0];
|
||||
});
|
||||
|
||||
switch (baseOptions.type) {
|
||||
case "erc20":
|
||||
return ERC20Ext.setClaimConditions({
|
||||
contract: baseOptions.contract,
|
||||
phases,
|
||||
});
|
||||
case "erc721":
|
||||
return ERC721Ext.setClaimConditions({
|
||||
contract: baseOptions.contract,
|
||||
phases,
|
||||
});
|
||||
case "erc1155":
|
||||
return ERC1155Ext.setClaimConditions({
|
||||
contract: baseOptions.contract,
|
||||
phases,
|
||||
tokenId: baseOptions.tokenId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// HELPERS
|
||||
function snapshotToOverrides(snapshot: PhaseInput["snapshot"]) {
|
||||
if (!snapshot) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return snapshot.map((entry) => {
|
||||
if (typeof entry === "string") {
|
||||
return {
|
||||
address: entry,
|
||||
} satisfies OverrideEntry;
|
||||
}
|
||||
return {
|
||||
address: entry.address,
|
||||
maxClaimable: entry.maxClaimable?.toString(),
|
||||
price: entry.price?.toString(),
|
||||
currencyAddress: entry.currencyAddress,
|
||||
} satisfies OverrideEntry;
|
||||
});
|
||||
}
|
||||
function toDate(timestamp: number | Date | undefined) {
|
||||
if (timestamp === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (timestamp instanceof Date) {
|
||||
return timestamp;
|
||||
}
|
||||
return new Date(timestamp);
|
||||
}
|
||||
function toBigInt(value: string | number | undefined) {
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (value === "unlimited") {
|
||||
return maxUint256;
|
||||
}
|
||||
return BigInt(value);
|
||||
}
|
||||
|
||||
function toUnlimited(value: bigint) {
|
||||
return value === maxUint256 ? "unlimited" : value.toString();
|
||||
}
|
||||
|
||||
async function fetchSnapshot(
|
||||
merkleRoot: string,
|
||||
merkleMetadata: Record<string, string> | undefined,
|
||||
client: ThirdwebClient,
|
||||
): Promise<SnapshotEntry[] | null> {
|
||||
if (!merkleMetadata) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const snapshotUri = merkleMetadata[merkleRoot];
|
||||
if (snapshotUri) {
|
||||
const raw = await download({
|
||||
uri: snapshotUri,
|
||||
client: client,
|
||||
}).then((r) => r.json());
|
||||
if (raw.isShardedMerkleTree && raw.merkleRoot === merkleRoot) {
|
||||
return download({
|
||||
uri: raw.originalEntriesUri,
|
||||
client: client,
|
||||
})
|
||||
.then((r) => r.json())
|
||||
.catch(() => null);
|
||||
}
|
||||
if (merkleRoot === raw.merkleRoot) {
|
||||
return raw.claims.map(
|
||||
(claim: {
|
||||
address: string;
|
||||
maxClaimable: bigint;
|
||||
price: bigint;
|
||||
currencyAddress: string;
|
||||
}) => ({
|
||||
address: claim.address,
|
||||
maxClaimable: claim.maxClaimable,
|
||||
price: claim.price,
|
||||
currencyAddress: claim.currencyAddress,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} catch (e) {
|
||||
console.error("failed to fetch snapshot", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+120
-135
@@ -13,11 +13,6 @@ import {
|
||||
MenuList,
|
||||
Spinner,
|
||||
} from "@chakra-ui/react";
|
||||
import {
|
||||
type DropContract,
|
||||
useClaimConditions,
|
||||
useSetClaimConditions,
|
||||
} from "@thirdweb-dev/react";
|
||||
import {
|
||||
type ClaimConditionInput,
|
||||
ClaimConditionInputSchema,
|
||||
@@ -42,12 +37,17 @@ import {
|
||||
ZERO_ADDRESS,
|
||||
} from "thirdweb";
|
||||
import { decimals } from "thirdweb/extensions/erc20";
|
||||
import { useActiveAccount, useReadContract } from "thirdweb/react";
|
||||
import {
|
||||
useActiveAccount,
|
||||
useReadContract,
|
||||
useSendAndConfirmTransaction,
|
||||
} from "thirdweb/react";
|
||||
import invariant from "tiny-invariant";
|
||||
import { Button, Heading, MenuItem, Text } from "tw-components";
|
||||
import * as z from "zod";
|
||||
import { ZodError } from "zod";
|
||||
import { ResetClaimEligibility } from "../reset-claim-eligibility";
|
||||
import { getClaimPhasesInLegacyFormat, setClaimPhasesTx } from "./hooks";
|
||||
import { ClaimConditionsPhase } from "./phase";
|
||||
|
||||
type ClaimConditionDashboardInput = ClaimConditionInput & {
|
||||
@@ -59,7 +59,6 @@ const DEFAULT_PHASE: ClaimConditionDashboardInput = {
|
||||
startTime: new Date(),
|
||||
maxClaimableSupply: "unlimited",
|
||||
maxClaimablePerWallet: "unlimited",
|
||||
waitInSeconds: "0",
|
||||
price: "0",
|
||||
currencyAddress: NATIVE_TOKEN_ADDRESS,
|
||||
snapshot: undefined,
|
||||
@@ -153,7 +152,6 @@ interface ClaimsConditionFormContextData {
|
||||
phaseIndex: number;
|
||||
formDisabled: boolean;
|
||||
tokenDecimals: number;
|
||||
isClaimPhaseV1: boolean;
|
||||
isMultiPhase: boolean;
|
||||
isActive: boolean;
|
||||
dropType: DropType;
|
||||
@@ -177,14 +175,10 @@ export function useClaimConditionsFormContext() {
|
||||
return data;
|
||||
}
|
||||
|
||||
// TODO: refactor, this is no longer supported
|
||||
const isClaimPhaseV1 = false;
|
||||
|
||||
interface ClaimConditionsFormProps {
|
||||
contract: DropContract;
|
||||
contract: ThirdwebContract;
|
||||
tokenId?: string;
|
||||
isColumn?: true;
|
||||
contractV5: ThirdwebContract;
|
||||
isErc20: boolean;
|
||||
isMultiPhase: boolean;
|
||||
}
|
||||
@@ -193,26 +187,39 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
|
||||
contract,
|
||||
tokenId,
|
||||
isColumn,
|
||||
contractV5,
|
||||
isErc20,
|
||||
isMultiPhase,
|
||||
}) => {
|
||||
// assume 1155 if we have a tokenId
|
||||
const isErc1155 = tokenId !== undefined;
|
||||
// if neither 1155 or 20 then it's 721
|
||||
const isErc721 = !isErc20 && !isErc1155;
|
||||
const walletAddress = useActiveAccount()?.address;
|
||||
const trackEvent = useTrack();
|
||||
const [resetFlag, setResetFlag] = useState(false);
|
||||
const isAdmin = useIsAdmin(contractV5);
|
||||
const [openSnapshotIndex, setOpenSnapshotIndex] = useState(-1);
|
||||
const setClaimConditionsQuery = useSetClaimConditions(contract, tokenId);
|
||||
|
||||
const tokenDecimals = useReadContract(decimals, { contract: contractV5 });
|
||||
const isAdmin = useIsAdmin(contract);
|
||||
const [openSnapshotIndex, setOpenSnapshotIndex] = useState(-1);
|
||||
const sendTx = useSendAndConfirmTransaction();
|
||||
|
||||
const tokenDecimals = useReadContract(decimals, {
|
||||
contract,
|
||||
queryOptions: {
|
||||
enabled: isErc20,
|
||||
},
|
||||
});
|
||||
const tokenDecimalsData = tokenDecimals.data ?? 0;
|
||||
const saveClaimPhaseNotification = useTxNotifications(
|
||||
"Saved claim phases",
|
||||
"Failed to save claim phases",
|
||||
);
|
||||
|
||||
const claimConditionsQuery = useClaimConditions(contract, tokenId, {
|
||||
withAllowList: true,
|
||||
const claimConditionsQuery = useReadContract(getClaimPhasesInLegacyFormat, {
|
||||
contract,
|
||||
...(isErc20
|
||||
? { type: "erc20" }
|
||||
: isErc721
|
||||
? { type: "erc721" }
|
||||
: { type: "erc1155", tokenId: BigInt(tokenId || 0) }),
|
||||
});
|
||||
|
||||
const transformedQueryData = useMemo(() => {
|
||||
@@ -227,7 +234,6 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
|
||||
},
|
||||
currencyAddress: phase.currencyAddress?.toLowerCase() || "0",
|
||||
maxClaimablePerWallet: phase.maxClaimablePerWallet?.toString() || "0",
|
||||
waitInSeconds: phase.waitInSeconds?.toString() || "0",
|
||||
startTime: new Date(phase.startTime),
|
||||
snapshot: phase.snapshot?.map(
|
||||
({ address, maxClaimable, price, currencyAddress }) => ({
|
||||
@@ -249,8 +255,7 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
|
||||
);
|
||||
}, [claimConditionsQuery.data, isMultiPhase]);
|
||||
|
||||
const isFetchingData =
|
||||
claimConditionsQuery.isFetching || setClaimConditionsQuery.isLoading;
|
||||
const isFetchingData = claimConditionsQuery.isFetching || sendTx.isPending;
|
||||
|
||||
const canEditForm = isAdmin && !isFetchingData;
|
||||
|
||||
@@ -346,10 +351,18 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
|
||||
});
|
||||
|
||||
try {
|
||||
await setClaimConditionsQuery.mutateAsync({
|
||||
phases: d.phases as ClaimConditionInput[],
|
||||
reset: resetFlag,
|
||||
});
|
||||
const tx = setClaimPhasesTx(
|
||||
{
|
||||
contract,
|
||||
...(isErc20
|
||||
? { type: "erc20" }
|
||||
: isErc721
|
||||
? { type: "erc721" }
|
||||
: { type: "erc1155", tokenId: BigInt(tokenId || 0) }),
|
||||
},
|
||||
d.phases,
|
||||
);
|
||||
await sendTx.mutateAsync(tx);
|
||||
trackEvent({
|
||||
category,
|
||||
action: "set-claim-conditions",
|
||||
@@ -365,6 +378,7 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
|
||||
|
||||
form.setValue("phases", newPhases);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
trackEvent({
|
||||
category,
|
||||
action: "set-claim-conditions",
|
||||
@@ -448,8 +462,7 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
|
||||
)}
|
||||
{controlledFields.map((field, index) => {
|
||||
const dropType: DropType = field.snapshot
|
||||
? isClaimPhaseV1 ||
|
||||
field.maxClaimablePerWallet?.toString() === "0"
|
||||
? field.maxClaimablePerWallet?.toString() === "0"
|
||||
? "specific"
|
||||
: "overrides"
|
||||
: "any";
|
||||
@@ -478,7 +491,6 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
|
||||
<Fragment key={`snapshot_${field.id}_${index}`}>
|
||||
<SnapshotUpload
|
||||
dropType={dropType}
|
||||
isV1ClaimCondition={isClaimPhaseV1}
|
||||
isOpen={openSnapshotIndex === index}
|
||||
onClose={() => setOpenSnapshotIndex(-1)}
|
||||
value={snapshotValue}
|
||||
@@ -496,7 +508,6 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
|
||||
formDisabled: !canEditForm,
|
||||
isErc20,
|
||||
tokenDecimals: tokenDecimalsData,
|
||||
isClaimPhaseV1,
|
||||
dropType,
|
||||
setOpenSnapshotIndex,
|
||||
isAdmin,
|
||||
@@ -507,14 +518,11 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
|
||||
}}
|
||||
>
|
||||
<ClaimConditionsPhase
|
||||
contract={contractV5}
|
||||
contract={contract}
|
||||
onRemove={() => {
|
||||
removePhase(index);
|
||||
if (!isMultiPhase) {
|
||||
setResetFlag(true);
|
||||
}
|
||||
}}
|
||||
isLoading={setClaimConditionsQuery.isLoading}
|
||||
isLoading={sendTx.isPending}
|
||||
/>
|
||||
</ClaimsConditionFormContext.Provider>
|
||||
</Fragment>
|
||||
@@ -545,115 +553,92 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
|
||||
gap={2}
|
||||
>
|
||||
<Flex gap={2}>
|
||||
{contractV5 && (
|
||||
<AdminOnly contract={contractV5}>
|
||||
{!isClaimPhaseV1 ? (
|
||||
<>
|
||||
<Menu>
|
||||
<MenuButton
|
||||
as={Button}
|
||||
size="sm"
|
||||
colorScheme="primary"
|
||||
variant={phases?.length > 0 ? "outline" : "solid"}
|
||||
borderRadius="md"
|
||||
leftIcon={<Icon as={FiPlus} />}
|
||||
isDisabled={
|
||||
setClaimConditionsQuery.isLoading ||
|
||||
(!isMultiPhase && phases?.length > 0)
|
||||
}
|
||||
>
|
||||
Add {isMultiPhase ? "Phase" : "Claim Conditions"}
|
||||
</MenuButton>
|
||||
<MenuList
|
||||
borderRadius="lg"
|
||||
overflow="hidden"
|
||||
zIndex="overlay"
|
||||
>
|
||||
{Object.keys(ClaimConditionTypeData).map((key) => {
|
||||
const type = key as ClaimConditionType;
|
||||
<AdminOnly contract={contract}>
|
||||
<Menu>
|
||||
<MenuButton
|
||||
as={Button}
|
||||
size="sm"
|
||||
colorScheme="primary"
|
||||
variant={phases?.length > 0 ? "outline" : "solid"}
|
||||
borderRadius="md"
|
||||
leftIcon={<Icon as={FiPlus} />}
|
||||
isDisabled={
|
||||
sendTx.isPending || (!isMultiPhase && phases?.length > 0)
|
||||
}
|
||||
>
|
||||
Add {isMultiPhase ? "Phase" : "Claim Conditions"}
|
||||
</MenuButton>
|
||||
<MenuList
|
||||
borderRadius="lg"
|
||||
overflow="hidden"
|
||||
zIndex="overlay"
|
||||
>
|
||||
{Object.keys(ClaimConditionTypeData).map((key) => {
|
||||
const type = key as ClaimConditionType;
|
||||
|
||||
if (type === "custom") {
|
||||
return null;
|
||||
}
|
||||
if (type === "custom") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
key={type}
|
||||
onClick={() => {
|
||||
addPhase(type);
|
||||
// TODO: Automatically start editing the new phase after adding it
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
{ClaimConditionTypeData[type].name}
|
||||
<TooltipBox
|
||||
content={
|
||||
<Text size="body.md">
|
||||
{ClaimConditionTypeData[type].description}
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</AdminOnly>
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
key={type}
|
||||
onClick={() => {
|
||||
addPhase(type);
|
||||
// TODO: Automatically start editing the new phase after adding it
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
{ClaimConditionTypeData[type].name}
|
||||
<TooltipBox
|
||||
content={
|
||||
<Text size="body.md">
|
||||
{
|
||||
ClaimConditionTypeData[type]
|
||||
.description
|
||||
}
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</>
|
||||
) : (
|
||||
<Button
|
||||
size="sm"
|
||||
colorScheme="primary"
|
||||
variant="solid"
|
||||
borderRadius="md"
|
||||
leftIcon={<Icon as={FiPlus} />}
|
||||
onClick={() => addPhase("custom")}
|
||||
isDisabled={setClaimConditionsQuery.isLoading}
|
||||
>
|
||||
Add {isMultiPhase ? "Phase" : "Claim Conditions"}
|
||||
</Button>
|
||||
)}
|
||||
</AdminOnly>
|
||||
)}
|
||||
{controlledFields.some((field) => field.fromSdk) && (
|
||||
<ResetClaimEligibility
|
||||
isErc20={isErc20}
|
||||
contract={contractV5}
|
||||
contract={contract}
|
||||
tokenId={tokenId}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
<Flex>
|
||||
{contractV5 && (
|
||||
<AdminOnly contract={contractV5} fallback={<Box pb={5} />}>
|
||||
<Flex justifyContent="center" alignItems="center" gap={3}>
|
||||
{(hasRemovedPhases || hasAddedPhases) && (
|
||||
<Text color="red.500" fontWeight="bold">
|
||||
You have unsaved changes
|
||||
</Text>
|
||||
)}
|
||||
{controlledFields.length > 0 ||
|
||||
hasRemovedPhases ||
|
||||
!isMultiPhase ? (
|
||||
<TransactionButton
|
||||
colorScheme="primary"
|
||||
transactionCount={1}
|
||||
isDisabled={claimConditionsQuery.isLoading}
|
||||
type="submit"
|
||||
isLoading={setClaimConditionsQuery.isLoading}
|
||||
loadingText="Saving..."
|
||||
size="md"
|
||||
>
|
||||
Save Phases
|
||||
</TransactionButton>
|
||||
) : null}
|
||||
</Flex>
|
||||
</AdminOnly>
|
||||
)}
|
||||
<AdminOnly contract={contract} fallback={<Box pb={5} />}>
|
||||
<Flex justifyContent="center" alignItems="center" gap={3}>
|
||||
{(hasRemovedPhases || hasAddedPhases) && (
|
||||
<Text color="red.500" fontWeight="bold">
|
||||
You have unsaved changes
|
||||
</Text>
|
||||
)}
|
||||
{controlledFields.length > 0 ||
|
||||
hasRemovedPhases ||
|
||||
!isMultiPhase ? (
|
||||
<TransactionButton
|
||||
colorScheme="primary"
|
||||
transactionCount={1}
|
||||
isDisabled={claimConditionsQuery.isLoading}
|
||||
type="submit"
|
||||
isLoading={sendTx.isPending}
|
||||
loadingText="Saving..."
|
||||
size="md"
|
||||
>
|
||||
Save Phases
|
||||
</TransactionButton>
|
||||
) : null}
|
||||
</Flex>
|
||||
</AdminOnly>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
+3
-15
@@ -30,7 +30,6 @@ export const ClaimConditionsPhase: React.FC<ClaimConditionsPhaseProps> = ({
|
||||
form,
|
||||
field,
|
||||
isErc20,
|
||||
isClaimPhaseV1,
|
||||
isAdmin,
|
||||
claimConditionType,
|
||||
isActive,
|
||||
@@ -82,26 +81,15 @@ export const ClaimConditionsPhase: React.FC<ClaimConditionsPhaseProps> = ({
|
||||
|
||||
<Flex flexDir="column" gap={2} mt={{ base: 4, md: 0 }}>
|
||||
<Flex gap={3} alignItems="center">
|
||||
<Heading>
|
||||
{isClaimPhaseV1
|
||||
? isMultiPhase
|
||||
? `Claim Phase ${phaseIndex + 1}`
|
||||
: "Claim Conditions"
|
||||
: ClaimConditionTypeData[claimConditionType].name}
|
||||
</Heading>
|
||||
<Heading>{ClaimConditionTypeData[claimConditionType].name}</Heading>
|
||||
{isActive && (
|
||||
<Badge colorScheme="green" borderRadius="lg" p={1.5}>
|
||||
Currently active
|
||||
</Badge>
|
||||
)}
|
||||
</Flex>
|
||||
{isClaimPhaseV1 ? (
|
||||
""
|
||||
) : (
|
||||
<Text>
|
||||
{ClaimConditionTypeData[claimConditionType].description}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<Text>{ClaimConditionTypeData[claimConditionType].description}</Text>
|
||||
</Flex>
|
||||
|
||||
{!field.isEditing ? (
|
||||
|
||||
+8
-14
@@ -1,5 +1,4 @@
|
||||
import { Flex, Stack } from "@chakra-ui/react";
|
||||
import { useContract } from "@thirdweb-dev/react";
|
||||
import type { ThirdwebContract } from "thirdweb";
|
||||
import { Heading, Text } from "tw-components";
|
||||
import { ClaimConditionsForm } from "./claim-conditions-form/index";
|
||||
@@ -16,8 +15,6 @@ export const ClaimConditions: React.FC<ClaimConditionsProps> = ({
|
||||
isColumn,
|
||||
isERC20,
|
||||
}) => {
|
||||
const contractQuery = useContract(contract.address);
|
||||
|
||||
return (
|
||||
<Stack spacing={8}>
|
||||
<Flex p={0} position="relative">
|
||||
@@ -39,17 +36,14 @@ export const ClaimConditions: React.FC<ClaimConditionsProps> = ({
|
||||
</Flex>
|
||||
|
||||
{/* Set Claim Conditions */}
|
||||
{contractQuery.contract && (
|
||||
<ClaimConditionsForm
|
||||
isErc20={isERC20}
|
||||
contract={contractQuery.contract}
|
||||
tokenId={tokenId}
|
||||
isColumn={isColumn}
|
||||
contractV5={contract}
|
||||
// always multi phase!
|
||||
isMultiPhase={true}
|
||||
/>
|
||||
)}
|
||||
<ClaimConditionsForm
|
||||
isErc20={isERC20}
|
||||
contract={contract}
|
||||
tokenId={tokenId}
|
||||
isColumn={isColumn}
|
||||
// always multi phase!
|
||||
isMultiPhase={true}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Stack>
|
||||
|
||||
+1
-4
@@ -87,9 +87,6 @@ export const ResetClaimEligibility: React.FC<ResetClaimEligibilityProps> = ({
|
||||
return (
|
||||
<AdminOnly contract={contract} fallback={<div className="pb-5" />}>
|
||||
<TransactionButton
|
||||
colorScheme="secondary"
|
||||
bg="bgBlack"
|
||||
color="bgWhite"
|
||||
transactionCount={1}
|
||||
type="button"
|
||||
isLoading={sendTxMutation.isPending}
|
||||
@@ -97,7 +94,7 @@ export const ResetClaimEligibility: React.FC<ResetClaimEligibilityProps> = ({
|
||||
loadingText="Resetting..."
|
||||
size="sm"
|
||||
>
|
||||
Reset Eligibility{" "}
|
||||
Reset Eligibility
|
||||
<ToolTipLabel
|
||||
label={
|
||||
<>
|
||||
|
||||
+66
-115
@@ -27,7 +27,7 @@ import {
|
||||
} from "@chakra-ui/react";
|
||||
import { Logo } from "components/logo";
|
||||
import Papa from "papaparse";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { type DropzoneOptions, useDropzone } from "react-dropzone";
|
||||
import { BsFillCloudUploadFill } from "react-icons/bs";
|
||||
import { FiDownload } from "react-icons/fi";
|
||||
@@ -64,7 +64,6 @@ interface SnapshotUploadProps {
|
||||
}[]
|
||||
| null;
|
||||
dropType: "specific" | "any" | "overrides";
|
||||
isV1ClaimCondition: boolean;
|
||||
isDisabled: boolean;
|
||||
}
|
||||
|
||||
@@ -74,7 +73,6 @@ export const SnapshotUpload: React.FC<SnapshotUploadProps> = ({
|
||||
onClose,
|
||||
value,
|
||||
dropType,
|
||||
isV1ClaimCondition,
|
||||
isDisabled,
|
||||
}) => {
|
||||
const [validSnapshot, setValidSnapshot] = useState<SnapshotAddressInput[]>(
|
||||
@@ -224,11 +222,7 @@ export const SnapshotUpload: React.FC<SnapshotUploadProps> = ({
|
||||
</Flex>
|
||||
|
||||
{validSnapshot.length > 0 ? (
|
||||
<SnapshotTable
|
||||
portalRef={paginationPortalRef}
|
||||
data={snapshotData}
|
||||
isV1ClaimCondition={isV1ClaimCondition}
|
||||
/>
|
||||
<SnapshotTable portalRef={paginationPortalRef} data={snapshotData} />
|
||||
) : (
|
||||
<Flex flexGrow={1} align="center" overflow="auto">
|
||||
<Container maxW="container.page">
|
||||
@@ -275,33 +269,7 @@ export const SnapshotUpload: React.FC<SnapshotUploadProps> = ({
|
||||
<Flex gap={2} flexDir="column">
|
||||
<Heading size="label.md">Requirements</Heading>
|
||||
<UnorderedList spacing={1}>
|
||||
{isV1ClaimCondition ? (
|
||||
<>
|
||||
<Text as={ListItem}>
|
||||
Files <em>must</em> contain one .csv file with a list
|
||||
of addresses.
|
||||
<br />
|
||||
<Link download color="blue.500" href="/snapshot.csv">
|
||||
<Icon as={FiDownload} /> Example snapshot
|
||||
</Link>
|
||||
</Text>
|
||||
<Text as={ListItem}>
|
||||
You may optionally add a <Code>maxClaimable</Code>{" "}
|
||||
column to specify how many NFTs can be claimed by that
|
||||
specific address per transaction, if not specified,
|
||||
the default value is the one you have set on your
|
||||
claim phase.
|
||||
<br />
|
||||
<Link
|
||||
download
|
||||
color="blue.500"
|
||||
href="/snapshot-with-maxclaimable.csv"
|
||||
>
|
||||
<Icon as={FiDownload} /> Example snapshot
|
||||
</Link>
|
||||
</Text>
|
||||
</>
|
||||
) : dropType === "specific" ? (
|
||||
{dropType === "specific" ? (
|
||||
<>
|
||||
<Text as={ListItem}>
|
||||
Files <em>must</em> contain one .csv file with a list
|
||||
@@ -384,13 +352,10 @@ export const SnapshotUpload: React.FC<SnapshotUploadProps> = ({
|
||||
Repeated addresses will be removed and only the first
|
||||
found will be kept.
|
||||
</Text>
|
||||
{!isV1ClaimCondition && (
|
||||
<Text as={ListItem}>
|
||||
The limit you set is for the maximum amount of NFTs a
|
||||
wallet can claim, not how many they can receive in
|
||||
total.
|
||||
</Text>
|
||||
)}
|
||||
<Text as={ListItem}>
|
||||
The limit you set is for the maximum amount of NFTs a
|
||||
wallet can claim, not how many they can receive in total.
|
||||
</Text>
|
||||
</UnorderedList>
|
||||
</Flex>
|
||||
</Flex>
|
||||
@@ -459,81 +424,67 @@ export const SnapshotUpload: React.FC<SnapshotUploadProps> = ({
|
||||
interface SnapshotTableProps {
|
||||
data: SnapshotAddressInput[];
|
||||
portalRef: React.RefObject<HTMLDivElement>;
|
||||
isV1ClaimCondition: boolean;
|
||||
}
|
||||
|
||||
const SnapshotTable: React.FC<SnapshotTableProps> = ({
|
||||
data,
|
||||
portalRef,
|
||||
isV1ClaimCondition,
|
||||
}) => {
|
||||
const columns = useMemo(() => {
|
||||
let cols = [
|
||||
{
|
||||
Header: "Address",
|
||||
accessor: ({ address, isValid }) => {
|
||||
if (isValid) {
|
||||
return address;
|
||||
}
|
||||
return (
|
||||
<Flex>
|
||||
<Tooltip
|
||||
label={
|
||||
address.startsWith("0x")
|
||||
? "Address is not valid"
|
||||
: "Address couldn't be resolved"
|
||||
}
|
||||
>
|
||||
<Stack direction="row" align="center">
|
||||
<Icon as={IoAlertCircleOutline} color="red.500" boxSize={5} />
|
||||
<Text fontWeight="bold" color="red.500" cursor="default">
|
||||
{address}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
Header: "Max claimable",
|
||||
accessor: ({ maxClaimable }) => {
|
||||
return maxClaimable === "0" || !maxClaimable
|
||||
? "Default"
|
||||
: maxClaimable === "unlimited"
|
||||
? "Unlimited"
|
||||
: maxClaimable;
|
||||
},
|
||||
},
|
||||
] as Column<SnapshotAddressInput>[];
|
||||
|
||||
if (!isV1ClaimCondition) {
|
||||
cols = cols.concat([
|
||||
{
|
||||
Header: "Price",
|
||||
accessor: ({ price }) => {
|
||||
return price === "0"
|
||||
? "Free"
|
||||
: !price || price === "unlimited"
|
||||
? "Default"
|
||||
: price;
|
||||
},
|
||||
},
|
||||
{
|
||||
Header: "Currency Address",
|
||||
accessor: ({ currencyAddress }) => {
|
||||
return currencyAddress ===
|
||||
"0x0000000000000000000000000000000000000000" || !currencyAddress
|
||||
? "Default"
|
||||
: currencyAddress;
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
return cols;
|
||||
}, [isV1ClaimCondition]);
|
||||
const SnapshotTableColumns = [
|
||||
{
|
||||
Header: "Address",
|
||||
accessor: ({ address, isValid }) => {
|
||||
if (isValid) {
|
||||
return address;
|
||||
}
|
||||
return (
|
||||
<Flex>
|
||||
<Tooltip
|
||||
label={
|
||||
address.startsWith("0x")
|
||||
? "Address is not valid"
|
||||
: "Address couldn't be resolved"
|
||||
}
|
||||
>
|
||||
<Stack direction="row" align="center">
|
||||
<Icon as={IoAlertCircleOutline} color="red.500" boxSize={5} />
|
||||
<Text fontWeight="bold" color="red.500" cursor="default">
|
||||
{address}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
Header: "Max claimable",
|
||||
accessor: ({ maxClaimable }) => {
|
||||
return maxClaimable === "0" || !maxClaimable
|
||||
? "Default"
|
||||
: maxClaimable === "unlimited"
|
||||
? "Unlimited"
|
||||
: maxClaimable;
|
||||
},
|
||||
},
|
||||
{
|
||||
Header: "Price",
|
||||
accessor: ({ price }) => {
|
||||
return price === "0"
|
||||
? "Free"
|
||||
: !price || price === "unlimited"
|
||||
? "Default"
|
||||
: price;
|
||||
},
|
||||
},
|
||||
{
|
||||
Header: "Currency Address",
|
||||
accessor: ({ currencyAddress }) => {
|
||||
return currencyAddress === "0x0000000000000000000000000000000000000000" ||
|
||||
!currencyAddress
|
||||
? "Default"
|
||||
: currencyAddress;
|
||||
},
|
||||
},
|
||||
] as Column<SnapshotAddressInput>[];
|
||||
|
||||
const SnapshotTable: React.FC<SnapshotTableProps> = ({ data, portalRef }) => {
|
||||
const {
|
||||
getTableProps,
|
||||
getTableBodyProps,
|
||||
@@ -555,7 +506,7 @@ const SnapshotTable: React.FC<SnapshotTableProps> = ({
|
||||
state: { pageIndex, pageSize },
|
||||
} = useTable(
|
||||
{
|
||||
columns,
|
||||
columns: SnapshotTableColumns,
|
||||
data,
|
||||
initialState: {
|
||||
pageSize: 50,
|
||||
|
||||
@@ -18,7 +18,6 @@ export const NFTRevealButton: React.FC<NFTRevealButtonProps> = ({
|
||||
const batchesQuery = useReadContract(getBatchesToReveal, {
|
||||
contract,
|
||||
});
|
||||
console.log("*** batches", batchesQuery);
|
||||
return batchesQuery.data?.length ? (
|
||||
<MinterOnly contract={contract}>
|
||||
<Drawer
|
||||
|
||||
@@ -54,6 +54,16 @@ function parseError(error: unknown): string | JSX.Element {
|
||||
return error.message;
|
||||
}
|
||||
|
||||
// handle rpc errors
|
||||
if (
|
||||
error &&
|
||||
typeof error === "object" &&
|
||||
"message" in error &&
|
||||
typeof error.message === "string"
|
||||
) {
|
||||
return error.message;
|
||||
}
|
||||
|
||||
// everything that falls through here should be logged and sent to posthog
|
||||
console.error("unknown error", error);
|
||||
posthog.capture("unknown_error", { error });
|
||||
|
||||
@@ -22,24 +22,30 @@ export async function convertErc20Amount(
|
||||
} & AmountOrAmountInWei
|
||||
>,
|
||||
): Promise<bigint> {
|
||||
if ("amount" in options) {
|
||||
// for native token, we know decimals are 18
|
||||
if (!options.erc20Address || isNativeTokenAddress(options.erc20Address)) {
|
||||
return toUnits(options.amount.toString(), 18);
|
||||
try {
|
||||
if ("amount" in options) {
|
||||
// for native token, we know decimals are 18
|
||||
if (!options.erc20Address || isNativeTokenAddress(options.erc20Address)) {
|
||||
return toUnits(options.amount.toString(), 18);
|
||||
}
|
||||
// otherwise get the decimals of the currency
|
||||
const currencyContract = getContract({
|
||||
client: options.client,
|
||||
chain: options.chain,
|
||||
address: options.erc20Address,
|
||||
});
|
||||
const { decimals } = await import(
|
||||
"../../extensions/erc20/read/decimals.js"
|
||||
);
|
||||
const currencyDecimals = await decimals({
|
||||
contract: currencyContract,
|
||||
});
|
||||
return toUnits(options.amount.toString(), currencyDecimals);
|
||||
}
|
||||
// otherwise get the decimals of the currency
|
||||
const currencyContract = getContract({
|
||||
client: options.client,
|
||||
chain: options.chain,
|
||||
address: options.erc20Address,
|
||||
});
|
||||
const { decimals } = await import(
|
||||
"../../extensions/erc20/read/decimals.js"
|
||||
return options.amountInWei;
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Failed to convert ERC20 amount for token: ${options.erc20Address}: ${e}`,
|
||||
);
|
||||
const currencyDecimals = await decimals({
|
||||
contract: currencyContract,
|
||||
});
|
||||
return toUnits(options.amount.toString(), currencyDecimals);
|
||||
}
|
||||
return options.amountInWei;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user