import { Connection , PublicKey , AccountInfo } from "@solana/web3.js" ; import chunks from "lodash.chunk" ; import orderBy from "lodash.orderby" ; import { decodeTokenMetadata , getSolanaMetadataAddress , isValidSolanaAddress , createConnectionConfig , } from "./utils" ; import { TOKEN_PROGRAM } from "./config/solana" ; import { Metadata } from "./config/metaplex" ; import { StringPublicKey , PromiseSettledResult , PromiseFulfilledResult , } from "./types" ; export type Options = { /**
     * Wallet public address
     */ publicAddress : StringPublicKey ; /**
     * Optionally provide your own connection object.
     * Otherwise createConnectionConfig() will be used
     */ connection ? : Connection ; /**
     * Remove possible rust's empty string symbols `\x00` from the values,
     * which is very common issue.
     * Default is true
     */ sanitize ? : boolean ; /**
     * Convert all PublicKey objects to string versions.
     * Default is true
     */ stringifyPubKeys ? : boolean ; /**
     * Sort tokens by Update Authority (read by Collection)
     * Default is true
     */ sort ? : boolean ; /**
     * Limit response by this number
     * by default response limited by 5000 NFTs
     */ limit ? : number ; } ; enum sortKeys { updateAuthority = "updateAuthority" } export const getParsedNftAccountsByOwner = async ( { publicAddress , connection = createConnectionConfig ( ) , sanitize = true , stringifyPubKeys = true , sort = true , limit = 5000 , } ) => { const isValidAddress = isValidSolanaAddress ( publicAddress ) ; if ( ! isValidAddress ) { return [ ] ; } const { value : splAccounts } = await connection . getParsedTokenAccountsByOwner ( new PublicKey ( publicAddress ) , { programId : new PublicKey ( TOKEN_PROGRAM ) , } ) ; const nftAccounts = splAccounts . filter ( ( t ) => { const amount = t . account ?. data ?. parsed ?. info ?. tokenAmount ?. uiAmount ; const decimals = t . account ?. data ?. parsed ?. info ?. tokenAmount ?. decimals ; return decimals === 0 && amount >= 1 ; } ) . map ( ( t ) => { const address = t . account ?. data ?. parsed ?. info ?. mint ; return new PublicKey ( address ) ; } ) ; const accountsSlice = nftAccounts ?. slice ( 0 , limit ) ; const metadataAcountsAddressPromises = await Promise . allSettled ( accountsSlice . map ( getSolanaMetadataAddress ) ) ; const metadataAccounts = metadataAcountsAddressPromises . filter ( onlySuccessfullPromises ) . map ( ( p ) => ( p as PromiseFulfilledResult < PublicKey > ) . value ) ; const metaAccountsRawPromises = await Promise . allSettled ( chunks ( metadataAccounts , 99 ) . map ( ( chunk ) => connection . getMultipleAccountsInfo ( chunk as PublicKey [ ] ) ) ) ; const accountsRawMeta = metaAccountsRawPromises . filter ( ( { status } ) => status === "fulfilled" ) . flatMap ( ( p ) => ( p as PromiseFulfilledResult < unknown > ) . value ) ; if ( ! accountsRawMeta ?. length || accountsRawMeta ?. length === 0 ) { return [ ] ; } const accountsDecodedMeta = await Promise . allSettled ( accountsRawMeta . map ( ( accountInfo ) => decodeTokenMetadata ( ( accountInfo as AccountInfo < Buffer > ) ?. data ) ) ) ; const accountsFiltered = accountsDecodedMeta . filter ( onlySuccessfullPromises ) . filter ( onlyNftsWithMetadata ) . map ( ( p ) => { const { value } = p as PromiseFulfilledResult < Metadata > ; return sanitize ? sanitizeTokenMeta ( value ) : value ; } ) . map ( ( token ) => ( stringifyPubKeys ? publicKeyToString ( token ) : token ) ) ; if ( stringifyPubKeys && sort ) { const accountsSorted = orderBy ( accountsFiltered , [ sortKeys . updateAuthority ] , [ "asc" ] ) ; return accountsSorted ; } return accountsFiltered ; } ; const sanitizeTokenMeta = ( tokenData ) => ( { ... tokenData , data : { ... tokenData ?. data , name : sanitizeMetaStrings ( tokenData ?. data ?. name ) , symbol : sanitizeMetaStrings ( tokenData ?. data ?. symbol ) , uri : sanitizeMetaStrings ( tokenData ?. data ?. uri ) , } , } ) ; const publicKeyToString = ( tokenData ) => ( { ... tokenData , mint : tokenData ?. mint ?. toString ?. ( ) , updateAuthority : tokenData ?. updateAuthority ?. toString ?. ( ) , data : { ... tokenData ?. data , creators : tokenData ?. data ?. creators ?. map ( ( c ) => ( { ... c , address : new PublicKey ( c ?. address ) ?. toString ?. ( ) , } ) ) , } , } ) ; export const sanitizeMetaStrings = ( metaString ) => metaString . replace ( /\0/g , "" ) ; const onlySuccessfullPromises = ( result ) : boolean => result && result . status === "fulfilled" ; const onlyNftsWithMetadata = ( t ) => { const uri = ( t as PromiseFulfilledResult < Metadata > ) . value . data ?. uri ?. replace ?. ( /\0/g , "" ) ; return uri !== "" && uri !== undefined ; } ;