From 634ee11213e47a203ec8eb862ad632eb1a8c397f Mon Sep 17 00:00:00 2001 From: Robert Zaremba Date: Wed, 6 Sep 2023 23:56:59 +0200 Subject: [PATCH 1/9] NEP: SBT Metadata Standard --- neps/nep-sbt-metadata.md | 230 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 neps/nep-sbt-metadata.md diff --git a/neps/nep-sbt-metadata.md b/neps/nep-sbt-metadata.md new file mode 100644 index 000000000..ffbbddc9b --- /dev/null +++ b/neps/nep-sbt-metadata.md @@ -0,0 +1,230 @@ +--- +NEP: 0 +Title: SBT Metadata Interface +Authors: Robert Zaremba (@robert-zaremba) +Status: New +DiscussionsTo: https://github.com/nearprotocol/neps/pull/0000 +Type: Contract Standard +Version: 1.0.0 +Created: 2023-08-21 +LastUpdated: 2023-09-06 +--- + +## Summary + +A standard interface for [SBT](https://github.com/near/NEPs/pull/393) Metadata: tokens, classes and issuers. +This allows an SBT registry and issuers to be interrogated for its name and for details an SBT represent. + +The proposal addresses potential challenges related to metadata reconstruction, off-chain storage security, owner/operator dependence, and data privacy. + +## Motivation + +[NEP-393](https://github.com/near/NEPs/pull/393) defines a standard for Soul Bound Tokens, which are a foundational for Decentralized Society. SBTs can represent certificates, badges and anything that should be bound to a "soul". +The contract issuers often need more details than what NEP-393 defines. Such details are meant to be set in the `reference` field, which has an open, non standardized format. + +NEAR doesn't have a well structured Metadata standard. The [NFT Metadata](https://github.com/near/NEPs/blob/master/neps/nep-0177.md) doesn't clearly define the `reference` extension. In fact, the NEP-393 Metadata is defined based on NFT Metadata. The schema defined here can be used by other token variations as well. + +Issuers, contract implementers and users demand a guideline and a minimum schema to make better integration with tools and projects. This document defines a minimum common schema for reference fields of `ContractMetadata` and `TokenMetadata`. Moreover, while working with various issuers, we found that issuers and minters often duplicate lot of data in the `TokenMetadata`. We found that it is unnecessary and it would be useful to create a new type: `ClassMetadata`, that defines common data for all tokens of a given (issuer, class) pair. + +## Specification + +NEP-393 `TokenMetadata` is minimalistic. It only requires data which a contract can reason about, and all user facing details (such as title, description) are meant to be part of the reference. +Similarly, `ContractMetadata` only defines issuer basic info. + +### Class Metadata + +We start by defining a `ClassMetadata` which will contain all common information about tokens of the same class. +This will allow to avoid duplicating such information in all `TokenMetadata` and potentially allow to leave `TokenMetadata.reference` empty, if the tokens only differentiate by the class, issued_at and expires_at attributes. + +```rust +pub struct ClassMetadata { + /// Issuer class name. Required. + pub name: String, + /// If defined, should be used instead of `contract_metadata.symbol`. + pub symbol: Option, + /// Icon content (SVG) or a link to an Icon. If it doesn't start with a scheme (eg: https://) + /// then `contract_metadata.base_uri` should be prepended. + pub icon: Option, + /// JSON or an URL to a JSON file with more info. If it doesn't start with a scheme + /// (eg: https://) then base_uri should be prepended. + pub reference: Option, + /// Base64-encoded sha256 hash of JSON from reference field. Required if `reference` is included. + pub reference_hash: Option, +} +``` + +The Class Metadata should be defined by the Issuer and stored by the issuer contract. Note, that Contract Metadata is stored in the issuer contract and the Token Metadata is stored in the registry. +Having Class Metadata in issuer contract allows more granular storage control, shard the data, and facilitate easier migration patterns. + +We recommend to store in the Class Metadata as much common information as possible, rather than pushing it down to the Token Metadata. For example if all tokens of give (issuer, class) pair have the same `icon`, then the `icon` should be defined in the `ClassMetadata` rather than `TokenMetadata.reference`. + +The API to set `ClassMetadata` is not part of the standard, and can be custom for every implementation. + +### Reference Field + +The `reference` is attribute is part of all Metadata structures (Token, Class and Contract). It must be a valid JSON string or an URL link to a valid JSON file. In the latter case we recommend to set `reference_hash` to facilitate secure off-chain metadata storage and a mechanism for consistency checks. +It's up to the issuer if he wants to store `reference` fully on chain (as a JSON string) or off-chain. The latter case won't guarantee data availability, but may provide faster data access. + +We define a `ReferenceBase` type. All entries are optional, and don't have to be set. If an entry is set, then it must have type compatible with `ReferenceBase`. The Reference object can extend the `ReferenceBase` and provide more fields. + +```rust +struct ReferenceBase { + description: Option, + website: Option, + minting_info: Option, + created_at: u64, + social_media: Map, + // keys are contact types + contact: Map>, + // keys are image format + icon: Map>, + // keys are video format + video: Map>, + // keys are audio format + audio: Map>, +} +``` + +Example JS value: + +```js +{ + "description": "Detailed description about the issuer and the contract", + // resource address. For token, it should point to a token website, or an project webiste. In + // the latter case it's recommended to set such information in the Class or Contract Metadata. + "website": "your.address.com/resource", + "minting_info": "additional information about minting or a link to a resource", + // Unix time in ms, when the project / issuer was created. + "created_at": 1689330000000, + // Sub object defining social media references. Keys should represent the social media domain, not a name. + "social_media": { + "near.social": "https://nearefi.org/bos," + "x.com": "https://nearefi.org/twitter", // twitter is X now + "facebook.com": "https://facebook.com/...", + "soundcloud.com": "https://soundcloud.com/..." + }, + // Sub object defining contact resources. Each value must be a list. + "contact": { + "email": ["contact@example.com", "contact2@example.com"], // must be a list + "telegram": ["telegram_handle_1", "telegram_handle2"], // must be a list + }, + // Default token icon + "icon": { + "png": "https://genadrop.mypinata.cloud/ipfs/...", + "svg": "https://genadrop.mypinata.cloud/ipfs/..." + }, + // additional video content. Sub object, where key is a video format. + "video": { + "mp4": "https://youtube.com/...", + "vp9": "ipfs://..." + }, + // additional audio content. Sub object, where key is an audio format. + "audio": { + "ogg": "ipfs://..." + } +``` + +All reference fields (`TokenMetadata.reference`, `ClassMetadata.reference`, `ContractMetadata.reference`) share the same scheme. Common information should be pushed up in the category. For example project information should be stored in Class Metadata or Contract Metadata, rather than the Token Metadata. Issuer information and issuer website should be stored in the `ContractMetadata`. + +### Information flow + +Projects and dapps should use all stack of information to correctly render token information. Let's consider the following examples, where each token is part of the same class (`class1`) and same issuer (`issuer1`) + +1. All `token1metadata.reference.icon`, `class1metadata.reference.icon` and `issuer1metadata.reference.icon` are defined. To render token, the `token1metadata` icon field should be used. Moreover, each application can choose which format (png, jpg, svg...) to use. +1. All `token1metadata.reference.icon` is not defined and `class1metadata.reference.icon` is defined. The `class1metadata` icon should be used to render token. +1. Both `token1metadata.reference.webiste` and `class1metadata.reference.webiste` are defined. A dapp can use both token and class level metadata to provide detailed information. + +### Events + +Whenever a metadata is updated, an event should be emitted. We propose to follow the same event principles as defined in NEP-393: + +- Events don't need to repeat all function arguments - these are easy to retrieve by indexer (events are consumed by indexers anyway). +- Events must include fields necessary to identify subject matters related to use case. +- When possible, events should contain aggregated data, with respect to the standard function related to the event. + +```typescript +type NepXXXEvent { + standard: "nepXXX"; + version: "1.0.0"; + event: "token" | "class" | "issuer"; + data: Token | Class | Issuer ; +} + +``` + +## Reference Implementation + +The concept was implemented and iterated in the [I Am Human](https://i-am-human.app/) project and used in the [Community SBT](https://github.com/near-ndc/i-am-human/tree/master/contracts/community-sbt) contracts. + +## Security Implications + +The proposal doesn't introduce new cross contract calls, nor extends the API of SBT reference. The implementation is encapsulated in the issuer contract, and should not impact any other contract. + +## Alternatives + +NEP-177 was used as a base for the Token and Contract Metadata in NEP-393. This proposal builds on top of that. + +## Future possibilities + +We can introduce Metadata versioning by wrapping the `ReferenceBase` object in an enum: + +```json +{ + "v1": reference +} +``` + +or adding new field: `"ver": 1` to the ReferenceBase. + +## Consequences + +### Positive + +- p1 + +### Neutral + +- n1 + +### Negative + +- n1 + +### Backwards Compatibility + +The standard is compatible with NEP-393 (SBT) and can be used by NEP-177 (NFT Metadata). + +## Unresolved Issues (Optional) + +[Explain any issues that warrant further discussion. Considerations + +- What parts of the design do you expect to resolve through the NEP process before this gets merged? +- What parts of the design do you expect to resolve through the implementation of this feature before stabilization? +- What related issues do you consider out of scope for this NEP that could be addressed in the future independently of the solution that comes out of this NEP?] + +## Changelog + +### 1.0.0 - Initial Version + +> Placeholder for the context about when and who approved this NEP version. + +#### Benefits + +> List of benefits filled by the Subject Matter Experts while reviewing this version: + +- Benefit 1 +- Benefit 2 + +#### Concerns + +> Template for Subject Matter Experts review for this version: +> Status: New | Ongoing | Resolved + +| # | Concern | Resolution | Status | +| --: | :------ | :--------- | -----: | +| 1 | | | | +| 2 | | | | + +## Copyright + +Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). From 3394cd0d8edcfeabc714bd088e5d139c2fed00d4 Mon Sep 17 00:00:00 2001 From: Robert Zaremba Date: Wed, 6 Sep 2023 23:59:39 +0200 Subject: [PATCH 2/9] update filename --- neps/{nep-sbt-metadata.md => nep-504.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename neps/{nep-sbt-metadata.md => nep-504.md} (100%) diff --git a/neps/nep-sbt-metadata.md b/neps/nep-504.md similarity index 100% rename from neps/nep-sbt-metadata.md rename to neps/nep-504.md From ca6b0a7bc607afd9668ff57bc87d558790adac10 Mon Sep 17 00:00:00 2001 From: Robert Zaremba Date: Thu, 7 Sep 2023 00:00:19 +0200 Subject: [PATCH 3/9] set nep number --- neps/nep-504.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/neps/nep-504.md b/neps/nep-504.md index ffbbddc9b..4ddce162c 100644 --- a/neps/nep-504.md +++ b/neps/nep-504.md @@ -1,5 +1,5 @@ --- -NEP: 0 +NEP: 504 Title: SBT Metadata Interface Authors: Robert Zaremba (@robert-zaremba) Status: New @@ -143,8 +143,8 @@ Whenever a metadata is updated, an event should be emitted. We propose to follow - When possible, events should contain aggregated data, with respect to the standard function related to the event. ```typescript -type NepXXXEvent { - standard: "nepXXX"; +type Nep504Event { + standard: "nep504"; version: "1.0.0"; event: "token" | "class" | "issuer"; data: Token | Class | Issuer ; From 2540920e1c3a4f22d8bf39d240cf74428d024a4a Mon Sep 17 00:00:00 2001 From: Robert Zaremba Date: Thu, 7 Sep 2023 00:23:00 +0200 Subject: [PATCH 4/9] add event info --- neps/nep-504.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/neps/nep-504.md b/neps/nep-504.md index 4ddce162c..85c2c318b 100644 --- a/neps/nep-504.md +++ b/neps/nep-504.md @@ -142,14 +142,24 @@ Whenever a metadata is updated, an event should be emitted. We propose to follow - Events must include fields necessary to identify subject matters related to use case. - When possible, events should contain aggregated data, with respect to the standard function related to the event. +The Contract and Class Metadata are stored and managed by the Issuer contract. This NEP covers only the Issuer contract. `TokenMetadata` is stored in the SBT registry, consequently Token Metadata updates and events are defined in NEP-393. + ```typescript type Nep504Event { standard: "nep504"; version: "1.0.0"; - event: "token" | "class" | "issuer"; - data: Token | Class | Issuer ; + event: "class" | "contract"; + data: Class | Contract ; +} + +/// An event emitted by an SBT Issuer when Class Metadata are updated. +type Class { + classes: ClassId[]; // list of class IDs whose metadata has been updated } +/// An event emitted by an SBT Issuer when Contract Metadata is updated. +/// Note: it's an empty object +type Class { } ``` ## Reference Implementation From 1705709943de4153be333d9c6e65cbee66929015 Mon Sep 17 00:00:00 2001 From: Robert Zaremba Date: Thu, 7 Sep 2023 01:35:55 +0200 Subject: [PATCH 5/9] consequences --- neps/nep-504.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/neps/nep-504.md b/neps/nep-504.md index 85c2c318b..eff14a216 100644 --- a/neps/nep-504.md +++ b/neps/nep-504.md @@ -190,15 +190,17 @@ or adding new field: `"ver": 1` to the ReferenceBase. ### Positive -- p1 +- Guideline for Issuers to correctly describe their tokens. +- `ReferenceBase` is a standarized and extendible type. +- `ReferenceBase` can be adopted by NFT Metadata. +- Better dapp integration. +- Added missing events to notify about metadata change. ### Neutral -- n1 - ### Negative -- n1 +No negative consequences have been indicated. ### Backwards Compatibility From 37aaa74bb436d25525f4d7300a7d2460f9706eaf Mon Sep 17 00:00:00 2001 From: Robert Zaremba Date: Fri, 29 Sep 2023 00:20:24 +0200 Subject: [PATCH 6/9] review --- neps/nep-504.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/neps/nep-504.md b/neps/nep-504.md index eff14a216..79919b487 100644 --- a/neps/nep-504.md +++ b/neps/nep-504.md @@ -3,11 +3,11 @@ NEP: 504 Title: SBT Metadata Interface Authors: Robert Zaremba (@robert-zaremba) Status: New -DiscussionsTo: https://github.com/nearprotocol/neps/pull/0000 +DiscussionsTo: https://github.com/nearprotocol/neps/pull/393 Type: Contract Standard Version: 1.0.0 Created: 2023-08-21 -LastUpdated: 2023-09-06 +LastUpdated: 2023-09-29 --- ## Summary @@ -42,7 +42,9 @@ pub struct ClassMetadata { pub name: String, /// If defined, should be used instead of `contract_metadata.symbol`. pub symbol: Option, - /// Icon content (SVG) or a link to an Icon. If it doesn't start with a scheme (eg: https://) + /// An URL to an Icon. To protect fellow developers from unintentionally triggering any + /// SSRF vulnerabilities with URL parsers, we don't allow to set an image bytes here. + /// If it doesn't start with a scheme (eg: https://) /// then `contract_metadata.base_uri` should be prepended. pub icon: Option, /// JSON or an URL to a JSON file with more info. If it doesn't start with a scheme @@ -69,10 +71,15 @@ We define a `ReferenceBase` type. All entries are optional, and don't have to be ```rust struct ReferenceBase { + // Human readable description. description: Option, + // URL to a website. website: Option, + // Information how to get a token. minting_info: Option, + // Time (as Unixt Time in miliseconds), when the class or issuer was created. created_at: u64, + // Map of social media kind to the profile link. social_media: Map, // keys are contact types contact: Map>, @@ -149,17 +156,17 @@ type Nep504Event { standard: "nep504"; version: "1.0.0"; event: "class" | "contract"; - data: Class | Contract ; + data: Class | Contract; } /// An event emitted by an SBT Issuer when Class Metadata are updated. -type Class { +type ClassEvent { classes: ClassId[]; // list of class IDs whose metadata has been updated } /// An event emitted by an SBT Issuer when Contract Metadata is updated. /// Note: it's an empty object -type Class { } +type ContractEvent { } ``` ## Reference Implementation From 35d179c6e0c51052a01d571cad2deee5e3ed219e Mon Sep 17 00:00:00 2001 From: Robert Zaremba Date: Thu, 5 Oct 2023 10:07:46 +0200 Subject: [PATCH 7/9] Add information where ClassMeetadata should be stored. --- neps/nep-504.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neps/nep-504.md b/neps/nep-504.md index 79919b487..067d9618d 100644 --- a/neps/nep-504.md +++ b/neps/nep-504.md @@ -33,7 +33,7 @@ Similarly, `ContractMetadata` only defines issuer basic info. ### Class Metadata -We start by defining a `ClassMetadata` which will contain all common information about tokens of the same class. +We start by defining a `ClassMetadata` which will contain all common information about tokens of the same class. The `ClassMeetadata` object must be stored in the SBT issuer contract and accessible via `class_metadata(class: ClassId)` query. This will allow to avoid duplicating such information in all `TokenMetadata` and potentially allow to leave `TokenMetadata.reference` empty, if the tokens only differentiate by the class, issued_at and expires_at attributes. ```rust From 1308eba9b025e61663af89dc7f78bc526c5daf68 Mon Sep 17 00:00:00 2001 From: Robert Zaremba Date: Thu, 25 Jan 2024 18:38:37 +0100 Subject: [PATCH 8/9] update class metadata --- neps/nep-504.md | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/neps/nep-504.md b/neps/nep-504.md index 067d9618d..f4feaceeb 100644 --- a/neps/nep-504.md +++ b/neps/nep-504.md @@ -33,29 +33,10 @@ Similarly, `ContractMetadata` only defines issuer basic info. ### Class Metadata -We start by defining a `ClassMetadata` which will contain all common information about tokens of the same class. The `ClassMeetadata` object must be stored in the SBT issuer contract and accessible via `class_metadata(class: ClassId)` query. +We start by defining a `ClassMetadata` which will contain all common information about tokens of the same class. The `ClassMeetadata` object must be stored in the SBT issuer contract and accessible via `sbt_class_metadata(class: ClassId)` query. This will allow to avoid duplicating such information in all `TokenMetadata` and potentially allow to leave `TokenMetadata.reference` empty, if the tokens only differentiate by the class, issued_at and expires_at attributes. -```rust -pub struct ClassMetadata { - /// Issuer class name. Required. - pub name: String, - /// If defined, should be used instead of `contract_metadata.symbol`. - pub symbol: Option, - /// An URL to an Icon. To protect fellow developers from unintentionally triggering any - /// SSRF vulnerabilities with URL parsers, we don't allow to set an image bytes here. - /// If it doesn't start with a scheme (eg: https://) - /// then `contract_metadata.base_uri` should be prepended. - pub icon: Option, - /// JSON or an URL to a JSON file with more info. If it doesn't start with a scheme - /// (eg: https://) then base_uri should be prepended. - pub reference: Option, - /// Base64-encoded sha256 hash of JSON from reference field. Required if `reference` is included. - pub reference_hash: Option, -} -``` - -The Class Metadata should be defined by the Issuer and stored by the issuer contract. Note, that Contract Metadata is stored in the issuer contract and the Token Metadata is stored in the registry. +The Class Metadata and Issuer Metadata must be provided by the issuer contract. SBT Registry only manages token metadata. See the [`SBTIssuer`](./nep-0393.md#sbt-issuer-interface). NOTE: Token Metadata is stored in the registry. Having Class Metadata in issuer contract allows more granular storage control, shard the data, and facilitate easier migration patterns. We recommend to store in the Class Metadata as much common information as possible, rather than pushing it down to the Token Metadata. For example if all tokens of give (issuer, class) pair have the same `icon`, then the `icon` should be defined in the `ClassMetadata` rather than `TokenMetadata.reference`. From c19b2d994c9bbfd2deb324a2df0b52b21b4755a1 Mon Sep 17 00:00:00 2001 From: Robert Zaremba Date: Thu, 25 Jan 2024 18:39:21 +0100 Subject: [PATCH 9/9] update filename --- neps/{nep-504.md => nep-0504.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename neps/{nep-504.md => nep-0504.md} (100%) diff --git a/neps/nep-504.md b/neps/nep-0504.md similarity index 100% rename from neps/nep-504.md rename to neps/nep-0504.md