From a2befef34adcb5854c0b02cbdd9f570420fa816b Mon Sep 17 00:00:00 2001 From: Jon C Date: Tue, 12 Dec 2023 14:34:45 +0100 Subject: [PATCH 1/3] token-2022: Add program and basic parsing --- .../[address]/attributes/page-client.tsx | 3 +- app/address/[address]/layout.tsx | 37 ++++++++++++++++--- .../[address]/metadata/page-client.tsx | 3 +- app/components/account/TokenHistoryCard.tsx | 5 ++- .../account/history/TokenTransfersCard.tsx | 4 +- app/components/common/InstructionDetails.tsx | 3 +- .../instruction/token/TokenDetailsCard.tsx | 3 +- app/providers/accounts/index.tsx | 18 +++++++-- app/providers/accounts/utils/isMetaplexNFT.ts | 11 +++--- app/utils/instruction.ts | 3 +- app/utils/programs.ts | 23 ++++++++++++ 11 files changed, 89 insertions(+), 24 deletions(-) diff --git a/app/address/[address]/attributes/page-client.tsx b/app/address/[address]/attributes/page-client.tsx index a161dd1d..3e4ecbc3 100644 --- a/app/address/[address]/attributes/page-client.tsx +++ b/app/address/[address]/attributes/page-client.tsx @@ -2,6 +2,7 @@ import { MetaplexNFTAttributesCard } from '@components/account/MetaplexNFTAttributesCard'; import { ParsedAccountRenderer } from '@components/account/ParsedAccountRenderer'; +import { isTokenProgramData } from '@providers/accounts'; import React from 'react'; type Props = Readonly<{ @@ -15,7 +16,7 @@ function MetaplexNFTAttributesCardRenderer({ onNotFound, }: React.ComponentProps['renderComponent']>) { const parsedData = account?.data?.parsed; - if (!parsedData || parsedData.program !== 'spl-token' || parsedData.parsed.type !== 'mint' || !parsedData.nftData) { + if (!parsedData || !isTokenProgramData(parsedData) || parsedData.parsed.type !== 'mint' || !parsedData.nftData) { return onNotFound(); } return ; diff --git a/app/address/[address]/layout.tsx b/app/address/[address]/layout.tsx index 41ef791e..d6b1030a 100644 --- a/app/address/[address]/layout.tsx +++ b/app/address/[address]/layout.tsx @@ -22,6 +22,7 @@ import { LoadingCard } from '@components/common/LoadingCard'; import { Account, AccountsProvider, + isTokenProgramData, TokenProgramData, useAccountInfo, useFetchAccountInfo, @@ -76,6 +77,30 @@ const TABS_LOOKUP: { [id: string]: Tab[] } = { title: 'Concurrent Merkle Tree', }, ], + 'spl-token-2022:mint': [ + { + path: 'transfers', + slug: 'transfers', + title: 'Transfers', + }, + { + path: 'instructions', + slug: 'instructions', + title: 'Instructions', + }, + ], + 'spl-token-2022:mint:metaplexNFT': [ + { + path: 'metadata', + slug: 'metadata', + title: 'Metadata', + }, + { + path: 'attributes', + slug: 'attributes', + title: 'Attributes', + }, + ], 'spl-token:mint': [ { path: 'transfers', @@ -142,7 +167,7 @@ const TABS_LOOKUP: { [id: string]: Tab[] } = { ], }; -const TOKEN_TABS_HIDDEN = ['spl-token:mint', 'config', 'vote', 'sysvar', 'config']; +const TOKEN_TABS_HIDDEN = ['spl-token:mint', 'spl-token-2022:mint', 'config', 'vote', 'sysvar', 'config']; type Props = PropsWithChildren<{ params: { address: string } }>; @@ -164,10 +189,10 @@ function AddressLayoutInner({ children, params: { address } }: Props) { } const infoStatus = info?.status; - const infoProgram = info?.data?.data.parsed?.program; + const infoParsed = info?.data?.data.parsed; const { data: fullTokenInfo, isLoading: isFullTokenInfoLoading } = useSWRImmutable( - infoStatus === FetchStatus.Fetched && infoProgram === "spl-token" && 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 ); @@ -208,7 +233,7 @@ function AccountHeader({ address, account, tokenInfo, isTokenInfoLoading }: { ad const mintInfo = useMintAccountInfo(address); const parsedData = account?.data.parsed; - const isToken = parsedData?.program === 'spl-token' && parsedData?.parsed.type === 'mint'; + const isToken = parsedData && isTokenProgramData(parsedData) && parsedData?.parsed.type === 'mint'; if (isMetaplexNFT(parsedData, mintInfo) && parsedData.nftData) { return ; @@ -346,7 +371,7 @@ function InfoSection({ account, tokenInfo }: { account: Account, tokenInfo?: Ful ); } else if (account.owner.toBase58() === NFTOKEN_ADDRESS) { return ; - } else if (parsedData && parsedData.program === 'spl-token') { + } else if (parsedData && isTokenProgramData(parsedData)) { return ; } else if (parsedData && parsedData.program === 'nonce') { return ; @@ -447,7 +472,7 @@ function getTabs(pubkey: PublicKey, account: Account): TabComponent[] { } // Add the key for Metaplex NFTs - if (parsedData && programTypeKey === 'spl-token: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`]); } diff --git a/app/address/[address]/metadata/page-client.tsx b/app/address/[address]/metadata/page-client.tsx index 23b797f0..ba2f1fae 100644 --- a/app/address/[address]/metadata/page-client.tsx +++ b/app/address/[address]/metadata/page-client.tsx @@ -2,6 +2,7 @@ import { MetaplexMetadataCard } from '@components/account/MetaplexMetadataCard'; import { ParsedAccountRenderer } from '@components/account/ParsedAccountRenderer'; +import { isTokenProgramData } from '@providers/accounts'; import React from 'react'; type Props = Readonly<{ @@ -15,7 +16,7 @@ function MetaplexMetadataCardRenderer({ onNotFound, }: React.ComponentProps['renderComponent']>) { const parsedData = account?.data?.parsed; - if (!parsedData || parsedData.program !== 'spl-token' || parsedData.parsed.type !== 'mint' || !parsedData.nftData) { + if (!parsedData || !isTokenProgramData(parsedData) || parsedData.parsed.type !== 'mint' || !parsedData.nftData) { return onNotFound(); } return ; diff --git a/app/components/account/TokenHistoryCard.tsx b/app/components/account/TokenHistoryCard.tsx index 85c4e290..4e3ffda4 100644 --- a/app/components/account/TokenHistoryCard.tsx +++ b/app/components/account/TokenHistoryCard.tsx @@ -12,6 +12,7 @@ import { parseTokenLendingInstructionTitle, } from '@components/instruction/token-lending/types'; import { isTokenSwapInstruction, parseTokenSwapInstructionTitle } from '@components/instruction/token-swap/types'; +import { isTokenProgramData } from '@providers/accounts'; import { useAccountHistories, useFetchAccountHistory } from '@providers/accounts/history'; import { TOKEN_PROGRAM_ID, TokenInfoWithPubkey, useAccountOwnedTokens } from '@providers/accounts/tokens'; import { CacheEntry, FetchStatus } from '@providers/cache'; @@ -391,7 +392,7 @@ const TokenTransactionRow = React.memo(function TokenTransactionRow({ } if ('parsed' in ix) { - if (ix.program === 'spl-token') { + if (isTokenProgramData(ix)) { name = getTokenProgramInstructionName(ix, tx); } else { return undefined; @@ -476,7 +477,7 @@ function InstructionDetails({ instructionType, tx }: { instructionType: Instruct const instructionTypes = instructionType.innerInstructions .map(ix => { - if ('parsed' in ix && ix.program === 'spl-token') { + if ('parsed' in ix && isTokenProgramData(ix)) { return getTokenProgramInstructionName(ix, tx); } return undefined; diff --git a/app/components/account/history/TokenTransfersCard.tsx b/app/components/account/history/TokenTransfersCard.tsx index 714135ee..e250b6d7 100644 --- a/app/components/account/history/TokenTransfersCard.tsx +++ b/app/components/account/history/TokenTransfersCard.tsx @@ -5,7 +5,7 @@ import { ErrorCard } from '@components/common/ErrorCard'; import { LoadingCard } from '@components/common/LoadingCard'; import { Signature } from '@components/common/Signature'; import { TokenInstructionType, Transfer, TransferChecked } from '@components/instruction/token/types'; -import { useAccountHistory } from '@providers/accounts'; +import { isTokenProgramData, useAccountHistory } from '@providers/accounts'; import { useFetchAccountHistory } from '@providers/accounts/history'; import { FetchStatus } from '@providers/cache'; import { useCluster } from '@providers/cluster'; @@ -218,7 +218,7 @@ function getTransfer( cluster: Cluster, signature: string ): Transfer | TransferChecked | undefined { - if ('parsed' in instruction && instruction.program === 'spl-token') { + if ('parsed' in instruction && isTokenProgramData(instruction)) { try { const { type: rawType } = instruction.parsed; const type = create(rawType, TokenInstructionType); diff --git a/app/components/common/InstructionDetails.tsx b/app/components/common/InstructionDetails.tsx index cb58d8ce..6243751f 100644 --- a/app/components/common/InstructionDetails.tsx +++ b/app/components/common/InstructionDetails.tsx @@ -1,3 +1,4 @@ +import { isTokenProgramData } from '@providers/accounts'; import { ConfirmedSignatureInfo } from '@solana/web3.js'; import { getTokenProgramInstructionName, InstructionType } from '@utils/instruction'; import React from 'react'; @@ -14,7 +15,7 @@ export function InstructionDetails({ const instructionTypes = instructionType.innerInstructions .map(ix => { - if ('parsed' in ix && ix.program === 'spl-token') { + if ('parsed' in ix && isTokenProgramData(ix)) { return getTokenProgramInstructionName(ix, tx); } return undefined; diff --git a/app/components/instruction/token/TokenDetailsCard.tsx b/app/components/instruction/token/TokenDetailsCard.tsx index fa5748ec..a1d7dd26 100644 --- a/app/components/instruction/token/TokenDetailsCard.tsx +++ b/app/components/instruction/token/TokenDetailsCard.tsx @@ -9,6 +9,7 @@ import useSWR from 'swr'; import { useCluster } from '@/app/providers/cluster'; import { Cluster } from '@/app/utils/cluster'; +import { TOKEN_IDS } from '@/app/utils/programs'; import { getTokenInfo, getTokenInfoSwrKey } from '@/app/utils/token-info'; import { InstructionCard } from '../InstructionCard'; @@ -27,7 +28,7 @@ export function TokenDetailsCard(props: DetailsProps) { const parsed = create(props.ix.parsed, ParsedInfo); const { type: rawType, info } = parsed; const type = create(rawType, TokenInstructionType); - const title = `Token Program: ${IX_TITLES[type]}`; + const title = `${TOKEN_IDS[props.ix.programId.toString()]}: ${IX_TITLES[type]}`; const created = create(info, IX_STRUCTS[type] as any); return ; } diff --git a/app/providers/accounts/index.tsx b/app/providers/accounts/index.tsx index 41cf3e6e..4fa4e639 100644 --- a/app/providers/accounts/index.tsx +++ b/app/providers/accounts/index.tsx @@ -16,6 +16,7 @@ import { } from '@solana/web3.js'; import { Cluster } from '@utils/cluster'; import { pubkeyToString } from '@utils/index'; +import { assertIsTokenProgram, TokenProgram } from '@utils/programs'; import { ParsedAddressLookupTableAccount } from '@validators/accounts/address-lookup-table'; import { ConfigAccount } from '@validators/accounts/config'; import { NonceAccount } from '@validators/accounts/nonce'; @@ -57,8 +58,16 @@ export type NFTData = { editionInfo: EditionInfo; }; +export function isTokenProgramData(data: { program: string }): data is TokenProgramData { + try { + assertIsTokenProgram(data.program); + return true; + } catch(e) { + return false; + } +} export type TokenProgramData = { - program: 'spl-token'; + program: TokenProgram; parsed: TokenAccount; nftData?: NFTData; }; @@ -374,7 +383,8 @@ async function handleParsedAccountData( }; } - case 'spl-token': { + case 'spl-token': + case 'spl-token-2022': { const parsed = create(info, TokenAccount); let nftData; @@ -484,7 +494,7 @@ export function useMintAccountInfo(address: string | undefined): MintAccountInfo try { const parsedData = account.data.parsed; if (!parsedData) return; - if (parsedData.program !== 'spl-token' || parsedData.parsed.type !== 'mint') { + if (!isTokenProgramData(parsedData) || parsedData.parsed.type !== 'mint') { return; } @@ -504,7 +514,7 @@ export function useTokenAccountInfo(address: string | undefined): TokenAccountIn try { const parsedData = account.data.parsed; if (!parsedData) return; - if (parsedData.program !== 'spl-token' || parsedData.parsed.type !== 'account') { + if (!isTokenProgramData(parsedData) || parsedData.parsed.type !== 'account') { return; } diff --git a/app/providers/accounts/utils/isMetaplexNFT.ts b/app/providers/accounts/utils/isMetaplexNFT.ts index 8da027d8..30feaffc 100644 --- a/app/providers/accounts/utils/isMetaplexNFT.ts +++ b/app/providers/accounts/utils/isMetaplexNFT.ts @@ -1,16 +1,17 @@ import { MintAccountInfo } from '@validators/accounts/token'; -import { ParsedData, TokenProgramData } from '..'; +import { isTokenProgramData, ParsedData, TokenProgramData } from '..'; export default function isMetaplexNFT( parsedData?: ParsedData, mintInfo?: MintAccountInfo ): parsedData is TokenProgramData { return !!( - parsedData?.program === 'spl-token' && - parsedData?.parsed.type === 'mint' && - parsedData?.nftData && + parsedData && + isTokenProgramData(parsedData) && + parsedData.parsed.type === 'mint' && + parsedData.nftData && mintInfo?.decimals === 0 && - (parseInt(mintInfo.supply) === 1 || parsedData?.nftData.metadata.tokenStandard === 1) + (parseInt(mintInfo.supply) === 1 || parsedData.nftData.metadata.tokenStandard === 1) ); } diff --git a/app/utils/instruction.ts b/app/utils/instruction.ts index c4cb5ea8..e4330420 100644 --- a/app/utils/instruction.ts +++ b/app/utils/instruction.ts @@ -12,6 +12,7 @@ import { ParsedTransactionWithMeta, PartiallyDecodedInstruction, } from '@solana/web3.js'; +import { isTokenProgram } from '@utils/programs'; import { intoTransactionInstruction } from '@utils/tx'; import { ParsedInfo } from '@validators/index'; import { create } from 'superstruct'; @@ -82,7 +83,7 @@ export function getTokenInstructionName( } if ('parsed' in ix) { - if (ix.program === 'spl-token') { + if (isTokenProgram(ix.program)) { return getTokenProgramInstructionName(ix, signatureInfo); } else { return undefined; diff --git a/app/utils/programs.ts b/app/utils/programs.ts index 02b6d10d..4b4a937e 100644 --- a/app/utils/programs.ts +++ b/app/utils/programs.ts @@ -24,6 +24,7 @@ export enum PROGRAM_NAMES { STAKE_POOL = 'Stake Pool Program', SWAP = 'Swap Program', TOKEN = 'Token Program', + TOKEN_2022 = 'Token-2022 Program', TOKEN_METADATA = 'Token Metadata Program', TOKEN_VAULT = 'Token Vault Program', @@ -343,6 +344,10 @@ export const PROGRAM_INFO_BY_ID: { [address: string]: ProgramInfo } = { deployments: ALL_CLUSTERS, name: PROGRAM_NAMES.TOKEN, }, + TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb: { + deployments: ALL_CLUSTERS, + name: PROGRAM_NAMES.TOKEN_2022, + }, Vote111111111111111111111111111111111111111: { deployments: ALL_CLUSTERS, name: PROGRAM_NAMES.VOTE, @@ -434,3 +439,21 @@ export const SYSVAR_IDS: { [key: string]: string } = { SysvarS1otHistory11111111111111111111111111: 'Sysvar: Slot History', SysvarStakeHistory1111111111111111111111111: 'Sysvar: Stake History', }; + +export const TOKEN_IDS: { [key: string]: string } = { + TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA: 'Token Program', + TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb: 'Token-2022 Program', +} as const; + +export type TokenProgram = 'spl-token' | 'spl-token-2022'; +export function assertIsTokenProgram(program: string): asserts program is TokenProgram { + if (program !== 'spl-token' && program !== 'spl-token-2022') throw new Error("Expected token program name of `spl-token` or `spl-token-2022`"); +} +export function isTokenProgram(program: string): program is TokenProgram { + try { + assertIsTokenProgram(program); + return true; + } catch(e) { + return false; + } +} From 4893272f52dbe13ac85b53b69e827f359a699277 Mon Sep 17 00:00:00 2001 From: Jon C Date: Wed, 13 Dec 2023 00:48:14 +0100 Subject: [PATCH 2/3] Use TOKEN_2022_PROGRAM_ID with TOKEN_PROGRAM_ID --- app/components/account/TokenHistoryCard.tsx | 4 +-- app/components/inspector/SimulatorCard.tsx | 27 +++++++++++++-------- app/providers/accounts/tokens.tsx | 11 +++++++-- app/utils/instruction.ts | 4 +-- 4 files changed, 30 insertions(+), 16 deletions(-) diff --git a/app/components/account/TokenHistoryCard.tsx b/app/components/account/TokenHistoryCard.tsx index 4e3ffda4..d70491a4 100644 --- a/app/components/account/TokenHistoryCard.tsx +++ b/app/components/account/TokenHistoryCard.tsx @@ -14,7 +14,7 @@ import { import { isTokenSwapInstruction, parseTokenSwapInstructionTitle } from '@components/instruction/token-swap/types'; import { isTokenProgramData } from '@providers/accounts'; import { useAccountHistories, useFetchAccountHistory } from '@providers/accounts/history'; -import { TOKEN_PROGRAM_ID, TokenInfoWithPubkey, useAccountOwnedTokens } from '@providers/accounts/tokens'; +import { isTokenProgramId, TokenInfoWithPubkey, useAccountOwnedTokens } from '@providers/accounts/tokens'; import { CacheEntry, FetchStatus } from '@providers/cache'; import { useCluster } from '@providers/cluster'; import { Details, useFetchTransactionDetails, useTransactionDetailsCache } from '@providers/transactions/parsed'; @@ -426,7 +426,7 @@ const TokenTransactionRow = React.memo(function TokenTransactionRow({ return undefined; } } else { - if (ix.accounts.findIndex(account => account.equals(TOKEN_PROGRAM_ID)) >= 0) { + if (ix.accounts.findIndex(account => isTokenProgramId(account)) >= 0) { name = 'Unknown (Inner)'; } else { return undefined; diff --git a/app/components/inspector/SimulatorCard.tsx b/app/components/inspector/SimulatorCard.tsx index 5bdb387d..9616cf10 100755 --- a/app/components/inspector/SimulatorCard.tsx +++ b/app/components/inspector/SimulatorCard.tsx @@ -1,6 +1,7 @@ import { ProgramLogsCardBody } from '@components/ProgramLogsCardBody'; +import { TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@providers/accounts/tokens'; import { useCluster } from '@providers/cluster'; -import { AccountLayout, MintLayout, TOKEN_PROGRAM_ID } from "@solana/spl-token"; +import { AccountLayout, MintLayout } from "@solana/spl-token"; import { AccountInfo, AddressLookupTableAccount, Connection, MessageAddressTableLookup, ParsedAccountData, ParsedMessageAccount, SimulatedTransactionAccountInfo, TokenBalance, VersionedMessage, VersionedTransaction } from '@solana/web3.js'; import { PublicKey } from '@solana/web3.js'; import { InstructionLogs, parseProgramLogs } from '@utils/program-logs'; @@ -140,9 +141,9 @@ function useSimulator(message: VersionedMessage) { const accountOwnerPost = resp.value.accounts?.at(index)?.owner; if ( - (parsedAccountPre?.owner.toBase58() == TOKEN_PROGRAM_ID.toBase58() || - parsedAccountPre?.owner.toBase58() == "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb") && - (parsedAccountPre?.data as ParsedAccountData).parsed.type === 'account' + parsedAccountPre && + isTokenProgramBase58(parsedAccountPre.owner.toBase58()) && + (parsedAccountPre.data as ParsedAccountData).parsed.type === 'account' ) { const mint = (parsedAccountPre?.data as ParsedAccountData).parsed.info.mint; const owner = (parsedAccountPre?.data as ParsedAccountData).parsed.info.owner; @@ -157,8 +158,8 @@ function useSimulator(message: VersionedMessage) { } if ( - (accountOwnerPost === TOKEN_PROGRAM_ID.toBase58() || - accountOwnerPost === "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb") && + accountOwnerPost && + isTokenProgramBase58(accountOwnerPost) && Buffer.from(accountDataPost!, 'base64').length >= 165 ) { const accountParsedPost = AccountLayout.decode(Buffer.from(accountDataPost!, 'base64')); @@ -235,6 +236,10 @@ function useSimulator(message: VersionedMessage) { }; } +function isTokenProgramBase58(programIdBase58: string): boolean { + return programIdBase58 === TOKEN_PROGRAM_ID.toBase58() || programIdBase58 === TOKEN_2022_PROGRAM_ID.toBase58(); +} + function getMintDecimals( accountKeys: PublicKey[], parsedAccountsPre: (AccountInfo | null)[], @@ -249,8 +254,9 @@ function getMintDecimals( // Token account before if ( - parsedAccount?.owner.toBase58() == TOKEN_PROGRAM_ID.toBase58() && - (parsedAccount?.data as ParsedAccountData).parsed.type === 'account' + parsedAccount && + isTokenProgramBase58(parsedAccount.owner.toBase58()) && + (parsedAccount.data as ParsedAccountData).parsed.type === 'account' ) { mintToDecimals[(parsedAccount?.data as ParsedAccountData).parsed.info.mint] = ( parsedAccount?.data as ParsedAccountData @@ -258,7 +264,8 @@ function getMintDecimals( } // Mint account before if ( - parsedAccount?.owner.toBase58() == TOKEN_PROGRAM_ID.toBase58() && + parsedAccount && + isTokenProgramBase58(parsedAccount.owner.toBase58()) && (parsedAccount?.data as ParsedAccountData).parsed.type === 'mint' ) { mintToDecimals[key.toBase58()] = (parsedAccount?.data as ParsedAccountData).parsed.info.decimals; @@ -267,7 +274,7 @@ function getMintDecimals( // Token account after const accountDataPost = accountDatasPost.at(index)?.data[0]; const accountOwnerPost = accountDatasPost.at(index)?.owner; - if (accountOwnerPost === TOKEN_PROGRAM_ID.toBase58() && Buffer.from(accountDataPost!, 'base64').length === 82) { + if (accountOwnerPost && isTokenProgramBase58(accountOwnerPost) && Buffer.from(accountDataPost!, 'base64').length === 82) { const accountParsedPost = MintLayout.decode(Buffer.from(accountDataPost!, 'base64')); mintToDecimals[key.toBase58()] = accountParsedPost.decimals; } diff --git a/app/providers/accounts/tokens.tsx b/app/providers/accounts/tokens.tsx index 227bd7dc..b3c0e133 100644 --- a/app/providers/accounts/tokens.tsx +++ b/app/providers/accounts/tokens.tsx @@ -46,6 +46,10 @@ export function TokensProvider({ children }: ProviderProps) { } export const TOKEN_PROGRAM_ID = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); +export const TOKEN_2022_PROGRAM_ID = new PublicKey('TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb'); +export function isTokenProgramId(programId: PublicKey) { + return programId.equals(TOKEN_PROGRAM_ID) || programId.equals(TOKEN_2022_PROGRAM_ID); +} async function fetchAccountTokens(dispatch: Dispatch, pubkey: PublicKey, cluster: Cluster, url: string) { const key = pubkey.toBase58(); @@ -59,11 +63,14 @@ async function fetchAccountTokens(dispatch: Dispatch, pubkey: PublicKey, cluster let status; let data; try { - const { value } = await new Connection(url, 'processed').getParsedTokenAccountsByOwner(pubkey, { + const { value: tokenAccounts } = await new Connection(url, 'processed').getParsedTokenAccountsByOwner(pubkey, { programId: TOKEN_PROGRAM_ID, }); + const { value: token2022Accounts } = await new Connection(url, 'processed').getParsedTokenAccountsByOwner(pubkey, { + programId: TOKEN_2022_PROGRAM_ID, + }); - const tokens: TokenInfoWithPubkey[] = value.slice(0, 101).map(accountInfo => { + const tokens: TokenInfoWithPubkey[] = tokenAccounts.concat(token2022Accounts).slice(0, 101).map(accountInfo => { const parsedInfo = accountInfo.account.data.parsed.info; const info = create(parsedInfo, TokenAccountInfo); return { info, pubkey: accountInfo.pubkey }; diff --git a/app/utils/instruction.ts b/app/utils/instruction.ts index e4330420..189dc720 100644 --- a/app/utils/instruction.ts +++ b/app/utils/instruction.ts @@ -5,7 +5,7 @@ import { parseTokenLendingInstructionTitle, } from '@components/instruction/token-lending/types'; import { isTokenSwapInstruction, parseTokenSwapInstructionTitle } from '@components/instruction/token-swap/types'; -import { TOKEN_PROGRAM_ID } from '@providers/accounts/tokens'; +import { isTokenProgramId } from '@providers/accounts/tokens'; import { ConfirmedSignatureInfo, ParsedInstruction, @@ -105,7 +105,7 @@ export function getTokenInstructionName( } } - if (ix.accounts.findIndex(account => account.equals(TOKEN_PROGRAM_ID)) >= 0) { + if (ix.accounts.findIndex(account => isTokenProgramId(account)) >= 0) { name = 'Unknown (Inner)'; } else { return undefined; From 9325362455cb054e2cb929311bdab792c6cc341d Mon Sep 17 00:00:00 2001 From: Jon C Date: Wed, 13 Dec 2023 01:11:05 +0100 Subject: [PATCH 3/3] Update page titles to reflect Token-2022-ness --- app/components/account/TokenAccountSection.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/components/account/TokenAccountSection.tsx b/app/components/account/TokenAccountSection.tsx index a80f6a73..2ef0fa58 100644 --- a/app/components/account/TokenAccountSection.tsx +++ b/app/components/account/TokenAccountSection.tsx @@ -3,6 +3,7 @@ import { Copyable } from '@components/common/Copyable'; import { LoadingCard } from '@components/common/LoadingCard'; import { TableCardBody } from '@components/common/TableCardBody'; import { Account, NFTData, TokenProgramData, useFetchAccountInfo } from '@providers/accounts'; +import { TOKEN_2022_PROGRAM_ID } from '@providers/accounts/tokens'; import isMetaplexNFT from '@providers/accounts/utils/isMetaplexNFT'; import { useCluster } from '@providers/cluster'; import { PublicKey } from '@solana/web3.js'; @@ -151,7 +152,7 @@ function FungibleTokenMintAccountCard({ account, mintInfo, tokenInfo }: { accoun

- {tokenInfo ? 'Overview' : 'Token Mint'} + {tokenInfo ? 'Overview' : account.owner.toBase58() === TOKEN_2022_PROGRAM_ID.toBase58() ? 'Token-2022 Mint' : 'Token Mint'}