ERC-7847: Social Media NFTs


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: ERC-7847: Social Media NFTs
status: Draft
type: Standards Track
category: ERC
created: 2024-12-18

Abstract

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.

We define a way to create and manage NFTs that represent social media posts. These NFTs can be original posts, replies, reposts, or quote posts. Each NFT encapsulates a single social media post as a Nostr-compatible object, including fields such as pubkey, content, kind, tags, and created_at. By providing a 1:1 mapping of nostr data structures, any NFT minted under this standard can be interpreted by Nostr-style clients, relays, or indexing services with minimal effort. Additionally, this standard defines how replies, reposts, and quotes should reference prior events, enabling rich social media interactions in a multi-chain, multi-contract, multi-platform environment.

Other kinds of common social network primitives including likes, follows and other interactions are easily represented by this standard using Nostr standard kinds and tags without changing this standard. This document will not be an exhaustive list of all possible events. Please refer to the Nostr protocol for more information on how to represent other possible kinds of events and further evolution.

This standard defines:

  1. Representation as ERC-721 orERC-1155 tokens.
  2. A single publishing mechanism: pubEvent.
  3. Interpretation of post type (original, reply, repost) via the presence of certain fields and metadata rather than separate functions or events. Any level of threading complexity is possible using only the reply tag.
  4. Media attachments and external references through flexible tags.
  5. NFT metadata aligned with common NFT practices to so existing NFT apps will display as much of the NFT as possible using the most common metadata structure.
  6. By implementing a separate public key and signature a third party may also publish a message on behalf of another user.

Posts may be “owned” by their creators. This may be a useful mechanic, but the independent signature of the post allows for a third party to publish a message on behalf of another user. This is useful for the convenience of third party services to publish or enshrine messages. Additionally editing a post may be limited to the original author. This is a design decision that should be made by the implementor.

Motivation

With the continued censorship and manipulation of social media platforms it becomes increasingly important for truly decentralized social media to exist. Unlike other attempts at blockchain decentralized social media, this method does not rely on a single set of smart contracts or even the blockchain itself. 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

Post Structure

Each post includes these fields emitted as an event and in the metadata. This metadata could be saved on-chain as well depending on the implementation:

  • id: A unique, deterministic identifier for the post.

  • author: The on-chain address of the post creator.

  • created_at: Timestamp of creation (e.g., block timestamp at minting).

  • kind: An integer representing the post category. This is expanded on below.

  • tags: Arrays of arrays of strings that describe attachments, references, and other metadata. There are more example tags below as well as all possible Nostr tags.

    • For example, ["e", "<id_of_referenced_post>"] indicates a reply.
    • Attach media:
    
      [
        "imeta",
        "url <URL>/i/my-image.jpg",
        "m image/jpeg",
        "blurhash eVF$^OI:${M{o#*0-nNFxakD-?xVM}WEWB%iNKxvR-oetmo#R-aen$",
        "dim 3024x4032",
        "alt A scenic photo overlooking the coast of Costa Rica",
        "fallback <URL>/alt1.jpg",
        "fallback <URL>/alt1.jpg"
      ]
    
    • ["r", "<URL>"] references an external URL.
    • ["ch", "<chain_id>"], ["c", "<contract_address>"] for cross-chain/contract references.
  • content: The textual content of the post.

Post Types (Examples)

  • Original Post: kind=1
  • Repost: kind=6 (for example)
  • Quote Post: Another distinct integer (e.g. 30023)
  • These are just example values. Different implementations may standardize on their own kind values or sets.

Kinds

Kinds specify how clients should interpret the meaning of each event and the other fields of each event (e.g. an “r” tag may have a meaning in an event of kind 1 and an entirely different meaning in an event of kind 10002). Each NIP may define the meaning of a set of kinds that weren’t defined elsewhere. This NIP defines two basic 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.
30023: Long form content: long-form text content, generally referred to as “articles” or “blog posts”. Should be written in Markdown syntax and may include off-chain or on-chain references to other media or events.

Ref: NIP-01 Basic Event Kinds for basic tags and kinds

Ref: Current Full List of Kinds

Tags

tags is 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. Some examples:

  • Multiple media tags can be attached by using multiple imeta tags ["imeta", ...]

    ["imeta", 
      "dim 1920x1080",
      "url <URL>/1080/12345.mp4",
      "x 3093509d1e0bc604ff60cb9286f4cd7c781553bc8991937befaacfdc28ec5cdc",
      "m video/mp4",
      "image <URL>/1080/12345.jpg",
      "image <URL>/1080/12345.jpg",
      "fallback <URL>/1080/12345.mp4",
      "fallback <URL>/1080/12345.mp4",
      "service nip96",
    ]
    
  • References to Other Posts:
    ["e", "<id_of_referenced_post>"]

  • External URLs:
    ["r", "<URL>"]

  • Cross-Chain/Contract References:
    ["ch", "<chain_id>"], ["c", "<contract_address>"]

