diff --git a/clients/js/src/helpers/fetch.ts b/clients/js/src/helpers/fetch.ts index d9f45c7e..300481bf 100644 --- a/clients/js/src/helpers/fetch.ts +++ b/clients/js/src/helpers/fetch.ts @@ -8,6 +8,7 @@ import { import { AssetV1, CollectionV1, + fetchAllAssetV1, fetchAllCollectionV1, fetchAssetV1, fetchCollectionV1, @@ -147,6 +148,65 @@ export const fetchAsset = async ( return deriveAssetPlugins(assetV1, await fetchCollectionV1(umi, collection)); }; +/** + * Helper function to fetch multiple assets and derive plugins from their collections if applicable. + * + * @param umi Context + * @param assets Array of asset addresses to fetch + * @param options Options, `skipDerivePlugins` plugins from collection is false by default; `chunksize` how many assets to fetch in a single rpc call. + * @returns Promise of a list of `AssetV1` + */ +export const fetchAllAssets = async ( + umi: Context, + assets: Array, + options: { + skipDerivePlugins?: boolean; + chunkSize?: number; + } & RpcGetAccountOptions = {} +): Promise => { + const chunkSize = options.chunkSize ?? 1000; + const assetChunks = []; + for (let i = 0; i < assets.length; i += chunkSize) { + assetChunks.push(assets.slice(i, i + chunkSize)); + } + + const assetV1s = ( + await Promise.all( + assetChunks.map((chunk) => + fetchAllAssetV1( + umi, + chunk.map((asset) => publicKey(asset)) + ) + ) + ) + ).flat(); + + if (options.skipDerivePlugins) { + return assetV1s; + } + + const collectionKeys = Array.from( + new Set(assetV1s.map((asset) => collectionAddress(asset))) + ).filter((collection): collection is PublicKey => !!collection); + + const collections = await fetchAllCollectionV1(umi, collectionKeys); + const collectionMap = collections.reduce( + (map, collection) => { + map[collection.publicKey] = collection; + return map; + }, + {} as { [key: string]: CollectionV1 } + ); + + return assetV1s.map((assetV1) => { + const collection = collectionAddress(assetV1); + if (!collection) { + return assetV1; + } + return deriveAssetPlugins(assetV1, collectionMap[collection]); + }); +}; + /** * Helper function to fetch a collection. * diff --git a/clients/js/test/helps/fetch.test.ts b/clients/js/test/helps/fetch.test.ts index 6e60c594..930b8be3 100644 --- a/clients/js/test/helps/fetch.test.ts +++ b/clients/js/test/helps/fetch.test.ts @@ -5,6 +5,7 @@ import { fetchAssetsByCollection, fetchAssetsByOwner, fetchCollectionsByUpdateAuthority, + fetchAllAssets, } from '../../src'; import { createUmi } from '../_setupRaw'; import { createAsset, createCollection } from '../_setupSdk'; @@ -161,3 +162,93 @@ test('it can use helper to fetch assets by collection and derive plugins', async } }); }); + +test('it can use helper to fetch all assets', async (t) => { + const umi = await createUmi(); + + const collection = await createCollection(umi, { + plugins: [ + { + type: 'ImmutableMetadata', + }, + ], + }); + + const assetsOfOwner1 = await Promise.all( + Array(2) + .fill(0) + .map((_, index) => + createAsset(umi, { + collection, + name: `Asset ${index + 1}`, + plugins: [ + { + type: 'Attributes', + attributeList: [ + { + key: 'asset', + value: 'asset', + }, + ], + }, + ], + }) + ) + ); + + const assetsOfOwner2 = await Promise.all( + Array(2) + .fill(0) + .map((_, index) => + createAsset(umi, { + collection, + name: `Asset ${index + 1}`, + plugins: [ + { + type: 'Attributes', + attributeList: [ + { + key: 'asset', + value: 'asset', + }, + ], + }, + ], + }) + ) + ); + + const allCreatedAssets = [...assetsOfOwner1, ...assetsOfOwner2]; + + const assetPublicKeys = [...assetsOfOwner1, ...assetsOfOwner2].map( + (asset) => asset.publicKey + ); + + const fetchedAssets = await fetchAllAssets(umi, assetPublicKeys); + + const createdAssetPubkeys = allCreatedAssets + .map((asset) => asset.publicKey) + .sort(); + const fetchedAssetPubkeys = fetchedAssets + .map((asset) => asset.publicKey) + .filter((pubkey) => createdAssetPubkeys.includes(pubkey)) + .sort(); + + t.deepEqual( + fetchedAssetPubkeys, + createdAssetPubkeys, + 'All created assets should be found in fetched assets' + ); + + t.deepEqual( + fetchedAssets[0].attributes, + allCreatedAssets[0].attributes, + 'Asset level attribute plugin should be found in fetched assets' + ); + + t.deepEqual( + fetchedAssets[0].immutableMetadata, + collection.immutableMetadata, + 'Collection level immutableMetadata plugin should be found in fetched assets' + ); +});