diff --git a/app/address/[address]/largest/page.tsx b/app/address/[address]/largest/page.tsx deleted file mode 100644 index 3cbcdf2e..00000000 --- a/app/address/[address]/largest/page.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { TokenLargestAccountsCard } from '@components/account/TokenLargestAccountsCard'; -import getReadableTitleFromAddress, { AddressPageMetadataProps } from '@utils/get-readable-title-from-address'; -import { Metadata } from 'next/types'; - -type Props = Readonly<{ - params: { - address: string; - }; -}>; - -export async function generateMetadata(props: AddressPageMetadataProps): Promise { - return { - description: `Largest holders of the token with address ${props.params.address} on Solana`, - title: `Token Distribution | ${await getReadableTitleFromAddress(props)} | Solana`, - }; -} - -export default function TokenDistributionPage({ params: { address } }: Props) { - return ; -} diff --git a/app/address/[address]/layout.tsx b/app/address/[address]/layout.tsx index 87ad522c..5afd01db 100644 --- a/app/address/[address]/layout.tsx +++ b/app/address/[address]/layout.tsx @@ -32,7 +32,7 @@ import isMetaplexNFT from '@providers/accounts/utils/isMetaplexNFT'; import { useAnchorProgram } from '@providers/anchor'; import { CacheEntry, FetchStatus } from '@providers/cache'; import { useCluster } from '@providers/cluster'; -import { useTokenRegistry } from '@providers/mints/token-registry'; +import { useTokenRegistry } from '@providers/token-registry'; import { PROGRAM_ID as ACCOUNT_COMPRESSION_ID } from '@solana/spl-account-compression'; import { PublicKey } from '@solana/web3.js'; import { ClusterStatus } from '@utils/cluster'; @@ -84,11 +84,6 @@ const TABS_LOOKUP: { [id: string]: Tab[] } = { slug: 'instructions', title: 'Instructions', }, - { - path: 'largest', - slug: 'largest', - title: 'Distribution', - }, ], 'spl-token:mint:metaplexNFT': [ { @@ -370,7 +365,6 @@ export type MoreTabs = | 'history' | 'tokens' | 'nftoken-collection-nfts' - | 'largest' | 'vote-history' | 'slot-hashes' | 'stake-history' diff --git a/app/components/SearchBar.tsx b/app/components/SearchBar.tsx index c6e0e29c..9a0e3496 100644 --- a/app/components/SearchBar.tsx +++ b/app/components/SearchBar.tsx @@ -1,7 +1,7 @@ 'use client'; import { useCluster } from '@providers/cluster'; -import { useTokenRegistry } from '@providers/mints/token-registry'; +import { useTokenRegistry } from '@providers/token-registry'; import { TokenInfoMap } from '@solana/spl-token-registry'; import { Connection } from '@solana/web3.js'; import { Cluster } from '@utils/cluster'; diff --git a/app/components/account/OwnedTokensCard.tsx b/app/components/account/OwnedTokensCard.tsx index a1672725..31516077 100644 --- a/app/components/account/OwnedTokensCard.tsx +++ b/app/components/account/OwnedTokensCard.tsx @@ -6,7 +6,7 @@ import { Identicon } from '@components/common/Identicon'; import { LoadingCard } from '@components/common/LoadingCard'; import { TokenInfoWithPubkey, useAccountOwnedTokens, useFetchAccountOwnedTokens } from '@providers/accounts/tokens'; import { FetchStatus } from '@providers/cache'; -import { useTokenRegistry } from '@providers/mints/token-registry'; +import { useTokenRegistry } from '@providers/token-registry'; import { PublicKey } from '@solana/web3.js'; import { BigNumber } from 'bignumber.js'; import Image from 'next/image'; diff --git a/app/components/account/TokenAccountSection.tsx b/app/components/account/TokenAccountSection.tsx index a88c7c97..3e6fc920 100644 --- a/app/components/account/TokenAccountSection.tsx +++ b/app/components/account/TokenAccountSection.tsx @@ -5,7 +5,7 @@ import { TableCardBody } from '@components/common/TableCardBody'; import { Account, NFTData, TokenProgramData, useFetchAccountInfo } from '@providers/accounts'; import isMetaplexNFT from '@providers/accounts/utils/isMetaplexNFT'; import { useCluster } from '@providers/cluster'; -import { useTokenRegistry } from '@providers/mints/token-registry'; +import { useTokenRegistry } from '@providers/token-registry'; import { PublicKey } from '@solana/web3.js'; import { Cluster } from '@utils/cluster'; import { CoingeckoStatus, useCoinGecko } from '@utils/coingecko'; diff --git a/app/components/account/TokenHistoryCard.tsx b/app/components/account/TokenHistoryCard.tsx index 021b21ea..ff16d58a 100644 --- a/app/components/account/TokenHistoryCard.tsx +++ b/app/components/account/TokenHistoryCard.tsx @@ -16,7 +16,7 @@ import { useAccountHistories, useFetchAccountHistory } from '@providers/accounts import { TOKEN_PROGRAM_ID, TokenInfoWithPubkey, useAccountOwnedTokens } from '@providers/accounts/tokens'; import { CacheEntry, FetchStatus } from '@providers/cache'; import { useCluster } from '@providers/cluster'; -import { useTokenRegistry } from '@providers/mints/token-registry'; +import { useTokenRegistry } from '@providers/token-registry'; import { Details, useFetchTransactionDetails, useTransactionDetailsCache } from '@providers/transactions/parsed'; import { TokenInfoMap } from '@solana/spl-token-registry'; import { ConfirmedSignatureInfo, ParsedInstruction, PartiallyDecodedInstruction, PublicKey } from '@solana/web3.js'; diff --git a/app/components/account/TokenLargestAccountsCard.tsx b/app/components/account/TokenLargestAccountsCard.tsx deleted file mode 100644 index 7287442e..00000000 --- a/app/components/account/TokenLargestAccountsCard.tsx +++ /dev/null @@ -1,132 +0,0 @@ -'use client'; - -import { Address } from '@components/common/Address'; -import { ErrorCard } from '@components/common/ErrorCard'; -import { LoadingCard } from '@components/common/LoadingCard'; -import { useMintAccountInfo } from '@providers/accounts'; -import { FetchStatus } from '@providers/cache'; -import { - TokenAccountBalancePairWithOwner, - useFetchTokenLargestAccounts, - useTokenLargestTokens, -} from '@providers/mints/largest'; -import { useTokenRegistry } from '@providers/mints/token-registry'; -import { PublicKey } from '@solana/web3.js'; -import { normalizeTokenAmount } from '@utils/index'; -import BigNumber from 'bignumber.js'; -import React, { useMemo } from 'react'; - -export function TokenLargestAccountsCard({ mintAddress }: { mintAddress: string }) { - const pubkey = useMemo(() => new PublicKey(mintAddress), [mintAddress]); - const mintInfo = useMintAccountInfo(mintAddress); - const largestAccounts = useTokenLargestTokens(mintAddress); - const fetchLargestAccounts = useFetchTokenLargestAccounts(); - const refreshLargest = React.useCallback(() => fetchLargestAccounts(pubkey), [pubkey, fetchLargestAccounts]); - const { tokenRegistry } = useTokenRegistry(); - const unit = tokenRegistry.get(mintAddress)?.symbol; - const unitLabel = unit ? `(${unit})` : ''; - - React.useEffect(() => { - if (mintInfo) refreshLargest(); - }, [mintInfo, refreshLargest]); - - // Largest accounts hasn't started fetching - if (largestAccounts === undefined) return null; - - // This is not a mint account - if (mintInfo === undefined) return null; - - if (largestAccounts?.data === undefined) { - if (largestAccounts.status === FetchStatus.Fetching) { - return ; - } - - return ; - } else if (largestAccounts.status === FetchStatus.Fetching) { - return ; - } - - const accounts = largestAccounts.data.largest; - if (accounts.length === 0) { - return ; - } - - // Find largest fixed point in accounts array - const balanceFixedPoint = accounts.reduce((prev: number, current: TokenAccountBalancePairWithOwner) => { - const amount = `${current.uiAmountString}`; - const length = amount.length; - const decimalIndex = amount.indexOf('.'); - if (decimalIndex >= 0 && length - decimalIndex - 1 > prev) { - return length - decimalIndex - 1; - } - return prev; - }, 0); - - const supplyTotal = normalizeTokenAmount(mintInfo.supply, mintInfo.decimals); - - return ( - <> -
-
-
-
-

Largest Accounts

-
-
-
- -
- - - - - - - - - - - - {accounts.map((account, index) => - renderAccountRow(account, index, balanceFixedPoint, supplyTotal) - )} - -
RankAddressOwnerBalance {unitLabel}% of Total Supply
-
-
- - ); -} - -const renderAccountRow = ( - account: TokenAccountBalancePairWithOwner, - index: number, - balanceFixedPoint: number, - supply: number -) => { - let percent = '-'; - if (supply > 0 && account.uiAmountString) { - const uiAmountPercent = new BigNumber(account.uiAmountString).times(100).dividedBy(supply); - - percent = `${uiAmountPercent.toFormat(3)}%`; - - if (parseFloat(percent) === 0 && new BigNumber(account.uiAmountString).gt(0)) { - percent = `~${percent}`; - } - } - return ( - - - {index + 1} - - -
- - {account.owner &&
} - - {account.uiAmountString && new BigNumber(account.uiAmountString).toFormat(balanceFixedPoint)} - - {percent} - - ); -}; diff --git a/app/components/account/UnknownAccountCard.tsx b/app/components/account/UnknownAccountCard.tsx index bf6acb10..fac7e212 100644 --- a/app/components/account/UnknownAccountCard.tsx +++ b/app/components/account/UnknownAccountCard.tsx @@ -3,7 +3,7 @@ import { SolBalance } from '@components/common/SolBalance'; import { TableCardBody } from '@components/common/TableCardBody'; import { Account } from '@providers/accounts'; import { useCluster } from '@providers/cluster'; -import { useTokenRegistry } from '@providers/mints/token-registry'; +import { useTokenRegistry } from '@providers/token-registry'; import { addressLabel } from '@utils/tx'; import React from 'react'; diff --git a/app/components/account/history/TokenTransfersCard.tsx b/app/components/account/history/TokenTransfersCard.tsx index 91a163da..eee07054 100644 --- a/app/components/account/history/TokenTransfersCard.tsx +++ b/app/components/account/history/TokenTransfersCard.tsx @@ -9,7 +9,7 @@ import { useAccountHistory } from '@providers/accounts'; import { useFetchAccountHistory } from '@providers/accounts/history'; import { FetchStatus } from '@providers/cache'; import { useCluster } from '@providers/cluster'; -import { useTokenRegistry } from '@providers/mints/token-registry'; +import { useTokenRegistry } from '@providers/token-registry'; import { ParsedInstruction, ParsedTransactionWithMeta, PartiallyDecodedInstruction, PublicKey } from '@solana/web3.js'; import { Cluster } from '@utils/cluster'; import { normalizeTokenAmount } from '@utils/index'; diff --git a/app/components/common/Address.tsx b/app/components/common/Address.tsx index 9f775d4c..03bf3dd1 100644 --- a/app/components/common/Address.tsx +++ b/app/components/common/Address.tsx @@ -2,7 +2,7 @@ import { Connection, programs } from '@metaplex/js'; import { useCluster } from '@providers/cluster'; -import { useTokenRegistry } from '@providers/mints/token-registry'; +import { useTokenRegistry } from '@providers/token-registry'; import { PublicKey } from '@solana/web3.js'; import { displayAddress } from '@utils/tx'; import { useClusterPath } from '@utils/url'; diff --git a/app/components/instruction/token/TokenDetailsCard.tsx b/app/components/instruction/token/TokenDetailsCard.tsx index c916ca1a..ea9813e3 100644 --- a/app/components/instruction/token/TokenDetailsCard.tsx +++ b/app/components/instruction/token/TokenDetailsCard.tsx @@ -1,6 +1,6 @@ import { Address } from '@components/common/Address'; import { useFetchAccountInfo, useMintAccountInfo, useTokenAccountInfo } from '@providers/accounts'; -import { useTokenRegistry } from '@providers/mints/token-registry'; +import { useTokenRegistry } from '@providers/token-registry'; import { ParsedInstruction, ParsedTransaction, PublicKey, SignatureResult } from '@solana/web3.js'; import { normalizeTokenAmount } from '@utils/index'; import { ParsedInfo } from '@validators/index'; diff --git a/app/components/transaction/TokenBalancesCard.tsx b/app/components/transaction/TokenBalancesCard.tsx index 2d4d933f..dab78cd2 100644 --- a/app/components/transaction/TokenBalancesCard.tsx +++ b/app/components/transaction/TokenBalancesCard.tsx @@ -1,6 +1,6 @@ import { Address } from '@components/common/Address'; import { BalanceDelta } from '@components/common/BalanceDelta'; -import { useTokenRegistry } from '@providers/mints/token-registry'; +import { useTokenRegistry } from '@providers/token-registry'; import { useTransactionDetails } from '@providers/transactions'; import { ParsedMessageAccount, PublicKey, TokenAmount, TokenBalance } from '@solana/web3.js'; import { SignatureProps } from '@utils/index'; diff --git a/app/layout.tsx b/app/layout.tsx index 3ad63937..a67c1d7e 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -6,11 +6,12 @@ import { MessageBanner } from '@components/MessageBanner'; import { Navbar } from '@components/Navbar'; import { SearchBar } from '@components/SearchBar'; import { ClusterProvider } from '@providers/cluster'; -import { MintsProvider } from '@providers/mints'; import { ScrollAnchorProvider } from '@providers/scroll-anchor'; import { Rubik } from 'next/font/google'; import { Metadata } from 'next/types'; +import { TokenRegistryProvider } from './providers/token-registry'; + export const metadata: Metadata = { description: 'Inspect transactions, accounts, blocks, and more on the Solana blockchain', manifest: '/manifest.json', @@ -41,7 +42,7 @@ export default function RootLayout({ - +
@@ -50,7 +51,7 @@ export default function RootLayout({ {children}
-
+
{analytics} diff --git a/app/providers/mints/index.tsx b/app/providers/mints/index.tsx deleted file mode 100644 index 6abb817b..00000000 --- a/app/providers/mints/index.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; - -import { LargestAccountsProvider } from './largest'; -import { TokenRegistryProvider } from './token-registry'; - -type ProviderProps = { children: React.ReactNode }; -export function MintsProvider({ children }: ProviderProps) { - return ( - - {children} - - ); -} diff --git a/app/providers/mints/largest.tsx b/app/providers/mints/largest.tsx deleted file mode 100644 index 87f29b66..00000000 --- a/app/providers/mints/largest.tsx +++ /dev/null @@ -1,128 +0,0 @@ -'use client'; - -import * as Cache from '@providers/cache'; -import { ActionType, FetchStatus } from '@providers/cache'; -import { useCluster } from '@providers/cluster'; -import { Connection, ParsedAccountData, PublicKey, TokenAccountBalancePair } from '@solana/web3.js'; -import { Cluster } from '@utils/cluster'; -import { reportError } from '@utils/sentry'; -import { TokenAccount, TokenAccountInfo } from '@validators/accounts/token'; -import { ParsedInfo } from '@validators/index'; -import React from 'react'; -import { create } from 'superstruct'; - -type LargestAccounts = { - largest: TokenAccountBalancePairWithOwner[]; -}; - -type State = Cache.State; -type Dispatch = Cache.Dispatch; - -const StateContext = React.createContext(undefined); -const DispatchContext = React.createContext(undefined); - -type ProviderProps = { children: React.ReactNode }; -export function LargestAccountsProvider({ children }: ProviderProps) { - const { url } = useCluster(); - const [state, dispatch] = Cache.useReducer(url); - - // Clear cache whenever cluster is changed - React.useEffect(() => { - dispatch({ type: ActionType.Clear, url }); - }, [dispatch, url]); - - return ( - - {children} - - ); -} - -type OptionalOwner = { - owner?: PublicKey; -}; - -export type TokenAccountBalancePairWithOwner = TokenAccountBalancePair & OptionalOwner; - -async function fetchLargestAccounts(dispatch: Dispatch, pubkey: PublicKey, cluster: Cluster, url: string) { - dispatch({ - key: pubkey.toBase58(), - status: Cache.FetchStatus.Fetching, - type: ActionType.Update, - url, - }); - - let data; - let fetchStatus; - try { - data = { - largest: (await new Connection(url, 'confirmed').getTokenLargestAccounts(pubkey)).value, - }; - - data.largest = await Promise.all( - data.largest.map(async (account): Promise => { - try { - const accountInfo = (await new Connection(url, 'confirmed').getParsedAccountInfo(account.address)) - .value; - if (accountInfo && 'parsed' in accountInfo.data) { - const info = createParsedAccountInfo(accountInfo.data); - return { - ...account, - owner: info.owner, - }; - } - } catch (error) { - if (cluster !== Cluster.Custom) { - reportError(error, { url }); - } - } - return account; - }) - ); - - fetchStatus = FetchStatus.Fetched; - } catch (error) { - if (cluster !== Cluster.Custom) { - reportError(error, { url }); - } - fetchStatus = FetchStatus.FetchFailed; - } - dispatch({ - data, - key: pubkey.toBase58(), - status: fetchStatus, - type: ActionType.Update, - url, - }); -} - -export function useFetchTokenLargestAccounts() { - const dispatch = React.useContext(DispatchContext); - if (!dispatch) { - throw new Error(`useFetchTokenLargestAccounts must be used within a MintsProvider`); - } - - const { cluster, url } = useCluster(); - return React.useCallback( - (pubkey: PublicKey) => { - fetchLargestAccounts(dispatch, pubkey, cluster, url); - }, - [dispatch, cluster, url] - ); -} - -export function useTokenLargestTokens(address: string): Cache.CacheEntry | undefined { - const context = React.useContext(StateContext); - - if (!context) { - throw new Error(`useTokenLargestTokens must be used within a MintsProvider`); - } - - return context.entries[address]; -} - -function createParsedAccountInfo(parsedData: ParsedAccountData): TokenAccountInfo { - const data = create(parsedData.parsed, ParsedInfo); - const parsed = create(data, TokenAccount); - return create(parsed.info, TokenAccountInfo); -} diff --git a/app/providers/mints/token-registry.tsx b/app/providers/token-registry.tsx similarity index 100% rename from app/providers/mints/token-registry.tsx rename to app/providers/token-registry.tsx