diff --git a/app/address/[address]/layout.tsx b/app/address/[address]/layout.tsx
index a7cf6627..8d212a91 100644
--- a/app/address/[address]/layout.tsx
+++ b/app/address/[address]/layout.tsx
@@ -41,15 +41,16 @@ import { useClusterPath } from '@utils/url';
import { MetadataPointer, TokenMetadata } from '@validators/accounts/token-extension';
import Link from 'next/link';
import { redirect, useSelectedLayoutSegment } from 'next/navigation';
-import React, { PropsWithChildren, Suspense } from 'react';
+import React, { PropsWithChildren, Suspense, useMemo } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import { create } from 'superstruct';
import useSWRImmutable from 'swr/immutable';
import { Address } from 'web3js-experimental';
import { CompressedNftAccountHeader, CompressedNftCard } from '@/app/components/account/CompressedNftCard';
-import { useCompressedNft } from '@/app/providers/compressed-nft';
+import { useCompressedNft, useMetadataJsonLink } from '@/app/providers/compressed-nft';
import { FullTokenInfo, getFullTokenInfo } from '@/app/utils/token-info';
+import { MintAccountInfo } from '@/app/validators/accounts/token';
const IDENTICON_WIDTH = 64;
@@ -272,76 +273,7 @@ function AccountHeader({
}
if (isToken && !isTokenInfoLoading) {
- let token: { logoURI?: string; name?: string } = {};
- let unverified = false;
-
- const metadataExtension = mintInfo?.extensions?.find(
- ({ extension }: { extension: string }) => extension === 'tokenMetadata'
- );
- const metadataPointerExtension = mintInfo?.extensions?.find(
- ({ extension }: { extension: string }) => extension === 'metadataPointer'
- );
-
- if (metadataPointerExtension && metadataExtension) {
- const tokenMetadata = create(metadataExtension.state, TokenMetadata);
- const { metadataAddress } = create(metadataPointerExtension.state, MetadataPointer);
-
- // Handles the basic case where MetadataPointer is reference the Token Metadata extension directly
- // Does not handle the case where MetadataPointer is pointing at a separate account.
- if (metadataAddress?.toString() === address) {
- token.name = tokenMetadata.name;
- }
- }
- // Fall back to legacy token list when there is stub metadata (blank uri), updatable by default by the mint authority
- else if (!parsedData?.nftData?.metadata.data.uri && tokenInfo) {
- token = tokenInfo;
- } else if (parsedData?.nftData) {
- token = {
- logoURI: parsedData?.nftData?.json?.image,
- name: parsedData?.nftData?.json?.name ?? parsedData?.nftData.metadata.data.name,
- };
- if (!tokenInfo?.verified) {
- unverified = true;
- }
- } else if (tokenInfo) {
- token = tokenInfo;
- }
-
- return (
-
- {unverified && (
-
- Warning! Token names and logos are not unique. This token may have spoofed its name and logo to
- look like another token. Verify the token's mint address to ensure it is correct.
-
- )}
-
-
- {token?.logoURI ? (
- // eslint-disable-next-line @next/next/no-img-element
-

- ) : (
-
- )}
-
-
-
-
-
Token
- {token?.name || 'Unknown Token'}
-
-
- );
+ return ;
}
const fallback = (
@@ -362,6 +294,142 @@ function AccountHeader({
return fallback;
}
+function TokenMintHeader({
+ address,
+ tokenInfo,
+ mintInfo,
+ parsedData,
+}: {
+ address: string;
+ tokenInfo?: FullTokenInfo;
+ mintInfo?: MintAccountInfo;
+ parsedData?: TokenProgramData;
+}): JSX.Element {
+ const metadataExtension = mintInfo?.extensions?.find(
+ ({ extension }: { extension: string }) => extension === 'tokenMetadata'
+ );
+ const metadataPointerExtension = mintInfo?.extensions?.find(
+ ({ extension }: { extension: string }) => extension === 'metadataPointer'
+ );
+
+ const defaultCard = useMemo(
+ () => (
+
+ ),
+ [address, tokenInfo]
+ );
+
+ if (metadataPointerExtension && metadataExtension) {
+ return (
+ <>
+
+
+
+
+
+ >
+ );
+ }
+ // Fall back to legacy token list when there is stub metadata (blank uri), updatable by default by the mint authority
+ else if (!parsedData?.nftData?.metadata.data.uri && tokenInfo) {
+ return defaultCard;
+ } else if (parsedData?.nftData) {
+ const token = {
+ logoURI: parsedData?.nftData?.json?.image,
+ name: parsedData?.nftData?.json?.name ?? parsedData?.nftData.metadata.data.name,
+ };
+ return ;
+ } else if (tokenInfo) {
+ return defaultCard;
+ }
+ return defaultCard;
+}
+
+function Token22MintHeader({
+ address,
+ metadataExtension,
+ metadataPointerExtension,
+}: {
+ address: string;
+ metadataExtension: { extension: 'tokenMetadata'; state?: any };
+ metadataPointerExtension: { extension: 'metadataPointer'; state?: any };
+}) {
+ const tokenMetadata = create(metadataExtension.state, TokenMetadata);
+ const { metadataAddress } = create(metadataPointerExtension.state, MetadataPointer);
+ const metadata = useMetadataJsonLink(tokenMetadata.uri);
+
+ if (!metadata) {
+ throw new Error('Could not load metadata from given URI');
+ }
+
+ // Handles the basic case where MetadataPointer is referencing the Token Metadata extension directly
+ // Does not handle the case where MetadataPointer is pointing at a separate account.
+ if (metadataAddress?.toString() === address) {
+ return (
+
+ );
+ }
+ throw new Error('Metadata loading for non-token 2022 programs is not yet supported');
+}
+
+function TokenMintHeaderCard({
+ address,
+ token,
+ unverified,
+}: {
+ address: string;
+ token: { name?: string | undefined; logoURI?: string | undefined };
+ unverified: boolean;
+}) {
+ return (
+
+ {unverified && (
+
+ Warning! Token names and logos are not unique. This token may have spoofed its name and logo to look
+ like another token. Verify the token's mint address to ensure it is correct.
+
+ )}
+
+
+ {token?.logoURI ? (
+ // eslint-disable-next-line @next/next/no-img-element
+

+ ) : (
+
+ )}
+
+
+
+
+
Token
+ {token?.name || 'Unknown Token'}
+
+
+ );
+}
+
function DetailsSections({
children,
pubkey,