Basic Tags

Current Full List of Tags

Generating a Key

Private key: A Nostr compatible key can be generated locally with a command like openssl rand -hex 32 or web3.utils.sha3(web3.utils.randomHex(32)). This key is used to sign the post and is stored in the pubkey field. An Ethereum private key or mnemonic phrase can also be used, as long as the result is a 32-byte hex string.

Public key: Public keys are based on Taproot + Schnorr, bitcoin BIP-0341. And can be generated with nostril compatible signing tools.

Alternatively bech32-(not-m) can be used to encode private and public keys so that the prefixes npub and nsec can be used to differentiate between the two. Ref: NIP-19

Token Standards Compatibility

  • ERC-721: Each post is a unique NFT (tokenId).
  • ERC-1155: A tokenId can represent one post event, potentially minted multiple times. The id ensures a unique reference to the event data.

Events

There are many possible types of events, but we will focus on posts as the would most likely be turned into an NFT as opposed to a like or a follow. A more complete list can be found here: List of event kinds

CreateEvent

When a new post is created

event PubEvent(
    uint256 tokenId,
    string uri,
    bytes32 indexed replyTo,
    bytes32 id,
    bytes32 indexed pubkey,
    uint256 created_at,
    uint32 indexed kind,
    string content,
    string tags,
    string sig,
);
  • tokenId: the NFT ID.
  • uri: the URI of the NFT metadata.
  • replyTo: the id of the post being replied to; should match e tag if present. If not a reply, this should be 0.
  • 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.

replyTo

The replyTo field is necessary for indexing on-chain. In this way clients can more easily fetch all replies without querying metadata on each NFT.

To derive the id

To obtain the id, we sha256 the serialized these attributes in this order. The serialization is done over the UTF-8 JSON-serialized string (which is described below) of the following structure:

[
  0,
  <pubkey, as a lowercase hex string>,
  <created_at, as a number>,
  <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

Ref: NIP-01

Sign a post

Signatures are based on schnorr signatures standard for the curve secp256k1.

To reply to a post

event PubEvent(
    uint256 tokenId,
    string uri,
    bytes32 indexed replyTo,
    bytes32 id,
    bytes32 indexed pubkey,
    uint256 created_at,
    uint32 indexed kind,
    string content,
    string replyTags,
    string sig
);
  • tokenId: This NFT ID.
  • uri: The URI of the NFT metadata.
  • replyTo: The id of the post being replied to; should match e tag.
  • 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.
  • replyTags: The structured metadata including outlined below.
  • sig: The signature of the post data. Should be 64 bytes.

Marked “e” tags

["e", <event-id>, <relay-url>, <marker>, <pubkey>]

Where:

<event-id> is the id of the event being referenced.
<relay-url> optionaly is the URL of a recommended off-chain relayer. Use “” if none or 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 authors write relays where relay hints did not resolve the event.

The “p” tag

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.

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 field includes the author and optionally the platform, formatted as "<author> via <platform> as <type>" where author should be a human readable name and platform should be a human readable platform name. If no platform is specified the format should be "<author> as <type>". Type may be “Social Post” or “Event” or any other type that is appropriate.

{
  "name": "<author> via <platform> as <type>",
  "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 publicized 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>,
  ]
}

References

ERC-721 Non-Fungible Token Standard
ERC-1155 Multi Token Standard
Nostr Kinds and Events
NIP-01 Basic Kinds
NIP-10 Replies and Mentions
NIP-18 Reposts

Rationale

Why Nostr Compatible? Nostr is a simple and flexible protocol for decentralized social media. By making NFTs Nostr-compatible, this ERC allows NFTs to become a fundamental building block for existing and future decentralized social platforms.

  • Interoperability: Content can be freely moved between Ethereum-based platforms and Nostr-based infrastructure.
  • Open permissionless Identity: Anyone can create a private key and start posting.
  • Decentralized and Self Authenticating Content: Each post is signed by the author, ensuring authenticity and enabling third-party publishing.
  • Cross-Chain and Cross-Platform Referencing: A standardized tagging system enables referencing posts on other blockchains, other contracts, or external URLs.
  • Flexible Content: Support for multimedia, external links, and arbitrary metadata, making make this protocol suitable for a wide range of social media applications and publishing applications.
  • Strong community: Nostr has a strong community of developers and users who are actively building and using the protocol.

Nostr’s minimal and extensible protocol is ideal for decentralized publishing. Integrating Nostr with NFTs ensures that users own their content while maintaining interoperability with Nostr clients. Supporting multimedia and external links allows richer content, making NFTs viable as building blocks for decentralized social networks, forums, classifieds, messaging, and multimedia platforms.

