diff --git a/app/licenses/page.tsx b/app/licenses/page.tsx index 5c6ced5..153f608 100644 --- a/app/licenses/page.tsx +++ b/app/licenses/page.tsx @@ -1,10 +1,10 @@ -import ErrorComponent from '@/app/server-components/shared/ErrorComponent'; -import { getLicensesTotalSupply } from '@/lib/api/blockchain'; -import { LicenseItem } from '@/typedefs/general'; -import { unstable_cache } from 'next/cache'; -import List from '../server-components/Licenses/List'; -import { BorderedCard } from '../server-components/shared/cards/BorderedCard'; -import { CardHorizontal } from '../server-components/shared/cards/CardHorizontal'; +import ErrorComponent from '@/app/server-components/shared/ErrorComponent'; +import { getAllLicenseTokenIds } from '@/lib/api/blockchain'; +import { LicenseItem } from '@/typedefs/general'; +import { unstable_cache } from 'next/cache'; +import List from '../server-components/Licenses/List'; +import { BorderedCard } from '../server-components/shared/cards/BorderedCard'; +import { CardHorizontal } from '../server-components/shared/cards/CardHorizontal'; export async function generateMetadata({ searchParams }: { searchParams?: Promise<{ page?: string }> }) { const resolvedSearchParams = await searchParams; @@ -22,7 +22,7 @@ export async function generateMetadata({ searchParams }: { searchParams?: Promis }; } -const getCachedSupply = unstable_cache(getLicensesTotalSupply, ['licenses-total-supply'], { revalidate: 300 }); +const getCachedLicenseTokenIds = unstable_cache(getAllLicenseTokenIds, ['licenses-token-ids'], { revalidate: 300 }); export default async function LicensesPage(props: { searchParams?: Promise<{ @@ -32,25 +32,24 @@ export default async function LicensesPage(props: { const searchParams = await props.searchParams; const currentPage = Number(searchParams?.page) || 1; - let ndTotalSupply: number, mndTotalSupply: number; - let licenses: LicenseItem[]; - - try { - const { ndTotalSupply: ndTotalSupplyStr, mndTotalSupply: mndTotalSupplyStr } = await getCachedSupply(); - - ndTotalSupply = Number(ndTotalSupplyStr); - mndTotalSupply = Number(mndTotalSupplyStr); - - licenses = [ - ...Array.from({ length: Number(mndTotalSupply) }, (_, i) => ({ - licenseId: i + 1, - licenseType: i === 0 ? ('GND' as const) : ('MND' as const), - })), - ...Array.from({ length: Number(ndTotalSupply) }, (_, i) => ({ - licenseId: i + 1, - licenseType: 'ND' as const, - })), - ]; + let ndTotalSupply: number, mndTotalSupply: number; + let licenses: LicenseItem[]; + + try { + const { ndLicenseIds, mndLicenseIds } = await getCachedLicenseTokenIds(); + ndTotalSupply = ndLicenseIds.length; + mndTotalSupply = mndLicenseIds.length; + + licenses = [ + ...mndLicenseIds.map((licenseId) => ({ + licenseId, + licenseType: licenseId === 1 ? ('GND' as const) : ('MND' as const), + })), + ...ndLicenseIds.map((licenseId) => ({ + licenseId, + licenseType: 'ND' as const, + })), + ]; } catch (error) { console.error(error); console.log('[Licenses Page] Failed to fetch license data'); diff --git a/lib/api/blockchain.ts b/lib/api/blockchain.ts index 5fda8fb..9304674 100644 --- a/lib/api/blockchain.ts +++ b/lib/api/blockchain.ts @@ -278,6 +278,69 @@ export async function getLicensesTotalSupply(): Promise<{ }; } +const LICENSE_ENUMERATION_CHUNK_SIZE = 200; + +const getEnumerableLicenseIds = async ( + publicClient: Awaited>, + address: types.EthAddress, + abi: typeof NDContractAbi | typeof MNDContractAbi, + totalSupply: bigint, +): Promise => { + if (totalSupply === 0n) { + return []; + } + + const total = Number(totalSupply); + const licenseIds: number[] = []; + + for (let start = 0; start < total; start += LICENSE_ENUMERATION_CHUNK_SIZE) { + const end = Math.min(start + LICENSE_ENUMERATION_CHUNK_SIZE, total); + const tokenIds = await publicClient.multicall({ + contracts: Array.from({ length: end - start }, (_, index) => ({ + address, + abi, + functionName: 'tokenByIndex' as const, + args: [BigInt(start + index)], + })), + allowFailure: false, + }); + + licenseIds.push(...tokenIds.map((tokenId) => Number(tokenId))); + } + + return licenseIds.sort((a, b) => a - b); +}; + +export async function getAllLicenseTokenIds(): Promise<{ + mndLicenseIds: number[]; + ndLicenseIds: number[]; +}> { + const publicClient = await getPublicClient(); + + const [mndTotalSupply, ndTotalSupply] = await Promise.all([ + publicClient.readContract({ + address: config.mndContractAddress, + abi: MNDContractAbi, + functionName: 'totalSupply', + }), + publicClient.readContract({ + address: config.ndContractAddress, + abi: NDContractAbi, + functionName: 'totalSupply', + }), + ]); + + const [mndLicenseIds, ndLicenseIds] = await Promise.all([ + getEnumerableLicenseIds(publicClient, config.mndContractAddress, MNDContractAbi, mndTotalSupply), + getEnumerableLicenseIds(publicClient, config.ndContractAddress, NDContractAbi, ndTotalSupply), + ]); + + return { + mndLicenseIds, + ndLicenseIds, + }; +} + export async function getLicenseHolders(licenseType: 'ND' | 'MND' | 'GND'): Promise< { ownerOf: EvmAddress | undefined;