Security News
The Risks of Misguided Research in Supply Chain Security
Snyk's use of malicious npm packages for research raises ethical concerns, highlighting risks in public deployment, data exfiltration, and unauthorized testing.
@lens-protocol/metadata
Advanced tools
Schema validation and TS types for LIP-2 Lens Protocol Metadata Standards.
# npm:
npm install @lens-protocol/metadata zod
# yarn:
yarn add @lens-protocol/metadata zod
# pnpm:
pnpm add @lens-protocol/metadata zod
[!NOTE]
zod
is marked as optional peer dependency, so if you all you need is the JSON Schema definitions, you can install@lens-protocol/metadata
withoutzod
.
You can create compliant PublicationMetadata
objects via the following builder functions:
import {
article,
audio,
checkingIn,
embed,
event,
image,
link,
livestream,
mint,
space,
story,
textOnly,
threeD,
transaction,
video,
} from '@lens-protocol/metadata';
const json = article({
content: 'The content of the article',
});
[!NOTE] Use the type definitions to explore the available properties and their types. The builders will throw a
ValidationError
with instructions on how to fix the error if the object is not compliant with the schema.
We also provided a set of builder function for specific metadata sub-types (list to be expanded):
import { geoUri } from '@lens-protocol/metadata';
const uri = geoUri({
lat: 51.5074,
lng: 0.1278,
});
You can create compliant MirrorMetadata
objects via the following builder function:
import { mirror } from '@lens-protocol/metadata';
const json = mirror({
appId: 'foobar-app',
});
[!NOTE] Use the type definitions to explore the available properties and their types. The builder will throw a
ValidationError
with instructions on how to fix the error if the object is not compliant with the schema.
You can create compliant ProfileMetadata
objects via the following builder function:
import { profile } from '@lens-protocol/metadata';
const json = profile({
name: 'Bob',
bio: 'I am a Lens user',
});
[!NOTE] Use the type definitions to explore the available properties and their types. The builder will throw a
ValidationError
with instructions on how to fix the error if the object is not compliant with the schema.
Assuming we have 2 JS objects:
const valid = {
/** example of valid metadata **/
};
const invalid = {
/** example of invalid metadata **/
};
Publication metadata schema is a union of all content schemas (e.g. ArticleMetadata
, AudioMetadata
, etc. but NOT MirrorMetadata
).
Use it to parse the metadata referenced by contentURI
of Comment
, Mirror
, and Quote
publications.
import { PublicationMetadataSchema } from '@lens-protocol/metadata';
PublicationMetadataSchema.parse(valid); // => PublicationMetadata
PublicationMetadataSchema.parse(invalid); // => throws ZodError
// OR
PublicationMetadataSchema.safeParse(valid);
// => { success: true, data: PublicationMetadata }
PublicationMetadataSchema.safeParse(invalid);
// => { success: false, error: ZodError }
Mirror metadata schema serve the purpose allowing mirrors be associated to a given Lens app (via the appId
) as well as specify some operational flags (e.g. hideFromFeed
and globalReach
).
Use it to parse the metadata referenced by metadataURI
of Mirror
publications.
import { MirrorMetadataSchema } from '@lens-protocol/metadata';
MirrorMetadataSchema.parse(valid); // => MirrorMetadata
MirrorMetadataSchema.parse(invalid); // => throws ZodError
// OR
MirrorMetadataSchema.safeParse(valid);
// => { success: true, data: MirrorMetadata }
MirrorMetadataSchema.safeParse(invalid);
// => { success: false, error: ZodError }
import { ProfileMetadataSchema } from '@lens-protocol/metadata';
ProfileMetadataSchema.parse(valid); // => ProfileMetadata
ProfileMetadataSchema.parse(invalid); // => throws ZodError
// OR
ProfileMetadataSchema.safeParse(valid);
// => { success: true, data: ProfileMetadata }
ProfileMetadataSchema.safeParse(invalid);
// => { success: false, error: ZodError }
A convenience extractVersion
function is available to extract the version from a parsed PublicationMetadata
or ProfileMetadata
.
import {
extractVersion,
PublicationMetadataSchema,
ProfileMetadataSchema,
} from '@lens-protocol/metadata';
const publicationMetadata = PublicationMetadataSchema.parse(valid);
extractVersion(publicationMetadata); // => '3.0.0'
const profileMetadata = ProfileMetadataSchema.parse(valid);
extractVersion(profileMetadata); // => '2.0.0'
ZodError
contains all the information needed to inform you about the validation error, but it's not very user friendly. You can use formatZodError
to get a more readable error message.
import { PublicationMetadataSchema, formatZodError } from '@lens-protocol/metadata';
const result = PublicationMetadataSchema.safeParse(invalid);
if (!result.success) {
console.log(formatZodError(result.error));
}
Every time you have a discriminated union, you can use the discriminant to narrow the type. See few examples below.
PublicationMetadata
import {
PublicationMetadata,
PublicationMetadataSchema,
PublicationSchemaId,
} from '@lens-protocol/metadata';
const publicationMetadata = PublicationMetadataSchema.parse(valid);
switch (publicationMetadata.$schema) {
case PublicationSchemaId.ARTICLE_LATEST:
// publicationMetadata is ArticleMetadata
break;
case PublicationSchemaId.AUDIO_LATEST:
// publicationMetadata is AudioMetadata
break;
case PublicationSchemaId.IMAGE_LATEST:
// publicationMetadata is ImageMetadata
break;
case PublicationSchemaId.TEXT_ONLY_LATEST:
// publicationMetadata is TextOnlyMetadata
break;
// ...
}
AccessCondition
import { AccessCondition, ConditionType, PublicationMetadataSchema } from '@lens-protocol/metadata';
const publicationMetadata = PublicationMetadataSchema.parse(valid);
switch (publicationMetadata.encryptedWith?.accessCondition.type) {
case ConditionType.AND:
// accessCondition is AndCondition
break;
case ConditionType.OR:
// accessCondition is OrCondition
break;
case ConditionType.NFT_OWNERSHIP:
// accessCondition is NftOwnershipCondition
break;
case ConditionType.EOA_OWNERSHIP:
// accessCondition is EoaOwnershipCondition
break;
// ...
}
MetadataAttribute
import { MetadataAttribute, MetadataAttributeType } from '@lens-protocol/metadata';
switch (attribute.type) {
case MetadataAttributeType.BOOLEAN:
// attribute is BooleanAttribute
// value is a string "true" or "false"
break;
case MetadataAttributeType.DATE:
// attribute is DateAttribute
// value is a string in ISO 8601 format
break;
case MetadataAttributeType.NUMBER:
// attribute is NumberAttribute
// value is a string containing a valid JS number
break;
case MetadataAttributeType.STRING:
// attribute is StringAttribute
// value is a string
break;
case MetadataAttributeType.JSON:
// attribute is JSONAttribute
// value is a string allegedly containing a valid JSON, consumers should validate it
break;
}
The package also exports all enums and types that you might need to work with the metadata.
Use your IDE's autocomplete to explore the available types.
Some examples:
import {
// enums
MediaAudioKind,
MediaAudioMimeType,
MediaImageMimeType,
MediaVideoMimeType,
MetadataAttributeType,
PublicationMainFocus,
ThreeDFormat,
// main types
ArticleMetadata,
AudioMetadata,
CheckingInMetadata,
EmbedMetadata,
EventMetadata,
ImageMetadata,
LinkMetadata,
LivestreamMetadata,
MintMetadata,
ProfileMetadata,
PublicationMetadata,
SpaceMetadata,
StoryMetadata,
TextOnlyMetadata,
ThreeDMetadata,
TransactionMetadata,
VideoMetadata,
// others
MetadataAttribute,
MediaAudio,
MediaImage,
MediaVideo,
AnyMedia,
GeoLocation,
BooleanAttribute,
DateAttribute,
NumberAttribute,
StringAttribute,
JSONAttribute,
// branded aliases
Locale,
Markdown,
Signature,
URI,
AppId,
Datetime,
} from '@lens-protocol/metadata';
The package also exports parsers for legacy metadata formats via the @lens-protocol/metadata/legacy
entrypoint.
[!WARNING] DO NOT mix and match legacy and new metadata TS types and enums. Although they share some similarities they are not meant to be interoperable. For example if you are checking
mainContentFocus
ofPublicationMetadataV2
use thePublicationMainFocus
exported from@lens-protocol/metadata/legacy
and NOT the one from the main@lens-protocol/metadata
entrypoint.
You can parse legacy Publication Metadata v1 and v2 via:
import { PublicationMetadataSchema } from '@lens-protocol/metadata/legacy';
PublicationMetadataSchema.parse(valid); // => PublicationMetadata
PublicationMetadataSchema.parse(invalid); // => throws ZodError
// OR
PublicationMetadataSchema.safeParse(valid);
// => { success: true, data: PublicationMetadata }
PublicationMetadataSchema.safeParse(invalid);
// => { success: false, error: ZodError }
Legacy PublicationMetadata
is a discriminated union of PublicationMetadataV1
and PublicationMetadataV2
where the version
property is the discriminant.
In turn legacy.PublicationMetadataV2
is a discriminated union of:
PublicationMetadataV2Article
PublicationMetadataV2Audio
PublicationMetadataV2Embed
PublicationMetadataV2Image
PublicationMetadataV2Link
PublicationMetadataV2TextOnly
PublicationMetadataV2Video
where the mainContentFocus
property is the discriminant.
import {
PublicationMetadataSchema,
PublicationMetadataVersion,
PublicationMainFocus,
} from '@lens-protocol/metadata/legacy';
const publicationMetadata = PublicationMetadataSchema.parse(valid);
switch (publicationMetadata.version) {
case PublicationMetadataVersion.V1:
// publicationMetadata is PublicationMetadataV1
break;
case PublicationMetadataVersion.V2:
// publicationMetadata is PublicationMetadataV2
switch (publicationMetadata.mainContentFocus) {
case PublicationMainFocus.ARTICLE:
// publicationMetadata is PublicationMetadataV2Article
break;
case PublicationMainFocus.VIDEO:
// publicationMetadata is PublicationMetadataV2Video
break;
// ...
}
break;
}
You can also parse legacy Profile Metadata (aka v1) via:
import { ProfileMetadataSchema } from '@lens-protocol/metadata/legacy';
ProfileMetadataSchema.parse(valid); // => ProfileMetadata
ProfileMetadataSchema.parse(invalid); // => throws ZodError
// OR
ProfileMetadataSchema.safeParse(valid);
// => { success: true, data: ProfileMetadata }
ProfileMetadataSchema.safeParse(invalid);
// => { success: false, error: ZodError }
Similarly to the main entrypoint the @lens-protocol/metadata/legacy
entrypoint also exports all the types and enums that you might need to work with the legacy metadata (some examples below).
import {
// enums
AudioMimeType,
ImageMimeType,
PublicationMainFocus,
PublicationMetadataVersion,
VideoMimeType,
// main types
ProfileMetadata,
PublicationMetadata,
PublicationMetadataV1,
PublicationMetadataV2,
PublicationMetadataV2Article,
PublicationMetadataV2Audio,
PublicationMetadataV2Embed,
PublicationMetadataV2Image,
PublicationMetadataV2Link,
PublicationMetadataV2TextOnly,
PublicationMetadataV2Video,
// others
AccessCondition,
AndCondition,
CollectCondition,
EncryptedFields,
EncryptedMedia,
EoaOwnership,
Erc20Ownership,
FollowCondition,
MarketplaceMetadata,
MarketplaceMetadataAttribute,
Media,
NftOwnership,
OrCondition,
ProfileMetadataAttribute,
ProfileOwnership,
// branded aliases
Locale,
Markdown,
Signature,
URI,
AppId,
Datetime,
} from '@lens-protocol/metadata/legacy';
[!NOTE] If you find yourself in a position of importing from both
@lens-protocol/metadata
and@lens-protocol/metadata/legacy
entrypoints in the same module. You can you can use ESModule aliasing to avoid conflicts:import * as legacy from '@lens-protocol/metadata/legacy'
and then use the legacy types, enums, and parsers underlegacy.*
.
Importing JSON schema in TypeScript is a simple as:
import audio from '@lens-protocol/metadata/jsonschemas/publications/audio/3.0.0.json' assert { type: 'json' };
import audio from '@lens-protocol/metadata/jsonschemas/publications/article/3.0.0.json' assert { type: 'json' };
import mirror from '@lens-protocol/metadata/jsonschemas/publications/mirror/1.0.0.json' assert { type: 'json' };
import profile from '@lens-protocol/metadata/jsonschemas/profile/2.0.0.json' assert { type: 'json' };
You can the use them in your JSON Schema validator of choice, for example ajv.
The Lens Protocol Metadata Standards use a self-describing JSON format. All metadata files that adopt this standard MUST have a $schema
property that identifies the schema the file conforms to.
{
"$schema": "https://json-schemas.lens.dev/publications/article/3.0.0.json",
"lens": {
"id": "b3d7f1a0-1f75-11ec-9621-0242ac130002",
"content": "The content of the article",
"locale": "en"
}
}
The $schema
property is a URI that identify the schema type and its version.
Schemas are versioned using Semantic Versioning.
[!NOTE]
Even though schemas are identified by URIs, those identifiers are not necessarily network-addressable. They are just identifiers. Generally, JSON schema validators don’t make HTTP requests (https://
) to fetch schemas. Instead, they provide a way to load schemas into an internal schema database. When a schema is referenced by its URI identifier, the schema is retrieved from the internal schema database.
Future changes should aim to be backwards compatible as much as possible.
When adding a new version of a schema, the previous version should be kept for a reasonable amount of time to allow consumers to migrate and to support existing publications.
In this example we will add a new version of the AudioSchema
schema, but the same process applies to all the other schemas.
PublicationSchemaId
enum entry with value of PublicationSchemaId.AUDIO_LATEST
. Name it after the current schema version (e.g. AUDIO_V1_0_0
).AudioSchema
into AudioV1_0_0Schema
and update the $schema
value to PublicationSchemaId.AUDIO_V1_0_0
PublicationSchemaId.AUDIO_LATEST
based on the nature of your changes. Remember to follow semver rules.AudioSchema
with the new schema definition and use the PublicationSchemaId.AUDIO_LATEST
as $schema
valuescripts/build.ts
script to include the new schema and old schema files under the correct version file name in the jsonschemas/publications/audio
folderIn case the changes are backwards compatible, you could create a single AudioMetadataDetailsSchema
definition and just declare 2 schemas out of it, one for the old version and one for the new version. For example:
export const AudioMetadataDetailsSchema = metadataDetailsWith({
mainContentFocus: mainContentFocus(PublicationMainFocus.AUDIO),
audio: MediaAudioSchema,
attachments: AnyMediaSchema.array()
.min(1)
.optional()
.describe('The other attachments you want to include with it.'),
/** e.g. new optional fields */
});
export type AudioMetadataDetails = z.infer<typeof AudioMetadataDetailsSchema>;
export const AudioSchema = publicationWith({
$schema: z.literal(PublicationSchemaId.AUDIO_LATEST),
lens: AudioMetadataDetailsSchema,
});
export type AudioMetadata = z.infer<typeof AudioSchema>;
export const AudioV1Schema = publicationWith({
$schema: z.literal(PublicationSchemaId.AUDIO_V1_0_0),
lens: AudioMetadataDetailsSchema,
});
export type AudioV1Metadata = z.infer<typeof AudioV1Schema>;
In this case consumers of this package can take advantage of the structural likeness and just do the following:
switch (publicationMetadata.$schema) {
case PublicationSchemaId.AUDIO_V1_0_0:
case PublicationSchemaId.AUDIO_LATEST:
// publicationMetadata.lens is AudioMetadataDetails
break;
// ...
}
To contribute to the Lens Protocol Metadata Standards, please fork this repository and submit a pull request with your changes.
To run the unit tests, run:
pnpm test:unit
Pro-tip: you can run pnpm test:unit --watch
to run the tests in watch mode.
To build the project, run:
pnpm build
Generate and include up to date documentation with:
pnpm typedoc:docs
Add changeset with:
pnpm changeset add
Use keepachangelog format for the changeset message.
Release flow is managed by changesets.
To release a new version follow the steps below:
main
with the name release/<version>
pnpm install && pnpm build && pnpm typedoc:docs
package.json
's versions and update CHANGELOG.md
with:pnpm changeset version
release/<version>
to main
@lens-protocol
):pnpm changeset publish
git push origin release/<version> --follow-tags
Lens Protocol Metadata Standards is MIT licensed
See the Lens API and SDK channel on our Discord
1.2.0
signMetadata
and lensMessage
helpers@lens-protocol/metadata/*
imports from VSCode autocompleteFAQs
Lens Protocol Metadata Standards
The npm package @lens-protocol/metadata receives a total of 12,069 weekly downloads. As such, @lens-protocol/metadata popularity was classified as popular.
We found that @lens-protocol/metadata demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 8 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Snyk's use of malicious npm packages for research raises ethical concerns, highlighting risks in public deployment, data exfiltration, and unauthorized testing.
Research
Security News
Socket researchers found several malicious npm packages typosquatting Chalk and Chokidar, targeting Node.js developers with kill switches and data theft.
Security News
pnpm 10 blocks lifecycle scripts by default to improve security, addressing supply chain attack risks but sparking debate over compatibility and workflow changes.