Skip to content

Commit

Permalink
feat(core): add 'strict' option to NftMetadata.fromPlutusData
Browse files Browse the repository at this point in the history
loose mode is the default, which:
- allows omitting the 3rd 'extra' field (to support assets minted by NMKR)
- allows omitting 'name' field and uses empty string as default

BREAKING CHANGE: NftMetadata.fromPlutusData use '' as default name
  • Loading branch information
mkazlauskas committed May 8, 2024
1 parent 92a35a6 commit 7689602
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 9 deletions.
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
40 changes: 37 additions & 3 deletions packages/core/test/Asset/NftMetadata/fromPlutusData.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ const createFilesPlutusData = (files: Array<Partial<Asset.NftMetadataFile>>): Ca
const createPlutusData = (
nftMetadata: Partial<Omit<Asset.NftMetadata, 'files'>>,
files?: Partial<Asset.NftMetadataFile>[],
constructor = 0n
constructor = 0n,
nameTuple = stringKeyValueTupleIfExists(nftMetadata, 'name')
): Cardano.ConstrPlutusData => ({
constructor,
fields: {
Expand All @@ -69,10 +70,10 @@ const createPlutusData = (
data: new Map<Cardano.PlutusData, Cardano.PlutusData>([
...createPlutusMap(
[
...stringKeyValueTupleIfExists(nftMetadata, 'name'),
...stringKeyValueTupleIfExists(nftMetadata, 'image'),
...stringKeyValueTupleIfExists(nftMetadata, 'description'),
...stringKeyValueTupleIfExists(nftMetadata, 'mediaType')
...stringKeyValueTupleIfExists(nftMetadata, 'mediaType'),
...nameTuple
],
nftMetadata.otherProperties
).entries(),
Expand Down Expand Up @@ -155,6 +156,24 @@ describe('NftMetadata.fromPlutusData', () => {
expect(Asset.NftMetadata.fromPlutusData(minimalPlutusMetadata, logger)).toEqual(minimalCoreMetadata);
});

describe('metadatum without name', () => {
const plutusData = createPlutusData(minimalCoreMetadata, [], 0n, []);

describe('loose mode', () => {
it('returns metadata with empty name string', () => {
expect(Asset.NftMetadata.fromPlutusData(plutusData, logger)).toMatchObject({
name: ''
});
});
});

describe('strict mode', () => {
it('returns null', () => {
expect(Asset.NftMetadata.fromPlutusData(plutusData, logger, true)).toBeNull();
});
});
});

it('supports base64 decoded image following data URL scheme standard', () => {
const base64DecodedImage =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAATgAAAE4CAYAAADPf+' +
Expand Down Expand Up @@ -318,4 +337,19 @@ describe('NftMetadata.fromPlutusData', () => {
expect(typeof nftMetadata.otherProperties?.get('og')).toBe('bigint');
expect(nftMetadata.files).toBeUndefined();
});

it('can convert NMKR datum', () => {
const nmkrDatum = HexBlob(
'd8799fa5446e616d654b6e6d6b724e4654386d617945696d6167655835697066733a2f2f516d4e77566157314b655471424a4b4b6963355553443165424c41315a48396b596b455674535952423646314d64496d656469615479706549696d6167652f706e674b6465736372697074696f6e404566696c65739fa3496d656469615479706549696d6167652f706e67446e616d654b6e6d6b724e4654386d6179437372635835697066733a2f2f516d4e77566157314b655471424a4b4b6963355553443165424c41315a48396b596b455674535952423646314d64ff01ff'
);
const datum = Serialization.Datum.newInlineData(
Serialization.PlutusData.fromCbor(nmkrDatum)
).toCore() as Cardano.PlutusData;

const nftMetadata = Asset.NftMetadata.fromPlutusData(datum, logger)!;

expect(nftMetadata).toMatchObject({
name: 'nmkrNFT8may'
});
});
});

0 comments on commit 7689602

Please sign in to comment.