Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add nifty asset program pages #322

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
108 changes: 92 additions & 16 deletions app/address/[address]/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
'use client';

import { AddressLookupTableAccountSection } from '@components/account/address-lookup-table/AddressLookupTableAccountSection';
import { isAddressLookupTableAccount } from '@components/account/address-lookup-table/types';
import { NiftyAssetAccountHeader } from '@/app/components/account/nifty-asset/AssetAccountHeader';
import { ConfigAccountSection } from '@components/account/ConfigAccountSection';
import { FeatureAccountSection } from '@components/account/FeatureAccountSection';
import { MetaplexNFTHeader } from '@components/account/MetaplexNFTHeader';
import { isNFTokenAccount, parseNFTokenCollectionAccount } from '@components/account/nftoken/isNFTokenAccount';
import { NFTOKEN_ADDRESS } from '@components/account/nftoken/nftoken';
import { NFTokenAccountHeader } from '@components/account/nftoken/NFTokenAccountHeader';
import { NFTokenAccountSection } from '@components/account/nftoken/NFTokenAccountSection';
import { NonceAccountSection } from '@components/account/NonceAccountSection';
import { StakeAccountSection } from '@components/account/StakeAccountSection';
import { SysvarAccountSection } from '@components/account/SysvarAccountSection';
import { TokenAccountSection } from '@components/account/TokenAccountSection';
import { UnknownAccountCard } from '@components/account/UnknownAccountCard';
import { UpgradeableLoaderAccountSection } from '@components/account/UpgradeableLoaderAccountSection';
import { VoteAccountSection } from '@components/account/VoteAccountSection';
import { AddressLookupTableAccountSection } from '@components/account/address-lookup-table/AddressLookupTableAccountSection';
import { isAddressLookupTableAccount } from '@components/account/address-lookup-table/types';
import { NFTokenAccountHeader } from '@components/account/nftoken/NFTokenAccountHeader';
import { NFTokenAccountSection } from '@components/account/nftoken/NFTokenAccountSection';
import { isNFTokenAccount, parseNFTokenCollectionAccount } from '@components/account/nftoken/isNFTokenAccount';
import { NFTOKEN_ADDRESS } from '@components/account/nftoken/nftoken';
import { ErrorCard } from '@components/common/ErrorCard';
import { Identicon } from '@components/common/Identicon';
import { LoadingCard } from '@components/common/LoadingCard';
import {
Account,
AccountsProvider,
isTokenProgramData,
TokenProgramData,
isTokenProgramData,
useAccountInfo,
useFetchAccountInfo,
useMintAccountInfo,
Expand All @@ -44,7 +45,10 @@ import React, { PropsWithChildren } from 'react';
import useSWRImmutable from 'swr/immutable';
import { Base58EncodedAddress } from 'web3js-experimental';

import { NiftyAssetAccountCard } from '@/app/components/account/nifty-asset/AssetAccountCard';
import { isNiftyAssetAccount } from '@/app/components/account/nifty-asset/types';
import { FullTokenInfo, getFullTokenInfo } from '@/app/utils/token-info';
import { ASSET_PROGRAM_ID, Asset, ExtensionType, getAssetAccountDataSerializer, getExtension } from '@nifty-oss/asset';

const IDENTICON_WIDTH = 64;

Expand Down Expand Up @@ -192,7 +196,9 @@ function AddressLayoutInner({ children, params: { address } }: Props) {
const infoParsed = info?.data?.data.parsed;

const { data: fullTokenInfo, isLoading: isFullTokenInfoLoading } = useSWRImmutable(
infoStatus === FetchStatus.Fetched && infoParsed && isTokenProgramData(infoParsed) && pubkey ? ['get-full-token-info', address, cluster, url] : null,
infoStatus === FetchStatus.Fetched && infoParsed && isTokenProgramData(infoParsed) && pubkey
? ['get-full-token-info', address, cluster, url]
: null,
fetchFullTokenInfo
);

Expand All @@ -207,13 +213,23 @@ function AddressLayoutInner({ children, params: { address } }: Props) {
<div className="container mt-n3">
<div className="header">
<div className="header-body">
<AccountHeader address={address} account={info?.data} tokenInfo={fullTokenInfo} isTokenInfoLoading={isFullTokenInfoLoading} />
<AccountHeader
address={address}
account={info?.data}
tokenInfo={fullTokenInfo}
isTokenInfoLoading={isFullTokenInfoLoading}
/>
</div>
</div>
{!pubkey ? (
<ErrorCard text={`Address "${address}" is not valid`} />
) : (
<DetailsSections info={info} pubkey={pubkey} tokenInfo={fullTokenInfo} isTokenInfoLoading={isFullTokenInfoLoading}>
<DetailsSections
info={info}
pubkey={pubkey}
tokenInfo={fullTokenInfo}
isTokenInfoLoading={isFullTokenInfoLoading}
>
{children}
</DetailsSections>
)}
Expand All @@ -229,7 +245,17 @@ export default function AddressLayout({ children, params }: Props) {
);
}

function AccountHeader({ address, account, tokenInfo, isTokenInfoLoading }: { address: string; account?: Account, tokenInfo?: FullTokenInfo, isTokenInfoLoading: boolean }) {
function AccountHeader({
address,
account,
tokenInfo,
isTokenInfoLoading,
}: {
address: string;
account?: Account;
tokenInfo?: FullTokenInfo;
isTokenInfoLoading: boolean;
}) {
const mintInfo = useMintAccountInfo(address);

const parsedData = account?.data.parsed;
Expand All @@ -244,6 +270,10 @@ function AccountHeader({ address, account, tokenInfo, isTokenInfoLoading }: { ad
return <NFTokenAccountHeader account={account} />;
}

if (account && isNiftyAssetAccount(account.owner, account.data.raw)) {
return <NiftyAssetAccountHeader account={account} />;
}

if (isToken && !isTokenInfoLoading) {
let token;
let unverified = false;
Expand Down Expand Up @@ -314,7 +344,7 @@ function DetailsSections({
tab,
info,
tokenInfo,
isTokenInfoLoading
isTokenInfoLoading,
}: {
children: React.ReactNode;
pubkey: PublicKey;
Expand Down Expand Up @@ -348,7 +378,7 @@ function DetailsSections({
);
}

function InfoSection({ account, tokenInfo }: { account: Account, tokenInfo?: FullTokenInfo }) {
function InfoSection({ account, tokenInfo }: { account: Account; tokenInfo?: FullTokenInfo }) {
const parsedData = account.data.parsed;
const rawData = account.data.raw;

Expand All @@ -371,6 +401,8 @@ function InfoSection({ account, tokenInfo }: { account: Account, tokenInfo?: Ful
);
} else if (account.owner.toBase58() === NFTOKEN_ADDRESS) {
return <NFTokenAccountSection account={account} />;
} else if (account.owner.toBase58() === ASSET_PROGRAM_ID) {
return <NiftyAssetAccountCard account={account} />;
} else if (parsedData && isTokenProgramData(parsedData)) {
return <TokenAccountSection account={account} tokenAccount={parsedData.parsed} tokenInfo={tokenInfo} />;
} else if (parsedData && parsedData.program === 'nonce') {
Expand Down Expand Up @@ -425,7 +457,10 @@ export type MoreTabs =
| 'anchor-program'
| 'anchor-account'
| 'entries'
| 'concurrent-merkle-tree';
| 'concurrent-merkle-tree'
| 'nifty-asset-metadata'
| 'nifty-asset-extensions'
| 'nifty-asset-ruleset';

function MoreSection({ children, tabs }: { children: React.ReactNode; tabs: (JSX.Element | null)[] }) {
return (
Expand Down Expand Up @@ -467,12 +502,19 @@ function getTabs(pubkey: PublicKey, account: Account): TabComponent[] {
}

// Add the key for address lookup tables
if (account.data.raw && isAddressLookupTableAccount(account.owner.toBase58() as Base58EncodedAddress, account.data.raw)) {
if (
account.data.raw &&
isAddressLookupTableAccount(account.owner.toBase58() as Base58EncodedAddress, account.data.raw)
) {
tabs.push(...TABS_LOOKUP['address-lookup-table']);
}

// Add the key for Metaplex NFTs
if (parsedData && (programTypeKey === 'spl-token:mint' || programTypeKey == 'spl-token-2022:mint') && (parsedData as TokenProgramData).nftData) {
if (
parsedData &&
(programTypeKey === 'spl-token:mint' || programTypeKey == 'spl-token-2022:mint') &&
(parsedData as TokenProgramData).nftData
) {
tabs.push(...TABS_LOOKUP[`${programTypeKey}:metaplexNFT`]);
}

Expand All @@ -488,8 +530,42 @@ function getTabs(pubkey: PublicKey, account: Account): TabComponent[] {
}
}

const isNiftyAsset = account && isNiftyAssetAccount(account.owner, account.data.raw);
if (isNiftyAsset && account.data.raw) {
const asset = account && (getAssetAccountDataSerializer().deserialize(account.data.raw)[0] as Asset);

if (asset.extensions.length > 0) {
const metadata = getExtension(asset, ExtensionType.Metadata);

if (metadata && metadata.uri.length > 0) {
tabs.push({
path: 'nifty-asset-metadata',
slug: 'nifty-asset-metadata',
title: 'Metadata',
});
}

const royalties = getExtension(asset, ExtensionType.Royalties);

if (royalties && royalties.constraint.type !== 'Empty') {
tabs.push({
path: 'nifty-asset-ruleset',
slug: 'nifty-asset-ruleset',
title: 'Rule Set',
});
}

tabs.push({
path: 'nifty-asset-extensions',
slug: 'nifty-asset-extensions',
title: 'Extensions',
});
}
}

if (
!isNFToken &&
!isNiftyAsset &&
(!parsedData || !(TOKEN_TABS_HIDDEN.includes(parsedData.program) || TOKEN_TABS_HIDDEN.includes(programTypeKey)))
) {
tabs.push({
Expand Down
32 changes: 32 additions & 0 deletions app/address/[address]/nifty-asset-extensions/page-client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use client';

import { NiftyAssetExtensionsCard } from '@/app/components/account/nifty-asset/AssetExtensionsCard';
import { ParsedAccountRenderer } from '@components/account/ParsedAccountRenderer';
import { fromWeb3JsPublicKey } from '@metaplex-foundation/umi-web3js-adapters';
import { Asset, deserializeAsset, getAssetAccountDataSerializer } from '@nifty-oss/asset';
import React from 'react';

type Props = Readonly<{
params: {
address: string;
};
}>;

function NiftyAssetExtensionsCardRenderer({
account,
onNotFound,
}: React.ComponentProps<React.ComponentProps<typeof ParsedAccountRenderer>['renderComponent']>) {
const data = account?.data.raw;
const address = account?.pubkey;
const asset = data && (getAssetAccountDataSerializer().deserialize(data)[0] as Asset);

if (asset && address) {
asset.publicKey = fromWeb3JsPublicKey(address);
}

return asset && asset.extensions.length > 0 ? <NiftyAssetExtensionsCard asset={asset} /> : onNotFound();
}

export default function MetaplexNFTMetadataPageClient({ params: { address } }: Props) {
return <ParsedAccountRenderer address={address} renderComponent={NiftyAssetExtensionsCardRenderer} />;
}
21 changes: 21 additions & 0 deletions app/address/[address]/nifty-asset-extensions/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import getReadableTitleFromAddress, { AddressPageMetadataProps } from '@utils/get-readable-title-from-address';
import { Metadata } from 'next/types';

import NiftyAssetExtensionsPageClient from './page-client';

type Props = Readonly<{
params: {
address: string;
};
}>;

export async function generateMetadata(props: AddressPageMetadataProps): Promise<Metadata> {
return {
description: `Extensions for the asset with address ${props.params.address} on Solana`,
title: `Asset Extensions | ${await getReadableTitleFromAddress(props)} | Solana`,
};
}

export default function MetaplexNFTMetadataPage(props: Props) {
return <NiftyAssetExtensionsPageClient {...props} />;
}
34 changes: 34 additions & 0 deletions app/address/[address]/nifty-asset-metadata/page-client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
'use client';

import { NiftyAssetMetadataCard } from '@/app/components/account/nifty-asset/AssetMetadataCard';
import { ParsedAccountRenderer } from '@components/account/ParsedAccountRenderer';
import { Asset, ExtensionType, getAssetAccountDataSerializer, getExtension } from '@nifty-oss/asset';
import React from 'react';

type Props = Readonly<{
params: {
address: string;
};
}>;

function NiftyAssetMetadataCardRenderer({
account,
onNotFound,
}: React.ComponentProps<React.ComponentProps<typeof ParsedAccountRenderer>['renderComponent']>) {
const data = account?.data.raw;
const asset = data && getAssetAccountDataSerializer().deserialize(data);

if (asset) {
const metadata = asset && getExtension(asset[0] as Asset, ExtensionType.Metadata);

if (metadata && metadata.uri) {
return <NiftyAssetMetadataCard asset={asset[0] as Asset} />;
}
}

return onNotFound();
}

export default function MetaplexNFTMetadataPageClient({ params: { address } }: Props) {
return <ParsedAccountRenderer address={address} renderComponent={NiftyAssetMetadataCardRenderer} />;
}
21 changes: 21 additions & 0 deletions app/address/[address]/nifty-asset-metadata/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import getReadableTitleFromAddress, { AddressPageMetadataProps } from '@utils/get-readable-title-from-address';
import { Metadata } from 'next/types';

import NiftyAssetMetadataPageClient from './page-client';

type Props = Readonly<{
params: {
address: string;
};
}>;

export async function generateMetadata(props: AddressPageMetadataProps): Promise<Metadata> {
return {
description: `Metadata for the asset with address ${props.params.address} on Solana`,
title: `Asset Metadata | ${await getReadableTitleFromAddress(props)} | Solana`,
};
}

export default function MetaplexNFTMetadataPage(props: Props) {
return <NiftyAssetMetadataPageClient {...props} />;
}
30 changes: 30 additions & 0 deletions app/address/[address]/nifty-asset-ruleset/page-client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use client';

import { NiftyAssetRuleSetCard } from '@/app/components/account/nifty-asset/AssetRuleSetCard';
import { ParsedAccountRenderer } from '@components/account/ParsedAccountRenderer';
import { Asset, ExtensionType, getAssetAccountDataSerializer, getExtension } from '@nifty-oss/asset';
import React from 'react';

type Props = Readonly<{
params: {
address: string;
};
}>;

function NiftyAssetRuleSetCardRenderer({
account,
onNotFound,
}: React.ComponentProps<React.ComponentProps<typeof ParsedAccountRenderer>['renderComponent']>) {
const data = account?.data.raw;
const asset = data && (getAssetAccountDataSerializer().deserialize(data)[0] as Asset);

return asset && getExtension(asset, ExtensionType.Royalties) ? (
<NiftyAssetRuleSetCard asset={asset} />
) : (
onNotFound()
);
}

export default function MetaplexNFTMetadataPageClient({ params: { address } }: Props) {
return <ParsedAccountRenderer address={address} renderComponent={NiftyAssetRuleSetCardRenderer} />;
}
21 changes: 21 additions & 0 deletions app/address/[address]/nifty-asset-ruleset/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import getReadableTitleFromAddress, { AddressPageMetadataProps } from '@utils/get-readable-title-from-address';
import { Metadata } from 'next/types';

import NiftyAssetRuleSetPageClient from './page-client';

type Props = Readonly<{
params: {
address: string;
};
}>;

export async function generateMetadata(props: AddressPageMetadataProps): Promise<Metadata> {
return {
description: `Royalties Rule Set for the asset with address ${props.params.address} on Solana`,
title: `Rule Set for Asset | ${await getReadableTitleFromAddress(props)} | Solana`,
};
}

export default function MetaplexNFTMetadataPage(props: Props) {
return <NiftyAssetRuleSetPageClient {...props} />;
}