-
Notifications
You must be signed in to change notification settings - Fork 138
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
NEP: SBT Metadata Standard #504
base: master
Are you sure you want to change the base?
Changes from 8 commits
634ee11
3394cd0
ca6b0a7
2540920
1705709
ab36573
37aaa74
35d179c
1308eba
c19b2d9
b5eebfe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,249 @@ | ||||||
--- | ||||||
NEP: 504 | ||||||
Title: SBT Metadata Interface | ||||||
Authors: Robert Zaremba (@robert-zaremba) | ||||||
Status: New | ||||||
DiscussionsTo: https://github.com/nearprotocol/neps/pull/393 | ||||||
Type: Contract Standard | ||||||
Version: 1.0.0 | ||||||
Created: 2023-08-21 | ||||||
LastUpdated: 2023-09-29 | ||||||
--- | ||||||
|
||||||
## 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. 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 | ||||||
pub struct ClassMetadata { | ||||||
/// Issuer class name. Required. | ||||||
pub name: String, | ||||||
/// If defined, should be used instead of `contract_metadata.symbol`. | ||||||
pub symbol: Option<String>, | ||||||
/// 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<String>, | ||||||
/// 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<String>, | ||||||
/// Base64-encoded sha256 hash of JSON from reference field. Required if `reference` is included. | ||||||
pub reference_hash: Option<Base64VecU8>, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @robert-zaremba Let me try to deliver my point once again: Lines 45 to 46 in 6eee9dc
If you want to have more fields stored as part of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Issue about adding more records to the
|
||||||
} | ||||||
``` | ||||||
|
||||||
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 { | ||||||
robert-zaremba marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
// Human readable description. | ||||||
description: Option<String>, | ||||||
// URL to a website. | ||||||
website: Option<String>, | ||||||
// Information how to get a token. | ||||||
minting_info: Option<String>, | ||||||
// 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<String, String>, | ||||||
// keys are contact types | ||||||
contact: Map<String, Vec<String>>, | ||||||
// keys are image format | ||||||
icon: Map<String, Vec<String>>, | ||||||
// keys are video format | ||||||
video: Map<String, Vec<String>>, | ||||||
// keys are audio format | ||||||
audio: Map<String, Vec<String>>, | ||||||
} | ||||||
``` | ||||||
|
||||||
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/..." | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe we should have one more nesting level, to indicate the picture size? This will allow to set multiple sizes. Example:
Similarly we can do with vide and audio: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @robert-zaremba ...or it may be just a part of the URL: "https://image.com/?w=1024&h=748" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what if a http server doesn't support that? |
||||||
}, | ||||||
// 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. | ||||||
|
||||||
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: "class" | "contract"; | ||||||
data: Class | Contract; | ||||||
} | ||||||
|
||||||
/// An event emitted by an SBT Issuer when Class Metadata are updated. | ||||||
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 ContractEvent { } | ||||||
``` | ||||||
|
||||||
## 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 | ||||||
|
||||||
- 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 | ||||||
|
||||||
### Negative | ||||||
|
||||||
No negative consequences have been indicated. | ||||||
|
||||||
### 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/). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The big question is: where the class metadata is actually used? Any API (view/call methods)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should represent information about the whole class. For example see the "SBT" cards https://i-am-human.app/community-sbts - it will use information from the class metadata (currently this is hardcoded in the UI).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@robert-zaremba I think I still don't get whether it's returned via API, or is posted as a log string in the L1 block (?).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ClassMetadata
is stored in the Issuer, and returned via an issuer call. Will add this informationThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added in the paragraph below