diff --git a/apps/dashboard/src/@/actions/getWalletNFTs.ts b/apps/dashboard/src/@/actions/getWalletNFTs.ts index 519237727a8..6c3dff1ffcb 100644 --- a/apps/dashboard/src/@/actions/getWalletNFTs.ts +++ b/apps/dashboard/src/@/actions/getWalletNFTs.ts @@ -10,12 +10,9 @@ import { isMoralisSupported, transformMoralisResponseToNFT, } from "lib/wallet/nfts/moralis"; -import { - generateSimpleHashUrl, - isSimpleHashSupported, - transformSimpleHashResponseToNFT, -} from "lib/wallet/nfts/simpleHash"; import type { WalletNFT } from "lib/wallet/nfts/types"; +import { getVercelEnv } from "../../lib/vercel-utils"; +import { DASHBOARD_THIRDWEB_CLIENT_ID } from "../constants/env"; type WalletNFTApiReturn = | { result: WalletNFT[]; error?: undefined } @@ -24,40 +21,20 @@ type WalletNFTApiReturn = export async function getWalletNFTs(params: { chainId: number; owner: string; + isInsightSupported: boolean; }): Promise { const { chainId, owner } = params; - const supportedChainSlug = await isSimpleHashSupported(chainId); - if (supportedChainSlug && process.env.SIMPLEHASH_API_KEY) { - const url = generateSimpleHashUrl({ chainSlug: supportedChainSlug, owner }); + if (params.isInsightSupported) { + const response = await getWalletNFTsFromInsight({ chainId, owner }); - const response = await fetch(url, { - method: "GET", - headers: { - "X-API-KEY": process.env.SIMPLEHASH_API_KEY, - }, - next: { - revalidate: 10, // cache for 10 seconds - }, - }); - - if (response.status >= 400) { + if (!response.ok) { return { - error: response.statusText, + error: response.error, }; } - try { - const parsedResponse = await response.json(); - const result = await transformSimpleHashResponseToNFT( - parsedResponse, - owner, - ); - - return { result }; - } catch { - return { error: "error parsing response" }; - } + return { result: response.data }; } if (isAlchemySupported(chainId)) { @@ -115,3 +92,115 @@ export async function getWalletNFTs(params: { return { error: "unsupported chain" }; } + +type OwnedNFTInsightResponse = { + name: string; + description: string; + image_url: string; + background_color: string; + external_url: string; + metadata_url: string; + extra_metadata: { + customImage?: string; + customAnimationUrl?: string; + animation_original_url?: string; + image_original_url?: string; + }; + collection: { + name: string; + description: string; + extra_metadata: Record; + }; + contract: { + chain_id: number; + address: string; + type: "erc1155" | "erc721"; + name: string; + }; + owner_addresses: string[]; + token_id: string; + balance: string; + token_type: "erc1155" | "erc721"; +}; + +async function getWalletNFTsFromInsight(params: { + chainId: number; + owner: string; +}): Promise< + | { + data: WalletNFT[]; + ok: true; + } + | { + ok: false; + error: string; + } +> { + const { chainId, owner } = params; + + const thirdwebDomain = + getVercelEnv() === "production" ? "thirdweb" : "thirdweb-dev"; + const url = new URL(`https://insight.${thirdwebDomain}.com/v1/nfts`); + url.searchParams.append("chain", chainId.toString()); + url.searchParams.append("limit", "10"); + url.searchParams.append("owner_address", owner); + + const response = await fetch(url, { + headers: { + "x-client-id": DASHBOARD_THIRDWEB_CLIENT_ID, + }, + }); + + if (!response.ok) { + const errorMessage = await response.text(); + return { + ok: false, + error: errorMessage, + }; + } + + const nftsResponse = (await response.json()) as { + data: OwnedNFTInsightResponse[]; + }; + + const isDev = getVercelEnv() !== "production"; + + // NOTE: ipfscdn.io/ to thirdwebstorage-dev.com/ replacement is temporary + // This should be fixed in the insight dev endpoint + + const walletNFTs = nftsResponse.data.map((nft) => { + const walletNFT: WalletNFT = { + id: nft.token_id, + contractAddress: nft.contract.address, + metadata: { + uri: isDev + ? nft.metadata_url.replace("ipfscdn.io/", "thirdwebstorage-dev.com/") + : nft.metadata_url, + name: nft.name, + description: nft.description, + image: isDev + ? nft.image_url.replace("ipfscdn.io/", "thirdwebstorage-dev.com/") + : nft.image_url, + animation_url: isDev + ? nft.extra_metadata.animation_original_url?.replace( + "ipfscdn.io/", + "thirdwebstorage-dev.com/", + ) + : nft.extra_metadata.animation_original_url, + external_url: nft.external_url, + background_color: nft.background_color, + }, + owner: params.owner, + tokenURI: nft.metadata_url, + type: nft.token_type === "erc721" ? "ERC721" : "ERC1155", + supply: nft.balance, + }; + + return walletNFT; + }); + + return { + data: walletNFTs, + ok: true, + }; +} diff --git a/apps/dashboard/src/@3rdweb-sdk/react/hooks/useWalletNFTs.ts b/apps/dashboard/src/@3rdweb-sdk/react/hooks/useWalletNFTs.ts index cdb8f9e3125..67db7375954 100644 --- a/apps/dashboard/src/@3rdweb-sdk/react/hooks/useWalletNFTs.ts +++ b/apps/dashboard/src/@3rdweb-sdk/react/hooks/useWalletNFTs.ts @@ -5,6 +5,7 @@ import invariant from "tiny-invariant"; export function useWalletNFTs(params: { chainId: number; walletAddress?: string; + isInsightSupported: boolean; }) { return useQuery({ queryKey: ["walletNfts", params.chainId, params.walletAddress], @@ -13,6 +14,7 @@ export function useWalletNFTs(params: { return getWalletNFTs({ chainId: params.chainId, owner: params.walletAddress, + isInsightSupported: params.isInsightSupported, }); }, enabled: !!params.walletAddress, diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/components/list-button.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/components/list-button.tsx index 5e3ad7cc488..3f321cfe58d 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/components/list-button.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/components/list-button.tsx @@ -13,7 +13,6 @@ import { ListerOnly } from "@3rdweb-sdk/react/components/roles/lister-only"; import type { Account } from "@3rdweb-sdk/react/hooks/useApi"; import { isAlchemySupported } from "lib/wallet/nfts/alchemy"; import { isMoralisSupported } from "lib/wallet/nfts/moralis"; -import { useSimplehashSupport } from "lib/wallet/nfts/simpleHash"; import { PlusIcon } from "lucide-react"; import { useState } from "react"; import type { ThirdwebContract } from "thirdweb"; @@ -25,6 +24,7 @@ interface CreateListingButtonProps { createText?: string; type?: "direct-listings" | "english-auctions"; twAccount: Account | undefined; + isInsightSupported: boolean; } const LISTING_MODES = ["Select NFT", "Manual"] as const; @@ -34,6 +34,7 @@ export const CreateListingButton: React.FC = ({ type, contract, twAccount, + isInsightSupported, ...restButtonProps }) => { const address = useActiveAccount()?.address; @@ -41,13 +42,12 @@ export const CreateListingButton: React.FC = ({ const [listingMode, setListingMode] = useState<(typeof LISTING_MODES)[number]>("Select NFT"); - const simplehashQuery = useSimplehashSupport(contract.chain.id); - const isSupportedChain = contract.chain.id && - (simplehashQuery.data || + (isInsightSupported || isAlchemySupported(contract.chain.id) || isMoralisSupported(contract.chain.id)); + return ( @@ -83,6 +83,7 @@ export const CreateListingButton: React.FC = ({ actionText={createText} setOpen={setOpen} mode={listingMode === "Select NFT" ? "automatic" : "manual"} + isInsightSupported={isInsightSupported} /> @@ -95,6 +96,7 @@ export const CreateListingButton: React.FC = ({ actionText={createText} setOpen={setOpen} mode="manual" + isInsightSupported={isInsightSupported} /> )} diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/components/list-form.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/components/list-form.tsx index 67c6d02e134..c619197e6c5 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/components/list-form.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/components/list-form.tsx @@ -21,7 +21,6 @@ import { useAllChainsData } from "hooks/chains/allChains"; import { useTxNotifications } from "hooks/useTxNotifications"; import { isAlchemySupported } from "lib/wallet/nfts/alchemy"; import { isMoralisSupported } from "lib/wallet/nfts/moralis"; -import { useSimplehashSupport } from "lib/wallet/nfts/simpleHash"; import type { WalletNFT } from "lib/wallet/nfts/types"; import { CircleAlertIcon, InfoIcon } from "lucide-react"; import Link from "next/link"; @@ -87,6 +86,7 @@ type CreateListingsFormProps = { mode: "automatic" | "manual"; type?: "direct-listings" | "english-auctions"; twAccount: Account | undefined; + isInsightSupported: boolean; }; const auctionTimes = [ @@ -106,16 +106,17 @@ export const CreateListingsForm: React.FC = ({ setOpen, twAccount, mode, + isInsightSupported, }) => { const trackEvent = useTrack(); const chainId = contract.chain.id; const { idToChain } = useAllChainsData(); const network = idToChain.get(chainId); const [isFormLoading, setIsFormLoading] = useState(false); - const simplehashQuery = useSimplehashSupport(contract.chain.id); + const isSupportedChain = chainId && - (!!simplehashQuery.data || + (isInsightSupported || isAlchemySupported(chainId) || isMoralisSupported(chainId)); @@ -124,7 +125,9 @@ export const CreateListingsForm: React.FC = ({ const { data: walletNFTs, isPending: isWalletNFTsLoading } = useWalletNFTs({ chainId, walletAddress: account?.address, + isInsightSupported, }); + const sendAndConfirmTx = useSendAndConfirmTransaction(); const listingNotifications = useTxNotifications( "NFT listed Successfully", diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/direct-listings/ContractDirectListingsPage.client.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/direct-listings/ContractDirectListingsPage.client.tsx index 6b9a2973770..a98bc3cb26d 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/direct-listings/ContractDirectListingsPage.client.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/direct-listings/ContractDirectListingsPage.client.tsx @@ -29,6 +29,7 @@ export function ContractDirectListingsPageClient(props: { ); } diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/direct-listings/ContractDirectListingsPage.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/direct-listings/ContractDirectListingsPage.tsx index a8580fd0838..b542aa8ffd4 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/direct-listings/ContractDirectListingsPage.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/direct-listings/ContractDirectListingsPage.tsx @@ -8,17 +8,19 @@ import { DirectListingsTable } from "./components/table"; interface ContractDirectListingsPageProps { contract: ThirdwebContract; twAccount: Account | undefined; + isInsightSupported: boolean; } export const ContractDirectListingsPage: React.FC< ContractDirectListingsPageProps -> = ({ contract, twAccount }) => { +> = ({ contract, twAccount, isInsightSupported }) => { return (

Contract Listings

+ ); } diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/english-auctions/ContractEnglishAuctionsPage.client.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/english-auctions/ContractEnglishAuctionsPage.client.tsx index 23283c2a5c1..2288bf727f1 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/english-auctions/ContractEnglishAuctionsPage.client.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/english-auctions/ContractEnglishAuctionsPage.client.tsx @@ -29,6 +29,7 @@ export function ContractEnglishAuctionsPageClient(props: { ); } diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/english-auctions/ContractEnglishAuctionsPage.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/english-auctions/ContractEnglishAuctionsPage.tsx index cd7a1ad96dd..434848af6ad 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/english-auctions/ContractEnglishAuctionsPage.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/english-auctions/ContractEnglishAuctionsPage.tsx @@ -8,11 +8,12 @@ import { EnglishAuctionsTable } from "./components/table"; interface ContractEnglishAuctionsProps { contract: ThirdwebContract; twAccount: Account | undefined; + isInsightSupported: boolean; } export const ContractEnglishAuctionsPage: React.FC< ContractEnglishAuctionsProps -> = ({ contract, twAccount }) => { +> = ({ contract, twAccount, isInsightSupported }) => { return (
@@ -23,6 +24,7 @@ export const ContractEnglishAuctionsPage: React.FC< type="english-auctions" createText="Create English Auction" twAccount={twAccount} + isInsightSupported={isInsightSupported} />
diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/english-auctions/page.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/english-auctions/page.tsx index 244348d58bf..03d44ac6d9e 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/english-auctions/page.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/english-auctions/page.tsx @@ -30,9 +30,8 @@ export default async function Page(props: { ); } - const { isEnglishAuctionSupported } = await getContractPageMetadata( - info.contract, - ); + const { isEnglishAuctionSupported, isInsightSupported } = + await getContractPageMetadata(info.contract); if (!isEnglishAuctionSupported) { redirect(`/${params.chain_id}/${params.contractAddress}`); @@ -42,6 +41,7 @@ export default async function Page(props: { ); } diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/getContractPageMetadata.ts b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/getContractPageMetadata.ts index bc3e3d660a8..ff5621d9019 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/getContractPageMetadata.ts +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/getContractPageMetadata.ts @@ -21,7 +21,7 @@ import { } from "./detectedFeatures/permissions"; import { supportedERCs } from "./detectedFeatures/supportedERCs"; import { type EmbedTypeToShow, getEmbedTypeToShow } from "./getEmbedTypeToShow"; -import { isAnalyticsSupportedForChain } from "./isAnalyticsSupportedForChain"; +import { isInsightSupportedForChain } from "./isAnalyticsSupportedForChain"; export type ContractPageMetadata = { supportedERCs: { @@ -36,7 +36,7 @@ export type ContractPageMetadata = { isPermissionsEnumerableSupported: boolean; isModularCore: boolean; embedType: EmbedTypeToShow; - isAnalyticsSupported: boolean; + isInsightSupported: boolean; isSplitSupported: boolean; isERC721ClaimConditionsSupported: boolean; isERC20ClaimConditionsSupported: boolean; @@ -47,7 +47,7 @@ export type ContractPageMetadata = { }; export async function getContractPageMetadata(contract: ThirdwebContract) { - return getContractPageMetadataSetup(contract, isAnalyticsSupportedForChain); + return getContractPageMetadataSetup(contract, isInsightSupportedForChain); } export async function getContractPageMetadataSetup( @@ -56,7 +56,7 @@ export async function getContractPageMetadataSetup( ): Promise { const [ functionSelectorsResult, - isAnalyticsSupportedResult, + isInsightSupportedResult, contractTypeResult, ] = await Promise.allSettled([ resolveFunctionSelectors(contract), @@ -69,9 +69,9 @@ export async function getContractPageMetadataSetup( ? functionSelectorsResult.value : []; - const isAnalyticsSupported = - isAnalyticsSupportedResult.status === "fulfilled" - ? isAnalyticsSupportedResult.value + const isInsightSupported = + isInsightSupportedResult.status === "fulfilled" + ? isInsightSupportedResult.value : true; const contractType = @@ -86,7 +86,7 @@ export async function getContractPageMetadataSetup( isPermissionsEnumerableSupported(functionSelectors), isModularCore: isModularCoreContract(functionSelectors), embedType: getEmbedTypeToShow(functionSelectors), - isAnalyticsSupported: isAnalyticsSupported, + isInsightSupported: isInsightSupported, isSplitSupported: contractType === "Split", isVoteContract: contractType === "VoteERC20", isERC721ClaimConditionsSupported: diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/getContractPageSidebarLinks.ts b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/getContractPageSidebarLinks.ts index 19149871a3e..425841d8528 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/getContractPageSidebarLinks.ts +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/getContractPageSidebarLinks.ts @@ -47,7 +47,7 @@ export function getContractPageSidebarLinks(data: { { label: "Analytics", href: `${layoutPrefix}/analytics`, - hide: !data.metadata.isAnalyticsSupported, + hide: !data.metadata.isInsightSupported, exactMatch: true, }, { diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/isAnalyticsSupportedForChain.ts b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/isAnalyticsSupportedForChain.ts index 2f76daae0cb..37c2217f739 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/isAnalyticsSupportedForChain.ts +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/isAnalyticsSupportedForChain.ts @@ -4,7 +4,7 @@ import { getVercelEnv } from "lib/vercel-utils"; const thirdwebDomain = getVercelEnv() !== "production" ? "thirdweb-dev" : "thirdweb"; -export async function isAnalyticsSupportedForChain( +export async function isInsightSupportedForChain( chainId: number, ): Promise { try { diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/account/AccountPage.client.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/account/AccountPage.client.tsx index 7531ca892df..889fa1373a1 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/account/AccountPage.client.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/account/AccountPage.client.tsx @@ -32,6 +32,7 @@ export function AccountPageClient(props: { contract={props.contract} chainMetadata={props.chainMetadata} twAccount={props.twAccount} + isInsightSupported={metadataQuery.data.isInsightSupported} /> ); } diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/account/AccountPage.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/account/AccountPage.tsx index 7256f06e096..548ebd8bc42 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/account/AccountPage.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/account/AccountPage.tsx @@ -12,12 +12,14 @@ interface AccountPageProps { contract: ThirdwebContract; chainMetadata: ChainMetadata; twAccount: Account | undefined; + isInsightSupported: boolean; } export const AccountPage: React.FC = ({ contract, chainMetadata, twAccount, + isInsightSupported, }) => { const symbol = chainMetadata.nativeCurrency.symbol || "Native Token"; @@ -43,7 +45,7 @@ export const AccountPage: React.FC = ({
NFTs owned
- +
); }; diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/account/components/nfts-owned.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/account/components/nfts-owned.tsx index cc577f6e1a7..5e758802e33 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/account/components/nfts-owned.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/account/components/nfts-owned.tsx @@ -6,12 +6,17 @@ import { NFTCards } from "../../_components/NFTCards"; interface NftsOwnedProps { contract: ThirdwebContract; + isInsightSupported: boolean; } -export const NftsOwned: React.FC = ({ contract }) => { +export const NftsOwned: React.FC = ({ + contract, + isInsightSupported, +}) => { const { data: walletNFTs, isPending: isWalletNFTsLoading } = useWalletNFTs({ chainId: contract.chain.id, walletAddress: contract.address, + isInsightSupported: isInsightSupported, }); const nfts = walletNFTs?.result || []; diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/account/page.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/account/page.tsx index f9ebbf082e3..600af8198db 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/account/page.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/account/page.tsx @@ -33,7 +33,8 @@ export default async function Page(props: { ); } - const { isAccount } = await getContractPageMetadata(contract); + const { isAccount, isInsightSupported } = + await getContractPageMetadata(contract); if (!isAccount) { redirect(`/${params.chain_id}/${params.contractAddress}`); @@ -44,6 +45,7 @@ export default async function Page(props: { contract={contract} chainMetadata={chainMetadata} twAccount={account} + isInsightSupported={isInsightSupported} /> ); } diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/ContractAnalyticsPage.client.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/ContractAnalyticsPage.client.tsx index 8d214a962f0..d885457b7bf 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/ContractAnalyticsPage.client.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/ContractAnalyticsPage.client.tsx @@ -21,7 +21,7 @@ export function ContractAnalyticsPageClient(props: { return ; } - if (!metadataQuery.data.isAnalyticsSupported) { + if (!metadataQuery.data.isInsightSupported) { return ; } diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/page.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/page.tsx index 402eea70bf7..de9fe1a2678 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/page.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/page.tsx @@ -34,9 +34,9 @@ export default async function Page(props: { ); } - const { isAnalyticsSupported } = await getContractPageMetadata(info.contract); + const { isInsightSupported } = await getContractPageMetadata(info.contract); - if (!isAnalyticsSupported) { + if (!isInsightSupported) { redirect(`/${params.chain_id}/${params.contractAddress}`); } diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/overview/contract-overview-page.client.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/overview/contract-overview-page.client.tsx index 6dd3fe53788..ad4fe37fbbe 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/overview/contract-overview-page.client.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/overview/contract-overview-page.client.tsx @@ -34,7 +34,7 @@ export function ContractOverviewPageClient(props: { contractPageMetadata.isPermissionsEnumerableSupported } chainSlug={chainMetadata.slug} - isAnalyticsSupported={contractPageMetadata.isAnalyticsSupported} + isAnalyticsSupported={contractPageMetadata.isInsightSupported} functionSelectors={contractPageMetadata.functionSelectors} // TODO - create a fully client rendered version of publishedBy and ContractCard and plug it here publishedBy={undefined} diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/page.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/page.tsx index 25373d617db..4d501708802 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/page.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/page.tsx @@ -44,7 +44,7 @@ export default async function Page(props: { contractPageMetadata.isPermissionsEnumerableSupported } chainSlug={chainMetadata.slug} - isAnalyticsSupported={contractPageMetadata.isAnalyticsSupported} + isAnalyticsSupported={contractPageMetadata.isInsightSupported} functionSelectors={contractPageMetadata.functionSelectors} publishedBy={ diff --git a/apps/dashboard/src/app/api/nft/is-simplehash-supported/route.ts b/apps/dashboard/src/app/api/nft/is-simplehash-supported/route.ts deleted file mode 100644 index e0a3ccb977e..00000000000 --- a/apps/dashboard/src/app/api/nft/is-simplehash-supported/route.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { isSimpleHashSupported } from "lib/wallet/nfts/simpleHash"; -import { NextResponse } from "next/server"; -import type { NextRequest } from "next/server"; - -export const runtime = "edge"; - -export const GET = async (req: NextRequest) => { - const searchParams = req.nextUrl.searchParams; - const chainIdStr = searchParams.get("chainId"); - - if (!chainIdStr) { - return NextResponse.json( - { - error: "Missing chain ID parameter.", - }, - { status: 400 }, - ); - } - - const chainId = Number.parseInt(chainIdStr); - - if (!Number.isInteger(chainId)) { - return NextResponse.json( - { - error: "Invalid chain ID parameter.", - }, - { status: 400 }, - ); - } - - const found = await isSimpleHashSupported(chainId); - - return NextResponse.json({ result: found }); -}; diff --git a/apps/dashboard/src/lib/wallet/nfts/simpleHash.ts b/apps/dashboard/src/lib/wallet/nfts/simpleHash.ts deleted file mode 100644 index ad7bf7e0147..00000000000 --- a/apps/dashboard/src/lib/wallet/nfts/simpleHash.ts +++ /dev/null @@ -1,226 +0,0 @@ -import { useQuery } from "@tanstack/react-query"; -import type { WalletNFT } from "./types"; - -/** - * Return the chain slug if the chain is supported by Simplehash, otherwise `undefined` - * Chain slug because we need it to fetch the owned NFTs using another endpoint - */ -export async function isSimpleHashSupported( - chainId: number, -): Promise { - if (!process.env.SIMPLEHASH_API_KEY) { - throw new Error("No Simplehash API Key"); - } - - const options = { - method: "GET", - headers: { - "X-API-KEY": process.env.SIMPLEHASH_API_KEY, - }, - }; - const response: Array<{ - chain: string; - eip155_network_id: number; - is_testnet: boolean; - }> = await fetch("https://api.simplehash.com/api/v0/chains", options) - .then((r) => r.json()) - .catch(() => []); - - const found = response.find((chain) => chain.eip155_network_id === chainId); - return found?.chain; -} - -export function useSimplehashSupport(chainId: number) { - return useQuery({ - queryKey: ["simplehash-supported", chainId], - queryFn: () => isSimpleHashSupported(chainId), - enabled: !!chainId, - }); -} - -export function generateSimpleHashUrl({ - chainSlug, - owner, -}: { chainSlug: string; owner: string }) { - const url = new URL("https://api.simplehash.com/api/v0/nfts/owners"); - url.searchParams.append("wallet_addresses", owner); - url.searchParams.append("chains", chainSlug); - return url.toString(); -} - -export async function transformSimpleHashResponseToNFT( - simpleHashResponse: SimpleHashResponse, - walletAddress: string, -): Promise { - return ( - await Promise.all( - simpleHashResponse.nfts.map(async (simpleHashNft) => { - try { - return { - id: simpleHashNft.token_id, - contractAddress: simpleHashNft.contract_address, - tokenId: simpleHashNft.token_id, - metadata: { - name: simpleHashNft.name, - description: simpleHashNft.description, - image: simpleHashNft.extra_metadata.image_original_url, - external_url: simpleHashNft.external_url, - attributes: simpleHashNft.extra_metadata.attributes, - properties: simpleHashNft.extra_metadata.properties, - id: simpleHashNft.token_id, - uri: simpleHashNft.extra_metadata.metadata_original_url, - animation_url: - simpleHashNft.extra_metadata.animation_original_url, - background_color: simpleHashNft.background_color, - // biome-ignore lint/suspicious/noExplicitAny: FIXME - } as any, - owner: walletAddress, - supply: simpleHashNft.token_count.toString(), - type: simpleHashNft.contract.type, - tokenURI: simpleHashNft.extra_metadata.metadata_original_url, - } as WalletNFT; - } catch { - return undefined as unknown as WalletNFT; - } - }), - ) - ).filter(Boolean); -} - -interface PreviewImages { - image_small_url: string; - image_medium_url: string; - image_large_url: string; - image_opengraph_url: string; - blurhash: string; - predominant_color: string; -} - -interface ImageProperties { - width: number; - height: number; - size: number; - mime_type: string; -} - -interface Owner { - owner_address: string; - quantity: number; - first_acquired_date: string; - last_acquired_date: string; -} - -interface Contract { - type: string; - name: string; - symbol: string; - deployed_by: string; - deployed_via_contract: string; -} - -interface MarketplacePage { - marketplace_id: string; - marketplace_name: string; - marketplace_collection_id: string; - nft_url: string; - collection_url: string; - verified: boolean; -} - -interface Collection { - collection_id: string; - name: string; - description: string; - image_url: string; - banner_image_url: string | null; - category: string | null; - is_nsfw: boolean | null; - external_url: string | null; - twitter_username: string | null; - discord_url: string | null; - instagram_username: string | null; - medium_username: string | null; - telegram_url: string | null; - marketplace_pages: MarketplacePage[]; - metaplex_mint: string | null; - metaplex_first_verified_creator: string | null; - floor_prices: unknown[]; - top_bids: unknown[]; - distinct_owner_count: number; - distinct_nft_count: number; - total_quantity: number; - chains: string[]; - top_contracts: string[]; -} - -interface FirstCreated { - minted_to: string; - quantity: number; - timestamp: string; - block_number: number; - transaction: string; - transaction_initiator: string; -} - -interface Rarity { - rank: number; - score: number; - unique_attributes: number; -} - -interface ExtraMetadataAttribute { - trait_type: string; - value: string; - display_type: string | null; -} - -interface ExtraMetadataProperties { - number: number; - name: string; -} - -interface ExtraMetadata { - attributes: ExtraMetadataAttribute[]; - properties: ExtraMetadataProperties; - image_original_url: string; - animation_original_url: string | null; - metadata_original_url: string; -} - -interface NFT { - nft_id: string; - chain: string; - contract_address: string; - token_id: string; - name: string; - description: string; - previews: PreviewImages; - image_url: string; - image_properties: ImageProperties; - video_url: string | null; - video_properties: unknown; - audio_url: string | null; - audio_properties: unknown; - model_url: string | null; - model_properties: unknown; - other_url: string | null; - other_properties: unknown; - background_color: string | null; - external_url: string | null; - created_date: string; - status: string; - token_count: number; - owner_count: number; - owners: Owner[]; - contract: Contract; - collection: Collection; - last_sale: unknown; - first_created: FirstCreated; - rarity: Rarity; - royalty: unknown[]; - extra_metadata: ExtraMetadata; -} - -interface SimpleHashResponse { - nfts: NFT[]; -} diff --git a/apps/dashboard/src/lib/wallet/nfts/types.ts b/apps/dashboard/src/lib/wallet/nfts/types.ts index 3c1fda87626..8f19696f752 100644 --- a/apps/dashboard/src/lib/wallet/nfts/types.ts +++ b/apps/dashboard/src/lib/wallet/nfts/types.ts @@ -27,7 +27,7 @@ import { } from "thirdweb/chains"; // Cannot use BigInt for the values here because it will result in error: "fail to serialize bigint" -// when the data is being sent from server to client (when we fetch the owned NFTs from simplehash/alchemy/moralis) +// when the data is being sent from server to client (when we fetch the owned NFTs from insight/alchemy/moralis) export type WalletNFT = Omit & { id: string; contractAddress: string;