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;
|
isLoading: boolean;
|
||||||
isGasless?: boolean;
|
isGasless?: boolean;
|
||||||
upsellTestnet?: boolean;
|
upsellTestnet?: boolean;
|
||||||
onChainSelect?: (chainId: number) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function useWalletRequiresExternalConfirmation() {
|
function useWalletRequiresExternalConfirmation() {
|
||||||
@@ -55,7 +54,7 @@ export const TransactionButton: React.FC<TransactionButtonProps> = ({
|
|||||||
colorScheme,
|
colorScheme,
|
||||||
variant,
|
variant,
|
||||||
isGasless,
|
isGasless,
|
||||||
onChainSelect,
|
|
||||||
...restButtonProps
|
...restButtonProps
|
||||||
}) => {
|
}) => {
|
||||||
const activeWallet = useActiveWallet();
|
const activeWallet = useActiveWallet();
|
||||||
@@ -93,7 +92,6 @@ export const TransactionButton: React.FC<TransactionButtonProps> = ({
|
|||||||
>
|
>
|
||||||
<PopoverTrigger>
|
<PopoverTrigger>
|
||||||
<ButtonComponent
|
<ButtonComponent
|
||||||
onChainSelect={onChainSelect}
|
|
||||||
borderRadius="md"
|
borderRadius="md"
|
||||||
position="relative"
|
position="relative"
|
||||||
role="group"
|
role="group"
|
||||||
|
|||||||
+1
-2
@@ -17,10 +17,9 @@ export const ClaimPriceInput = () => {
|
|||||||
field,
|
field,
|
||||||
isColumn,
|
isColumn,
|
||||||
claimConditionType,
|
claimConditionType,
|
||||||
isClaimPhaseV1,
|
|
||||||
} = useClaimConditionsFormContext();
|
} = useClaimConditionsFormContext();
|
||||||
|
|
||||||
if (!isClaimPhaseV1 && claimConditionType === "creator") {
|
if (claimConditionType === "creator") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+25
-40
@@ -18,7 +18,6 @@ export const ClaimerSelection = () => {
|
|||||||
field,
|
field,
|
||||||
dropType,
|
dropType,
|
||||||
isErc20,
|
isErc20,
|
||||||
isClaimPhaseV1,
|
|
||||||
setOpenSnapshotIndex: setOpenIndex,
|
setOpenSnapshotIndex: setOpenIndex,
|
||||||
isAdmin,
|
isAdmin,
|
||||||
isColumn,
|
isColumn,
|
||||||
@@ -31,14 +30,10 @@ export const ClaimerSelection = () => {
|
|||||||
if (val === "any") {
|
if (val === "any") {
|
||||||
form.setValue(`phases.${phaseIndex}.snapshot`, undefined);
|
form.setValue(`phases.${phaseIndex}.snapshot`, undefined);
|
||||||
} else {
|
} else {
|
||||||
if (val === "specific" && !isClaimPhaseV1) {
|
if (val === "specific") {
|
||||||
form.setValue(`phases.${phaseIndex}.maxClaimablePerWallet`, 0);
|
form.setValue(`phases.${phaseIndex}.maxClaimablePerWallet`, 0);
|
||||||
}
|
}
|
||||||
if (
|
if (val === "overrides" && field.maxClaimablePerWallet !== 1) {
|
||||||
val === "overrides" &&
|
|
||||||
!isClaimPhaseV1 &&
|
|
||||||
field.maxClaimablePerWallet !== 1
|
|
||||||
) {
|
|
||||||
form.setValue(`phases.${phaseIndex}.maxClaimablePerWallet`, 1);
|
form.setValue(`phases.${phaseIndex}.maxClaimablePerWallet`, 1);
|
||||||
}
|
}
|
||||||
form.setValue(`phases.${phaseIndex}.snapshot`, []);
|
form.setValue(`phases.${phaseIndex}.snapshot`, []);
|
||||||
@@ -50,39 +45,31 @@ export const ClaimerSelection = () => {
|
|||||||
|
|
||||||
const disabledSnapshotButton = isAdmin && formDisabled;
|
const disabledSnapshotButton = isAdmin && formDisabled;
|
||||||
|
|
||||||
if (isClaimPhaseV1) {
|
if (dropType === "specific") {
|
||||||
helperText =
|
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.";
|
<>
|
||||||
|
<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 {
|
} else {
|
||||||
if (dropType === "specific") {
|
helperText = (
|
||||||
helperText = (
|
<>
|
||||||
<>
|
<b>Anyone</b> can claim based on the rules defined in this phase.
|
||||||
<b>Only</b> wallets on the <b>allowlist</b> can claim.
|
<br />
|
||||||
</>
|
<b>Wallets in the snapshot</b> can claim with special rules defined in
|
||||||
);
|
the snapshot.
|
||||||
} 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.
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (claimConditionType === "public" || claimConditionType === "creator") {
|
||||||
!isClaimPhaseV1 &&
|
|
||||||
(claimConditionType === "public" || claimConditionType === "creator")
|
|
||||||
) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,9 +100,7 @@ export const ClaimerSelection = () => {
|
|||||||
onChange={handleClaimerChange}
|
onChange={handleClaimerChange}
|
||||||
>
|
>
|
||||||
<option value="any">Any wallet</option>
|
<option value="any">Any wallet</option>
|
||||||
{!isClaimPhaseV1 ? (
|
<option value="overrides">Any wallet (with overrides)</option>
|
||||||
<option value="overrides">Any wallet (with overrides)</option>
|
|
||||||
) : null}
|
|
||||||
<option value="specific">Only specific wallets</option>
|
<option value="specific">Only specific wallets</option>
|
||||||
</Select>
|
</Select>
|
||||||
)}
|
)}
|
||||||
|
|||||||
+6
-15
@@ -10,7 +10,6 @@ export const MaxClaimablePerWalletInput: React.FC = () => {
|
|||||||
const {
|
const {
|
||||||
formDisabled,
|
formDisabled,
|
||||||
isErc20,
|
isErc20,
|
||||||
isClaimPhaseV1,
|
|
||||||
form,
|
form,
|
||||||
tokenDecimals,
|
tokenDecimals,
|
||||||
field,
|
field,
|
||||||
@@ -26,9 +25,7 @@ export const MaxClaimablePerWalletInput: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<CustomFormControl
|
<CustomFormControl
|
||||||
disabled={formDisabled}
|
disabled={formDisabled}
|
||||||
label={`How many ${isErc20 ? "tokens" : "NFTs"} can be claimed per ${
|
label={`How many ${isErc20 ? "tokens" : "NFTs"} can be claimed per wallet?`}
|
||||||
isClaimPhaseV1 ? "transaction" : "wallet"
|
|
||||||
}?`}
|
|
||||||
error={
|
error={
|
||||||
form.getFieldState(
|
form.getFieldState(
|
||||||
`phases.${phaseIndex}.maxClaimablePerWallet`,
|
`phases.${phaseIndex}.maxClaimablePerWallet`,
|
||||||
@@ -37,14 +34,10 @@ export const MaxClaimablePerWalletInput: React.FC = () => {
|
|||||||
}
|
}
|
||||||
helperText={
|
helperText={
|
||||||
<>
|
<>
|
||||||
{!isClaimPhaseV1 ? (
|
This value applies for <strong>all</strong> wallets
|
||||||
<>
|
{claimConditionType !== "public"
|
||||||
This value applies for <strong>all</strong> wallets
|
? ", and can be overridden for specific wallets in the snapshot. "
|
||||||
{claimConditionType !== "public"
|
: ". "}
|
||||||
? ", and can be overridden for specific wallets in the snapshot. "
|
|
||||||
: ". "}
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
Limits are set per wallets and not per user, sophisticated actors
|
Limits are set per wallets and not per user, sophisticated actors
|
||||||
could get around wallet restrictions.{" "}
|
could get around wallet restrictions.{" "}
|
||||||
<Link
|
<Link
|
||||||
@@ -61,9 +54,7 @@ export const MaxClaimablePerWalletInput: React.FC = () => {
|
|||||||
<QuantityInputWithUnlimited
|
<QuantityInputWithUnlimited
|
||||||
isRequired
|
isRequired
|
||||||
decimals={tokenDecimals}
|
decimals={tokenDecimals}
|
||||||
isDisabled={
|
isDisabled={dropType === "specific" || formDisabled}
|
||||||
(!isClaimPhaseV1 && dropType === "specific") || formDisabled
|
|
||||||
}
|
|
||||||
value={field?.maxClaimablePerWallet?.toString() || "0"}
|
value={field?.maxClaimablePerWallet?.toString() || "0"}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
form.setValue(
|
form.setValue(
|
||||||
|
|||||||
+1
-2
@@ -14,10 +14,9 @@ export const MaxClaimableSupplyInput: React.FC = () => {
|
|||||||
tokenDecimals,
|
tokenDecimals,
|
||||||
field,
|
field,
|
||||||
claimConditionType,
|
claimConditionType,
|
||||||
isClaimPhaseV1,
|
|
||||||
} = useClaimConditionsFormContext();
|
} = useClaimConditionsFormContext();
|
||||||
|
|
||||||
if (!isClaimPhaseV1 && claimConditionType === "creator") {
|
if (claimConditionType === "creator") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-5
@@ -6,15 +6,11 @@ import { useClaimConditionsFormContext } from "../index";
|
|||||||
* Allows users to edit the name for the claim phase.
|
* Allows users to edit the name for the claim phase.
|
||||||
*/
|
*/
|
||||||
export const PhaseNameInput: React.FC = () => {
|
export const PhaseNameInput: React.FC = () => {
|
||||||
const { phaseIndex, formDisabled, isClaimPhaseV1, form, field } =
|
const { phaseIndex, formDisabled, form, field } =
|
||||||
useClaimConditionsFormContext();
|
useClaimConditionsFormContext();
|
||||||
const inputPlaceholder = `Phase ${phaseIndex + 1}`;
|
const inputPlaceholder = `Phase ${phaseIndex + 1}`;
|
||||||
const inputValue = field.metadata?.name;
|
const inputValue = field.metadata?.name;
|
||||||
|
|
||||||
if (isClaimPhaseV1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomFormControl
|
<CustomFormControl
|
||||||
disabled={formDisabled}
|
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,
|
MenuList,
|
||||||
Spinner,
|
Spinner,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import {
|
|
||||||
type DropContract,
|
|
||||||
useClaimConditions,
|
|
||||||
useSetClaimConditions,
|
|
||||||
} from "@thirdweb-dev/react";
|
|
||||||
import {
|
import {
|
||||||
type ClaimConditionInput,
|
type ClaimConditionInput,
|
||||||
ClaimConditionInputSchema,
|
ClaimConditionInputSchema,
|
||||||
@@ -42,12 +37,17 @@ import {
|
|||||||
ZERO_ADDRESS,
|
ZERO_ADDRESS,
|
||||||
} from "thirdweb";
|
} from "thirdweb";
|
||||||
import { decimals } from "thirdweb/extensions/erc20";
|
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 invariant from "tiny-invariant";
|
||||||
import { Button, Heading, MenuItem, Text } from "tw-components";
|
import { Button, Heading, MenuItem, Text } from "tw-components";
|
||||||
import * as z from "zod";
|
import * as z from "zod";
|
||||||
import { ZodError } from "zod";
|
import { ZodError } from "zod";
|
||||||
import { ResetClaimEligibility } from "../reset-claim-eligibility";
|
import { ResetClaimEligibility } from "../reset-claim-eligibility";
|
||||||
|
import { getClaimPhasesInLegacyFormat, setClaimPhasesTx } from "./hooks";
|
||||||
import { ClaimConditionsPhase } from "./phase";
|
import { ClaimConditionsPhase } from "./phase";
|
||||||
|
|
||||||
type ClaimConditionDashboardInput = ClaimConditionInput & {
|
type ClaimConditionDashboardInput = ClaimConditionInput & {
|
||||||
@@ -59,7 +59,6 @@ const DEFAULT_PHASE: ClaimConditionDashboardInput = {
|
|||||||
startTime: new Date(),
|
startTime: new Date(),
|
||||||
maxClaimableSupply: "unlimited",
|
maxClaimableSupply: "unlimited",
|
||||||
maxClaimablePerWallet: "unlimited",
|
maxClaimablePerWallet: "unlimited",
|
||||||
waitInSeconds: "0",
|
|
||||||
price: "0",
|
price: "0",
|
||||||
currencyAddress: NATIVE_TOKEN_ADDRESS,
|
currencyAddress: NATIVE_TOKEN_ADDRESS,
|
||||||
snapshot: undefined,
|
snapshot: undefined,
|
||||||
@@ -153,7 +152,6 @@ interface ClaimsConditionFormContextData {
|
|||||||
phaseIndex: number;
|
phaseIndex: number;
|
||||||
formDisabled: boolean;
|
formDisabled: boolean;
|
||||||
tokenDecimals: number;
|
tokenDecimals: number;
|
||||||
isClaimPhaseV1: boolean;
|
|
||||||
isMultiPhase: boolean;
|
isMultiPhase: boolean;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
dropType: DropType;
|
dropType: DropType;
|
||||||
@@ -177,14 +175,10 @@ export function useClaimConditionsFormContext() {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: refactor, this is no longer supported
|
|
||||||
const isClaimPhaseV1 = false;
|
|
||||||
|
|
||||||
interface ClaimConditionsFormProps {
|
interface ClaimConditionsFormProps {
|
||||||
contract: DropContract;
|
contract: ThirdwebContract;
|
||||||
tokenId?: string;
|
tokenId?: string;
|
||||||
isColumn?: true;
|
isColumn?: true;
|
||||||
contractV5: ThirdwebContract;
|
|
||||||
isErc20: boolean;
|
isErc20: boolean;
|
||||||
isMultiPhase: boolean;
|
isMultiPhase: boolean;
|
||||||
}
|
}
|
||||||
@@ -193,26 +187,39 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
|
|||||||
contract,
|
contract,
|
||||||
tokenId,
|
tokenId,
|
||||||
isColumn,
|
isColumn,
|
||||||
contractV5,
|
|
||||||
isErc20,
|
isErc20,
|
||||||
isMultiPhase,
|
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 walletAddress = useActiveAccount()?.address;
|
||||||
const trackEvent = useTrack();
|
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 tokenDecimalsData = tokenDecimals.data ?? 0;
|
||||||
const saveClaimPhaseNotification = useTxNotifications(
|
const saveClaimPhaseNotification = useTxNotifications(
|
||||||
"Saved claim phases",
|
"Saved claim phases",
|
||||||
"Failed to save claim phases",
|
"Failed to save claim phases",
|
||||||
);
|
);
|
||||||
|
|
||||||
const claimConditionsQuery = useClaimConditions(contract, tokenId, {
|
const claimConditionsQuery = useReadContract(getClaimPhasesInLegacyFormat, {
|
||||||
withAllowList: true,
|
contract,
|
||||||
|
...(isErc20
|
||||||
|
? { type: "erc20" }
|
||||||
|
: isErc721
|
||||||
|
? { type: "erc721" }
|
||||||
|
: { type: "erc1155", tokenId: BigInt(tokenId || 0) }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const transformedQueryData = useMemo(() => {
|
const transformedQueryData = useMemo(() => {
|
||||||
@@ -227,7 +234,6 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
|
|||||||
},
|
},
|
||||||
currencyAddress: phase.currencyAddress?.toLowerCase() || "0",
|
currencyAddress: phase.currencyAddress?.toLowerCase() || "0",
|
||||||
maxClaimablePerWallet: phase.maxClaimablePerWallet?.toString() || "0",
|
maxClaimablePerWallet: phase.maxClaimablePerWallet?.toString() || "0",
|
||||||
waitInSeconds: phase.waitInSeconds?.toString() || "0",
|
|
||||||
startTime: new Date(phase.startTime),
|
startTime: new Date(phase.startTime),
|
||||||
snapshot: phase.snapshot?.map(
|
snapshot: phase.snapshot?.map(
|
||||||
({ address, maxClaimable, price, currencyAddress }) => ({
|
({ address, maxClaimable, price, currencyAddress }) => ({
|
||||||
@@ -249,8 +255,7 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
|
|||||||
);
|
);
|
||||||
}, [claimConditionsQuery.data, isMultiPhase]);
|
}, [claimConditionsQuery.data, isMultiPhase]);
|
||||||
|
|
||||||
const isFetchingData =
|
const isFetchingData = claimConditionsQuery.isFetching || sendTx.isPending;
|
||||||
claimConditionsQuery.isFetching || setClaimConditionsQuery.isLoading;
|
|
||||||
|
|
||||||
const canEditForm = isAdmin && !isFetchingData;
|
const canEditForm = isAdmin && !isFetchingData;
|
||||||
|
|
||||||
@@ -346,10 +351,18 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await setClaimConditionsQuery.mutateAsync({
|
const tx = setClaimPhasesTx(
|
||||||
phases: d.phases as ClaimConditionInput[],
|
{
|
||||||
reset: resetFlag,
|
contract,
|
||||||
});
|
...(isErc20
|
||||||
|
? { type: "erc20" }
|
||||||
|
: isErc721
|
||||||
|
? { type: "erc721" }
|
||||||
|
: { type: "erc1155", tokenId: BigInt(tokenId || 0) }),
|
||||||
|
},
|
||||||
|
d.phases,
|
||||||
|
);
|
||||||
|
await sendTx.mutateAsync(tx);
|
||||||
trackEvent({
|
trackEvent({
|
||||||
category,
|
category,
|
||||||
action: "set-claim-conditions",
|
action: "set-claim-conditions",
|
||||||
@@ -365,6 +378,7 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
|
|||||||
|
|
||||||
form.setValue("phases", newPhases);
|
form.setValue("phases", newPhases);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
trackEvent({
|
trackEvent({
|
||||||
category,
|
category,
|
||||||
action: "set-claim-conditions",
|
action: "set-claim-conditions",
|
||||||
@@ -448,8 +462,7 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
|
|||||||
)}
|
)}
|
||||||
{controlledFields.map((field, index) => {
|
{controlledFields.map((field, index) => {
|
||||||
const dropType: DropType = field.snapshot
|
const dropType: DropType = field.snapshot
|
||||||
? isClaimPhaseV1 ||
|
? field.maxClaimablePerWallet?.toString() === "0"
|
||||||
field.maxClaimablePerWallet?.toString() === "0"
|
|
||||||
? "specific"
|
? "specific"
|
||||||
: "overrides"
|
: "overrides"
|
||||||
: "any";
|
: "any";
|
||||||
@@ -478,7 +491,6 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
|
|||||||
<Fragment key={`snapshot_${field.id}_${index}`}>
|
<Fragment key={`snapshot_${field.id}_${index}`}>
|
||||||
<SnapshotUpload
|
<SnapshotUpload
|
||||||
dropType={dropType}
|
dropType={dropType}
|
||||||
isV1ClaimCondition={isClaimPhaseV1}
|
|
||||||
isOpen={openSnapshotIndex === index}
|
isOpen={openSnapshotIndex === index}
|
||||||
onClose={() => setOpenSnapshotIndex(-1)}
|
onClose={() => setOpenSnapshotIndex(-1)}
|
||||||
value={snapshotValue}
|
value={snapshotValue}
|
||||||
@@ -496,7 +508,6 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
|
|||||||
formDisabled: !canEditForm,
|
formDisabled: !canEditForm,
|
||||||
isErc20,
|
isErc20,
|
||||||
tokenDecimals: tokenDecimalsData,
|
tokenDecimals: tokenDecimalsData,
|
||||||
isClaimPhaseV1,
|
|
||||||
dropType,
|
dropType,
|
||||||
setOpenSnapshotIndex,
|
setOpenSnapshotIndex,
|
||||||
isAdmin,
|
isAdmin,
|
||||||
@@ -507,14 +518,11 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ClaimConditionsPhase
|
<ClaimConditionsPhase
|
||||||
contract={contractV5}
|
contract={contract}
|
||||||
onRemove={() => {
|
onRemove={() => {
|
||||||
removePhase(index);
|
removePhase(index);
|
||||||
if (!isMultiPhase) {
|
|
||||||
setResetFlag(true);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
isLoading={setClaimConditionsQuery.isLoading}
|
isLoading={sendTx.isPending}
|
||||||
/>
|
/>
|
||||||
</ClaimsConditionFormContext.Provider>
|
</ClaimsConditionFormContext.Provider>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
@@ -545,115 +553,92 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
|
|||||||
gap={2}
|
gap={2}
|
||||||
>
|
>
|
||||||
<Flex gap={2}>
|
<Flex gap={2}>
|
||||||
{contractV5 && (
|
<AdminOnly contract={contract}>
|
||||||
<AdminOnly contract={contractV5}>
|
<Menu>
|
||||||
{!isClaimPhaseV1 ? (
|
<MenuButton
|
||||||
<>
|
as={Button}
|
||||||
<Menu>
|
size="sm"
|
||||||
<MenuButton
|
colorScheme="primary"
|
||||||
as={Button}
|
variant={phases?.length > 0 ? "outline" : "solid"}
|
||||||
size="sm"
|
borderRadius="md"
|
||||||
colorScheme="primary"
|
leftIcon={<Icon as={FiPlus} />}
|
||||||
variant={phases?.length > 0 ? "outline" : "solid"}
|
isDisabled={
|
||||||
borderRadius="md"
|
sendTx.isPending || (!isMultiPhase && phases?.length > 0)
|
||||||
leftIcon={<Icon as={FiPlus} />}
|
}
|
||||||
isDisabled={
|
>
|
||||||
setClaimConditionsQuery.isLoading ||
|
Add {isMultiPhase ? "Phase" : "Claim Conditions"}
|
||||||
(!isMultiPhase && phases?.length > 0)
|
</MenuButton>
|
||||||
}
|
<MenuList
|
||||||
>
|
borderRadius="lg"
|
||||||
Add {isMultiPhase ? "Phase" : "Claim Conditions"}
|
overflow="hidden"
|
||||||
</MenuButton>
|
zIndex="overlay"
|
||||||
<MenuList
|
>
|
||||||
borderRadius="lg"
|
{Object.keys(ClaimConditionTypeData).map((key) => {
|
||||||
overflow="hidden"
|
const type = key as ClaimConditionType;
|
||||||
zIndex="overlay"
|
|
||||||
>
|
|
||||||
{Object.keys(ClaimConditionTypeData).map((key) => {
|
|
||||||
const type = key as ClaimConditionType;
|
|
||||||
|
|
||||||
if (type === "custom") {
|
if (type === "custom") {
|
||||||
return null;
|
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) && (
|
{controlledFields.some((field) => field.fromSdk) && (
|
||||||
<ResetClaimEligibility
|
<ResetClaimEligibility
|
||||||
isErc20={isErc20}
|
isErc20={isErc20}
|
||||||
contract={contractV5}
|
contract={contract}
|
||||||
tokenId={tokenId}
|
tokenId={tokenId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Flex>
|
<Flex>
|
||||||
{contractV5 && (
|
<AdminOnly contract={contract} fallback={<Box pb={5} />}>
|
||||||
<AdminOnly contract={contractV5} fallback={<Box pb={5} />}>
|
<Flex justifyContent="center" alignItems="center" gap={3}>
|
||||||
<Flex justifyContent="center" alignItems="center" gap={3}>
|
{(hasRemovedPhases || hasAddedPhases) && (
|
||||||
{(hasRemovedPhases || hasAddedPhases) && (
|
<Text color="red.500" fontWeight="bold">
|
||||||
<Text color="red.500" fontWeight="bold">
|
You have unsaved changes
|
||||||
You have unsaved changes
|
</Text>
|
||||||
</Text>
|
)}
|
||||||
)}
|
{controlledFields.length > 0 ||
|
||||||
{controlledFields.length > 0 ||
|
hasRemovedPhases ||
|
||||||
hasRemovedPhases ||
|
!isMultiPhase ? (
|
||||||
!isMultiPhase ? (
|
<TransactionButton
|
||||||
<TransactionButton
|
colorScheme="primary"
|
||||||
colorScheme="primary"
|
transactionCount={1}
|
||||||
transactionCount={1}
|
isDisabled={claimConditionsQuery.isLoading}
|
||||||
isDisabled={claimConditionsQuery.isLoading}
|
type="submit"
|
||||||
type="submit"
|
isLoading={sendTx.isPending}
|
||||||
isLoading={setClaimConditionsQuery.isLoading}
|
loadingText="Saving..."
|
||||||
loadingText="Saving..."
|
size="md"
|
||||||
size="md"
|
>
|
||||||
>
|
Save Phases
|
||||||
Save Phases
|
</TransactionButton>
|
||||||
</TransactionButton>
|
) : null}
|
||||||
) : null}
|
</Flex>
|
||||||
</Flex>
|
</AdminOnly>
|
||||||
</AdminOnly>
|
|
||||||
)}
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
+3
-15
@@ -30,7 +30,6 @@ export const ClaimConditionsPhase: React.FC<ClaimConditionsPhaseProps> = ({
|
|||||||
form,
|
form,
|
||||||
field,
|
field,
|
||||||
isErc20,
|
isErc20,
|
||||||
isClaimPhaseV1,
|
|
||||||
isAdmin,
|
isAdmin,
|
||||||
claimConditionType,
|
claimConditionType,
|
||||||
isActive,
|
isActive,
|
||||||
@@ -82,26 +81,15 @@ export const ClaimConditionsPhase: React.FC<ClaimConditionsPhaseProps> = ({
|
|||||||
|
|
||||||
<Flex flexDir="column" gap={2} mt={{ base: 4, md: 0 }}>
|
<Flex flexDir="column" gap={2} mt={{ base: 4, md: 0 }}>
|
||||||
<Flex gap={3} alignItems="center">
|
<Flex gap={3} alignItems="center">
|
||||||
<Heading>
|
<Heading>{ClaimConditionTypeData[claimConditionType].name}</Heading>
|
||||||
{isClaimPhaseV1
|
|
||||||
? isMultiPhase
|
|
||||||
? `Claim Phase ${phaseIndex + 1}`
|
|
||||||
: "Claim Conditions"
|
|
||||||
: ClaimConditionTypeData[claimConditionType].name}
|
|
||||||
</Heading>
|
|
||||||
{isActive && (
|
{isActive && (
|
||||||
<Badge colorScheme="green" borderRadius="lg" p={1.5}>
|
<Badge colorScheme="green" borderRadius="lg" p={1.5}>
|
||||||
Currently active
|
Currently active
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
{isClaimPhaseV1 ? (
|
|
||||||
""
|
<Text>{ClaimConditionTypeData[claimConditionType].description}</Text>
|
||||||
) : (
|
|
||||||
<Text>
|
|
||||||
{ClaimConditionTypeData[claimConditionType].description}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{!field.isEditing ? (
|
{!field.isEditing ? (
|
||||||
|
|||||||
+8
-14
@@ -1,5 +1,4 @@
|
|||||||
import { Flex, Stack } from "@chakra-ui/react";
|
import { Flex, Stack } from "@chakra-ui/react";
|
||||||
import { useContract } from "@thirdweb-dev/react";
|
|
||||||
import type { ThirdwebContract } from "thirdweb";
|
import type { ThirdwebContract } from "thirdweb";
|
||||||
import { Heading, Text } from "tw-components";
|
import { Heading, Text } from "tw-components";
|
||||||
import { ClaimConditionsForm } from "./claim-conditions-form/index";
|
import { ClaimConditionsForm } from "./claim-conditions-form/index";
|
||||||
@@ -16,8 +15,6 @@ export const ClaimConditions: React.FC<ClaimConditionsProps> = ({
|
|||||||
isColumn,
|
isColumn,
|
||||||
isERC20,
|
isERC20,
|
||||||
}) => {
|
}) => {
|
||||||
const contractQuery = useContract(contract.address);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing={8}>
|
<Stack spacing={8}>
|
||||||
<Flex p={0} position="relative">
|
<Flex p={0} position="relative">
|
||||||
@@ -39,17 +36,14 @@ export const ClaimConditions: React.FC<ClaimConditionsProps> = ({
|
|||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{/* Set Claim Conditions */}
|
{/* Set Claim Conditions */}
|
||||||
{contractQuery.contract && (
|
<ClaimConditionsForm
|
||||||
<ClaimConditionsForm
|
isErc20={isERC20}
|
||||||
isErc20={isERC20}
|
contract={contract}
|
||||||
contract={contractQuery.contract}
|
tokenId={tokenId}
|
||||||
tokenId={tokenId}
|
isColumn={isColumn}
|
||||||
isColumn={isColumn}
|
// always multi phase!
|
||||||
contractV5={contract}
|
isMultiPhase={true}
|
||||||
// always multi phase!
|
/>
|
||||||
isMultiPhase={true}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
+1
-4
@@ -87,9 +87,6 @@ export const ResetClaimEligibility: React.FC<ResetClaimEligibilityProps> = ({
|
|||||||
return (
|
return (
|
||||||
<AdminOnly contract={contract} fallback={<div className="pb-5" />}>
|
<AdminOnly contract={contract} fallback={<div className="pb-5" />}>
|
||||||
<TransactionButton
|
<TransactionButton
|
||||||
colorScheme="secondary"
|
|
||||||
bg="bgBlack"
|
|
||||||
color="bgWhite"
|
|
||||||
transactionCount={1}
|
transactionCount={1}
|
||||||
type="button"
|
type="button"
|
||||||
isLoading={sendTxMutation.isPending}
|
isLoading={sendTxMutation.isPending}
|
||||||
@@ -97,7 +94,7 @@ export const ResetClaimEligibility: React.FC<ResetClaimEligibilityProps> = ({
|
|||||||
loadingText="Resetting..."
|
loadingText="Resetting..."
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
Reset Eligibility{" "}
|
Reset Eligibility
|
||||||
<ToolTipLabel
|
<ToolTipLabel
|
||||||
label={
|
label={
|
||||||
<>
|
<>
|
||||||
|
|||||||
+66
-115
@@ -27,7 +27,7 @@ import {
|
|||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import { Logo } from "components/logo";
|
import { Logo } from "components/logo";
|
||||||
import Papa from "papaparse";
|
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 { type DropzoneOptions, useDropzone } from "react-dropzone";
|
||||||
import { BsFillCloudUploadFill } from "react-icons/bs";
|
import { BsFillCloudUploadFill } from "react-icons/bs";
|
||||||
import { FiDownload } from "react-icons/fi";
|
import { FiDownload } from "react-icons/fi";
|
||||||
@@ -64,7 +64,6 @@ interface SnapshotUploadProps {
|
|||||||
}[]
|
}[]
|
||||||
| null;
|
| null;
|
||||||
dropType: "specific" | "any" | "overrides";
|
dropType: "specific" | "any" | "overrides";
|
||||||
isV1ClaimCondition: boolean;
|
|
||||||
isDisabled: boolean;
|
isDisabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +73,6 @@ export const SnapshotUpload: React.FC<SnapshotUploadProps> = ({
|
|||||||
onClose,
|
onClose,
|
||||||
value,
|
value,
|
||||||
dropType,
|
dropType,
|
||||||
isV1ClaimCondition,
|
|
||||||
isDisabled,
|
isDisabled,
|
||||||
}) => {
|
}) => {
|
||||||
const [validSnapshot, setValidSnapshot] = useState<SnapshotAddressInput[]>(
|
const [validSnapshot, setValidSnapshot] = useState<SnapshotAddressInput[]>(
|
||||||
@@ -224,11 +222,7 @@ export const SnapshotUpload: React.FC<SnapshotUploadProps> = ({
|
|||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{validSnapshot.length > 0 ? (
|
{validSnapshot.length > 0 ? (
|
||||||
<SnapshotTable
|
<SnapshotTable portalRef={paginationPortalRef} data={snapshotData} />
|
||||||
portalRef={paginationPortalRef}
|
|
||||||
data={snapshotData}
|
|
||||||
isV1ClaimCondition={isV1ClaimCondition}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<Flex flexGrow={1} align="center" overflow="auto">
|
<Flex flexGrow={1} align="center" overflow="auto">
|
||||||
<Container maxW="container.page">
|
<Container maxW="container.page">
|
||||||
@@ -275,33 +269,7 @@ export const SnapshotUpload: React.FC<SnapshotUploadProps> = ({
|
|||||||
<Flex gap={2} flexDir="column">
|
<Flex gap={2} flexDir="column">
|
||||||
<Heading size="label.md">Requirements</Heading>
|
<Heading size="label.md">Requirements</Heading>
|
||||||
<UnorderedList spacing={1}>
|
<UnorderedList spacing={1}>
|
||||||
{isV1ClaimCondition ? (
|
{dropType === "specific" ? (
|
||||||
<>
|
|
||||||
<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" ? (
|
|
||||||
<>
|
<>
|
||||||
<Text as={ListItem}>
|
<Text as={ListItem}>
|
||||||
Files <em>must</em> contain one .csv file with a list
|
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
|
Repeated addresses will be removed and only the first
|
||||||
found will be kept.
|
found will be kept.
|
||||||
</Text>
|
</Text>
|
||||||
{!isV1ClaimCondition && (
|
<Text as={ListItem}>
|
||||||
<Text as={ListItem}>
|
The limit you set is for the maximum amount of NFTs a
|
||||||
The limit you set is for the maximum amount of NFTs a
|
wallet can claim, not how many they can receive in total.
|
||||||
wallet can claim, not how many they can receive in
|
</Text>
|
||||||
total.
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</UnorderedList>
|
</UnorderedList>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -459,81 +424,67 @@ export const SnapshotUpload: React.FC<SnapshotUploadProps> = ({
|
|||||||
interface SnapshotTableProps {
|
interface SnapshotTableProps {
|
||||||
data: SnapshotAddressInput[];
|
data: SnapshotAddressInput[];
|
||||||
portalRef: React.RefObject<HTMLDivElement>;
|
portalRef: React.RefObject<HTMLDivElement>;
|
||||||
isV1ClaimCondition: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const SnapshotTable: React.FC<SnapshotTableProps> = ({
|
const SnapshotTableColumns = [
|
||||||
data,
|
{
|
||||||
portalRef,
|
Header: "Address",
|
||||||
isV1ClaimCondition,
|
accessor: ({ address, isValid }) => {
|
||||||
}) => {
|
if (isValid) {
|
||||||
const columns = useMemo(() => {
|
return address;
|
||||||
let cols = [
|
}
|
||||||
{
|
return (
|
||||||
Header: "Address",
|
<Flex>
|
||||||
accessor: ({ address, isValid }) => {
|
<Tooltip
|
||||||
if (isValid) {
|
label={
|
||||||
return address;
|
address.startsWith("0x")
|
||||||
}
|
? "Address is not valid"
|
||||||
return (
|
: "Address couldn't be resolved"
|
||||||
<Flex>
|
}
|
||||||
<Tooltip
|
>
|
||||||
label={
|
<Stack direction="row" align="center">
|
||||||
address.startsWith("0x")
|
<Icon as={IoAlertCircleOutline} color="red.500" boxSize={5} />
|
||||||
? "Address is not valid"
|
<Text fontWeight="bold" color="red.500" cursor="default">
|
||||||
: "Address couldn't be resolved"
|
{address}
|
||||||
}
|
</Text>
|
||||||
>
|
</Stack>
|
||||||
<Stack direction="row" align="center">
|
</Tooltip>
|
||||||
<Icon as={IoAlertCircleOutline} color="red.500" boxSize={5} />
|
</Flex>
|
||||||
<Text fontWeight="bold" color="red.500" cursor="default">
|
);
|
||||||
{address}
|
},
|
||||||
</Text>
|
},
|
||||||
</Stack>
|
{
|
||||||
</Tooltip>
|
Header: "Max claimable",
|
||||||
</Flex>
|
accessor: ({ maxClaimable }) => {
|
||||||
);
|
return maxClaimable === "0" || !maxClaimable
|
||||||
},
|
? "Default"
|
||||||
},
|
: maxClaimable === "unlimited"
|
||||||
{
|
? "Unlimited"
|
||||||
Header: "Max claimable",
|
: maxClaimable;
|
||||||
accessor: ({ maxClaimable }) => {
|
},
|
||||||
return maxClaimable === "0" || !maxClaimable
|
},
|
||||||
? "Default"
|
{
|
||||||
: maxClaimable === "unlimited"
|
Header: "Price",
|
||||||
? "Unlimited"
|
accessor: ({ price }) => {
|
||||||
: maxClaimable;
|
return price === "0"
|
||||||
},
|
? "Free"
|
||||||
},
|
: !price || price === "unlimited"
|
||||||
] as Column<SnapshotAddressInput>[];
|
? "Default"
|
||||||
|
: price;
|
||||||
if (!isV1ClaimCondition) {
|
},
|
||||||
cols = cols.concat([
|
},
|
||||||
{
|
{
|
||||||
Header: "Price",
|
Header: "Currency Address",
|
||||||
accessor: ({ price }) => {
|
accessor: ({ currencyAddress }) => {
|
||||||
return price === "0"
|
return currencyAddress === "0x0000000000000000000000000000000000000000" ||
|
||||||
? "Free"
|
!currencyAddress
|
||||||
: !price || price === "unlimited"
|
? "Default"
|
||||||
? "Default"
|
: currencyAddress;
|
||||||
: price;
|
},
|
||||||
},
|
},
|
||||||
},
|
] as Column<SnapshotAddressInput>[];
|
||||||
{
|
|
||||||
Header: "Currency Address",
|
|
||||||
accessor: ({ currencyAddress }) => {
|
|
||||||
return currencyAddress ===
|
|
||||||
"0x0000000000000000000000000000000000000000" || !currencyAddress
|
|
||||||
? "Default"
|
|
||||||
: currencyAddress;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return cols;
|
|
||||||
}, [isV1ClaimCondition]);
|
|
||||||
|
|
||||||
|
const SnapshotTable: React.FC<SnapshotTableProps> = ({ data, portalRef }) => {
|
||||||
const {
|
const {
|
||||||
getTableProps,
|
getTableProps,
|
||||||
getTableBodyProps,
|
getTableBodyProps,
|
||||||
@@ -555,7 +506,7 @@ const SnapshotTable: React.FC<SnapshotTableProps> = ({
|
|||||||
state: { pageIndex, pageSize },
|
state: { pageIndex, pageSize },
|
||||||
} = useTable(
|
} = useTable(
|
||||||
{
|
{
|
||||||
columns,
|
columns: SnapshotTableColumns,
|
||||||
data,
|
data,
|
||||||
initialState: {
|
initialState: {
|
||||||
pageSize: 50,
|
pageSize: 50,
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ export const NFTRevealButton: React.FC<NFTRevealButtonProps> = ({
|
|||||||
const batchesQuery = useReadContract(getBatchesToReveal, {
|
const batchesQuery = useReadContract(getBatchesToReveal, {
|
||||||
contract,
|
contract,
|
||||||
});
|
});
|
||||||
console.log("*** batches", batchesQuery);
|
|
||||||
return batchesQuery.data?.length ? (
|
return batchesQuery.data?.length ? (
|
||||||
<MinterOnly contract={contract}>
|
<MinterOnly contract={contract}>
|
||||||
<Drawer
|
<Drawer
|
||||||
|
|||||||
@@ -54,6 +54,16 @@ function parseError(error: unknown): string | JSX.Element {
|
|||||||
return error.message;
|
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
|
// everything that falls through here should be logged and sent to posthog
|
||||||
console.error("unknown error", error);
|
console.error("unknown error", error);
|
||||||
posthog.capture("unknown_error", { error });
|
posthog.capture("unknown_error", { error });
|
||||||
|
|||||||
@@ -22,24 +22,30 @@ export async function convertErc20Amount(
|
|||||||
} & AmountOrAmountInWei
|
} & AmountOrAmountInWei
|
||||||
>,
|
>,
|
||||||
): Promise<bigint> {
|
): Promise<bigint> {
|
||||||
if ("amount" in options) {
|
try {
|
||||||
// for native token, we know decimals are 18
|
if ("amount" in options) {
|
||||||
if (!options.erc20Address || isNativeTokenAddress(options.erc20Address)) {
|
// for native token, we know decimals are 18
|
||||||
return toUnits(options.amount.toString(), 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
|
return options.amountInWei;
|
||||||
const currencyContract = getContract({
|
} catch (e) {
|
||||||
client: options.client,
|
throw new Error(
|
||||||
chain: options.chain,
|
`Failed to convert ERC20 amount for token: ${options.erc20Address}: ${e}`,
|
||||||
address: options.erc20Address,
|
|
||||||
});
|
|
||||||
const { decimals } = await import(
|
|
||||||
"../../extensions/erc20/read/decimals.js"
|
|
||||||
);
|
);
|
||||||
const currencyDecimals = await decimals({
|
|
||||||
contract: currencyContract,
|
|
||||||
});
|
|
||||||
return toUnits(options.amount.toString(), currencyDecimals);
|
|
||||||
}
|
}
|
||||||
return options.amountInWei;
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user