Proton: A SocialFi Dapp totally base on Actor model

Howdy Y’all! I wanted to give y’all a friendly intro to the Constellation Book “development dapp”.

We went ahead and built ourselves a socialfi dapp, like Twitter but with modular data sovereignty based on the Actor model. She’s got tutorials and everything to help get y’all up to speed.

Now here’s some more details about this here dapp in case you’re interested:

We’re cultivating a community of dapp developers learnin’ and buildin’ together. The dapp lets you share posts, follow friends, like and comment on their posts too. Real social-like.

But we got data ownership - your content is yours. We divide it up logical-like into “canisters” so you control your piece. And it’s all decentralized on the IC blockchain. No big bosses.

We’re currently developing the base code and tutorials, aiming to launch mid-January 2024, so stay tuned!

We’re hoping to grow a community of dapp devs like you who want to learn and create the future of the web. So mosey on in and join us!

Let me know if y’all have any other questions! Happy to jaw with ya.


👇The following reveals details about this dapp. 👇


Proton - A SocialFi Dapp

Slow is fast.
Decentralization.

Now we are designing a decentralized social media called Proton based on the Actor model. First we need to understand the basic principles of the Actor model. The Actor model is a concurrent computing model that achieves parallel and distributed computing through message passing and asynchronous processing.

So when designing a Dapp, each Canister should be responsible for a different functional module. For example, some Canisters are responsible for recording user profiles, some for storing posts. Also, pay attention to the scalability of the Canisters. We can dynamically create Canisters of the same type to handle high loads.


Design Philosophy

We want to build a truly open Web3 dapp that is modular and data sovereign based on the Actor model.

We hope that each user has their own independent space (Feed Canister) that they fully control. Users can even directly deploy their own independent Feed Canister with code to interact with Proton. (This is cumbersome, only suitable for programmer users, who can develop advanced custom features for the container.) This allows the community to create custom advanced features.


Idea

First there is a scalable public area that receives posts from all users. There is also a user area that records user registration, personal profiles, and following relationships.

We create a Feed for each user to store their own information flow. The Feed is also the user’s private space. Users can store posts in their own Canister (Feed). Except for themselves, no one else can delete it.

The interaction between users and the public area is automatically handled by the Feed Canister. Users only need to query their own Feed to access the latest information stream they are following. Posting, commenting, and liking interactions are also automatically completed by Feed after the initial action.


Users can also add some advanced custom features by deploying their own independent Feed to interact with the public area. For example, only send posts point-to-point to some Feeds, establishing private small social circles; or only connect AI for automatic posting, etc. Any function can be implemented. The community can develop secondary and freely expand various functions. For example, adding a point-to-point private messaging feature.


This is a completely open, slightly slower decentralized application. This design sacrifices some speed for decentralization, just like Bitcoin.

The advantage is that the user’s front end only needs to query their own Feed to get posts from people they follow. It’s convenient and fast. Everything in the background is completed by decentralized collaboration between Canisters, completely decoupled. If a few Canisters go down, it does not affect the continued operation of this system. (If Fetch goes down, you can create a few more)

If the system cannot be recovered temporarily, the Feed can send posts in batches directly to followers using ignore call. That is, two post delivery processes are built into the Feed: posting through the Fetch relay station, and point-to-point posting.


Message Transmission Process

When a user posts, the Feed first stores the post in its own information flow, and then sends the post point-to-point to the follower’s Feeds and the public area according to the followers list. But what if there are 10,000 followers? The situation is not so good, because message sending and receiving between Canisters is limited by max input/output queue size, and cannot send so many at once. It would take the Feed a long time to send them all in batches.

To increase throughput, we add a message relay station: Fetch. The Feed first sends the post to the public area, then sends the post ID and followers to Fetch. Fetch records it, and then notifies these followers’ Feeds according to the algorithm to fetch which posts, and finally the Feed fetches the posts from the public area.

This way, even with many followers, they can fetch posts from the public area in turn under the coordination of Fetch.


Automatic Pressure Adjustment

After Fetch accumulates a certain number of messages, it adjusts the notification order and interval through algorithms (which Feed to notify first and how many milliseconds to wait before notifying other Feeds after each notification), to ensure the query pressure on the public area cannot be too high. When the Feed receives the notification, if the fetch fails, it should wait 20 seconds before trying again.


If the public area faces too much query pressure, it will tell the Root Fetch: “Slow down notifying Feeds”, and the Root Fetch will notify the Fetches under it to reduce notification frequency. If the pressure remains high after 10 minutes, the Root Post can also create a new Bucket to send new posts to.

Also, if the number of users increases, Fetch itself can also be added as needed.


Because every time a Feed sends a message, Fetch has to notify many other Feeds. So in this open environment, it is easy to cause Dos attacks. Therefore, when the Feed sends information to Fetch, it needs to pay a Gas fee. Only after receiving the Gas fee will Fetch put the information into the “pending notification” list.

The Gas fees for posting, liking, and commenting are 100000000, 10000000, and 1000000 respectively.

