[Update] Migrate contract analytics to v5 (#4366)

<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on updating imports and types related to ABI parameters in the codebase.

### Detailed summary
- Updated imports from `"@thirdweb-dev/sdk"` to `"abitype"`
- Changed types to use `AbiParameter` instead of previous types like `Abi`, `AbiFunction`, `AbiEvent`
- Fixed type errors and updated type annotations throughout the codebase

> The following files were skipped due to too many changes: `apps/dashboard/src/components/contract-functions/interactive-abi-function.tsx`, `apps/dashboard/src/contract-ui/tabs/overview/components/NFTDetails.tsx`, `packages/thirdweb/src/extensions/thirdweb/read/contractType.ts`, `apps/dashboard/src/contract-ui/tabs/code/components/code-overview.tsx`, `apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx`, `apps/dashboard/src/components/contract-components/contract-publish-form/index.tsx`, `apps/dashboard/src/contract-ui/tabs/analytics/page.tsx`, `apps/dashboard/src/contract-ui/tabs/overview/page.tsx`, `apps/dashboard/src/components/contract-components/hooks.ts`, `apps/dashboard/src/components/contract-functions/contract-function.tsx`, `apps/dashboard/src/contract-ui/hooks/useRouteConfig.tsx`

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
This commit is contained in:
jnsdls
2024-09-04 01:54:02 +00:00
parent 961445f9d6
commit 0246529e61
26 changed files with 394 additions and 901 deletions
@@ -12,6 +12,7 @@ import {
Flex,
FormControl,
} from "@chakra-ui/react";
import type { Abi } from "abitype";
import { TransactionButton } from "components/buttons/TransactionButton";
import { NetworkSelectorButton } from "components/selects/NetworkSelectorButton";
import { DeprecatedAlert } from "components/shared/DeprecatedAlert";
@@ -32,7 +33,6 @@ import { encodeAbiParameters } from "thirdweb/utils";
import invariant from "tiny-invariant";
import { FormHelperText, FormLabel, Heading, Text } from "tw-components";
import {
useConstructorParamsFromABI,
useContractEnabledExtensions,
useContractFullPublishMetadata,
useContractPublishMetadataFromURI,
@@ -123,9 +123,9 @@ const CustomContractForm: React.FC<CustomContractFormProps> = ({
const compilerMetadata = useContractPublishMetadataFromURI(ipfsHash);
const fullPublishMetadata = useContractFullPublishMetadata(ipfsHash);
const constructorParams = useConstructorParamsFromABI(
compilerMetadata.data?.abi,
);
const constructorParams =
compilerMetadata.data?.abi?.find((c) => c.type === "constructor")?.inputs ||
[];
const [customFactoryNetwork, customFactoryAddress] = Object.entries(
fullPublishMetadata.data?.factoryDeploymentData?.customFactoryInput
@@ -141,10 +141,10 @@ const CustomContractForm: React.FC<CustomContractFormProps> = ({
fullPublishMetadata.data?.publisher === "deployer.thirdweb.eth";
const initializerParams = useFunctionParamsFromABI(
fullPublishMetadata.data?.deployType === "customFactory" &&
customFactoryAbi?.data
(fullPublishMetadata.data?.deployType === "customFactory" &&
customFactoryAbi?.data
? customFactoryAbi.data
: compilerMetadata.data?.abi,
: compilerMetadata.data?.abi) as Abi | undefined,
fullPublishMetadata.data?.deployType === "customFactory"
? fullPublishMetadata.data?.factoryDeploymentData?.customFactoryInput
?.factoryFunction || "deployProxyByImplementation"
@@ -168,9 +168,8 @@ const CustomContractForm: React.FC<CustomContractFormProps> = ({
isQueryEnabled: isModular,
});
const deployParams = isFactoryDeployment
? initializerParams
: constructorParams;
const deployParams =
(isFactoryDeployment ? initializerParams : constructorParams) || [];
const isAccountFactory =
!isFactoryDeployment &&
@@ -319,7 +318,9 @@ const CustomContractForm: React.FC<CustomContractFormProps> = ({
return false;
};
const extensions = useContractEnabledExtensions(compilerMetadata.data?.abi);
const extensions = useContractEnabledExtensions(
compilerMetadata.data?.abi as Abi | undefined,
);
const isErc721SharedMetadadata = extensions.some(
(extension) => extension.name === "ERC721SharedMetadata",
);
@@ -17,7 +17,7 @@ interface ParamProps {
};
deployParam?: {
type: string;
name: string;
name?: string;
};
}
@@ -1,5 +1,5 @@
import { Flex } from "@chakra-ui/react";
import type { Abi } from "@thirdweb-dev/sdk";
import type { Abi } from "abitype";
import { Select } from "chakra-react-select";
import { useMemo } from "react";
@@ -16,16 +16,15 @@ export const AbiSelector: React.FC<AbiSelectorProps> = ({
defaultValue,
onChange,
}) => {
const writeFunctions = abi.filter(
(f) => f.type === "function" && f.stateMutability !== "view",
);
const options = useMemo(() => {
return writeFunctions.map((f) => ({
label: f.name,
value: f.name,
}));
}, [writeFunctions]);
return abi
.filter((f) => f.type === "function")
.filter((f) => f.stateMutability !== "view")
.map((f) => ({
label: f.name,
value: f.name,
}));
}, [abi]);
return (
<Flex gap={2} alignItems="center" w="full">
@@ -9,6 +9,7 @@ import {
Tooltip,
useBreakpointValue,
} from "@chakra-ui/react";
import type { AbiParameter } from "abitype";
import { SolidityInput } from "contract-ui/components/solidity-inputs";
import { camelToTitle } from "contract-ui/components/solidity-inputs/helpers";
import { getTemplateValuesForType } from "lib/deployment/template-values";
@@ -23,15 +24,9 @@ import {
Heading,
Text,
} from "tw-components";
import type {
useConstructorParamsFromABI,
useFunctionParamsFromABI,
} from "../hooks";
interface ContractParamsFieldsetProps {
deployParams:
| ReturnType<typeof useFunctionParamsFromABI>
| ReturnType<typeof useConstructorParamsFromABI>;
deployParams: readonly AbiParameter[];
}
export const ContractParamsFieldset: React.FC<ContractParamsFieldsetProps> = ({
deployParams,
@@ -8,7 +8,7 @@ import {
ListItem,
UnorderedList,
} from "@chakra-ui/react";
import type { Abi } from "@thirdweb-dev/sdk";
import type { Abi } from "abitype";
import { type Dispatch, type SetStateAction, useEffect } from "react";
import { Controller, useFieldArray, useFormContext } from "react-hook-form";
import { FiPlus, FiTrash } from "react-icons/fi";
@@ -1,11 +1,7 @@
import { Flex, FormControl, ListItem, UnorderedList } from "@chakra-ui/react";
import type { Abi } from "@thirdweb-dev/sdk";
import type { Abi, AbiParameter } from "abitype";
import { useFormContext } from "react-hook-form";
import { Heading, Link, Text } from "tw-components";
import type {
useConstructorParamsFromABI,
useFunctionParamsFromABI,
} from "../hooks";
import { AbiSelector } from "./abi-selector";
import { DynamicContractsFieldset } from "./dynamic-contract-fieldset";
import { ModularContractsFieldset } from "./modular-contract-fieldset";
@@ -17,9 +13,7 @@ interface DefaultFactoryProps {
shouldShowDynamicFactoryInput: boolean;
shouldShowModularFactoryInput: boolean;
shouldShowModulesParamInput: boolean;
deployParams:
| ReturnType<typeof useFunctionParamsFromABI>
| ReturnType<typeof useConstructorParamsFromABI>;
deployParams: readonly AbiParameter[];
}
export const DefaultFactory: React.FC<DefaultFactoryProps> = ({
@@ -1,12 +1,8 @@
import { ButtonGroup, Flex } from "@chakra-ui/react";
import type { Abi } from "@thirdweb-dev/sdk";
import type { Abi, AbiParameter } from "abitype";
import type { Dispatch, SetStateAction } from "react";
import { useFormContext } from "react-hook-form";
import { Button, Heading } from "tw-components";
import type {
useConstructorParamsFromABI,
useFunctionParamsFromABI,
} from "../hooks";
import { CustomFactory } from "./custom-factory";
import { DefaultFactory } from "./default-factory";
@@ -16,9 +12,7 @@ interface FactoryFieldsetProps {
shouldShowDynamicFactoryInput: boolean;
shouldShowModularFactoryInput: boolean;
shouldShowModulesParamInput: boolean;
deployParams:
| ReturnType<typeof useFunctionParamsFromABI>
| ReturnType<typeof useConstructorParamsFromABI>;
deployParams: readonly AbiParameter[];
}
export const FactoryFieldset: React.FC<FactoryFieldsetProps> = ({
@@ -4,12 +4,12 @@ import { useIsomorphicLayoutEffect } from "@/lib/useIsomorphicLayoutEffect";
import { CustomConnectWallet } from "@3rdweb-sdk/react/components/connect-wallet";
import { Box, Divider, Flex, Icon, IconButton } from "@chakra-ui/react";
import {
type Abi,
CONTRACT_ADDRESSES,
ExtraPublishMetadataSchemaInput,
detectFeatures,
isExtensionEnabled,
} from "@thirdweb-dev/sdk";
import type { Abi } from "abitype";
import { defaultChains } from "constants/chains";
import { useTrack } from "hooks/analytics/useTrack";
import { useTxNotifications } from "hooks/useTxNotifications";
@@ -21,7 +21,6 @@ import { useActiveAccount } from "thirdweb/react";
import { Button, Text } from "tw-components";
import { z } from "zod";
import {
useConstructorParamsFromABI,
useContractFullPublishMetadata,
useContractPrePublishMetadata,
useContractPublishMetadataFromURI,
@@ -212,14 +211,14 @@ export const ContractPublishForm: React.FC<ContractPublishFormProps> = ({
);
const fullPublishMetadata = useContractFullPublishMetadata(contractId);
const constructorParams = useConstructorParamsFromABI(
publishMetadata.data?.abi,
);
const constructorParams =
publishMetadata.data?.abi?.find((c) => c.type === "constructor")?.inputs ||
[];
const initializerParams = useFunctionParamsFromABI(
form.watch("deployType") === "customFactory"
(form.watch("deployType") === "customFactory"
? customFactoryAbi
: publishMetadata.data?.abi,
: publishMetadata.data?.abi) as Abi,
form.watch("deployType") === "customFactory"
? form.watch(
"factoryDeploymentData.customFactoryInput.factoryFunction",
@@ -238,12 +237,12 @@ export const ContractPublishForm: React.FC<ContractPublishFormProps> = ({
? constructorParams
: initializerParams;
const extensions = detectFeatures(publishMetadata.data?.abi as Abi);
const extensions = detectFeatures(publishMetadata.data?.abi || []);
const isPluginRouter = useMemo(
() =>
isExtensionEnabled(
publishMetadata.data?.abi as Abi,
publishMetadata.data?.abi || [],
"PluginRouter",
extensions,
),
@@ -253,7 +252,7 @@ export const ContractPublishForm: React.FC<ContractPublishFormProps> = ({
const isDynamicContract = useMemo(
() =>
isExtensionEnabled(
publishMetadata.data?.abi as Abi,
publishMetadata.data?.abi || [],
"DynamicContract",
extensions,
),
@@ -263,7 +262,7 @@ export const ContractPublishForm: React.FC<ContractPublishFormProps> = ({
const isModularContract = useMemo(
() =>
isExtensionEnabled(
publishMetadata.data?.abi as Abi,
publishMetadata.data?.abi || [],
"ModularCore",
extensions,
),
@@ -445,7 +444,7 @@ export const ContractPublishForm: React.FC<ContractPublishFormProps> = ({
{fieldsetToShow === "factory" && (
<Flex flexDir="column" gap={24}>
<FactoryFieldset
abi={publishMetadata.data?.abi || []}
abi={(publishMetadata.data?.abi || []) as Abi}
setCustomFactoryAbi={setCustomFactoryAbi}
shouldShowDynamicFactoryInput={shouldShowDynamicFactoryInput}
shouldShowModularFactoryInput={shouldShowModularFactoryInput}
@@ -6,16 +6,11 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import type { AbiParameter } from "abitype";
import type React from "react";
import type {
useConstructorParamsFromABI,
useFunctionParamsFromABI,
} from "../hooks";
interface ModulesParamSelectorProps {
deployParams:
| ReturnType<typeof useFunctionParamsFromABI>
| ReturnType<typeof useConstructorParamsFromABI>;
deployParams: readonly AbiParameter[];
value: string;
onChange: (fn: string) => void;
}
@@ -34,7 +29,7 @@ export const ModulesParamSelector: React.FC<ModulesParamSelectorProps> = ({
<SelectGroup>
{deployParams.map((param) => {
return (
<SelectItem key={param.name} value={param.name}>
<SelectItem key={param.name} value={param.name || ""}>
{param.name}
</SelectItem>
);
@@ -12,7 +12,6 @@ import {
} from "@tanstack/react-query";
import { useSDK, useSigner } from "@thirdweb-dev/react";
import {
type Abi,
type DeploymentTransaction,
type ExtraPublishMetadata,
type FeatureName,
@@ -20,10 +19,6 @@ import {
type PublishedContract,
type ThirdwebSDK,
detectFeatures,
extractConstructorParamsFromAbi,
extractEventsFromAbi,
extractFunctionParamsFromAbi,
extractFunctionsFromAbi,
fetchAndCacheDeployMetadata,
fetchContractMetadata,
fetchPreDeployMetadata,
@@ -35,6 +30,7 @@ import {
getZkTransactionsForDeploy,
zkDeployContractFromUri,
} from "@thirdweb-dev/sdk/evm/zksync";
import type { Abi } from "abitype";
import type { ProfileMetadata, ProfileMetadataInput } from "constants/schemas";
import type { providers } from "ethers";
import { useSupportedChain } from "hooks/chains/configureChains";
@@ -43,6 +39,7 @@ import { getDashboardChainRpc } from "lib/rpc";
import { StorageSingleton, getThirdwebSDK } from "lib/sdk";
import { useRouter } from "next/router";
import { useMemo } from "react";
import { getContract } from "thirdweb";
import {
abstractTestnet,
polygon,
@@ -50,6 +47,7 @@ import {
zkSync,
zkSyncSepolia,
} from "thirdweb/chains";
import { resolveContractAbi } from "thirdweb/contract";
import {
useActiveAccount,
useActiveWallet,
@@ -58,6 +56,8 @@ import {
import { isAddress } from "thirdweb/utils";
import invariant from "tiny-invariant";
import { Web3Provider } from "zksync-ethers";
import { thirdwebClient } from "../../@/constants/client";
import { useV5DashboardChain } from "../../lib/v5-adapter";
import type { CustomContractDeploymentFormData } from "./contract-deploy-form/custom-contract";
import type {
DeployModalStep,
@@ -439,12 +439,10 @@ export function usePublishedContractFunctions(contract: PublishedContract) {
"MarketplaceV3",
))
) {
return extractFunctionsFromAbi(compositeAbi);
return compositeAbi.filter((f) => f.type === "function");
}
return meta
? extractFunctionsFromAbi(meta.abi as Abi, meta?.compilerMetadata)
: undefined;
return meta?.abi?.filter((f) => f.type === "function") || [];
}
export function usePublishedContractEvents(contract: PublishedContract) {
const publishedContractInfo = usePublishedContractInfo(contract);
@@ -466,12 +464,10 @@ export function usePublishedContractEvents(contract: PublishedContract) {
"MarketplaceV3",
))
) {
return extractEventsFromAbi(compositeAbi);
return compositeAbi.filter((f) => f.type === "event");
}
return meta
? extractEventsFromAbi(meta.abi as Abi, meta?.compilerMetadata)
: undefined;
return meta?.abi?.filter((f) => f.type === "event") || [];
}
export function usePublishedContractCompilerMetadata(
@@ -480,17 +476,12 @@ export function usePublishedContractCompilerMetadata(
return useContractPublishMetadataFromURI(contract.metadataUri);
}
export function useConstructorParamsFromABI(abi?: Abi) {
return useMemo(() => {
return abi ? extractConstructorParamsFromAbi(abi) : [];
}, [abi]);
}
// biome-ignore lint/suspicious/noExplicitAny: FIXME
export function useFunctionParamsFromABI(abi?: any, functionName?: string) {
export function useFunctionParamsFromABI(abi?: Abi, functionName?: string) {
return useMemo(() => {
return abi && functionName
? extractFunctionParamsFromAbi(abi, functionName)
? abi
.filter((a) => a.type === "function")
.find((a) => a.name === functionName)?.inputs || []
: [];
}, [abi, functionName]);
}
@@ -991,7 +982,9 @@ export async function fetchPublishedContractsWithFeature(
);
return resultWithFeature.filter((c) => {
// @ts-expect-error - this is the "wrong" abi type, but it works fine
const extensions = detectFeatures(c.deployMetadata.abi as Abi);
// @ts-expect-error - this is the "wrong" abi type, but it works fine
return isExtensionEnabled(c.deployMetadata.abi as Abi, feature, extensions);
});
}
@@ -1063,10 +1056,10 @@ function extractExtensions(
};
}
// biome-ignore lint/suspicious/noExplicitAny: FIXME
function useContractDetectedExtensions(abi?: any) {
function useContractDetectedExtensions(abi?: Abi) {
const features = useMemo(() => {
if (abi) {
// @ts-expect-error - this is the "wrong" abi type, but it works fine
return extractExtensions(detectFeatures(abi));
}
return undefined;
@@ -1074,8 +1067,7 @@ function useContractDetectedExtensions(abi?: any) {
return features;
}
// biome-ignore lint/suspicious/noExplicitAny: FIXME
export function useContractEnabledExtensions(abi?: any) {
export function useContractEnabledExtensions(abi?: Abi) {
const extensions = useContractDetectedExtensions(abi);
return extensions ? extensions.enabledExtensions : [];
}
@@ -1139,22 +1131,31 @@ export function useEns(addressOrEnsName?: string) {
}
export function useContractFunctions(abi: Abi) {
return abi ? extractFunctionsFromAbi(abi) : undefined;
return abi
.filter((a) => a.type === "function")
.map((f) => ({
...f,
// fake "field" for the "signature"
signature: `${f.name}(${f.inputs.map((i) => i.type).join(",")})`,
}));
}
export function useContractEvents(abi: Abi) {
return abi ? extractEventsFromAbi(abi) : undefined;
return abi.filter((a) => a.type === "event");
}
export function useCustomFactoryAbi(contractAddress: string, chainId: number) {
const chain = useSupportedChain(chainId);
const chain = useV5DashboardChain(chainId);
const contract = useMemo(() => {
return getContract({
client: thirdwebClient,
address: contractAddress,
chain,
});
}, [contractAddress, chain]);
return useQuery({
queryKey: ["custom-factory-abi", { contractAddress, chainId }],
queryFn: async () => {
const sdk = getThirdwebSDK(chainId, getDashboardChainRpc(chainId, chain));
return (await sdk.getContract(contractAddress)).abi;
},
enabled: !!contractAddress && !!chainId,
queryKey: ["custom-factory-abi", contract],
queryFn: () => resolveContractAbi<Abi>(contract),
enabled: !!contract,
});
}
@@ -13,6 +13,7 @@ import {
type PublishedMetadata,
fetchSourceFilesFromMetadata,
} from "@thirdweb-dev/sdk";
import type { Abi } from "abitype";
import { ContractFunctionsOverview } from "components/contract-functions/contract-functions";
import { replaceDeployerAddress } from "components/explore/publisher";
import { ShareButton } from "components/share-buttom";
@@ -101,7 +102,9 @@ export const PublishedContract: React.FC<PublishedContractProps> = ({
? compositeAbi
: contractPublishMetadata.data?.abi;
const enabledExtensions = useContractEnabledExtensions(abi);
const enabledExtensions = useContractEnabledExtensions(
abi as Abi | undefined,
);
const publisherProfile = usePublisherProfile(contract.publisher);
@@ -333,9 +336,12 @@ Deploy it in one click`,
{contractFunctions && (
<Card p={0}>
<ContractFunctionsOverview
// @ts-expect-error - wrong function type
functions={contractFunctions}
// @ts-expect-error - wrong event type
events={contractEvents}
sources={sources.data}
// @ts-expect-error - wrong contract type
abi={contractPublishMetadata.data?.abi}
/>
</Card>
@@ -457,6 +463,7 @@ Deploy it in one click`,
<Divider />
{contractPublishMetadata.data?.abi && (
<Extensions
// @ts-expect-error - wrong abi type
abi={
compositeAbi &&
(dynamicContractType === "plugin" ||
@@ -20,14 +20,8 @@ import {
Thead,
Tr,
} from "@chakra-ui/react";
import {
type AbiEvent,
type AbiFunction,
extractFunctionsFromAbi,
joinABIs,
} from "@thirdweb-dev/sdk";
import type { Abi, AbiEvent, AbiFunction } from "abitype";
import { useContractEnabledExtensions } from "components/contract-components/hooks";
import { MarkdownRenderer } from "components/contract-components/published-contract/markdown-renderer";
import { camelToTitle } from "contract-ui/components/solidity-inputs/helpers";
import { useRouter } from "next/router";
import { type Dispatch, type SetStateAction, useMemo, useState } from "react";
@@ -41,6 +35,48 @@ import { CodeSegment } from "../contract-tabs/code/CodeSegment";
import type { CodeEnvironment } from "../contract-tabs/code/types";
import { InteractiveAbiFunction } from "./interactive-abi-function";
// internal helpers
export function unique<T>(a: T[], fn: (a: T, b: T) => boolean): T[] {
if (a.length === 0 || a.length === 1) {
return a;
}
if (!fn) {
return a;
}
for (let i = 0; i < a.length; i++) {
for (let j = i + 1; j < a.length; j++) {
if (fn(a[i], a[j])) {
a.splice(j, 1);
j--;
}
}
}
return a;
}
function joinABIs(abis: Abi[], abiWithConstructor?: Abi): Abi {
const filteredABIs: Abi[] = abis.map((abi) =>
abi.filter((item) => item.type !== "constructor"),
);
if (abiWithConstructor) {
filteredABIs.push(abiWithConstructor);
}
const flattenedAbis = filteredABIs.flat();
const finalABIs = unique(flattenedAbis, (a, b): boolean => {
return (
"name" in a &&
"name" in b &&
a.name === b.name &&
a.type === b.type &&
a.inputs.length === b.inputs.length
);
});
return finalABIs;
}
interface ContractFunctionProps {
fn?: AbiFunction | AbiEvent;
contract?: ThirdwebContract;
@@ -96,7 +132,8 @@ const ContractFunction: React.FC<ContractFunctionProps> = ({
</Badge>
)}
</Flex>
{fn.comment && (
{/* TODO: bring this back eventually */}
{/* {fn.comment && (
<MarkdownRenderer
markdownText={fn.comment
?.replaceAll(/See \{(.+)\}(\.)?/gm, "")
@@ -104,7 +141,7 @@ const ContractFunction: React.FC<ContractFunctionProps> = ({
.replaceAll("}", '"')
.replaceAll("'", '"')}
/>
)}
)} */}
{fn.inputs?.length && !contract ? (
<>
<Divider my={2} />
@@ -176,28 +213,30 @@ const ContractFunction: React.FC<ContractFunctionProps> = ({
/>
)}
<Heading size="subtitle.md" mt={6}>
Use this function in your app
</Heading>
<Divider mb={2} />
{contract && (
<CodeSegment
environment={environment}
setEnvironment={setEnvironment}
snippet={formatSnippet(
COMMANDS[
isFunction ? (isRead ? "read" : "write") : "events"
// biome-ignore lint/suspicious/noExplicitAny: FIXME
] as any,
{
contractAddress: contract.address,
fn,
args: fn.inputs?.map((i) => i.name),
chainId: contract.chain.id,
extensionNamespace,
},
)}
/>
<>
<Heading size="subtitle.md" mt={6}>
Use this function in your app
</Heading>
<Divider mb={2} />
<CodeSegment
environment={environment}
setEnvironment={setEnvironment}
snippet={formatSnippet(
COMMANDS[
isFunction ? (isRead ? "read" : "write") : "events"
// biome-ignore lint/suspicious/noExplicitAny: FIXME
] as any,
{
contractAddress: contract.address,
fn,
args: fn.inputs?.map((i) => i.name || ""),
chainId: contract.chain.id,
extensionNamespace,
},
)}
/>
</>
)}
</Flex>
);
@@ -219,15 +258,14 @@ export const ContractFunctionsPanel: React.FC<ContractFunctionsPanelProps> = ({
}) => {
const abiQuery = useResolveContractAbi(contract);
const extensions = useContractEnabledExtensions(abiQuery.data);
const isFunction = "stateMutability" in fnsOrEvents[0];
const functionsWithExtension = useMemo(() => {
let allFunctions = fnsOrEvents as AbiFunction[];
let allFunctions = fnsOrEvents.filter((f) => f.type === "function");
const results: ExtensionFunctions[] = [];
const processedFunctions: string[] = [];
// biome-ignore lint/complexity/noForEach: FIXME
extensions.forEach((ext) => {
// biome-ignore lint/suspicious/noExplicitAny: FIXME
let functions = extractFunctionsFromAbi(joinABIs(ext.abis as any));
// @ts-expect-error - this is the "wrong" abi type
let functions = joinABIs(ext.abis).filter((f) => f.type === "function");
allFunctions = allFunctions.filter(
(fn) => !functions.map((f) => f.name).includes(fn.name),
);
@@ -251,8 +289,7 @@ export const ContractFunctionsPanel: React.FC<ContractFunctionsPanelProps> = ({
.map((e) => {
const filteredFunctions = e.functions.filter(
(fn) =>
(fn as AbiFunction).stateMutability !== "pure" &&
(fn as AbiFunction).stateMutability !== "view",
fn.stateMutability !== "pure" && fn.stateMutability !== "view",
);
if (filteredFunctions.length === 0) {
return undefined;
@@ -332,9 +369,8 @@ export const ContractFunctionsPanel: React.FC<ContractFunctionsPanelProps> = ({
)}
{e.functions.map((fn) => (
<FunctionsOrEventsListItem
key={fn.signature}
key={`${fn.name}_${fn.type}_${fn.inputs.length}`}
fn={fn}
isFunction={isFunction}
selectedFunction={selectedFunction}
setSelectedFunction={setSelectedFunction}
/>
@@ -398,9 +434,8 @@ export const ContractFunctionsPanel: React.FC<ContractFunctionsPanelProps> = ({
<Box px={4} pt={2} overflowX="hidden">
{events.map((fn) => (
<FunctionsOrEventsListItem
key={isFunction ? (fn as AbiFunction).signature : fn.name}
key={`${fn.name}_${fn.type}_${fn.inputs.length}`}
fn={fn}
isFunction={isFunction}
selectedFunction={selectedFunction}
setSelectedFunction={setSelectedFunction}
/>
@@ -423,40 +458,26 @@ export const ContractFunctionsPanel: React.FC<ContractFunctionsPanelProps> = ({
interface FunctionsOrEventsListItemProps {
fn: AbiFunction | AbiEvent;
isFunction: boolean;
selectedFunction: AbiFunction | AbiEvent;
setSelectedFunction: Dispatch<SetStateAction<AbiFunction | AbiEvent>>;
}
const FunctionsOrEventsListItem: React.FC<FunctionsOrEventsListItemProps> = ({
fn,
isFunction,
selectedFunction,
setSelectedFunction,
}) => {
const isActive =
selectedFunction?.name === fn.name &&
selectedFunction.inputs?.length === fn.inputs?.length;
const router = useRouter();
return (
<ListItem my={0.5}>
<Button
size="sm"
fontWeight={
(isFunction &&
(selectedFunction as AbiFunction).signature ===
(fn as AbiFunction).signature) ||
(!isFunction &&
(selectedFunction as AbiEvent).name === (fn as AbiEvent).name)
? 600
: 400
}
opacity={
(isFunction &&
(selectedFunction as AbiFunction).signature ===
(fn as AbiFunction).signature) ||
(!isFunction &&
(selectedFunction as AbiEvent).name === (fn as AbiEvent).name)
? 1
: 0.65
}
fontWeight={isActive ? 600 : 400}
opacity={isActive ? 1 : 0.65}
onClick={() => {
setSelectedFunction(fn);
const { name } = fn;
@@ -6,7 +6,7 @@ import {
TabPanels,
Tabs,
} from "@chakra-ui/react";
import type { Abi, AbiEvent, AbiFunction } from "@thirdweb-dev/sdk";
import type { Abi, AbiEvent, AbiFunction } from "abitype";
import { SourcesPanel } from "components/contract-components/shared/sources-panel";
import type { SourceFile } from "components/contract-components/types";
import { CodeOverview } from "contract-ui/tabs/code/components/code-overview";
@@ -100,7 +100,6 @@ export const ContractFunctionsOverview: React.FC<ContractFunctionsOverview> = ({
)}
{(sources || abi) && (
<TabPanel>
{/* @ts-expect-error This won't cause any error and will auto-resolve itself once we have fully migrated to v5 */}
<SourcesPanel sources={sources} abi={abi} />
</TabPanel>
)}
@@ -8,7 +8,7 @@ import {
Input,
} from "@chakra-ui/react";
import { useMutation } from "@tanstack/react-query";
import type { AbiFunction } from "@thirdweb-dev/sdk";
import type { AbiFunction, AbiParameter } from "abitype";
import { TransactionButton } from "components/buttons/TransactionButton";
import { SolidityInput } from "contract-ui/components/solidity-inputs";
import { camelToTitle } from "contract-ui/components/solidity-inputs/helpers";
@@ -84,14 +84,7 @@ function formatContractCall(
key: string;
value: string;
type: string;
components:
| {
// biome-ignore lint/suspicious/noExplicitAny: FIXME
[x: string]: any;
type: string;
name?: string;
}[]
| undefined;
components: Readonly<AbiParameter[]> | undefined;
}[],
) {
const parsedParams = params
@@ -142,7 +135,7 @@ export const InteractiveAbiFunction: React.FC<InteractiveAbiFunctionProps> = ({
key: i.name || "key",
value: "",
type: i.type,
components: i.components,
components: "components" in i ? i.components : undefined,
})) || [],
value: "0",
},
@@ -266,7 +259,7 @@ export const InteractiveAbiFunction: React.FC<InteractiveAbiFunctionProps> = ({
<SolidityInput
solidityName={item.key}
solidityType={item.type}
solidityComponents={item.components}
// solidityComponents={item.components}
{...form.register(`params.${index}.value`)}
functionName={abiFunction.name}
/>
@@ -1,8 +1,4 @@
import {
contractType,
getAllDetectedFeatureNames,
useContract,
} from "@thirdweb-dev/react";
import { useContract } from "@thirdweb-dev/react";
import { extensionDetectedState } from "components/buttons/ExtensionDetectButton";
import { useEns } from "components/contract-components/hooks";
import { detectFeatures } from "components/contract-components/utils";
@@ -28,6 +24,7 @@ import {
} from "thirdweb/extensions/erc20";
import { isERC721 } from "thirdweb/extensions/erc721";
import { isERC1155 } from "thirdweb/extensions/erc1155";
import { contractType } from "thirdweb/extensions/thirdweb";
import { useReadContract } from "thirdweb/react";
import { useAnalyticsSupportedForChain } from "../../data/analytics/hooks";
import { useContractFunctionSelectors } from "./useContractFunctionSelectors";
@@ -124,19 +121,19 @@ export function useContractRouteConfig(
contractAddress: string,
contract: ThirdwebContract,
): EnhancedRoute[] {
const ensQuery = useEns(contractAddress);
const functinSelectorQuery = useContractFunctionSelectors(contract);
const contractQuery = useContract(ensQuery.data?.address);
const contractTypeQuery = contractType.useQuery(contractAddress);
const analyticsSupported = useAnalyticsSupportedForChain(
contractQuery.contract?.chainId,
);
// ContractTokensPage
// new
const functionSelectorQuery = useContractFunctionSelectors(contract);
const contractTypeQuery = useReadContract(contractType, { contract });
const analyticsSupported = useAnalyticsSupportedForChain(contract.chain.id);
const isERC721Query = useReadContract(isERC721, { contract });
const isERC1155Query = useReadContract(isERC1155, { contract });
// old
const ensQuery = useEns(contractAddress);
// TODO: remove
const contractQuery = useContract(ensQuery.data?.address);
// TODO: remove all below
const contractData = useMemo(() => {
const claimconditionExtensionDetection = extensionDetectedState({
contractQuery,
@@ -211,10 +208,6 @@ export function useContractRouteConfig(
["PermissionsEnumerable"],
);
const detectedFeatureNames = contractQuery.contract?.abi
? getAllDetectedFeatureNames(contractQuery.contract.abi)
: [];
return {
claimconditionExtensionDetection,
detectedAccountFeature,
@@ -226,7 +219,6 @@ export function useContractRouteConfig(
detectedModularExtension,
hasNewClaimConditions,
detectedPermissionEnumerable,
detectedFeatureNames,
};
}, [contractQuery]);
@@ -266,8 +258,18 @@ export function useContractRouteConfig(
{contract && (
<ContractOverviewPage
contract={contract}
contractType={contractTypeQuery.data || "custom"}
detectedFeatureNames={contractData.detectedFeatureNames}
hasDirectListings={
contractData.detectedDirectListings === "enabled"
}
hasEnglishAuctions={
contractData.detectedEnglishAuctions === "enabled"
}
isErc1155={isERC1155Query.data || false}
isErc20={isERC20(functionSelectorQuery.data)}
isErc721={isERC721Query.data || false}
isPermissionsEnumerable={
contractData.detectedPermissionEnumerable
}
/>
)}
</>
@@ -285,32 +287,22 @@ export function useContractRouteConfig(
{
title: "Code Snippets",
path: "code",
component: () => (
<>{contract && <LazyContractCodePage contract={contract} />}</>
),
component: () =>
contract ? <LazyContractCodePage contract={contract} /> : null,
isDefault: true,
},
{
title: "Explorer",
path: "explorer",
component: () => (
<>
{contract && contractQuery.contract?.abi && (
<LazyContractExplorerPage
contract={contract}
abi={contractQuery.contract.abi}
/>
)}
</>
),
component: () =>
contract ? <LazyContractExplorerPage contract={contract} /> : null,
isDefault: true,
},
{
title: "Events",
path: "events",
component: () => (
<>{contract && <LazyContractEventsPage contract={contract} />}</>
),
component: () =>
contract ? <LazyContractEventsPage contract={contract} /> : null,
isDefault: true,
},
{
@@ -324,22 +316,19 @@ export function useContractRouteConfig(
? "loading"
: "disabled",
component: () => {
return (
<>
{contract && (
<LazyContractEmbedPage
contract={contract}
ercOrMarketplace={embedType}
/>
)}
</>
);
return contract ? (
<LazyContractEmbedPage
contract={contract}
ercOrMarketplace={embedType}
/>
) : null;
},
},
{
title: "Analytics",
path: "analytics",
component: LazyContractAnalyticsPage,
component: () =>
contract ? <LazyContractAnalyticsPage contract={contract} /> : null,
isDefault: true,
isBeta: true,
isEnabled: analyticsSupported.isLoading
@@ -364,9 +353,9 @@ export function useContractRouteConfig(
{
title: "Tokens",
path: "tokens",
isEnabled: functinSelectorQuery.isLoading
isEnabled: functionSelectorQuery.isLoading
? "loading"
: isERC20(functinSelectorQuery.data)
: isERC20(functionSelectorQuery.data)
? "enabled"
: "disabled",
component: () => (
@@ -374,9 +363,11 @@ export function useContractRouteConfig(
{contract && (
<LazyContractTokensPage
contract={contract}
isERC20={isERC20(functinSelectorQuery.data)}
isMintToSupported={isMintToSupported(functinSelectorQuery.data)}
isClaimToSupported={isClaimToSupported(functinSelectorQuery.data)}
isERC20={isERC20(functionSelectorQuery.data)}
isMintToSupported={isMintToSupported(functionSelectorQuery.data)}
isClaimToSupported={isClaimToSupported(
functionSelectorQuery.data,
)}
/>
)}
</>
@@ -407,7 +398,7 @@ export function useContractRouteConfig(
path: "split",
isEnabled: contractTypeQuery.isLoading
? "loading"
: contractTypeQuery.data === "split"
: contractTypeQuery.data === "Split"
? "enabled"
: "disabled",
component: () => (
@@ -419,10 +410,12 @@ export function useContractRouteConfig(
path: "proposals",
isEnabled: contractTypeQuery.isLoading
? "loading"
: contractTypeQuery.data === "vote"
: contractTypeQuery.data === "Vote"
? "enabled"
: "disabled",
component: LazyContractProposalsPage,
component: () => (
<>{contract && <LazyContractProposalsPage contract={contract} />}</>
),
},
{
title: "Claim Conditions",
@@ -436,7 +429,7 @@ export function useContractRouteConfig(
claimconditionExtensionDetection={
contractData.claimconditionExtensionDetection
}
isERC20={isERC20(functinSelectorQuery.data)}
isERC20={isERC20(functionSelectorQuery.data)}
hasNewClaimConditions={contractData.hasNewClaimConditions}
/>
)}
@@ -513,22 +506,22 @@ export function useContractRouteConfig(
{contract && (
<LazyContractSettingsPage
contract={contract}
isLoading={functinSelectorQuery.isLoading}
isLoading={functionSelectorQuery.isLoading}
isContractMetadataSupported={[
isGetContractMetadataSupported(functinSelectorQuery.data),
isSetContractMetadataSupported(functinSelectorQuery.data),
isGetContractMetadataSupported(functionSelectorQuery.data),
isSetContractMetadataSupported(functionSelectorQuery.data),
].every(Boolean)}
isPrimarySaleSupported={[
isPrimarySaleRecipientSupported(functinSelectorQuery.data),
isSetPrimarySaleRecipientSupported(functinSelectorQuery.data),
isPrimarySaleRecipientSupported(functionSelectorQuery.data),
isSetPrimarySaleRecipientSupported(functionSelectorQuery.data),
].every(Boolean)}
isRoyaltiesSupported={[
isGetDefaultRoyaltyInfoSupported(functinSelectorQuery.data),
isSetDefaultRoyaltyInfoSupported(functinSelectorQuery.data),
isGetDefaultRoyaltyInfoSupported(functionSelectorQuery.data),
isSetDefaultRoyaltyInfoSupported(functionSelectorQuery.data),
].every(Boolean)}
isPlatformFeesSupported={[
isGetPlatformFeeInfoSupported(functinSelectorQuery.data),
isSetPlatformFeeInfoSupported(functinSelectorQuery.data),
isGetPlatformFeeInfoSupported(functionSelectorQuery.data),
isSetPlatformFeeInfoSupported(functionSelectorQuery.data),
].every(Boolean)}
/>
)}
@@ -1,6 +1,4 @@
"use client";
import { useEVMContractInfo } from "@3rdweb-sdk/react/hooks/useActiveChainId";
import {
Alert,
AlertDescription,
@@ -28,16 +26,17 @@ import {
useUniqueWalletsAnalytics,
} from "data/analytics/hooks";
import { Suspense, useMemo, useState } from "react";
import type { ThirdwebContract } from "thirdweb";
import { Card, Heading } from "tw-components";
import { ThirdwebBarChart } from "../../../@/components/blocks/charts/bar-chart";
import { useIsomorphicLayoutEffect } from "../../../@/lib/useIsomorphicLayoutEffect";
interface ContractAnalyticsPageProps {
contractAddress?: string;
contract: ThirdwebContract;
}
export const ContractAnalyticsPage: React.FC<ContractAnalyticsPageProps> = ({
contractAddress,
contract,
}) => {
const [startDate] = useState(
(() => {
@@ -52,17 +51,9 @@ export const ContractAnalyticsPage: React.FC<ContractAnalyticsPageProps> = ({
window?.scrollTo({ top: 0, behavior: "smooth" });
}, []);
const evmContractInfo = useEVMContractInfo();
const analyticsSupported = useAnalyticsSupportedForChain(contract.chain.id);
const analyticsSupported = useAnalyticsSupportedForChain(
evmContractInfo?.chain?.chainId,
);
if (
!contractAddress ||
!evmContractInfo?.chain?.chainId ||
analyticsSupported.isLoading
) {
if (analyticsSupported.isLoading) {
// TODO build a skeleton for this
return <div>Loading...</div>;
}
@@ -82,7 +73,7 @@ export const ContractAnalyticsPage: React.FC<ContractAnalyticsPageProps> = ({
return (
<Flex direction="column" gap={6}>
{contractAddress && evmContractInfo?.chain && (
{contract && (
<>
<Flex gap={10} direction="column">
<Flex direction="column" gap={2}>
@@ -98,24 +89,24 @@ export const ContractAnalyticsPage: React.FC<ContractAnalyticsPageProps> = ({
</Heading>
<Flex gap={4}>
<AnalyticsStat
chainId={evmContractInfo.chain.chainId}
contractAddress={contractAddress}
chainId={contract.chain.id}
contractAddress={contract.address}
// FIXME
// eslint-disable-next-line react-compiler/react-compiler
useTotal={useTotalWalletsAnalytics}
label="Unique Wallets"
/>
<AnalyticsStat
chainId={evmContractInfo.chain.chainId}
contractAddress={contractAddress}
chainId={contract.chain.id}
contractAddress={contract.address}
// FIXME
// eslint-disable-next-line react-compiler/react-compiler
useTotal={useTotalTransactionAnalytics}
label="Total Transactions"
/>
<AnalyticsStat
chainId={evmContractInfo.chain.chainId}
contractAddress={contractAddress}
chainId={contract.chain.id}
contractAddress={contract.address}
// FIXME
// eslint-disable-next-line react-compiler/react-compiler
useTotal={useTotalLogsAnalytics}
@@ -126,36 +117,36 @@ export const ContractAnalyticsPage: React.FC<ContractAnalyticsPageProps> = ({
</Flex>
<SimpleGrid columns={{ base: 1, md: 1 }} gap={4}>
<UniqueWalletsChart
chainId={evmContractInfo.chain.chainId}
contractAddress={contractAddress}
chainId={contract.chain.id}
contractAddress={contract.address}
startDate={startDate}
endDate={endDate}
/>
<TotalTransactionsChart
chainId={evmContractInfo.chain.chainId}
contractAddress={contractAddress}
chainId={contract.chain.id}
contractAddress={contract.address}
startDate={startDate}
endDate={endDate}
/>
<TotalEventsChart
chainId={evmContractInfo.chain.chainId}
contractAddress={contractAddress}
chainId={contract.chain.id}
contractAddress={contract.address}
startDate={startDate}
endDate={endDate}
/>
<FunctionBreakdownChart
chainId={evmContractInfo.chain.chainId}
contractAddress={contractAddress}
chainId={contract.chain.id}
contractAddress={contract.address}
startDate={startDate}
endDate={endDate}
/>
<EventBreakdownChart
chainId={evmContractInfo.chain.chainId}
contractAddress={contractAddress}
chainId={contract.chain.id}
contractAddress={contract.address}
startDate={startDate}
endDate={endDate}
/>
@@ -16,9 +16,12 @@ import {
Tabs,
useBreakpointValue,
} from "@chakra-ui/react";
import type { Abi, AbiEvent, AbiFunction } from "@thirdweb-dev/sdk";
import { formatAbiItem } from "abitype";
import type { Abi as AbiType } from "abitype";
import {
type Abi,
type AbiEvent,
type AbiFunction,
formatAbiItem,
} from "abitype";
import {
useContractEnabledExtensions,
useContractEvents,
@@ -33,8 +36,7 @@ import { useActiveAccount } from "thirdweb/react";
import { Button, Card, Heading, Link, Text, TrackedLink } from "tw-components";
interface CodeOverviewProps {
// TODO: Remove v4's `Abi` type
abi?: Abi | AbiType;
abi?: Abi;
contractAddress?: string;
onlyInstall?: boolean;
chainId?: number;
@@ -562,7 +564,7 @@ export const CodeOverview: React.FC<CodeOverviewProps> = ({
const chainId = useDashboardEVMChainId() || chainIdProp || 1;
const chainInfo = useSupportedChain(chainId || -1);
const functions = useContractFunctions(abi as Abi);
const functions = useContractFunctions(abi || []);
const events = useContractEvents(abi as Abi);
const { readFunctions, writeFunctions } = useMemo(() => {
return {
@@ -778,15 +780,13 @@ export const CodeOverview: React.FC<CodeOverviewProps> = ({
size="sm"
fontWeight={
tab === "write" &&
(write as AbiFunction).signature ===
(fn as AbiFunction).signature
write?.signature === fn.signature
? 600
: 400
}
opacity={
tab === "write" &&
(write as AbiFunction).signature ===
(fn as AbiFunction).signature
write?.signature === fn.signature
? 1
: 0.65
}
@@ -814,15 +814,13 @@ export const CodeOverview: React.FC<CodeOverviewProps> = ({
size="sm"
fontWeight={
tab === "read" &&
(read as AbiFunction).signature ===
(fn as AbiFunction).signature
read?.signature === fn.signature
? 600
: 400
}
opacity={
tab === "read" &&
(read as AbiFunction).signature ===
(fn as AbiFunction).signature
read?.signature === fn.signature
? 1
: 0.65
}
@@ -900,22 +898,18 @@ export const CodeOverview: React.FC<CodeOverviewProps> = ({
contractAddress,
fn:
tab === "read" ? read : tab === "write" ? write : event,
args: (tab === "read"
? readFunctions
: tab === "write"
? writeFunctions
: events
)
?.find(
(f) =>
f.name ===
(tab === "read"
? read?.name
: tab === "write"
? write?.name
: event?.name),
)
?.inputs?.map((i) => i.name),
args:
abi
?.filter(
(f) => f.type === "function" || f.type === "event",
)
?.find((f) =>
f.type === "function"
? f.name ===
(tab === "read" ? read?.name : write?.name)
: f.name === event?.name,
)
?.inputs.map((i) => i.name || "") || [],
chainId,
extensionNamespace,
@@ -1,23 +1,30 @@
import { Center, Flex, Skeleton } from "@chakra-ui/react";
import type { Abi } from "@thirdweb-dev/sdk";
import { useQuery } from "@tanstack/react-query";
import { useContractFunctions } from "components/contract-components/hooks";
import { ContractFunctionsOverview } from "components/contract-functions/contract-functions";
import type { ThirdwebContract } from "thirdweb";
import { resolveContractAbi } from "thirdweb/contract";
import { Heading, Text } from "tw-components";
interface ContractCodePageProps {
contract: ThirdwebContract;
abi: Abi;
}
export const ContractExplorerPage: React.FC<ContractCodePageProps> = ({
contract,
abi,
}) => {
const functions = useContractFunctions(abi);
const contractAbiQuery = useQuery({
queryKey: ["contractAbi", contract],
queryFn: () => resolveContractAbi(contract),
enabled: !!contract,
});
const functions = useContractFunctions(contractAbiQuery.data || []);
return (
<Flex direction="column" h="70vh">
<Skeleton height="100%" isLoaded={!!contract}>
<Skeleton
height="100%"
isLoaded={!!contract && contractAbiQuery.isSuccess}
>
{functions && functions.length > 0 ? (
<ContractFunctionsOverview
onlyFunctions
@@ -1,10 +1,10 @@
import { Flex, Image, List, ListItem } from "@chakra-ui/react";
import type { Abi } from "abitype";
import { useContractEnabledExtensions } from "components/contract-components/hooks";
import type { ContractInterface } from "ethers";
import { Heading, Text, TrackedLink } from "tw-components";
type ExtensionsProps = {
abi: ContractInterface;
abi: Abi;
};
export const Extensions: React.FC<ExtensionsProps> = ({ abi }) => {
@@ -41,26 +41,6 @@ type ListingData =
currencyValue: DirectListing["currencyValuePerToken"];
});
type MarketplaceDetailsProps = {
contract: ThirdwebContract;
features: string[];
trackingCategory: string;
};
export const MarketplaceDetails: React.FC<MarketplaceDetailsProps> = ({
contract,
trackingCategory,
features,
}) => {
return (
<MarketplaceV3Details
contract={contract}
trackingCategory={trackingCategory}
features={features}
/>
);
};
type ListingCardsSectionProps = {
contract: ThirdwebContract;
trackingCategory: string;
@@ -193,17 +173,16 @@ const EnglishAuctionCards: React.FC<ListingCardsSectionProps> = ({
interface MarketplaceDetailsVersionProps {
contract: ThirdwebContract;
trackingCategory: string;
features: MarketplaceDetailsProps["features"];
hasEnglishAuctions: boolean;
hasDirectListings: boolean;
}
const MarketplaceV3Details: React.FC<MarketplaceDetailsVersionProps> = ({
export const MarketplaceDetails: React.FC<MarketplaceDetailsVersionProps> = ({
contract,
trackingCategory,
features,
hasDirectListings,
hasEnglishAuctions,
}) => {
const hasDirectListings = features.includes("DirectListings");
const hasEnglishAuctions = features.includes("EnglishAuctions");
return (
<Flex gap={6} flexDirection="column">
<Heading size="title.sm">Listings</Heading>
@@ -1,5 +1,4 @@
import { Flex, useBreakpointValue } from "@chakra-ui/react";
import { SupplyCards } from "contract-ui/tabs/nfts/components/supply-cards";
import { useTabHref } from "contract-ui/utils";
import type { ThirdwebContract } from "thirdweb";
import { getNFTs } from "thirdweb/extensions/erc721";
@@ -10,13 +9,11 @@ import { NFTCards } from "./NFTCards";
interface NFTDetailsProps {
contract: ThirdwebContract;
trackingCategory: TrackedLinkProps["category"];
features: string[];
}
export const NFTDetails: React.FC<NFTDetailsProps> = ({
contract,
trackingCategory,
features,
}) => {
const isMobile = useBreakpointValue({ base: true, md: false });
const nftsHref = useTabHref("nfts");
@@ -32,17 +29,7 @@ export const NFTDetails: React.FC<NFTDetailsProps> = ({
?.filter((token) => token.metadata.image || token.metadata.animation_url)
.slice(0, isMobile ? 2 : 3) || [];
const showSupplyCards = [
"ERC721ClaimPhasesV1",
"ERC721ClaimPhasesV2",
"ERC721ClaimConditionsV1",
"ERC721ClaimConditionsV2",
"ERC721ClaimCustom",
].some((type) => features.includes(type));
return displayableNFTs.length === 0 &&
!showSupplyCards &&
!nftQuery.isLoading ? null : (
return displayableNFTs.length === 0 && !nftQuery.isLoading ? null : (
<Flex direction="column" gap={{ base: 3, md: 6 }}>
<Flex align="center" justify="space-between" w="full">
<Heading size="title.sm">NFT Details</Heading>
@@ -59,7 +46,6 @@ export const NFTDetails: React.FC<NFTDetailsProps> = ({
View all -&gt;
</TrackedLink>
</Flex>
{showSupplyCards && contract && <SupplyCards contract={contract} />}
<NFTCards
contractAddress={contract.address}
nfts={displayableNFTs}
@@ -1,488 +0,0 @@
import type { ContractType } from "constants/contracts";
type Item = {
title: string;
url: string;
};
const ALL_GUIDES: Record<string, Item> = {
claimErc20TokenNextjs: {
title: "Build An ERC20 Token Claim App in React",
url: "https://blog.thirdweb.com/guides/claim-erc20-token-nextjs/",
},
sellYourInGameCurrencyForCryptoInUnity: {
title: "Sell An ERC20 Token As In-Game Currency In Unity",
url: "https://blog.thirdweb.com/guides/sell-your-in-game-currency-for-crypto-in-unity/",
},
tokenButtonReact: {
title: "Build a Token Minting Button with React",
url: "https://blog.thirdweb.com/guides/token-button-react/",
},
howToSellNftsInACustomErc20Token: {
title: "How To Sell NFTs In A Custom ERC20 token",
url: "https://blog.thirdweb.com/guides/how-to-sell-nfts-in-a-custom-erc20-token/",
},
nftDropWithDelayedReveal: {
title: "Release an NFT drop with Delayed Reveal",
url: "https://blog.thirdweb.com/guides/nft-drop-with-delayed-reveal/",
},
realeaseAnNftDropWithNoCode: {
title: "Release an NFT Drop on your own site without writing any code",
url: "https://blog.thirdweb.com/guides/release-an-nft-drop-with-no-code/",
},
biconomyGaslessGuide: {
title: "Create an NFT Drop with Gasless Transactions using Biconomy",
url: "https://blog.thirdweb.com/guides/biconomy-gasless-guide/",
},
acceptCreditCardPayments: {
title: "How to accept credit card payments for your NFT drop",
url: "https://blog.thirdweb.com/guides/accept-credit-card-payments/",
},
createGaslessNftDrop: {
title: "Create a gasless NFT drop with Next.js and OpenZeppelin",
url: "https://blog.thirdweb.com/guides/create-gasless-nft-drop/",
},
createNftDropWithClaimPhases: {
title: "Release an NFT Drop with an Allowlist and Multiple Claim Phases",
url: "https://blog.thirdweb.com/guides/create-nft-drop-with-claim-phases/",
},
createAnMaycCollectionClone: {
title: "Build A Mutant Ape Yacht Club (MAYC) NFT Collection Clone",
url: "https://blog.thirdweb.com/guides/create-an-mayc-collection-clone/",
},
howToBatchUpload: {
title: "How to use batch upload with an NFT Drop",
url: "https://blog.thirdweb.com/guides/how-to-batch-upload/",
},
releaseAnErc721DropWithCentralizedMetadata: {
title: "Create an NFT Drop with Centralized Metadata",
url: "https://blog.thirdweb.com/guides/release-an-erc721-drop-with-centralized-metadata/",
},
createNftWithNoCode: {
title: "Mint an NFT with no code",
url: "https://blog.thirdweb.com/guides/create-nft-with-no-code/",
},
howToRenderNftMetadataInAReactAppUsingThirdwebNftMedia: {
title: "How to Render NFT Metadata In a React App",
url: "https://blog.thirdweb.com/guides/how-to-render-nft-metadata-in-a-react-app-using-thirdwebnftmedia/",
},
onDemandPass: {
title: "Create ERC721 Access Pass NFTs Using TypeScript",
url: "https://blog.thirdweb.com/guides/on-demand-pass/",
},
mintSpecificNft: {
title: "Let Users Pick Which NFT They Want to Mint",
url: "https://blog.thirdweb.com/guides/mint-specific-nft/",
},
mintNftUniqueCode: {
title:
"Create A Community-Made NFT Collection using Signature-Based Minting",
url: "https://blog.thirdweb.com/guides/mint-nft-unique-code/",
},
airdropEditionNftsToHoldersOfAnNftCollection: {
title: "Airdrop Edition NFTs to Holders of an NFT Collection",
url: "https://blog.thirdweb.com/guides/airdrop-edition-nfts-to-holders-of-an-nft-collection/",
},
createAnNftGalleryUsingThirdwebAndNextJs: {
title: "Create an NFT Gallery using thirdweb and Next.js",
url: "https://blog.thirdweb.com/guides/create-an-nft-gallery-using-thirdweb-and-next-js/",
},
nftGatedWebsite: {
title: "Create An NFT Gated Website",
url: "https://blog.thirdweb.com/guides/nft-gated-website/",
},
allowGithubContributorsToMintAnNft: {
title: "Allow GitHub contributors to mint an NFT",
url: "https://blog.thirdweb.com/guides/allow-github-contributors-to-mint-an-nft/",
},
airdropFreeToOwnNftsForAWeb3Game: {
title: "Airdrop Free-To-Own NFTs For Your Web3 Game",
url: "https://blog.thirdweb.com/guides/airdrop-free-to-own-nfts-for-a-web3-game/",
},
createAnNftLootbox: {
title: "Create NFT Loot-Boxes Using the Pack Contract",
url: "https://blog.thirdweb.com/guides/create-an-nft-lootbox/",
},
signatureDrop: {
title: "Create an ERC721A NFT Drop with Signature-Based Minting",
url: "https://blog.thirdweb.com/guides/signature-drop/",
},
nftMarketplaceWithTypescriptNext: {
title: "Create Your Own NFT Marketplace with TypeScript and Next.js",
url: "https://blog.thirdweb.com/guides/nft-marketplace-with-typescript-next/",
},
auctionButtonReact: {
title: "Build An Auction Button For Your NFT Marketplace",
url: "https://blog.thirdweb.com/guides/auction-button-react/",
},
buildTreasuryAndGovernanceForYourDao: {
title: "Build a DAO With a Treasury and a Governance Token",
url: "https://blog.thirdweb.com/guides/build-treasury-and-governance-for-your-dao/",
},
deploySmartWallet: {
title: "How to Deploy a Smart Account (ERC-4337)",
url: "https://blog.thirdweb.com/guides/how-to-use-erc4337-smart-wallets/",
},
customSmartWallet: {
title:
"How to Extend the Base Account Abstraction Contracts Using the Solidity SDK",
url: "https://blog.thirdweb.com/guides/custom-smart-wallet-contracts/",
},
};
const GUIDES_FOR_TYPE: Record<ContractType, Item[]> = {
pack: [ALL_GUIDES.createAnNftLootbox],
"signature-drop": [ALL_GUIDES.signatureDrop],
marketplace: [
ALL_GUIDES.nftMarketplaceWithTypescriptNext,
ALL_GUIDES.auctionButtonReact,
],
vote: [ALL_GUIDES.buildTreasuryAndGovernanceForYourDao],
custom: [],
"edition-drop": [],
edition: [],
"marketplace-v3": [],
multiwrap: [],
"nft-collection": [],
"nft-drop": [],
split: [],
"token-drop": [],
token: [],
};
const GUIDES_FOR_EXTENSION: Record<string, Item[]> = {
ERC20ClaimConditionsV1: [
ALL_GUIDES.claimErc20TokenNextjs,
ALL_GUIDES.tokenButtonReact,
ALL_GUIDES.sellYourInGameCurrencyForCryptoInUnity,
],
ERC20ClaimConditionsV2: [
ALL_GUIDES.claimErc20TokenNextjs,
ALL_GUIDES.tokenButtonReact,
ALL_GUIDES.sellYourInGameCurrencyForCryptoInUnity,
],
ERC20ClaimPhasesV2: [
ALL_GUIDES.claimErc20TokenNextjs,
ALL_GUIDES.tokenButtonReact,
ALL_GUIDES.sellYourInGameCurrencyForCryptoInUnity,
],
ERC20ClaimPhasesV1: [
ALL_GUIDES.claimErc20TokenNextjs,
ALL_GUIDES.tokenButtonReact,
ALL_GUIDES.sellYourInGameCurrencyForCryptoInUnity,
],
ERC20Burnable: [],
ERC20BatchMintable: [],
ERC20Mintable: [],
ERC20: [ALL_GUIDES.howToSellNftsInACustomErc20Token],
ERC721Burnable: [],
ERC721Revealable: [ALL_GUIDES.nftDropWithDelayedReveal],
ERC721TieredDrop: [],
ERC721ClaimConditionsV1: [
ALL_GUIDES.realeaseAnNftDropWithNoCode,
ALL_GUIDES.biconomyGaslessGuide,
ALL_GUIDES.acceptCreditCardPayments,
ALL_GUIDES.createGaslessNftDrop,
ALL_GUIDES.createNftDropWithClaimPhases,
],
ERC721ClaimConditionsV2: [
ALL_GUIDES.realeaseAnNftDropWithNoCode,
ALL_GUIDES.biconomyGaslessGuide,
ALL_GUIDES.acceptCreditCardPayments,
ALL_GUIDES.createGaslessNftDrop,
ALL_GUIDES.createNftDropWithClaimPhases,
],
ERC721ClaimPhasesV1: [
ALL_GUIDES.realeaseAnNftDropWithNoCode,
ALL_GUIDES.biconomyGaslessGuide,
ALL_GUIDES.acceptCreditCardPayments,
ALL_GUIDES.createGaslessNftDrop,
ALL_GUIDES.createNftDropWithClaimPhases,
],
ERC721ClaimPhasesV2: [
ALL_GUIDES.realeaseAnNftDropWithNoCode,
ALL_GUIDES.biconomyGaslessGuide,
ALL_GUIDES.acceptCreditCardPayments,
ALL_GUIDES.createGaslessNftDrop,
ALL_GUIDES.createNftDropWithClaimPhases,
],
ERC721ClaimCustom: [],
ERC721LazyMintable: [ALL_GUIDES.createAnMaycCollectionClone],
ERC721BatchMintable: [
ALL_GUIDES.howToBatchUpload,
ALL_GUIDES.releaseAnErc721DropWithCentralizedMetadata,
],
ERC721Mintable: [
ALL_GUIDES.createNftWithNoCode,
ALL_GUIDES.howToRenderNftMetadataInAReactAppUsingThirdwebNftMedia,
],
ERC721SignatureMintV2: [
ALL_GUIDES.onDemandPass,
ALL_GUIDES.mintSpecificNft,
ALL_GUIDES.mintNftUniqueCode,
],
ERC721SignatureMintV1: [
ALL_GUIDES.onDemandPass,
ALL_GUIDES.mintSpecificNft,
ALL_GUIDES.mintNftUniqueCode,
],
ERC721Enumerable: [],
ERC721Supply: [
ALL_GUIDES.airdropEditionNftsToHoldersOfAnNftCollection,
ALL_GUIDES.createAnNftGalleryUsingThirdwebAndNextJs,
],
ERC721: [ALL_GUIDES.nftGatedWebsite],
ERC1155Burnable: [],
ERC1155ClaimConditionsV1: [ALL_GUIDES.nftGatedWebsite],
ERC1155ClaimConditionsV2: [ALL_GUIDES.nftGatedWebsite],
ERC1155ClaimPhasesV2: [ALL_GUIDES.nftGatedWebsite],
ERC1155ClaimPhasesV1: [ALL_GUIDES.nftGatedWebsite],
ERC1155ClaimCustom: [],
ERC1155Revealable: [ALL_GUIDES.nftDropWithDelayedReveal],
ERC1155LazyMintableV2: [],
ERC1155LazyMintableV1: [],
ERC1155SignatureMintable: [ALL_GUIDES.allowGithubContributorsToMintAnNft],
ERC1155BatchMintable: [],
ERC1155Mintable: [],
ERC1155Enumerable: [ALL_GUIDES.createAnNftGalleryUsingThirdwebAndNextJs],
ERC1155UpdatableMetadata: [],
ERC1155Supply: [],
ERC1155: [ALL_GUIDES.airdropFreeToOwnNftsForAWeb3Game],
Royalty: [],
PrimarySale: [],
PlatformFee: [],
PermissionsEnumerable: [],
Permissions: [],
ContractMetadata: [],
AppURI: [],
Ownable: [],
ERC20SignatureMintable: [],
ERC20Permit: [],
ERC721AQueryable: [],
ERC721ClaimZora: [],
ERC721SharedMetadata: [],
ERC721LoyaltyCard: [],
ERC721UpdatableMetadata: [],
Gasless: [],
PackVRF: [],
PluginRouter: [],
DynamicContract: [],
ModularCore: [],
ModularModule: [],
DirectListings: [],
EnglishAuctions: [],
Offers: [],
Account: [],
AccountFactory: [ALL_GUIDES.deploySmartWallet, ALL_GUIDES.customSmartWallet],
AccountPermissions: [],
AccountPermissionsV1: [],
AirdropERC20: [],
AirdropERC721: [],
AirdropERC1155: [],
Fallback: [],
};
const ALL_TEMPLATES: Record<string, Item> = {
tokenDrop: {
title: "Token Drop",
url: "https://github.com/thirdweb-example/token-drop",
},
nftStakingApp: {
title: "NFT Staking App",
url: "https://github.com/thirdweb-example/nft-staking-app/",
},
nftDrop: {
title: "NFT Drop Minting Page",
url: "https://github.com/thirdweb-example/erc721/",
},
burn1155Mint721: {
title: "Burn an ERC1155 to Mint an ERC721 NFT",
url: "https://github.com/thirdweb-example/burn1155-mint721",
},
signatureBasedMinting: {
title: "Signature Based Minting",
url: "https://github.com/thirdweb-example/signature-based-minting",
},
nftGatedWebsite: {
title: "NFT Gated Website",
url: "https://github.com/thirdweb-example/nft-gated-website",
},
editionDrop: {
title: "Edition Drop Minting Page",
url: "https://github.com/thirdweb-example/edition-drop",
},
packs: {
title: "Packs",
url: "https://github.com/thirdweb-example/packs",
},
signatureDrop: {
title: "Signature Drop",
url: "https://github.com/thirdweb-example/signature-drop",
},
marketplace: {
title: "Build your own Marketplace",
url: "https://github.com/thirdweb-example/marketplace",
},
dao: {
title: "Build your own DAO",
url: "https://github.com/thirdweb-example/dao",
},
multiwrap: {
title: "Multiwrap",
url: "https://github.com/thirdweb-example/multiwrap",
},
smartWalletScript: {
title: "Account Abstraction Demo Script",
url: "https://github.com/thirdweb-example/smart-wallet-script",
},
smartWalletReact: {
title: "Account Abstraction React Demo",
url: "https://github.com/thirdweb-example/smart-wallet-react",
},
};
const TEMPLATES_FOR_TYPE: Record<ContractType, Item[]> = {
pack: [ALL_TEMPLATES.packs],
"signature-drop": [ALL_TEMPLATES.signatureDrop],
marketplace: [ALL_TEMPLATES.marketplace],
vote: [ALL_TEMPLATES.dao],
multiwrap: [ALL_TEMPLATES.multiwrap],
"nft-drop": [ALL_TEMPLATES.nftDrop],
"edition-drop": [ALL_TEMPLATES.editionDrop, ALL_TEMPLATES.nftGatedWebsite],
"token-drop": [ALL_TEMPLATES.tokenDrop],
custom: [],
edition: [],
"marketplace-v3": [],
"nft-collection": [],
split: [],
token: [],
};
const TEMPLATES_FOR_EXTENSION: Record<string, Item[]> = {
ERC20ClaimConditionsV1: [ALL_TEMPLATES.tokenDrop],
ERC20ClaimConditionsV2: [ALL_TEMPLATES.tokenDrop],
ERC20ClaimPhasesV1: [ALL_TEMPLATES.tokenDrop],
ERC20ClaimPhasesV2: [ALL_TEMPLATES.tokenDrop],
ERC20Burnable: [],
ERC20BatchMintable: [],
ERC20Mintable: [],
ERC20: [ALL_TEMPLATES.nftStakingApp],
ERC721Burnable: [],
ERC721Revealable: [],
ERC721TieredDrop: [],
ERC721ClaimConditionsV1: [ALL_TEMPLATES.nftDrop],
ERC721ClaimConditionsV2: [ALL_TEMPLATES.nftDrop],
ERC721ClaimPhasesV1: [ALL_TEMPLATES.nftDrop],
ERC721ClaimPhasesV2: [ALL_TEMPLATES.nftDrop],
ERC721ClaimCustom: [],
ERC721LazyMintable: [ALL_TEMPLATES.burn1155Mint721],
ERC721BatchMintable: [],
ERC721Mintable: [],
ERC721SignatureMintV2: [ALL_TEMPLATES.signatureBasedMinting],
ERC721SignatureMintV1: [ALL_TEMPLATES.signatureBasedMinting],
ERC721Enumerable: [],
ERC721Supply: [],
ERC721: [ALL_TEMPLATES.nftGatedWebsite],
ERC1155Burnable: [],
ERC1155ClaimConditionsV1: [
ALL_TEMPLATES.editionDrop,
ALL_TEMPLATES.nftGatedWebsite,
],
ERC1155ClaimConditionsV2: [
ALL_TEMPLATES.editionDrop,
ALL_TEMPLATES.nftGatedWebsite,
],
ERC1155ClaimPhasesV2: [
ALL_TEMPLATES.editionDrop,
ALL_TEMPLATES.nftGatedWebsite,
],
ERC1155ClaimPhasesV1: [
ALL_TEMPLATES.editionDrop,
ALL_TEMPLATES.nftGatedWebsite,
],
ERC1155ClaimCustom: [],
ERC1155Revealable: [],
ERC1155LazyMintableV2: [],
ERC1155LazyMintableV1: [],
ERC1155SignatureMintable: [],
ERC1155BatchMintable: [],
ERC1155Mintable: [],
ERC1155Enumerable: [],
ERC1155Supply: [],
ERC1155: [],
Royalty: [],
PrimarySale: [],
PlatformFee: [],
PermissionsEnumerable: [],
Permissions: [],
ContractMetadata: [],
AppURI: [],
Ownable: [],
ERC20SignatureMintable: [],
ERC20Permit: [],
ERC721AQueryable: [],
ERC721ClaimZora: [],
ERC721SharedMetadata: [],
ERC721LoyaltyCard: [],
ERC721UpdatableMetadata: [],
Gasless: [],
PackVRF: [],
PluginRouter: [],
DynamicContract: [],
ModularCore: [],
ModularModule: [],
DirectListings: [],
EnglishAuctions: [],
Offers: [],
Account: [],
AccountFactory: [
ALL_TEMPLATES.smartWalletScript,
ALL_TEMPLATES.smartWalletReact,
],
AccountPermissions: [],
AccountPermissionsV1: [],
ERC1155UpdatableMetadata: [],
AirdropERC20: [],
AirdropERC721: [],
AirdropERC1155: [],
Fallback: [],
};
export function getGuidesAndTemplates(
features: string[],
contractType: ContractType,
): {
guides: Item[];
templates: Item[];
} {
const data = features.reduce(
(acc, guide) => ({
guides: [
...acc.guides,
...(GUIDES_FOR_EXTENSION[guide as keyof typeof GUIDES_FOR_EXTENSION] ||
[]),
],
templates: [
...acc.templates,
...(TEMPLATES_FOR_EXTENSION[
guide as keyof typeof TEMPLATES_FOR_EXTENSION
] || []),
],
}),
{
guides: GUIDES_FOR_TYPE[contractType] || [],
templates: TEMPLATES_FOR_TYPE[contractType] || [],
},
);
return {
guides: data.guides.filter(
(guide, index) =>
data.guides.findIndex((g) => g.url === guide.url) === index,
),
templates: data.templates.filter(
(template, index) =>
data.templates.findIndex((t) => t.url === template.url) === index,
),
};
}
@@ -1,80 +1,68 @@
import { Divider, Flex, GridItem, SimpleGrid } from "@chakra-ui/react";
import { Flex, GridItem, SimpleGrid } from "@chakra-ui/react";
import { PublishedBy } from "components/contract-components/shared/published-by";
import { RelevantDataSection } from "components/dashboard/RelevantDataSection";
import type { ContractType } from "constants/contracts";
import { useMemo } from "react";
import type { ThirdwebContract } from "thirdweb";
import { AnalyticsOverview } from "./components/Analytics";
import { BuildYourApp } from "./components/BuildYourApp";
import { ContractChecklist } from "./components/ContractChecklist";
import { Extensions } from "./components/Extensions";
import { LatestEvents } from "./components/LatestEvents";
import { MarketplaceDetails } from "./components/MarketplaceDetails";
import { NFTDetails } from "./components/NFTDetails";
import { PermissionsTable } from "./components/PermissionsTable";
import { TokenDetails } from "./components/TokenDetails";
import { getGuidesAndTemplates } from "./helpers/getGuidesAndTemplates";
interface ContractOverviewPageProps {
contract: ThirdwebContract;
contractType: ContractType;
detectedFeatureNames: string[];
hasEnglishAuctions: boolean;
hasDirectListings: boolean;
isErc721: boolean;
isErc1155: boolean;
isErc20: boolean;
isPermissionsEnumerable: boolean;
}
const TRACKING_CATEGORY = "contract_overview";
export const ContractOverviewPage: React.FC<ContractOverviewPageProps> = ({
contract,
contractType,
detectedFeatureNames,
}) => {
const { guides, templates } = useMemo(
() => getGuidesAndTemplates(detectedFeatureNames, contractType),
[detectedFeatureNames, contractType],
);
isErc1155,
isErc20,
isErc721,
hasEnglishAuctions,
hasDirectListings,
isPermissionsEnumerable,
}) => {
return (
<SimpleGrid columns={{ base: 1, xl: 10 }} gap={20}>
<GridItem as={Flex} colSpan={{ xl: 7 }} direction="column" gap={16}>
{contract && <ContractChecklist contract={contract} />}
{contract && (
<AnalyticsOverview
contractAddress={contract.address}
chainId={contract.chain.id}
<ContractChecklist contract={contract} />
<AnalyticsOverview
contractAddress={contract.address}
chainId={contract.chain.id}
trackingCategory={TRACKING_CATEGORY}
/>
{(hasEnglishAuctions || hasDirectListings) && (
<MarketplaceDetails
contract={contract}
trackingCategory={TRACKING_CATEGORY}
hasEnglishAuctions={hasEnglishAuctions}
hasDirectListings={hasDirectListings}
/>
)}
{(isErc1155 || isErc721) && (
<NFTDetails
contract={contract}
trackingCategory={TRACKING_CATEGORY}
/>
)}
{contract &&
(contractType === "marketplace" ||
["DirectListings", "EnglishAuctions"].some((type) =>
detectedFeatureNames.includes(type),
)) && (
<MarketplaceDetails
contract={contract}
trackingCategory={TRACKING_CATEGORY}
features={detectedFeatureNames}
/>
)}
{contract &&
["ERC1155", "ERC721"].some((type) =>
detectedFeatureNames.includes(type),
) && (
<NFTDetails
contract={contract}
trackingCategory={TRACKING_CATEGORY}
features={detectedFeatureNames}
/>
)}
{["ERC20"].some((type) => detectedFeatureNames.includes(type)) && (
<TokenDetails contract={contract} />
)}
{isErc20 && <TokenDetails contract={contract} />}
<LatestEvents
contract={contract}
trackingCategory={TRACKING_CATEGORY}
/>
{["PermissionsEnumerable"].some((type) =>
detectedFeatureNames.includes(type),
) && (
{isPermissionsEnumerable && (
<PermissionsTable
contract={contract}
trackingCategory={TRACKING_CATEGORY}
@@ -84,20 +72,8 @@ export const ContractOverviewPage: React.FC<ContractOverviewPageProps> = ({
</GridItem>
<GridItem colSpan={{ xl: 3 }} as={Flex} direction="column" gap={6}>
<PublishedBy contractAddress={contract.address} />
{contract?.abi && <Extensions abi={contract?.abi} />}
{(guides.length > 0 || templates.length > 0) && <Divider />}
<RelevantDataSection
data={guides}
title="guide"
TRACKING_CATEGORY={TRACKING_CATEGORY}
/>
{guides.length > 0 && templates.length > 0 && <Divider />}
<RelevantDataSection
data={templates}
title="template"
TRACKING_CATEGORY={TRACKING_CATEGORY}
/>
{templates.length > 0 && <Divider />}
{/* TODO: this is broken because it relies on something that no longer exists on contract instance */}
{/* {contract?.abi && <Extensions abi={contract?.abi} />} */}
</GridItem>
</SimpleGrid>
);
@@ -50,6 +50,7 @@ const ContractDeployDetailPage: ThirdwebNextPage = () => {
}, [publishMetadataQuery.data, trackEvent]);
const enabledFeatures = useContractEnabledExtensions(
// @ts-expect-error - "wrong" ABI data type
publishMetadataQuery.data?.abi,
);
@@ -1,3 +1,13 @@
// --------------------------------------------------------
// Generic
// --------------------------------------------------------
// Read
export {
contractType,
isContractTypeSupported,
} from "../../extensions/thirdweb/read/contractType.js";
// --------------------------------------------------------
// Publisher contract
// --------------------------------------------------------
@@ -0,0 +1,46 @@
import type { BaseTransactionOptions } from "../../../transaction/types.js";
import { hexToString } from "../../../utils/encoding/hex.js";
import * as ContractTypeGenerated from "../__generated__/IThirdwebContract/read/contractType.js";
/**
* Checks if the `contractType` method is supported by the given contract.
* @param availableSelectors An array of 4byte function selectors of the contract. You can get this in various ways, such as using "whatsabi" or if you have the ABI of the contract available you can use it to generate the selectors.
* @returns A boolean indicating if the `contractType` method is supported.
* @extension THIRDWEB
* @example
* ```ts
* import { isContractTypeSupported } from "thirdweb/extensions/thirdweb";
*
* const supported = isContractTypeSupported(["0x..."]);
* ```
*/
export function isContractTypeSupported(availableSelectors: string[]) {
return ContractTypeGenerated.isContractTypeSupported(availableSelectors);
}
/**
* Calls the "contractType" function on the contract.
* @param options - The options for the contractType function.
* @returns The parsed result of the function call.
* @extension THIRDWEB
* @example
* ```ts
* import { contractType } from "thirdweb/extensions/thirdweb";
*
* const result = await contractType({
* contract,
* });
*
* ```
*/
export async function contractType(options: BaseTransactionOptions) {
try {
const rawContractType = await ContractTypeGenerated.contractType(options);
const utf8 = hexToString(rawContractType);
// biome-ignore lint/suspicious/noControlCharactersInRegex: required here
return utf8.replace(/\x00/g, "") || "custom";
} catch {
// backwards compat, if this fails for any reason we assume it's a custom contract
return "custom";
}
}