-
Notifications
You must be signed in to change notification settings - Fork 613
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
Add ERC: Social Media NFTs #782
base: master
Are you sure you want to change the base?
Changes from all commits
ba1803d
c71484e
17758b3
35afd83
fd05f7d
a5b181c
491274d
2d2f99e
5c023a9
98b0a3d
d7fe0f9
bd1fc16
90ee06e
b40d903
9c46970
6d55983
901e5dc
09aa5ec
068ef5d
d5e7c3e
eebf3bc
f7f22ab
b051da5
5772bc3
6fbe48d
b84a847
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,292 @@ | ||
--- | ||
eip: 7847 | ||
title: Social Media NFTs | ||
description: Create a social media post or publication in the form of an NFT. | ||
author: Nick Juntilla (@nickjuntilla) <nick@ownerfy.com> | ||
discussions-to: https://ethereum-magicians.org/t/erc-7847-social-media-nfts/22280 | ||
status: Draft | ||
type: Standards Track | ||
category: ERC | ||
created: 2024-12-18 | ||
--- | ||
|
||
## Abstract | ||
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. Your abstract is a bit too long. You should cut it down to a high level technical summary and minimal context. Everything else should be moved to the appropriate sections. 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. Thanks, just updated it to be more streamlined and less redundant. |
||
|
||
This proposal defines a standardized format for representing decentralized social media posts as NFTs. The Nostr protocol has done most of the heavy lifting for creating an open decentralized social media network. This ERC serves to adapt those standards to the most common blockchain non-fungible token standard. In this way we can take advantage of the reach and longevity of a blockchain. It is genericized here so that it can be easily mapped to other event based decentralized social media like the AT protocol. An event can be used as a social media post, blog post, forum post, encrypted message, RSS feed or arbitrary electronic publication. This model is flexible where the meaning and type of an event (original, reply, repost, images, video, text, etc...) is derived from its metadata. A user is anyone who has a private key. There is no permission required and anyone can create content using any NFT contract. Anyone can collate that content into a feed or timeline. | ||
|
||
Posts may be "owned" by their creators, but the owner of the NFT itself is not meaningful in the standard. This may be a useful mechanic for financial purposes, but the independent signature of the post allows for a third party to publish a message on behalf of another user. | ||
|
||
## Motivation | ||
|
||
With the continued censorship and manipulation of social media platforms, it becomes increasingly important for truly decentralized and permissionless social media to exist. Unlike other attempts at blockchain decentralized social media, this method does not rely on a centralized set of smart contracts. Blockchain integration of author-signed event based social media simply adds a powerful substrate for standardized decentralized social media to exist in. The benefits of blockchain include, but are not limited to, longevity, censorship resistance, monetary, and neutrality. The NFT standard is the most common and widely used standard for representing unique digital data on the blockchain. Using the NFT standard and standard NFT properties means that every marketplace, wallet and app that makes NFTs viewable is also a publication channel. With the data publicly available, custom feed algorithms can also be built to give control back to users. | ||
|
||
## Specification | ||
|
||
### To derive the id | ||
|
||
To obtain the id, we sha256 the serialized attributes in this order. The serialization is done over the UTF-8 JSON-serialized string of the following structure: | ||
|
||
```json | ||
[ | ||
0, | ||
<pubkey, as a lowercase hex string>, | ||
<created_at, unix timestamp in seconds>, | ||
<kind, as a number>, | ||
<tags, as an array of arrays of non-null strings>, | ||
<content, as a string> | ||
] | ||
``` | ||
|
||
**To prevent implementation differences from creating a different event ID for the same event, the following rules MUST be followed while serializing:** | ||
|
||
- UTF-8 should be used for encoding. | ||
- Whitespace, line breaks or other unnecessary formatting should not be included in the output JSON. | ||
- The following characters in the content field must be escaped as shown, and all other characters must be included verbatim: | ||
- A line break (0x0A), use \n | ||
- A double quote (0x22), use \" | ||
- A backslash (0x5C), use \\ | ||
- A carriage return (0x0D), use \r | ||
- A tab character (0x09), use \t | ||
- A backspace, (0x08), use \b | ||
- A form feed, (0x0C), use \f | ||
|
||
### Kinds | ||
|
||
0: User metadata: the content is set to a stringified JSON object {name: `<username>`, about: `<string>`, picture: `<url, string>`} describing the user who created the event. Extra metadata fields may be set. A relay may delete older events once it gets a new one for the same pubkey. | ||
1: Original content: original generated user content is usually accompanied by short-form text, but may include off-chain or on-chain references to other media or events. | ||
|
||
### Tags | ||
|
||
Tags are a flexible mechanism to attach additional structured data to a post. Each tag is an array of one or more strings, with some conventions around them. | ||
|
||
- The blockchain tag is **recommended** for events published using this method. | ||
`["blockchain", "<blockchain-name-or-id>", "<contract>", "<token_id">]` | ||
|
||
where: | ||
|
||
- `<blockchain-name-or-id>` is the name of the chain. e.g. "Ethereum", "Polygon", "Base" or chain id. e.g. "1", "137", "8453" | ||
- `<contract>` is the contract address. e.g. "0x0000000000000000000000000000000000000000" | ||
- `<token_id>` is the token id. e.g. "12345". The token_id may be omitted if it is not known before publication as it is part of the signed data. | ||
|
||
**Optional tags:** | ||
|
||
- Multiple media tags can be attached by using multiple imeta tags `["imeta", ...]` | ||
|
||
```json | ||
["imeta", | ||
"dim 1920x1080", | ||
"url <URL>/1080/12345.mp4", | ||
"m video/mp4", | ||
] | ||
``` | ||
|
||
- References to Other Posts: | ||
`["e", "<id_of_referenced_post>"]` | ||
|
||
- Public addresses involved in this post: | ||
`["p", "<pubkey>", ...]` | ||
|
||
- External URLs: | ||
`["r", "<URL>"]` | ||
|
||
### Metadata JSON | ||
|
||
Event fields are stored in the NFT's metadata under `attributes`. The `description` field of the NFT is identical to `content`. The `name` may be user defined or include the author and time created. All attributes besides `id`, `pubkey`, `created_at`, `kind`, `sig`, and `content` are assumed to be event tags. | ||
|
||
```json | ||
{ | ||
"name": "<title of post>", | ||
"description": "<string should match the attribute content tag>", | ||
"image": "<optional usually the first m image tag>", | ||
"animation_url": "<optional use this for multi-media such as MP4, MP3, WAV, WEBM, etc... should be included in imeta tags as well>", | ||
"external_url": "<optional should be included in attribute r tags>", | ||
"attributes": [ | ||
{ | ||
"trait_type": "id", | ||
"value": "<32-bytes lowercase hex-encoded sha256 of the serialized attribute data>" | ||
}, | ||
{ | ||
"trait_type": "pubkey", | ||
"value": "<32-bytes lowercase hex-encoded public key of the public creator>" | ||
}, | ||
{ | ||
"trait_type": "created_at", | ||
"value": <unix timestamp in seconds> | ||
}, | ||
{ | ||
"trait_type": "kind", | ||
"value": <integer between 0 and 65535> | ||
}, | ||
{ | ||
"trait_type": "sig", | ||
"value": "<64-bytes lowercase hex of the signature of the sha256 hash of the serialized attribute data, which is the same as the id field>" | ||
}, | ||
{ | ||
"trait_type": "content", | ||
"value": "<this key should match the description even if empty string>" | ||
}, | ||
{ | ||
"trait_type": "imeta", | ||
"value": "<optional imeta tags>" | ||
}, | ||
{ | ||
"trait_type": "e", | ||
"value": "<optional ID of referenced event>" | ||
}, | ||
{ | ||
"trait_type": "r", | ||
"value": "<optional reference to external URL>" | ||
}, | ||
...<other_optional_attributes>, | ||
] | ||
} | ||
``` | ||
|
||
### Generating Keys | ||
|
||
**Private key:** Any Ethereum private key or mnemonic phrase can be used, as long as the result is a 32-byte hex string. A key can be generated locally as well with a command like `openssl rand -hex 32`. This key is used to sign the post and is stored in the pubkey field. | ||
|
||
**Public key:** Public keys are based on Taproot + Schnorr, bitcoin BIP-0341. It's recommended to use a tool like nostr-tools to generate a public key from a private key. | ||
|
||
### Sign a post | ||
|
||
Signatures are based on schnorr signatures standard for the curve secp256k1. To sign with Schnorr signatures on secp256k1, you need a private key, a message, and a random nonce (k). You then calculate a public nonce (R), a challenge (e), and finally, the signature (s) by combining these values. It's best to use a tool like nostr-tools or nostril or schnorr.c in the bitcoin core library. | ||
|
||
### The PubEvent | ||
|
||
### When a new post is created | ||
|
||
```solidity | ||
event PubEvent( | ||
bytes32 id, | ||
bytes32 indexed pubkey, | ||
uint256 created_at, | ||
uint32 indexed kind, | ||
string content, | ||
string tags, | ||
string sig, | ||
); | ||
``` | ||
|
||
- `id`: the unique identifier of the post. | ||
- `pubkey`: the public key of the post creator. | ||
- `created_at`: the timestamp of creation. | ||
- `kind`: the event kind; 1, for an original post. | ||
- `content`: the textual content of the post. | ||
- `tags`: the structured metadata. | ||
- `sig`: the signature of the post data. Should be 64 bytes. | ||
|
||
### To reply to a post | ||
|
||
```solidity | ||
event PubEvent( | ||
bytes32 id, | ||
bytes32 indexed pubkey, | ||
uint256 created_at, | ||
uint32 indexed kind, | ||
string content, | ||
string tags, | ||
string sig | ||
); | ||
``` | ||
|
||
- `id`: The unique identifier of the post. | ||
- `pubkey`: The public key of the post creator. | ||
- `created_at`: The timestamp of creation. | ||
- `kind`: Also 1 for replies. | ||
- `content`: The textual content of the post. | ||
- `tags`: The structured metadata including outlined below. | ||
- `sig`: The signature of the post data. Should be 64 bytes. | ||
|
||
### The reply "e" tag | ||
|
||
`["e", <event-id>, <relay-url>, <marker>, <pubkey>]` | ||
|
||
**Where:** | ||
|
||
`<event-id>` is the id of the event being referenced. | ||
`<relay-url>` optionally is the URL of a recommended off-chain relayer. Use empty string,"", if none or on the blockchain only. | ||
`<marker>` is optional and if present is one of "reply", "root", or "mention". | ||
`<pubkey>` is optional, SHOULD be the pubkey of the author of the referenced event | ||
|
||
Those marked with "reply" denote the id of the reply event being responded to. Those marked with "root" can denote the root id of the reply thread being responded to. Those marked with "mention" denote a quoted or reposted event id. | ||
|
||
`<pubkey>` SHOULD be the pubkey of the author of the e tagged event, this is used in the outbox model to search for that event from the author's write relays where relay hints did not resolve the event. | ||
|
||
### The "p" tag | ||
|
||
`["p", <pubkey>, ...]` | ||
Used in a text event contains a list of pubkeys used to record who is involved in a reply thread. | ||
|
||
When replying to a text event E the reply event's "p" tags can contain all of E's "p" tags as well as the "pubkey" of the event being replied to. | ||
|
||
Example: Given a text event authored by a1 with "p" tags [p1, p2, p3] then the "p" tags of the reply can be [a1, p1, p2, p3] in no particular order. | ||
|
||
### Reposts | ||
|
||
A repost is a kind 6 event that is used to signal to followers that a kind 1 text post is worth reading. | ||
|
||
The content of a repost event is the stringified JSON of the reposted post. It MAY also be empty, but that is not recommended. | ||
|
||
The repost event MUST include an e tag with the id of the post that is being reposted. That tag should include a blockchain tag or indexer relay where the post can be fetched. | ||
|
||
The repost SHOULD include a p tag with the pubkey of the event being reposted. | ||
|
||
### Quote Reposts | ||
|
||
Quote reposts are kind 1 events with an embedded q tag of the post being quote reposted. The q tag ensures quote reposts are not pulled and included as replies in threads. It also allows you to easily pull and count all of the quotes for a post. | ||
|
||
q tags should follow the same conventions as e tags, with the exception of the mark argument. | ||
|
||
`["q", <event-id>, <relay-url>, <pubkey>, <blockchain-name-or-id>]` | ||
|
||
## Rationale | ||
|
||
These attributes in the metadata are a 1:1 mapping of a Nostr-style event. Nostr is a blockchain compatible social media protocol because it uses a public/private key verification system that does not rely on a central set of smart contracts. It relies on the same format of private key EVM chains already use. It has a json based event system that is easily mapped to NFTs. Content can be freely moved between web3 and web2 based platforms. Each post is signed by the author, enabling tamperproof third-party transportation and publishing. A standardized tagging system enables referencing posts on other blockchains, other contracts, or external URLs. | ||
|
||
## Reference Implementation | ||
|
||
Only the metadata format and PubEvent is required. | ||
|
||
```solidity | ||
function createPost( | ||
uint256 tokenId, | ||
string uri, | ||
bytes32 id, | ||
bytes32 pubkey, | ||
uint256 created_at, | ||
uint32 kind, | ||
string content, | ||
string tags, | ||
string sig | ||
) public { | ||
|
||
mint(tokenId, uri); | ||
emit PubEvent(id, pubkey, created_at, kind, content, tags, sig); | ||
} | ||
|
||
``` | ||
|
||
In this example `PubEvent` is **required** to announce a publication event has occurred. This event is flexible and can be used for all event types and kinds. | ||
|
||
### Token Standards Compatibility | ||
|
||
- **[ERC-721](../EIPS/eip-721.md):** Each post should be a unique (`tokenId`). | ||
- **[ERC-1155](../EIPS/eip-1155.md):** A `tokenId` should only represent one post event even if minted multiple times. | ||
|
||
#### Backwards Compatibility | ||
|
||
This is an additive standard on top of [ERC-721](../EIPS/eip-721.md) and [ERC-1155](../EIPS/eip-1155.md). Existing NFTs remain compatible; clients or platforms that understand this standard can interpret these tokens as social posts as well as traditional NFTs. | ||
|
||
## Security Considerations | ||
|
||
**Data Integrity:** | ||
Ensure that id is consistently [derived, as described above](#to-derive-the-id), to prevent forgeries. The owner of an NFT is inconsequential to the authenticity of the post, if the post is properly signed. | ||
|
||
### Spam and Moderation | ||
|
||
Event driven social media and NFTs both allow permissionless creation of content. Platforms built on this standard should implement their own moderation layers, blocklists, or reputation systems. | ||
|
||
## Copyright | ||
|
||
Copyright and related rights waived via [CC0](../LICENSE.md). |
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.