Skip to content

Commit

Permalink
Merge pull request #1257 from input-output-hk/feat/nft-metadata-parsi…
Browse files Browse the repository at this point in the history
…ng-loose-mode

feat(core): nft metadata parsing loose mode
  • Loading branch information
mkazlauskas committed May 9, 2024
2 parents 3ed261c + 7689602 commit 898a821
Show file tree
Hide file tree
Showing 4 changed files with 275 additions and 171 deletions.
47 changes: 33 additions & 14 deletions packages/core/src/Asset/NftMetadata/fromMetadatum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { isNotNil } from '@cardano-sdk/util';
import difference from 'lodash/difference';

const isString = (obj: unknown): obj is string => typeof obj === 'string';
const VersionRegExp = /^\d+\.?\d?$/;

const metadatumToString = (metadatum: Cardano.Metadatum | undefined): string | undefined => {
if (Array.isArray(metadatum)) {
Expand Down Expand Up @@ -96,39 +97,57 @@ const getAssetMetadata = (policy: Cardano.MetadatumMap, assetName: Cardano.Asset
})()
);

type AssetIdParts = Pick<AssetInfo, 'policyId' | 'name'>;
const getName = (assetMetadata: Cardano.MetadatumMap, version: string, asset: AssetIdParts, logger: Logger) => {
const name = asString(assetMetadata.get('name'));
if (name) return name;
if (version === '1.0') {
try {
return AssetName.toUTF8(asset.name);
} catch (error) {
logger.warn(error);
}
}
};

const parseVersion = (version: Cardano.Metadatum | undefined) => {
if (!version) return '1.0';
if (typeof version === 'bigint') {
return `${version}.0`;
}
const stringVersion = asString(version);
if (stringVersion && VersionRegExp.test(stringVersion)) {
return `${Number(stringVersion)}.0`;
}
};

/**
* @param asset try to parse NftMetadata for this asset
* @param metadata transaction metadata (see CIP-0025)
* @param logger logger to use
*/
// eslint-disable-next-line complexity
export const fromMetadatum = (
asset: Pick<AssetInfo, 'policyId' | 'name'>,
asset: AssetIdParts,
metadata: Cardano.TxMetadata | undefined,
logger: Logger
logger: Logger,
strict = false
): NftMetadata | null => {
const cip25Metadata = metadata?.get(721n);
if (!cip25Metadata) return null;
const cip25MetadatumMap = asMetadatumMap(cip25Metadata);
if (!cip25MetadatumMap) return null;
const policy = getPolicyMetadata(cip25MetadatumMap, asset.policyId);
if (!policy) return null;
const version = parseVersion(policy.get('version'));
if (!version) return null;
const assetMetadata = getAssetMetadata(policy, asset.name);
if (!assetMetadata) return null;
const version = asString(policy.get('version')) || '1.0';
let name = asString(assetMetadata.get('name'));
const name = getName(assetMetadata, version, asset, logger);
const image = metadatumToString(assetMetadata.get('image'));
const assetId = Cardano.AssetId.fromParts(asset.policyId, asset.name);

if (version === '1.0' && !name) {
try {
name = AssetName.toUTF8(asset.name);
} catch (error) {
logger.warn(error);
}
}

if (!name || !image) {
if ((strict && !name) || !image) {
logger.warn(missingFieldLogMessage(!name ? 'name' : 'image', assetId, true));
return null;
}
Expand All @@ -142,7 +161,7 @@ export const fromMetadatum = (
files: files?.map((file) => mapFile(file, assetId, logger)).filter(isNotNil),
image: Uri(image),
mediaType: mediaType ? ImageMediaType(mediaType) : undefined,
name,
name: name || '',
otherProperties: mapOtherProperties(assetMetadata, ['name', 'image', 'mediaType', 'description', 'files']),
version
};
Expand Down
44 changes: 38 additions & 6 deletions packages/core/src/Asset/NftMetadata/fromPlutusData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,17 +83,45 @@ const mapFiles = (files: string | Cardano.PlutusData | undefined, logger: Logger
return files.items.map((file) => mapFile(file, logger)).filter(isNotNil);
};

const getConditionalValidators = (strict: boolean, logger: Logger) => ({
isNameValid: (name: Cardano.PlutusData | string | undefined): name is string | undefined => {
if (typeof name === 'string') return true;
if (typeof name === 'undefined') {
if (strict) {
logger.debug('Invalid PlutusData: "name" is required');
return false;
}
return true;
}
logger.debug('Invalid PlutusData: "name" must be utf8 bounded bytes');
return false;
},
isValidDatumShape: (plutusData: Cardano.PlutusData | undefined): plutusData is Cardano.ConstrPlutusData => {
const minNumberOfFields = strict ? 3 : 2;
const isValid =
isConstrPlutusData(plutusData) &&
plutusData.constructor === 0n &&
plutusData.fields.items.length >= minNumberOfFields;
if (!isValid)
logger.debug(
`Invalid PlutusData: expecting ConstrPlutusData with 0th constructor and ${minNumberOfFields} items`
);
return isValid;
}
});

/**
* @param plutusData CIP-0068 (label 222) datum
* @param parentLogger logger
*/
export const fromPlutusData = (
plutusData: Cardano.PlutusData | undefined,
parentLogger: Logger
parentLogger: Logger,
strict = false
): NftMetadata | null => {
const logger = contextLogger(parentLogger, 'NftMetadata.fromPlutusData');
if (!isConstrPlutusData(plutusData) || plutusData.constructor !== 0n || plutusData.fields.items.length < 3) {
logger.debug('Invalid PlutusData: expecting ConstrPlutusData with 0th constructor and 3 items');
const conditionalValidators = getConditionalValidators(strict, logger);
if (!conditionalValidators.isValidDatumShape(plutusData)) {
return null;
}

Expand All @@ -106,8 +134,12 @@ export const fromPlutusData = (
const nftMetadataRecord = tryConvertPlutusMapToUtf8Record(nftMetadata, logger);
const { name, image, mediaType, description, files, ...additionalProperties } = nftMetadataRecord;

if (typeof name !== 'string' || typeof image !== 'string') {
logger.debug('Invalid PlutusData: missing required field (name, image)');
if (!conditionalValidators.isNameValid(name)) {
return null;
}

if (typeof image !== 'string') {
logger.debug('Invalid PlutusData: "image" must be UTF-8 bounded bytes');
return null;
}

Expand All @@ -121,7 +153,7 @@ export const fromPlutusData = (
files: mapFiles(files, logger),
image: imageAsUri,
mediaType: tryCoerce(mediaType, ImageMediaType, logger),
name,
name: name || '',
otherProperties: undefinedIfEmpty(mapOtherProperties(additionalProperties, logger)),
version: version.toString()
};
Expand Down

0 comments on commit 898a821

Please sign in to comment.