Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add helpers, handle unknwon external plugins #6

Merged
merged 2 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@
"registry": "https://registry.npmjs.org"
},
"peerDependencies": {
"@metaplex-foundation/digital-asset-standard-api": ">=1.0.4-alpha.0",
"@metaplex-foundation/digital-asset-standard-api": ">=1.0.4-alpha.2",
"@metaplex-foundation/mpl-core": ">=1.0.1",
"@metaplex-foundation/umi": ">=0.8.2 < 1"
},
"devDependencies": {
"@ava/typescript": "^4.1.0",
"@metaplex-foundation/mpl-core": "1.0.1",
"@metaplex-foundation/digital-asset-standard-api": "^1.0.4-alpha.0",
"@metaplex-foundation/digital-asset-standard-api": "^1.0.4-alpha.2",
"@metaplex-foundation/umi": "^0.9.1",
"@metaplex-foundation/umi-bundle-tests": "^0.9.1",
"@typescript-eslint/eslint-plugin": "^7.5.0",
Expand Down
8 changes: 4 additions & 4 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;
Loading
Loading