Reference Implementation

These examples are possible partial implementations. Only metadata and events are required to be implemented. The rest is up to the implementor.

function createPost(
  uint256 tokenId,
  string uri,
  bytes32 replyTo,
  bytes32 id,
  bytes32 pubkey,
  uint256 created_at,
  uint32 kind,
  string content,
  string tags,
  string sig
) public {

  mint(tokenId, uri);
  emit PubEvent(tokenId, uri, replyTo, id, pubkey, created_at, kind, content, tags, sig);
}

[Full example here](https://github.com/Ownerfy/erc-7847-ref-implementation/tree/dad885416b10f3ded69a140bcabaaf89e7d6a06c)

In both these examples pubEvent is required to announce a publication event has occurred. This event is flexible and can be used for all event types and kinds.

Backwards Compatibility

This is an additive standard on top of ERC-721 and ERC-1155. Existing NFTs remain compatible; clients or platforms that understand this standard can interpret these tokens as social posts.

Security Considerations

Data Integrity:
Ensure that id is consistently derived, e.g., from metadata hash, to prevent forgeries. The owner of an NFT is inconsequential to the authenticity of the post, if the post is properly signed.

Compromised Keys:
If a private key were to be compromised it would be possible to alert the community in a standard way that a key has been compromised. This standard is not defined here. One consideration for such a message is that a forward address should not be trusted as this message could have been posted by the attacker.

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.

Update Log

  • 2024-12-18: Pull request, commit link to commit
  • 2024-12-18: Updated nested properties to attributes for better display in NFT apps link to commit
  • 2024-12-20: Update: grammar error link to commit
  • 2024-12-20: Update: ERC number and title link to commit
  • 2024-12-23: Updated: example to include reply rather than non-standard edit, made media linking consistent, markdown and json formatting link to commit
  • 2025-01-05: Updated: Added replyTo, indexes, and updates for clarity link to commit
  • 2025-01-05: Updated: ERC Pull Request linting link to commit

Reference Implementation

ERC-7847 Example Social Media NFT Contract

2 Likes

“Posts may be “owned” by their creators. This may be a useful mechanic, but the independent signature of the post allows for a third party to publish a message on behalf of another user.”

This is a great feature and very forward-thinking.

Small typo: " this EIP allows NFTs to become a fundamental building block for decentralized many existing and future social platforms."

Cross-Chain and Cross-Platform Referencing: A standardized tagging system enables referencing posts on other blockchains, other contracts, or external URLs.

This is really great and something that I don’t think is really adopted in traditional social media. It always feels awkward copying, say, a linkedin post to twitter.

Overall, great idea and interesting read. I’d say the main bottleneck is obviously going to be minimizing on-chain storage, especially considering the raw scale of social media activity, although this proposal does already take that into account. The other point I was thinking about was the feasibility of requiring users to pay gas fees on a per-post basis, although that is perhaps out of the scope of this proposal.

1 Like

Good catch! Thanks I updated it.

Thanks for the feedback! Yeah gas fees are definitely something people are always going to have to consider. I think a system like this probably works best on an L2 where minting and NFT isn’t going to break the bank. On an L2 even storing text content on-chain is probably feasible. Of course if someone really want to pay for a premium post they could always write straight to Ethereum. I purposely left the decision to store data on or off-chain up to the implementor. If they store it on-chain they would likely be returning a data-url they generate in solidity. In case anyone is unfamiliar it looks like this:

function getTokenURI(uint256 tokenId) public returns (string memory){
    bytes memory dataURI = abi.encodePacked(
        '{',
            '"name": "Chain Battles #', tokenId.toString(), '",',
            '"description": "Battles on chain",',
            '"image": "', generateCharacter(tokenId), '"',
        '}'
    );
    return string(
        abi.encodePacked(
            "data:application/json;base64,",
            Base64.encode(dataURI)
        )
    );
}

-Credit Alchemy

But of course they could always just reference IPFS or Arweave. If you only save the url of the metadata on-chain and emit the full event it’s something like 100x cheaper since event log data is super cheap in comparison.

The other point I was thinking about was the feasibility of requiring users to pay gas fees on a per-post basis, although that is perhaps out of the scope of this proposal.

Yes, I believe most implementations of this system will be built using either account abstraction, custodial accounts, or publishing all posts with some designated publisher account(s).

Of course you could self publish too. If anyone is listening on-chain for these events they should be able to see the stream coming from anyone. Actually that makes me think maybe the event emitted should be more recognizable like CreateSocialEvent so that they would be easier to parse?

Another idea is it would be easy to make an open contract for anyone to publish on. I’ll do that for the reference implementation.