// Copyright 2017-2025 @pezkuwi/app-nfts authors & contributors // SPDX-License-Identifier: Apache-2.0 import type { Option } from '@pezkuwi/types'; import type { AccountId } from '@pezkuwi/types/interfaces'; import type { PalletUniquesCollectionDetails, PalletUniquesCollectionMetadata } from '@pezkuwi/types/lookup'; import type { BN } from '@pezkuwi/util'; import type { CollectionInfo, CollectionSupportedMetadata } from './types.js'; import { useEffect, useMemo, useState } from 'react'; import { createNamedHook, useAccounts, useApi, useCall, useMetadataFetch } from '@pezkuwi/react-hooks'; import { normalizeMetadataLink } from '@pezkuwi/react-hooks/useMetadataFetch'; type FetchedMetadata = Map; const EMPTY_FLAGS = { isAdminMe: false, isFreezerMe: false, isIssuerMe: false, isOwnerMe: false }; const QUERY_OPTS = { withParams: true }; const METADATA_FETCH_OPTIONS = { transform: (data: string | undefined): CollectionSupportedMetadata | null => { if (!data) { return null; } try { const result = JSON.parse(data) as Record; if (result && typeof result === 'object') { return { image: typeof result.image === 'string' ? normalizeMetadataLink(result.image) : null, name: typeof result.name === 'string' ? result.name : null }; } } catch {} return null; } }; function isAccount (allAccounts: string[], accountId: AccountId): boolean { const address = accountId.toString(); return allAccounts.some((a) => a === address); } function extractInfo (allAccounts: string[], id: BN, optDetails: Option, metadata: Option): CollectionInfo { const details = optDetails.unwrapOr(null); return { ...(details ? { isAdminMe: isAccount(allAccounts, details.admin), isFreezerMe: isAccount(allAccounts, details.freezer), isIssuerMe: isAccount(allAccounts, details.issuer), isOwnerMe: isAccount(allAccounts, details.owner) } : EMPTY_FLAGS ), details, id, ipfsData: null, key: id.toString(), metadata: metadata.unwrapOr(null) }; } const addFetchedMetadata = (fetchedMetadata: FetchedMetadata) => (collectionInfo: CollectionInfo): CollectionInfo => { const metadataLink = normalizeMetadataLink(collectionInfo.metadata?.data.toPrimitive() as string); return { ...collectionInfo, ipfsData: (metadataLink && fetchedMetadata.has(metadataLink) && fetchedMetadata.get(metadataLink)) || null }; }; function useCollectionInfosImpl (ids?: BN[]): CollectionInfo[] | undefined { const { api } = useApi(); const { allAccounts } = useAccounts(); const metadata = useCall<[[BN[]], Option[]]>(api.query.uniques.classMetadataOf.multi, [ids], QUERY_OPTS); const details = useCall<[[BN[]], Option[]]>(api.query.uniques.class.multi, [ids], QUERY_OPTS); const [state, setState] = useState(); const metadataLinks = useMemo( () => metadata?.[1].length ? metadata[1].map((o) => o.isSome ? o.unwrap().data.toPrimitive() as string : '' ) : [], [metadata] ); const fetchedMetadata = useMetadataFetch(metadataLinks, METADATA_FETCH_OPTIONS); useEffect((): void => { if (fetchedMetadata && details && metadata && (details[0][0].length === metadata[0][0].length)) { const collectionInfos = details[0][0].map((id, index) => extractInfo(allAccounts, id, details[1][index], metadata[1][index]) ); setState(collectionInfos.map(addFetchedMetadata(fetchedMetadata))); } }, [allAccounts, details, ids, fetchedMetadata, metadata]); return state; } export default createNamedHook('useCollectionInfos', useCollectionInfosImpl);