From 562627f65233d751d44d6667274912cde76e352c Mon Sep 17 00:00:00 2001 From: MarkSackerberg <93528482+MarkSackerberg@users.noreply.github.com> Date: Thu, 19 Dec 2024 23:07:10 +0100 Subject: [PATCH 1/3] add fetchAllAssets helper --- clients/js/src/helpers/fetch.ts | 16 +++++ clients/js/test/helps/fetch.test.ts | 91 +++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/clients/js/src/helpers/fetch.ts b/clients/js/src/helpers/fetch.ts index d9f45c7e..bb228889 100644 --- a/clients/js/src/helpers/fetch.ts +++ b/clients/js/src/helpers/fetch.ts @@ -147,6 +147,22 @@ 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 + * @returns Promise of a list of `AssetV1` + */ +export const fetchAllAssets = async ( + umi: Context, + assets: Array, + options: { skipDerivePlugins?: boolean } & RpcGetAccountOptions = {} +): Promise => { + return Promise.all(assets.map((asset) => fetchAsset(umi, asset, options))); +}; + /** * 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' + ); +}); From e4c5ba196c762850b2a5b68c4c97c9dbfc017935 Mon Sep 17 00:00:00 2001 From: MarkSackerberg <93528482+MarkSackerberg@users.noreply.github.com> Date: Thu, 19 Dec 2024 23:31:35 +0100 Subject: [PATCH 2/3] improve to fetch a collection only once --- clients/js/src/helpers/fetch.ts | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/clients/js/src/helpers/fetch.ts b/clients/js/src/helpers/fetch.ts index bb228889..8aee59fa 100644 --- a/clients/js/src/helpers/fetch.ts +++ b/clients/js/src/helpers/fetch.ts @@ -160,7 +160,31 @@ export const fetchAllAssets = async ( assets: Array, options: { skipDerivePlugins?: boolean } & RpcGetAccountOptions = {} ): Promise => { - return Promise.all(assets.map((asset) => fetchAsset(umi, asset, options))); + const assetV1s = await Promise.all( + assets.map((asset) => fetchAssetV1(umi, publicKey(asset))) + ); + + 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); + + return assetV1s.map((assetV1) => { + const collection = collectionAddress(assetV1); + if (!collection) { + return assetV1; + } + + const matchingCollection = collections.find( + (c) => c.publicKey === collection + ); + return deriveAssetPlugins(assetV1, matchingCollection); + }); }; /** From a4eccbfbc4923852fe39636f5dc127f6d81ee203 Mon Sep 17 00:00:00 2001 From: MarkSackerberg <93528482+MarkSackerberg@users.noreply.github.com> Date: Fri, 20 Dec 2024 00:45:59 +0100 Subject: [PATCH 3/3] getMultipleAccounts with chunks --- clients/js/src/helpers/fetch.ts | 40 ++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/clients/js/src/helpers/fetch.ts b/clients/js/src/helpers/fetch.ts index 8aee59fa..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, @@ -152,17 +153,33 @@ export const fetchAsset = async ( * * @param umi Context * @param assets Array of asset addresses to fetch - * @param options Options, `skipDerivePlugins` plugins from collection is false by default + * @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 } & RpcGetAccountOptions = {} + options: { + skipDerivePlugins?: boolean; + chunkSize?: number; + } & RpcGetAccountOptions = {} ): Promise => { - const assetV1s = await Promise.all( - assets.map((asset) => fetchAssetV1(umi, publicKey(asset))) - ); + 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; @@ -173,17 +190,20 @@ export const fetchAllAssets = async ( ).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; } - - const matchingCollection = collections.find( - (c) => c.publicKey === collection - ); - return deriveAssetPlugins(assetV1, matchingCollection); + return deriveAssetPlugins(assetV1, collectionMap[collection]); }); };