However, these are plans for the future. For now, let’s design the overall framework first and continue to optimize the details later.

Okay, after saying so much, let me explain this architecture in detail now.


Architecture

The architecture is based on a distributed peer-to-peer Actor model with a push-pull design. Overall, you can divide Proton into four modules: User, Feed, Post, and Fetch.

  • User: The user area, responsible for recording user information and relationships. It stores users’ profiles and follow relationships.

  • Post: The public area, storing all publicly posted content. The Root Post can create many Buckets to store posts.

  • Feed: The information feed, storing personal feeds for each user. The Root Feed creates a Feed for every user.

  • Fetch: The transit station, responsible for pushing the latest feed to each user. It records the unpulled posts, comments, and likes in a user’s Feed.

Users can follow/unfollow others, view the latest public posts (from everyone), see their feed (their own and followed users’ posts), create posts, repost, comment, like, favorite/unfavorite.

User

The user area records user information and relationships, like profiles and follow relationships.

The User canister stores basic user info - UserId, name, company, school, occupation, bio, follow relationships, their Feed canister ID, etc.

Users can call functions here to follow someone, update their profile, or check who they follow and someone else’s relationships.

When a user follows someone new or gets a new follower, their Feed is notified to update the list.

Post

The public area stores all publicly posted content. The Root Post can create many Bucket canisters to store posts.

Root Post

The public area stores all public posts. The Root Post can create many Bucket canisters to store posts.

Root Post can create Buckets, check available Buckets, list all Buckets, and list full Buckets.

Root Post starts by creating 5 Buckets. When one fills up, it creates a new one, keeping 5 available.

When the frontend first loads, the Feed immediately queries Root Post for an available Bucket. Root Post randomly returns one. The Feed stores the “available Bucket” ID, updating it when queried.

  • Call Bucket to query the latest 5 posts to show latest public posts.
  • When a user posts, call Bucket to store it.

When a user’s Feed gets a bunch of post IDs from Fetch, it can then provide those post IDs to the Bucket to query for the actual posts.

Bucket

Buckets can store and query posts.

There are 3 query functions - total posts, get specific posts by ID (up to 7 at once), and latest n posts.

When querying specific or latest posts, it returns post details and current likes/comments.

Buckets receive new posts, comments, and likes. It checks for duplicate IDs before accepting a post.

It notifies Comment Fetch and Like Fetch about post IDs with new comments/likes.

Feed

The information feed stores personal feeds for each user. The Root Feed creates a Feed for every user.

Root Feed

Root Feed is responsible for creating a personal canister for each user and tracking the total canisters created and their IDs.

Feed

Users interact with Proton through their Feed - viewing, posting, commenting, liking, etc.


A user’s Feed stores their followers (for pushing posts, comments, likes), following (for receiving posts), feed (last 3000 posts only), and saved posts (up to 500 posts).


Each post has a timestamp, poster’s UserId, PostId, and RepostId (empty if not a repost).


The PostId is the Bucket canister ID + UserId + increment. This allows direct post ID creation without communicating with the Bucket.

For example: aaaaa-aaa-bbbbb-bbb-1, aaaaa-aaa-bbbbb-bbb-2, aaaaa-aaa-bbbbb-bbb-3…


Querying posts:

There are 3 functions to query Feed posts - total posts, get post by ID, and latest n posts.


Posting:

When user A posts, the frontend sends the post to their Feed.

The Feed stores the new post.

Then it sends the post to the public Bucket and notifies Fetch with the poster, post ID, and followers C & D.

Fetch records this and individually notifies C & D’s Feeds to pull the post by ID. After notifying, Fetch deletes the records.

When Feed gets the post IDs to pull, it adds the corresponding posts from the public Bucket into the feed stream. (Here, User C pulls posts 1, 6, 7, 15)

When Users C & D query their Feed, they see Poster A’s new post right away.

If User E later follows A, their Feed only receives A’s new posts going forward.

The frontend only sends one request. Further pushes (like notifying the public Bucket) are handled by the canisters.

With more users, one Fetch may get overwhelmed with posts. More Fetches can be added horizontally as needed.


Deleting posts:

Posts cannot be deleted. The blockchain is immutable.

Older posts past the last 3000 are dropped from feeds anyway. (And Feeds are personal spaces).


Reposting:

When User C reposts Post 15 to followers H, I, J, K: Reposter: C, Post ID: post15_id, Followers: H, I, J, K is sent to Fetch.

Fetch records and notifies H, I, J, K’s Feeds, which pull Post 15 by ID.

When User C reposts, the original poster A remains the same. Only the reposter is User C.


Commenting:

Similar to posting, handled by Comment Fetch.

Anyone (User X) can comment. The public Bucket is notified of the comment with the post ID.

Comment Fetch gets the post creator’s followers from User. It rejects if the creator isn’t found.

It adds the post ID, creator, and followers to its “to notify” queue.

After Comment Fetch notifies followers, they query the post’s comments from the public Bucket and update their Feeds.

If a follower D reposted the post, their Feed further notifies Comment Fetch when receiving a new comment.

Comments cannot be deleted.


