archive about
Clear 27°C — Kifisia, GR — #en #farcaster #ideas

Farcaster L2s

What could a Farcaster Layer 2 look like? How could we implement them? Is it a good idea?

Before you read

I often consider casts to be a special "Farcaster application": I could see an alternative universe where Farcaster launched without casts, and casts were an application leveraging Farcaster identity and social graph 1.

A Farcaster without protocol-level casts may sound crazy, but think of what Direct Casts are today: An application that leverages the Farcaster identity and social graph, without being part of the protocol.

This is not an FIP: I find the idea interesting, but it comes with a number of technical, architectural, business and social implications. Adopting this architecture could change what Farcaster is —I'm not even sure if I'd like a Farcaster that moved in this direction.

That said, I also think it’s a space we should explore, discuss and debate, so here’s my contribution.


Every technical design has its limitations, and so does Farcaster. One of them is the speed that messages propagate throughout the network. An other one is storage limits. And of course, there are a number of widely accepted norms, for example, we expect a cast to be readable text.

An other problem is disk space required for hubs: We know that if Farcaster grew 100x or 1000x, storage requirements for hubs would probably grow to a point that hosting a hub would require very expensive hardware, inevitably leading to centralization.


Bundles are Farcaster messages that contain references to messages stored externally (not in hubs).

A Bundle message can be added with a BundleAdd message and removed3 with a tombstone BundleRemove message.

A new UnbundleMessages RPC endpoint allows a client to submit the bundle data to a hub and get the messages back. The hub will validate each message included in the bundle and return only the messages that are valid.

Only Cast add/remove and Reaction add/remove messages can be part of a bundle.

message MessageBundle {
  // Only CastAdd, CastRemove, ReactionAdd and RecationRemove 
  // messages are allowed in messages.
  repeated Message messages = 1; 
  bytes data_hash = 2;  

message BundleAdd {
  string url = 1;
  bytes data_hash = 2;     // Hash of the data stored at url
  bytes parent_bundle = 3; // Message.hash value of the previous bundle

message BundleRemove {
  bytes target_hash = 1;  // Message.hash value of the bundle being removed

rpc UnbundleMessages(MessageBundle) returns (MessagesResponse);


The high-level idea of this proposal is (is some ways) similar to an Ethereum Validium L2: Messages are bundled and they are stored externally, and hubs store references to these bundles, instead of the messages themselves. In this context, hubs are "Farcaster L1", and the bundles submitted by a specific FID form a "Farcaster L2".

Hubs remain the authority for identity (FIDs), social graph (Links), user profiles and signers.

A bundler bundles multiple messages in a bundle, stores them and posts BundleAdd messages to hubs. A bundler can be centralized (a typical web2 application) or decentralized (for example, a group of hubs running a modified version of Hubble). If bundles are used to create an L2, then the bundler’s FID defines the L2.

This is the typical flow:


Cross-layer architecture

In interesting property of this architecture is that messages can be moved between layers:

A message posted on a L2 is a valid message that can be posted at any time to a hub (L1), or copied to any other L2. And a message that was posted to a hub (L1) can be copied to an L2 -even after it’s been purged.

Why would an app use this and not build something from scratch?

The design has a high level compatibility with existing tools and patterns used by developers. For example, shuttle 2 could be extended to support Bundles (developers could also configure their shuttle instance to accept only specific bundlers, using their FID) and make bundles messages available to apps that use it. Similarly, services like Neynar could support bundles from specific FIDs and make their messages available to devs using the existing APIs.

The design also takes advantage of the Farcaster identity infrastructure, social graph, and everything that comes with Farcaster.

Depending on the implementation (this is the case with any “layer 2”), users can have strong assurances that once a message is bundled and submitted, it will be available to others. In any case, users could “move” their messages to L1 if they like.

Farcaster scalability

Farcaster could decrease storage unit limits to push users to use bundles (L2s).

An extreme scenario that would force most users to use a L2 (but would also make it even cheaper for them to use Facraster):

Do we need an FIP for this?

The truth is someone could implement most of what’s described here by using CastAdd and setting embed.url to the location where messages are stored, in some custom format.

However, this would be a hack. The protocol gets no benefit, its messy, and the lack of standardisation limits interoperability between tools, layers and services.

Possible use-case examples

The default use case is an app like Warpcast has it's own L2, and posts a bundle every few seconds. All users of the app see the messages instantly, and everyone else who tracks the specific L2 sees them with a small delay of a few seconds.

Other, possible use cases:


An assorted list of concerns and points that need further discussion:

  1. Of course, from a project development pov, it was a wise decision to have casts from day one. 

  2. shuttle package 

  3. Update, 2024-06-30. The more I think about this, there should be no option to remove bundles. If you want to remove *Add messages in a bundle, post *Remove messages in a future bundle.