Skip to content

Commit

Permalink
Merge pull request #6 from metaplex-foundation/nhan/core-types-helpers
Browse files Browse the repository at this point in the history
add helpers, handle unknwon external plugins
  • Loading branch information
nhanphan authored Jul 4, 2024
2 parents b46543e + b76b159 commit 46351a4
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 33 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,22 @@ const assetsByCollection = await das.getAssetsByCollection(umi, {
const derivedAssets = assetsByCollection.map((asset) => deriveAssetPlugins(asset, collection))
```
## Using DAS-to-Core type conversions
If you are working with not only Core assets, it might be useful to directly access the conversion helpers along side the other DAS asset types when fetching using [@metaplex-foundation/digital-asset-standard-api](https://github.com/metaplex-foundation/digital-asset-standard-api).
```js
// ... standard setup for @metaplex-foundation/digital-asset-standard-api
const dasAssets = await umi.rpc.getAssetsByOwner({ owner: publicKey('<pubkey>') });
// filter out only core assets
const dasCoreAssets = assets.items.filter((a) => a.interface === 'MplCoreAsset')
// convert them to AssetV1 type (actually AssetResult type which will also have the content field populated from DAS)
const coreAssets = await das.dasAssetsToCoreAssets(umi, dasCoreAssets)
```
## Contributing
Expand Down
5 changes: 0 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

89 changes: 83 additions & 6 deletions src/das.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
import { PublicKey, Umi } from '@metaplex-foundation/umi';
import {
DasApiAssetInterface,
DasApiAsset,
SearchAssetsRpcInput,
} from '@metaplex-foundation/digital-asset-standard-api';
import {
AssetV1,
CollectionV1,
deriveAssetPluginsWithFetch,
} from '@metaplex-foundation/mpl-core';
import { MPL_CORE_ASSET, MPL_CORE_COLLECTION } from './constants';
import { AssetOptions, Pagination } from './types';
import {
AssetOptions,
AssetResult,
CollectionResult,
Pagination,
} from './types';
import { dasAssetToCoreAssetOrCollection } from './helpers';

async function searchAssets(
context: Umi,
input: Omit<SearchAssetsRpcInput, 'interface' | 'burnt'> & {
interface?: typeof MPL_CORE_ASSET;
} & AssetOptions
): Promise<AssetV1[]>;
): Promise<AssetResult[]>;
async function searchAssets(
context: Umi,
input: Omit<SearchAssetsRpcInput, 'interface' | 'burnt'> & {
interface?: typeof MPL_CORE_COLLECTION;
} & AssetOptions
): Promise<CollectionV1[]>;
): Promise<CollectionResult[]>;
async function searchAssets(
context: Umi,
input: Omit<SearchAssetsRpcInput, 'interface' | 'burnt'> & {
Expand All @@ -32,7 +36,7 @@ async function searchAssets(
) {
const dasAssets = await context.rpc.searchAssets({
...input,
interface: (input.interface ?? MPL_CORE_ASSET) as DasApiAssetInterface,
interface: input.interface ?? MPL_CORE_ASSET,
burnt: false,
});

Expand Down Expand Up @@ -93,6 +97,40 @@ function getAssetsByCollection(
});
}

/**
* Convenience function to fetch a single asset by pubkey
* @param context Umi
* @param asset pubkey of the asset
* @param options
* @returns
*/
async function getAsset(
context: Umi,
asset: PublicKey,
options: AssetOptions = {}
): Promise<AssetResult> {
const dasAsset = await context.rpc.getAsset(asset);

return (
await dasAssetsToCoreAssets(context, [dasAsset], options)
)[0] as AssetResult;
}

/**
* Convenience function to fetch a single collection by pubkey
* @param context
* @param collection
* @returns
*/
async function getCollection(
context: Umi,
collection: PublicKey
): Promise<CollectionResult> {
const dasCollection = await context.rpc.getAsset(collection);

return dasAssetToCoreCollection(context, dasCollection);
}

function getCollectionsByUpdateAuthority(
context: Umi,
input: {
Expand All @@ -106,11 +144,50 @@ function getCollectionsByUpdateAuthority(
});
}

async function dasAssetsToCoreAssets(
context: Umi,
assets: DasApiAsset[],
options: AssetOptions
): Promise<AssetResult[]> {
const coreAssets = assets.map((asset) => {
if (asset.interface !== MPL_CORE_ASSET) {
throw new Error(
`Invalid interface, expecting interface to be ${MPL_CORE_ASSET} but got ${asset.interface}`
);
}
return dasAssetToCoreAssetOrCollection(asset);
}) as AssetResult[];

if (options.skipDerivePlugins) {
return coreAssets;
}

return deriveAssetPluginsWithFetch(context, coreAssets) as Promise<
AssetResult[]
>;
}

async function dasAssetToCoreCollection(
context: Umi,
asset: DasApiAsset & AssetOptions
): Promise<CollectionResult> {
if (asset.interface !== MPL_CORE_COLLECTION) {
throw new Error(
`Invalid interface, expecting interface to be ${MPL_CORE_COLLECTION} but got ${asset.interface}`
);
}
return dasAssetToCoreAssetOrCollection(asset) as CollectionResult;
}

export const das = {
searchAssets,
searchCollections,
getAssetsByOwner,
getAssetsByAuthority,
getAssetsByCollection,
getCollectionsByUpdateAuthority,
getAsset,
getCollection,
dasAssetsToCoreAssets,
dasAssetToCoreCollection,
} as const;
83 changes: 64 additions & 19 deletions src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@ import {
} from '@metaplex-foundation/digital-asset-standard-api';
import {
AddBlocker,
AssetV1,
Attributes,
UpdateAuthority,
BurnDelegate,
CollectionV1,
Edition,
FreezeDelegate,
getPluginSerializer,
Expand All @@ -35,6 +33,10 @@ import {
CheckResult,
Autograph,
VerifiedCreators,
ExternalPluginAdapterType,
getExternalPluginAdapterSerializer,
BaseExternalPluginAdapter,
oracleFromBase,
} from '@metaplex-foundation/mpl-core';
import {
AccountHeader,
Expand All @@ -46,6 +48,7 @@ import {
some,
} from '@metaplex-foundation/umi';
import { MPL_CORE_COLLECTION } from './constants';
import { AssetResult, CollectionResult } from './types';

function convertSnakeCase(str: string, toCase: 'pascal' | 'camel' = 'camel') {
return str
Expand Down Expand Up @@ -379,18 +382,65 @@ function handleUnknownPlugins(unknownDasPlugins?: Record<string, any>[]) {
}, {});
}

function handleUnknownExternalPlugins(
unknownDasPlugins?: Record<string, any>[]
) {
if (!unknownDasPlugins) return {};

return unknownDasPlugins.reduce(
(acc: ExternalPluginAdaptersList, unknownPlugin) => {
if (!ExternalPluginAdapterType[unknownPlugin.type]) return acc;

const deserializedPlugin =
getExternalPluginAdapterSerializer().deserialize(
base64ToUInt8Array(unknownPlugin.data)
)[0];

const {
authority,
offset,
lifecycle_checks: lifecycleChecks,
} = unknownPlugin;

const mappedPlugin: BaseExternalPluginAdapter = {
lifecycleChecks: lifecycleChecks
? parseLifecycleChecks(lifecycleChecks)
: undefined,
authority,
offset: BigInt(offset),
};

if (deserializedPlugin.__kind === 'Oracle') {
if (!acc.oracles) {
acc.oracles = [];
}

acc.oracles.push({
type: 'Oracle',
...mappedPlugin,
// Oracle conversion does not use the record or account data so we pass in dummies
...oracleFromBase(
deserializedPlugin.fields[0],
{} as any,
new Uint8Array(0)
),
});
}

return acc;
},
{}
);
}

export function dasAssetToCoreAssetOrCollection(
dasAsset: DasApiAsset
): AssetV1 | CollectionV1 {
// TODO: Define types in Umi DAS client.
): AssetResult | CollectionResult {
const {
interface: assetInterface,
id,
ownership: { owner },
content: {
metadata: { name },
json_uri: uri,
},
content,
compression: { seq },
grouping,
authorities,
Expand All @@ -401,32 +451,27 @@ export function dasAssetToCoreAssetOrCollection(
rent_epoch: rentEpoch,
mpl_core_info: mplCoreInfo,
external_plugins: externalPlugins,
unknown_external_plugins: unknownExternalPlugins,
} = dasAsset as DasApiAsset & {
plugins: Record<string, any>;
unknown_plugins?: Array<Record<string, any>>;
executable?: boolean;
lamports?: number;
rent_epoch?: number;
mpl_core_info?: {
num_minted?: number;
current_size?: number;
plugins_json_version: number;
};
external_plugins: Record<string, any>[];
};
const { num_minted: numMinted = 0, current_size: currentSize = 0 } =
mplCoreInfo ?? {};

const commonFields = {
publicKey: id,
uri,
name,
uri: content.json_uri,
name: content.metadata.name,
content,
...getAccountHeader(executable, lamps, rentEpoch),
...dasPluginsToCorePlugins(plugins),
...(plugins ? dasPluginsToCorePlugins(plugins) : {}),
...(externalPlugins !== undefined
? dasExternalPluginsToCoreExternalPlugins(externalPlugins)
: {}),
...handleUnknownPlugins(unknownPlugins),
...handleUnknownExternalPlugins(unknownExternalPlugins),
// pluginHeader: // TODO: Reconstruct
};

Expand Down
16 changes: 15 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { SearchAssetsRpcInput } from '@metaplex-foundation/digital-asset-standard-api';
import {
DasApiAssetContent,
SearchAssetsRpcInput,
} from '@metaplex-foundation/digital-asset-standard-api';
import { AssetV1, CollectionV1 } from '@metaplex-foundation/mpl-core';

export type Pagination = Pick<
SearchAssetsRpcInput,
Expand All @@ -8,3 +12,13 @@ export type Pagination = Pick<
export type AssetOptions = {
skipDerivePlugins?: boolean;
};

/**
* Extra fields that are not on AssetV1 or CollectionV1 but returned by DAS
*/
export type DasExtra = {
content: DasApiAssetContent;
};

export type AssetResult = AssetV1 & DasExtra;
export type CollectionResult = CollectionV1 & DasExtra;
24 changes: 22 additions & 2 deletions test/das.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,7 @@ test.serial(
}
);

// TODO renenable after more das providers support this
test.skip('das: it can fetch asset with oracle', async (t) => {
test.serial('das: it can fetch asset with oracle', async (t) => {
const umi = createUmiWithDas(DAS_API_ENDPOINT);
const assets = await das.searchAssets(umi, {
owner: publicKey('APrZTeVysBJqAznfLXS71NAzjr2fCVTSF1A66MeErzM7'),
Expand Down Expand Up @@ -239,6 +238,27 @@ test.serial('das: it can fetch derived asset', async (t) => {
t.like(asset, mplCoreAsset);
});

test.serial('das: it can getAsset', async (t) => {
const umi = createUmiWithDas(DAS_API_ENDPOINT);
const asset = await das.getAsset(
umi,
publicKey('9KvAqZVYJbXZzNvaV1HhxvybD6xfguztQwnqhkmzxWV3')
);
prepareAssetForComparison(asset, false);

const mplCoreAsset = await fetchAsset(umi, asset.publicKey);
prepareAssetForComparison(mplCoreAsset);

t.like(asset, mplCoreAsset);
t.like(asset.content, {
$schema: 'https://schema.metaplex.com/nft1.0.json',
json_uri: 'https://example.com/asset',
files: [],
metadata: { name: 'new name 2', symbol: '' },
links: {},
});
});

// TODO
test.skip('das: lifecycle hooks', async (t) => {});
test.skip('das: app data', async (t) => {});

0 comments on commit 46351a4

Please sign in to comment.