Liking:

Similar to posting, handled by Like Fetch.

Anyone (User X) can like a post. The Bucket notifies Like Fetch of the new like with the post ID.

Like Fetch gets the post creator’s followers from User. It rejects if the creator isn’t found.

It adds the post ID, creator, and followers to its “to notify” queue.

After Like Fetch notifies followers, they update the post’s like count in their Feeds.

If a follower D reposted the post, their Feed further notifies Like Fetch about new likes.

Likes cannot be deleted.


Fetch

Receives all posting, commenting, and liking notifications and forwards them to relevant Feeds.

Root Fetch

Root Fetch dynamically creates multiple Fetch canisters - Post Fetch, Like Fetch, Comment Fetch. It can also list available Fetches.

Post Fetch

Receives: Post ID, poster, reposter, followers, cycles.

Maintains a table of posts to notify each user about.

Uses an algorithm with ignore calls to notify followers’ Feeds in batches.

Comment Fetch

Receives new comment notifications from Buckets: Post ID, poster (A), reposter (empty).

Gets the poster’s followers from User.

Maintains a table of posts to notify about.

Uses an algorithm with ignore calls to notify followers’ Feeds in batches.

If a follower C reposted the post, their Feed further notifies Comment Fetch when receiving a new comment.

Like Fetch

Similar to Comment Fetch, for likes instead of comments.



The core User, Post, Fetch, and Feed modules make up Proton’s architecture.

Beyond this, Feeds can communicate directly for more features…


Building

This is a complex, large-scale application.

In this tutorial, we’ll build the core functionality of this Dapp.


First, We …


16 Likes

Nice project! I would love to learn more and collaborate in the near future.

2 Likes

Hi NeutronStarPRO, it’s nice to see more projects like this to pop up in the IC ecosystem.

After reading above, I do have couple of questions to ask:

How to determine which Fetch should a Feed go if there are multiple Fetches?

Does this :point_up_2: mean I will lose all my post published before the 3000th from the end?

3 Likes

This is… interesting. Do you think people will want to use a social app where they can’t delete posts? Can they be edited? Can they be archived?

3 Likes

Root Fetch have get_fetch(), so we can query Root Fetch to know all Fetch.

No, the ID of the post I made can be retained in the feed, and then queried in the bucket.

In the future, we (or the community) can also improve the feed by directly storing the posts we make.

2 Likes

Currently, considering the private nature of the feed canister, it is challenging to delete already posted messages.

This difficulty arises because the posts are stored dispersedly in various feeds, and since feeds are controlled by others, the owner of the feed can refuse deletion requests.

Additionally, for posts in public area, deletion is, of course, possible. After future DAO control, there may be the option for collective voting to remove certain posts.

2 Likes

Very cool, as an IC dev I really appreciate the technical overview. I haven’t seen many projects really embrace the actor model paradigm

Question
I have this concern with all Web3 social medias that get created: what’s your strategy for network effects?
As we have seen with Twitter, it’s not the best tech that wins, but where everyone is. I see ActivityPub and Nostr have an open protocol model which may work, assuming they get adopted.
What is your project ling term strategy? Open protocol? Interop with other protocols? Or something else?

5 Likes

Now this is PodRacing! I love to see well-thought-out, open architecture like this. I see a lot of cross-over here with what @infu and I were proposing with Neutron: Neutron: Modular Rolling-Governed Applications System

I’d be happy to cross-pollinate ideas. There is some base-level infrastructure missing form our IC stack still and it makes sense to build it all together and interoperable.

4 Likes

Thank you!
I appreciate your question and thoughtful consideration very much. It’s a great question.


Currently, our focus is on designing an integrated architecture for educational purposes, providing basic back-end functionality without a front-end demo. Both tutorials and code will be open source.

As we grow within the community, if there’s widespread interest and community approval, we’ll proceed with further development to evolve it into a full-fledged social networking application.

The appeal lies in the privacy of individual canisters and the freedom of customization. Acting early allows us to accumulate more content over time.


In essence, this is essentially a public posting space. The architecture is designed for anyone wanting to publish content on a decentralized network.

There’s no concept of “boundaries” here. It’s no longer an isolated data island; people can freely post and access content. Proton is somewhat like Bitcoin, with a singular name, merely a symbol and belief. Its existence transcends any individual app; it can represent all platforms on the decentralized internet.

If people want to publish content on a decentralized network, they can choose to do so in a public space like this, instead of posting on platforms such as “X”, “Reddit” or “Quora”.

There’s no entity control here; content is king, and individual sovereignty is paramount.


We’re doing it for the future!

4 Likes

I’m really looking forward to having time to read through this :pray:

1 Like

This is truly groundbreaking - a comprehensive overhaul for decentralized networks!
:partying_face:

1 Like

Quick scoop – Whispering to everyone, Proton is now in an available state :tada: , but it’s still in demo mode.

Anyway, feel free to start experiencing this social network!

Please don’t tell your friends, as we don’t have enough cycles. If you’re interested in Proton, you can join us in contributing to the open-source DApp!

2 Likes