OpenSocial Protocol Reimagined on ICP - Platform-Agnostic Social Infrastructure with CanDB
Building a scalable, platform-agnostic social backbone on the Internet Computer using OpenSocial principles, CanDB horizontal partitioning, and TypeScript wrappers for universal accessibility

The Platform Problem
You've lived this nightmare: You build an audience on Twitter, YouTube, or Instagram. Thousands of followers. Years of content. Then the platform changes the rules, bans your account, or goes bankrupt. Everything you built? Gone.
Your data lived in their database. Your followers were their users. Your links pointed to their servers. You were renting, not owning.
This isn't just about social media giants. Even in Web3, most dApps trap you in the same pattern - each app has its own identity system, its own user data, its own walled garden. Want to use three different decentralized social apps? That's three profiles to maintain, three separate identities, three disconnected social graphs.
There's a better way.
The OpenSocial Vision
OpenSocial is a philosophy that's been evolving since the early 2000s, but has found new relevance in the age of blockchain and Web3. As Dan Abramov explains, "open social does for data what open source did for code" - it decentralizes control away from corporate platforms while maintaining the aggregation features users expect.
The core insight is beautifully simple: What if your social data lived in a repository you actually own? Not a database controlled by a company, but a personal data store under your control that any app can read from and write to (with your permission).
The AT Protocol implements this through:
- Domain-based identities -
@alice.cominstead of@alicelocked to one platform - Personal data repositories - Signed JSON served over HTTP that you control
- Portable records - Links between your content work across different apps
- True ownership - You can "change hosting providers because it's easy to walk away" without losing traffic or breaking links
Meanwhile, the broader OpenSocial Protocol has been pushing similar ideas with a modular, multi-chain architecture that lets users own their social graphs and interaction data.
Social as a Layer, Not Media
The OpenSocial Protocol documentation introduces a crucial distinction: "social as a layer and not media."
This isn't about building another Twitter clone or Instagram competitor. It's about embedding social functionality - profiles, relationships, reputation, activity feeds - as infrastructure that any application can leverage.
Think WeChat: It's not just a messaging app. It's a platform where you can message friends, order food, pay bills, book appointments, play games, and more - all with one identity and one social graph. The social layer unifies everything.
OpenSocial achieves this through:
- Modular, lego-like design enabling developers to compose experiences quickly
- Horizontal enablement across multiple blockchains (EVM and SVM chains)
- Social Actions and Modules as composable interaction primitives
- Tribe mechanisms for community structures that span applications
- Megaphone features for content amplification across the ecosystem
- Plugin architecture for extensibility
The key realization: Social apps should be content management systems, not data gatekeepers.
Why the Internet Computer Changes Everything
These concepts are powerful, but previous implementations had limitations:
- Ethereum: Prohibitive gas fees for social interactions
- IPFS: Complex to query, consistency issues
- Traditional cloud: Not actually decentralized
The Internet Computer Protocol (ICP) solves these problems:
1. Reverse Gas Model
Users don't pay transaction fees. Developers fund their canisters (smart contracts) with cycles. This means social interactions are free for users - no wallet signatures for every like or follow.
2. Internet Identity
Built-in decentralized authentication using WebAuthn. One identity across all canisters. No usernames, no passwords, no seed phrases to manage for basic usage.
3. HTTP Queries
Canisters can serve web content and APIs directly. No need for centralized gateways or complex infrastructure.
4. Scalability
Canisters can call other canisters, store gigabytes of data, and scale horizontally. Perfect for social applications.
5. Persistence
Orthogonal persistence means your data stays even if the app developer disappears. The blockchain maintains state.
The DappJak Labs Social Backbone
Here's where it gets interesting. DappJak Labs isn't building just one social app - we're building an ecosystem of social dApps that all share the same underlying identity and data infrastructure.
The Architecture
┌─────────────────────────────────────────────────────────────────┐
│ DappJak Social Backbone │
│ (Universal Profile Canister) │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Identity │ │ Social Graph│ │ Content Store│ │
│ │ │ │ │ │ │ │
│ │ • II Auth │ │ • Followers │ │ • Posts │ │
│ │ • Profile │ │ • Following │ │ • Comments │ │
│ │ • Bio │ │ • Friends │ │ • Media │ │
│ │ • Metadata │ │ • Connections│ │ • Activity │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Reputation │ │ Privacy │ │
│ │ │ │ │ │
│ │ • Karma │ │ • Permissions│ │
│ │ • Trust Score│ │ • Visibility │ │
│ │ • Badges │ │ • Data Access│ │
│ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
│ Shared API Layer
│
┌─────────────────────┼─────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Seachan │ │ Honk FM │ │ Atlas Network │
│ │ │ │ │ │
│ Decentralized │ │ Decentralized │ │ Knowledge │
│ Imageboards │ │ Radio Station │ │ Graph Network │
│ │ │ │ │ │
│ • Anonymous │ │ • DJ Profiles │ │ • Expertise │
│ posting │ │ • Playlists │ │ tracking │
│ • Boards │ │ • Shows │ │ • Wiki edits │
│ • Threads │ │ • Chat rooms │ │ • Citations │
└───────────────┘ └───────────────┘ └───────────────┘
│ │ │
│ │ │
└─────────────────────┼─────────────────────┘
│
▼
┌───────────────┐
│ isOS │
│ │
│ Decentralized │
│ Web OS │
│ │
│ Identity Hub │
│ & Dashboard │
└───────────────┘
How It Works
One Identity, Many Worlds:
When you authenticate with Internet Identity, you get access to the entire DappJak ecosystem with one login. Your identity is linked to a Universal Profile stored in the Social Backbone canister.
// Universal Profile Structure
type UniversalProfile = {
principal: Principal; // Internet Identity
displayName: ?Text; // User's chosen name
bio: ?Text; // Bio across all apps
avatar: ?Text; // Profile picture (IPFS hash)
created: Time.Time; // Account creation
// Social connections
followers: [Principal]; // Who follows you
following: [Principal]; // Who you follow
// Cross-app reputation
karma: Nat; // Aggregated across all apps
badges: [Badge]; // Achievements from any dApp
// Privacy controls
visibility: VisibilitySettings;
permissions: [AppPermission]; // Granular per-app access
};
Data Flow Between dApps
Here's what happens when you use multiple DappJak apps:
SCENARIO: Alice posts on Seachan, then joins Honk FM
Step 1: Alice posts on Seachan
┌──────────┐
│ Alice │ Authenticates with Internet Identity
└────┬─────┘
│
▼
┌─────────────────┐
│ Social Backbone │ Creates/loads Universal Profile
└────┬────────────┘
│
▼
┌──────────┐ Post content + metadata
│ Seachan │◄─────────────────────────────
└────┬─────┘
│
│ Stores reference to Social Backbone profile
│ Updates karma in Universal Profile
│
▼
┌─────────────────┐
│ Social Backbone │ Profile updated with:
└─────────────────┘ • +10 karma
• "Seachan Poster" badge
• Activity timestamp
Step 2: Alice joins Honk FM (same day)
┌──────────┐
│ Alice │ Same Internet Identity
└────┬─────┘
│
▼
┌─────────────────┐
│ Social Backbone │ Loads existing profile
└────┬────────────┘
│
│ Alice's profile ALREADY EXISTS with:
│ • Display name
│ • Bio
│ • Avatar
│ • 10 karma from Seachan
│ • Existing followers
│
▼
┌──────────┐
│ Honk FM │ Reads profile, no re-setup needed!
└──────────┘ Alice can immediately:
• Start broadcasting (DJ profile ready)
• Followers see her new show
• Her reputation carries over
Step 3: Bob discovers Alice
┌──────────┐
│ Bob │ Browses Honk FM
└────┬─────┘
│
│ Sees Alice's show
│ Clicks her profile
│
▼
┌──────────┐
│ Honk FM │ ───────► Queries Social Backbone
└──────────┘ │
▼
┌─────────────────┐
│ Social Backbone │
└─────────────────┘
Returns Alice's:
• Bio
• Karma (now 25)
• Badges (Seachan + Honk)
• Activity across ALL apps
│
▼
┌──────────┐
│ Bob │ Sees: "Alice has 25 karma"
└──────────┘ "Active on Seachan and Honk FM"
"Earned 'Seachan Poster' badge"
Bob can FOLLOW Alice once, and:
• See her Seachan posts
• Get notified of Honk FM shows
• See her Atlas Network contributions
• All from one follow relationship!
Detailed Cross-App Integration
Let's see how each dApp leverages the Social Backbone:
Seachan (Decentralized Imageboards)
Problem: Anonymous posting often leads to abuse and low-quality content.
Solution with Social Backbone:
// When posting on Seachan
public shared(msg) func createPost(
boardId: Text,
content: Text,
isAnonymous: Bool
) : async Result<PostId, Error> {
let caller = msg.caller;
// Check user's reputation from Social Backbone
let profile = await SocialBackbone.getProfile(caller);
if (profile.karma < 10 and not isAnonymous) {
// Low karma users post with "new user" badge
// Prevents spam while allowing new users
};
// Create post, update karma if quality content
let postId = await createPostInternal(content);
// Update Social Backbone
await SocialBackbone.updateKarma(caller, +5);
await SocialBackbone.recordActivity(caller, #SeachanPost(postId));
// Other apps can now see this activity!
};
Benefits:
- Anonymous posting with optional identity
- Karma from other apps affects permissions
- Followers from other apps get notified
- Reputation travels with you
Honk FM (Decentralized Radio)
Problem: New DJs have no audience, listeners don't know who to trust.
Solution with Social Backbone:
// Creating a DJ profile on Honk FM
public shared(msg) func createDJProfile(
djName: Text,
genre: [Text]
) : async Result<DJProfile, Error> {
let caller = msg.caller;
// Load existing profile from Social Backbone
let universalProfile = await SocialBackbone.getProfile(caller);
// DJ profile inherits trust
let djProfile = {
id = caller;
name = djName;
genre = genre;
// Reputation from across DappJak ecosystem!
karma = universalProfile.karma;
badges = universalProfile.badges;
// Followers carry over!
followers = universalProfile.followers;
// Bio and avatar reused
bio = universalProfile.bio;
avatar = universalProfile.avatar;
};
// Notify ALL followers across ALL apps
await SocialBackbone.notifyFollowers(
caller,
#NewActivity("Started DJing on Honk FM!")
);
};
Benefits:
- Instant credibility for established users
- Existing followers become your audience
- Cross-promotion between apps
- No "cold start" problem
Atlas Network (Knowledge Graph)
Problem: Wikipedia-style platforms struggle with vandalism and contributor trust.
Solution with Social Backbone:
// Editing a knowledge article
public shared(msg) func editArticle(
articleId: Text,
newContent: Text,
editSummary: Text
) : async Result<RevisionId, Error> {
let caller = msg.caller;
let profile = await SocialBackbone.getProfile(caller);
// High karma users = trusted editors
// Their edits go live immediately
let requiresReview = profile.karma < 100;
let revision = await createRevision(
articleId,
newContent,
requiresReview
);
// Update expertise tracking
await SocialBackbone.addExpertise(
caller,
extractTopics(articleId)
);
// Earn reputation for contributions
await SocialBackbone.updateKarma(caller, +15);
await SocialBackbone.awardBadge(
caller,
#KnowledgeContributor
);
};
Benefits:
- Reputation from Seachan/Honk FM affects editing privileges
- Expertise graph built across all contributions
- Trusted users have more permissions
- Single source of truth for user achievements
isOS (Web Operating System)
The Hub That Connects Everything:
┌────────────────────────────────────────────────────┐
│ isOS Desktop │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ Universal Profile Widget │ │
│ │ │ │
│ │ 👤 Alice (@alice) │ │
│ │ 📊 Karma: 150 │ │
│ │ 🎖️ Badges: 12 │ │
│ │ 👥 Followers: 342 │ │
│ │ │ │
│ │ Recent Activity: │ │
│ │ 🖼️ Posted on Seachan (2h ago) │ │
│ │ 🎵 Live on Honk FM (streaming now) │ │
│ │ 📚 Edited Atlas article (yesterday) │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Seachan │ │ Honk FM │ │ Atlas │ │
│ │ │ │ │ │ │ │
│ │ [OPEN] │ │ [OPEN] │ │ [OPEN] │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ Unified Notification Center │ │
│ │ │ │
│ │ • @bob followed you (via Honk FM) │ │
│ │ • Your Seachan post got 50 upvotes │ │
│ │ • Your Atlas edit was approved │ │
│ └─────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────┘
isOS acts as:
- Central dashboard for all your DappJak activity
- Unified notification system
- Profile management interface
- Privacy control center
- App launcher with context awareness
Cross-App Tribes (Communities)
One powerful feature of the Social Backbone is Tribes - communities that exist independently of any single app.
// Tribe (Community) Structure
type Tribe = {
id: Text;
name: Text;
description: Text;
creator: Principal;
members: [Principal];
moderators: [Principal];
// Cross-app presence
seachanBoard: ?Text; // Optional board on Seachan
honkFmStation: ?Text; // Optional radio station
atlasNamespace: ?Text; // Optional knowledge namespace
// Unified features
chat: ChatChannel;
events: [Event];
sharedContent: [Content];
};
Example: A "Cyberpunk Culture" Tribe
┌─────────────────────────────────────────────┐
│ Cyberpunk Culture Tribe │
│ (2,500 members across DappJak) │
└─────────────────────────────────────────────┘
│
│ Manifests differently per app
│
┌─────────┼─────────┬─────────────┐
│ │ │ │
▼ ▼ ▼ ▼
┌────────┐ ┌────────┐ ┌──────────┐ ┌──────┐
│Seachan │ │Honk FM │ │ Atlas │ │ isOS │
│ │ │ │ │ │ │ │
│/cyber/ │ │Cyber │ │Cyberpunk │ │Tribe │
│board │ │Radio │ │Wiki │ │Chat │
│ │ │Station │ │Namespace │ │ │
└────────┘ └────────┘ └──────────┘ └──────┘
Same 2,500 members in all contexts!
Benefits:
- Create a tribe once, it appears across all apps
- Members automatically get access to tribe spaces in each app
- Shared reputation and moderation
- Unified membership and permissions
The Megaphone: Cross-App Content Amplification
The Megaphone feature allows content to be amplified across the entire DappJak ecosystem, not just where it originated.
// Megaphone a post across apps
public shared(msg) func megaphoneContent(
contentId: Text,
sourceApp: AppType,
targetApps: [AppType]
) : async Result<(), Error> {
let caller = msg.caller;
let profile = await SocialBackbone.getProfile(caller);
// Requires karma threshold
if (profile.karma < 50) {
return #err(#InsufficientKarma);
};
// Get original content
let content = await getContent(sourceApp, contentId);
// Broadcast to follower feeds across ALL selected apps
for (app in targetApps.vals()) {
await app.amplifyToFeeds(
content,
profile.followers,
#Megaphone(caller)
);
};
// Cost karma to prevent spam
await SocialBackbone.updateKarma(caller, -10);
};
Real-world example:
Alice creates an epic Seachan post analyzing cyberpunk aesthetics.
She uses Megaphone to amplify it:
1. Seachan → All her followers see it in their Seachan feeds
2. Honk FM → Appears in her DJ profile feed
3. Atlas → Linked in relevant wiki articles
4. isOS → Pinned to her profile dashboard
One post, maximum reach across the entire ecosystem!
Megaphone Rules:
- Costs karma to use (prevents spam)
- Higher karma = more frequent megaphone access
- Community can downvote megaphoned content
- Abuse results in loss of megaphone privileges
The Benefits of This Framework
1. Network Effects Across Apps
Traditional model:
Twitter: 0 followers → 10,000 followers (2 years)
Launch YouTube: 0 followers → ??? (start over)
DappJak model:
Seachan: 0 followers → 10,000 followers (2 years)
Launch Honk FM: 10,000 followers → immediate audience!
Launch Atlas Network: 10,000 followers → instant credibility!
Each new app benefits from your existing community.
2. Portable Reputation
Your karma and badges follow you:
- Trusted on Seachan = trusted on Atlas Network
- Popular DJ on Honk FM = instant credibility on Seachan
- Knowledge expert on Atlas = verified contributor elsewhere
Build reputation once, use it everywhere.
3. Unified Social Graph
Old way:
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Twitter │ │ YouTube │ │ Discord │
│ │ │ │ │ │
│ 5K │ │ 2K │ │ 800 │
│followers│ │ subs │ │ members │
└─────────┘ └─────────┘ └─────────┘
(isolated) (isolated) (isolated)
DappJak way:
┌──────────────────┐
│ Social Backbone │
│ │
│ 5,000 followers │
└────────┬─────────┘
│
┌────────┼────────┐
│ │ │
┌───▼──┐ ┌──▼───┐ ┌──▼────┐
│Seachan│ │Honk FM│ │Atlas │
└───────┘ └───────┘ └───────┘
(all see same social graph)
One follow relationship works everywhere.
4. Privacy Without Fragmentation
Granular control:
// User can set per-app permissions
let permissions = {
seachan = {
showProfile = false; // Anonymous posting
showActivity = false;
showKarma = true; // But karma visible for trust
};
honkFm = {
showProfile = true; // Public DJ profile
showActivity = true;
showFollowers = true;
};
atlasNetwork = {
showProfile = true;
showExpertise = true; // Display knowledge areas
showActivity = false; // But hide other activities
};
};
Be anonymous on Seachan, public on Honk FM, expert on Atlas - all with one identity.
5. Developer Velocity
When building a new DappJak app:
❌ Old way:
├── Build authentication system
├── Build user profiles
├── Build social graph
├── Build reputation system
├── Build notification system
├── Build privacy controls
└── Build your actual app features
✅ DappJak way:
├── Import Social Backbone SDK
└── Build your actual app features
Ship features faster by reusing infrastructure.
6. Data Sovereignty
Your data isn't locked in any single app:
┌──────────────────────────────────────┐
│ Your Universal Profile │
│ │
│ Owned by: Your Internet Identity │
│ Stored in: Social Backbone Canister │
│ Accessed by: Apps you authorize │
│ Portable to: Any ICP app │
│ Exportable: Full JSON export │
└──────────────────────────────────────┘
You own it. You control it. You can leave.
7. Composability
Apps can build on each other:
// Example: Build a "Seachan to Atlas" bridge
// Reference Seachan thread in Atlas article
public func citeSeachanThread(
articleId: Text,
threadId: Text
) : async Result<Citation, Error> {
// Verify thread exists on Seachan
let thread = await Seachan.getThread(threadId);
// Create citation in Atlas
let citation = {
source = #Seachan(threadId);
preview = thread.summary;
author = thread.creator; // Social Backbone Principal
};
// Cross-app reputation boost
await SocialBackbone.updateKarma(
thread.creator,
+20 // Cited in knowledge base!
);
};
Apps enhance each other's value.
Technical Architecture Deep Dive
The Social Backbone Canister
// Simplified Social Backbone implementation
actor SocialBackbone {
// Storage
private stable var profiles = TrieMap.TrieMap<Principal, Profile>(
Principal.equal,
Principal.hash
);
private stable var socialGraph = TrieMap.TrieMap<Principal, Connections>(
Principal.equal,
Principal.hash
);
// Core API
public shared(msg) func getProfile(
user: Principal
) : async ?Profile {
profiles.get(user)
};
public shared(msg) func updateProfile(
updates: ProfileUpdates
) : async Result<(), Error> {
let caller = msg.caller;
switch (profiles.get(caller)) {
case null { #err(#NotFound) };
case (?profile) {
let updated = applyUpdates(profile, updates);
profiles.put(caller, updated);
#ok()
};
}
};
public shared(msg) func follow(
target: Principal
) : async Result<(), Error> {
let caller = msg.caller;
// Update both sides of relationship
await addFollower(target, caller);
await addFollowing(caller, target);
// Notify target
await notifyUser(target, #NewFollower(caller));
#ok()
};
public shared(msg) func updateKarma(
user: Principal,
delta: Int
) : async Result<(), Error> {
// Only authorized apps can update karma
assert(isAuthorizedApp(msg.caller));
switch (profiles.get(user)) {
case null { #err(#NotFound) };
case (?profile) {
let newKarma = Int.max(0, profile.karma + delta);
let updated = { profile with karma = newKarma };
profiles.put(user, updated);
// Check for badge eligibility
await checkBadges(user, updated);
#ok()
};
}
};
public query func getActivityFeed(
user: Principal,
limit: Nat
) : async [Activity] {
// Aggregate activity from all authorized apps
let activities = Buffer.Buffer<Activity>(limit);
for (app in authorizedApps.vals()) {
let appActivities = await app.getUserActivity(user);
activities.append(appActivities);
};
// Sort by timestamp, return latest
Array.sort(
Buffer.toArray(activities),
compareTimestamp
)
|> Array.take(_, limit)
};
};
App Integration SDK
Apps integrate via a simple SDK:
// In your dApp
import SocialBackbone "canister:social-backbone";
actor MyDApp {
public shared(msg) func performAction() : async Result<(), Error> {
let user = msg.caller;
// Load user context
let profile = await SocialBackbone.getProfile(user);
// Check permissions
if (not hasPermission(profile, #MyDApp)) {
return #err(#Unauthorized);
};
// Do your app logic...
// Update user's social data
await SocialBackbone.recordActivity(user, #MyDAppAction);
await SocialBackbone.updateKarma(user, +10);
// Notify followers
await SocialBackbone.notifyFollowers(
user,
#NewActivity("Did something on MyDApp!")
);
#ok()
};
};
Data Ownership and Export
Users can export ALL their data at any time:
public query(msg) func exportMyData() : async ExportPackage {
let caller = msg.caller;
{
profile = await getProfile(caller);
connections = await getConnections(caller);
activities = await getAllActivities(caller);
// Data from all apps
seachanData = await Seachan.exportUserData(caller);
honkFmData = await HonkFM.exportUserData(caller);
atlasData = await AtlasNetwork.exportUserData(caller);
// Metadata
exportedAt = Time.now();
format = "DappJak-Export-v1.0";
}
};
This export can be:
- Downloaded as JSON
- Imported to another ICP app
- Used to migrate to a fork
- Kept as personal archive
Practical Implementation: CanDB Scaling Architecture
While the Social Backbone design is conceptually powerful, the real magic happens in how we handle scale. A single canister has storage limits (~8GB stable memory on ICP). With thousands of users posting content, we need horizontal scaling.
Enter CanDB - a horizontally scalable database framework for the Internet Computer that automatically partitions data across multiple canisters.
The Problem: Traditional Canister Limits
Traditional Single Canister Approach:
┌─────────────────────────┐
│ Social Backbone │ ⚠️ Problems:
│ (Single Canister) │ • 8GB storage limit
│ │ • All users compete for cycles
│ User 1: [data] │ • Single point of bottleneck
│ User 2: [data] │ • Can't scale past ~50k users
│ User 3: [data] │
│ ... │
│ User 50,000: [data] │ 💥 Out of memory!
└─────────────────────────┘
The Solution: CanDB Per-User Partitioning
CanDB automatically creates partition canisters and routes data based on partition keys (in our case, user Principals).
CanDB Horizontally Scaled Architecture:
┌──────────────────────────────────────────────────┐
│ Social Backbone Index Canister │
│ │
│ Routes by Principal to partition canisters │
└──────────────┬───────────────────────────────────┘
│
┌───────┼───────┬───────────┐
│ │ │ │
▼ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐ ... ┌──────────┐
│Partition │ │Partition │ │Partition │ │Partition │
│ #0 │ │ #1 │ │ #2 │ │ #99 │
│ │ │ │ │ │ │ │
│Users │ │Users │ │Users │ │Users │
│1-500 │ │501-1000 │ │1001-1500 │ │49k-50k │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
✅ Each partition: ~8GB storage
✅ Scales horizontally: unlimited users
✅ Automatic routing and load balancing
✅ Each user's data isolated to their partition
Real Implementation: User Data Store
Here's how we implement per-user storage with CanDB that returns platform-agnostic JSON:
// social-backbone-storage/main.mo
import CanDB "mo:candb/CanDB";
import Entity "mo:candb/Entity";
actor class SocialBackboneStorage() {
// CanDB instance with auto-scaling
stable var db : CanDB.DB = CanDB.init({
pk = "user#"; // Partition key prefix
scalingOptions = {
autoScalingHook = autoScale;
sizeLimit = #heapSize(400_000_000); // ~400MB per partition
// When partition fills, auto-create new one
};
});
// Store user profile as JSON-serializable entity
public shared(msg) func storeProfile(
profile: {
displayName: ?Text;
bio: ?Text;
avatar: ?Text; // IPFS hash or data URL
metadata: [(Text, Text)]; // Custom key-value pairs
}
) : async Result<(), Error> {
let caller = msg.caller;
let pk = "user#" # Principal.toText(caller);
// Store as Entity (auto-partitioned by CanDB)
await* db.put({
pk = pk;
sk = "profile"; // Sort key
attributes = [
("displayName", #text(Option.get(profile.displayName, ""))),
("bio", #text(Option.get(profile.bio, ""))),
("avatar", #text(Option.get(profile.avatar, ""))),
("metadata", #tuple(profile.metadata)),
("updatedAt", #int(Time.now()))
];
});
#ok()
};
// Store user post (can be JSON or media reference)
public shared(msg) func storePost(
content: {
text: ?Text;
media: ?{
type_: Text; // "image/png", "video/mp4", etc.
url: Text; // IPFS hash or IC asset URL
thumbnail: ?Text;
};
metadata: [(Text, Text)]; // Tags, mentions, etc.
}
) : async Result<PostId, Error> {
let caller = msg.caller;
let postId = generateId();
let pk = "user#" # Principal.toText(caller);
let sk = "post#" # postId;
let attributes = Buffer.Buffer<(Text, Entity.AttributeValue)>(10);
attributes.add(("postId", #text(postId)));
attributes.add(("createdAt", #int(Time.now())));
switch (content.text) {
case (?text) { attributes.add(("text", #text(text))) };
case null {};
};
switch (content.media) {
case (?media) {
attributes.add(("mediaType", #text(media.type_)));
attributes.add(("mediaUrl", #text(media.url)));
switch (media.thumbnail) {
case (?thumb) { attributes.add(("thumbnail", #text(thumb))) };
case null {};
};
};
case null {};
};
for ((key, value) in content.metadata.vals()) {
attributes.add((key, #text(value)));
};
await* db.put({
pk = pk;
sk = sk;
attributes = Buffer.toArray(attributes);
});
#ok(postId)
};
// Get user profile as JSON
public query func getProfileJSON(
user: Principal
) : async ?Text {
let pk = "user#" # Principal.toText(user);
switch (await db.get({ pk = pk; sk = "profile" })) {
case null { null };
case (?entity) {
// Convert entity to JSON
let json = entityToJSON(entity);
?json
};
}
};
// Get user posts as JSON array
public query func getPostsJSON(
user: Principal,
limit: Nat
) : async Text {
let pk = "user#" # Principal.toText(user);
// Scan for all posts
let posts = await db.scan({
pk = pk;
skLowerBound = "post#";
skUpperBound = "post#~"; // Scan all posts
limit = limit;
ascending = false; // Newest first
});
// Convert to JSON array
let jsonArray = postsToJSONArray(posts.entities);
jsonArray
};
// Helper: Convert Entity to JSON string
private func entityToJSON(entity: Entity.Entity) : Text {
var json = "{";
var first = true;
for ((key, value) in entity.attributes.vals()) {
if (not first) { json #= "," };
first := false;
json #= """ # key # "":";
switch (value) {
case (#text(t)) { json #= """ # escapeJSON(t) # """ };
case (#int(i)) { json #= Int.toText(i) };
case (#bool(b)) { json #= Bool.toText(b) };
case (#float(f)) { json #= Float.toText(f) };
case _ { json #= "null" };
};
};
json #= "}";
json
};
private func postsToJSONArray(entities: [Entity.Entity]) : Text {
var json = "[";
var first = true;
for (entity in entities.vals()) {
if (not first) { json #= "," };
first := false;
json #= entityToJSON(entity);
};
json #= "]";
json
};
};
Candid Interface: Platform-Agnostic API
The killer feature: Return pure JSON data that any platform can consume. We expose simple Candid methods that return JSON strings.
// social-backbone-api/main.mo
// Candid interface that returns JSON
actor SocialBackboneAPI {
// Returns profile as JSON string
public query func getProfile(user: Principal) : async ?Text {
await Storage.getProfileJSON(user)
};
// Returns posts array as JSON string
public query func getPosts(user: Principal, limit: Nat) : async Text {
await Storage.getPostsJSON(user, limit)
};
// Returns followers as JSON array
public query func getFollowers(user: Principal) : async Text {
let pk = "user#" # Principal.toText(user);
let followers = await Storage.getFollowersList(pk);
followersToJSON(followers)
};
// Returns following as JSON array
public query func getFollowing(user: Principal) : async Text {
let pk = "user#" # Principal.toText(user);
let following = await Storage.getFollowingList(pk);
followingToJSON(following)
};
// Update methods (authenticated)
public shared(msg) func updateProfile(
displayName: ?Text,
bio: ?Text,
avatar: ?Text
) : async Result<(), Error> {
await Storage.storeProfile({
displayName = displayName;
bio = bio;
avatar = avatar;
metadata = [];
})
};
public shared(msg) func createPost(
text: ?Text,
mediaType: ?Text,
mediaUrl: ?Text
) : async Result<Text, Error> {
let media = switch (mediaType, mediaUrl) {
case (?t, ?u) {
?{
type_ = t;
url = u;
thumbnail = null;
}
};
case _ { null };
};
await Storage.storePost({
text = text;
media = media;
metadata = [];
})
};
};
Candid Interface Definition
The auto-generated Candid file (social-backbone.did):
type Profile = record {
displayName : opt text;
bio : opt text;
avatar : opt text;
};
type Result = variant {
ok : text;
err : Error;
};
type Error = variant {
NotFound;
Unauthorized;
InvalidInput : text;
};
service : {
// Query methods (fast, cached)
getProfile : (principal) -> (opt text) query;
getPosts : (principal, nat) -> (text) query;
getFollowers : (principal) -> (text) query;
getFollowing : (principal) -> (text) query;
// Update methods (authenticated)
updateProfile : (opt text, opt text, opt text) -> (Result);
createPost : (opt text, opt text, opt text) -> (Result);
}
TypeScript Wrapper: Universal Client Library
We create a TypeScript wrapper that works on any JavaScript platform - web, mobile, desktop, Node.js, Deno, Bun.
// social-backbone-client/src/index.ts
import { Actor, HttpAgent } from '@dfinity/agent';
import { Principal } from '@dfinity/principal';
import { idlFactory } from './social-backbone.did.js';
// Auto-generated from Candid
interface SocialBackboneService {
getProfile: (user: Principal) => Promise<string | null>;
getPosts: (user: Principal, limit: bigint) => Promise<string>;
getFollowers: (user: Principal) => Promise<string>;
getFollowing: (user: Principal) => Promise<string>;
updateProfile: (
displayName: string | null,
bio: string | null,
avatar: string | null
) => Promise<{ ok: string } | { err: Error }>;
createPost: (
text: string | null,
mediaType: string | null,
mediaUrl: string | null
) => Promise<{ ok: string } | { err: Error }>;
}
export class SocialBackboneClient {
private actor: SocialBackboneService;
constructor(canisterId: string, identity?: any) {
const agent = new HttpAgent({
host: 'https://ic0.app',
identity,
});
this.actor = Actor.createActor<SocialBackboneService>(idlFactory, {
agent,
canisterId,
});
}
/**
* Get user profile - returns parsed JSON object
*/
async getProfile(principal: string | Principal): Promise<UserProfile | null> {
const p = typeof principal === 'string'
? Principal.fromText(principal)
: principal;
const jsonString = await this.actor.getProfile(p);
if (!jsonString) return null;
return JSON.parse(jsonString) as UserProfile;
}
/**
* Get user posts - returns parsed JSON array
*/
async getPosts(principal: string | Principal, limit = 50): Promise<Post[]> {
const p = typeof principal === 'string'
? Principal.fromText(principal)
: principal;
const jsonString = await this.actor.getPosts(p, BigInt(limit));
return JSON.parse(jsonString) as Post[];
}
/**
* Get user followers
*/
async getFollowers(principal: string | Principal): Promise<Follower[]> {
const p = typeof principal === 'string'
? Principal.fromText(principal)
: principal;
const jsonString = await this.actor.getFollowers(p);
return JSON.parse(jsonString) as Follower[];
}
/**
* Update your profile (requires authentication)
*/
async updateProfile(profile: {
displayName?: string;
bio?: string;
avatar?: string;
}): Promise<void> {
const result = await this.actor.updateProfile(
profile.displayName ?? null,
profile.bio ?? null,
profile.avatar ?? null
);
if ('err' in result) {
throw new Error(`Failed to update profile: ${JSON.stringify(result.err)}`);
}
}
/**
* Create a post (requires authentication)
*/
async createPost(post: {
text?: string;
media?: { type: string; url: string };
}): Promise<string> {
const result = await this.actor.createPost(
post.text ?? null,
post.media?.type ?? null,
post.media?.url ?? null
);
if ('err' in result) {
throw new Error(`Failed to create post: ${JSON.stringify(result.err)}`);
}
return result.ok;
}
}
// TypeScript types for the returned JSON
export interface UserProfile {
displayName: string;
bio: string;
avatar: string;
updatedAt: number;
}
export interface Post {
postId: string;
text?: string;
mediaType?: string;
mediaUrl?: string;
thumbnail?: string;
createdAt: number;
[key: string]: any; // Custom metadata
}
export interface Follower {
principal: string;
displayName: string;
avatar?: string;
followedAt: number;
}
// Export singleton for easy import
export const socialBackbone = new SocialBackboneClient(
process.env.SOCIAL_BACKBONE_CANISTER_ID || ''
);
Using the TypeScript Wrapper Anywhere
Now any JavaScript/TypeScript platform can use the client:
Example 1: Web App (React/Next.js)
// app/profile/[principal]/page.tsx
import { socialBackbone } from '@dappjak/social-backbone-client';
export default async function ProfilePage({
params
}: {
params: { principal: string }
}) {
// Fetch on server or client - works everywhere!
const profile = await socialBackbone.getProfile(params.principal);
const posts = await socialBackbone.getPosts(params.principal, 20);
if (!profile) {
return <div>Profile not found</div>;
}
return (
<div>
<h1>{profile.displayName}</h1>
<p>{profile.bio}</p>
<img src={profile.avatar} alt={profile.displayName} />
<h2>Posts</h2>
{posts.map(post => (
<article key={post.postId}>
<p>{post.text}</p>
{post.mediaUrl && <img src={post.mediaUrl} />}
<time>{new Date(post.createdAt).toLocaleString()}</time>
</article>
))}
</div>
);
}
// No blockchain complexity - just async/await!
Example 2: Mobile App (React Native)
// screens/ProfileScreen.tsx
import { useEffect, useState } from 'react';
import { View, Text, Image, FlatList } from 'react-native';
import { socialBackbone } from '@dappjak/social-backbone-client';
export function ProfileScreen({ route }) {
const { principal } = route.params;
const [profile, setProfile] = useState(null);
const [posts, setPosts] = useState([]);
useEffect(() => {
async function load() {
// Same client works on React Native!
const [profileData, postsData] = await Promise.all([
socialBackbone.getProfile(principal),
socialBackbone.getPosts(principal, 50),
]);
setProfile(profileData);
setPosts(postsData);
}
load();
}, [principal]);
if (!profile) return <Text>Loading...</Text>;
return (
<View>
<Image source={{ uri: profile.avatar }} />
<Text style={styles.name}>{profile.displayName}</Text>
<Text>{profile.bio}</Text>
<FlatList
data={posts}
renderItem={({ item }) => (
<View>
<Text>{item.text}</Text>
{item.mediaUrl && <Image source={{ uri: item.mediaUrl }} />}
</View>
)}
/>
</View>
);
}
// Same TypeScript client, works on iOS & Android!
Example 3: Desktop App (Electron)
// src/main/services/social.ts
import { socialBackbone } from '@dappjak/social-backbone-client';
// Electron main process
export class SocialService {
async getUserData(principal: string) {
// Same client works in Electron!
const [profile, posts, followers] = await Promise.all([
socialBackbone.getProfile(principal),
socialBackbone.getPosts(principal),
socialBackbone.getFollowers(principal),
]);
return { profile, posts, followers };
}
async updateProfile(data: {
displayName?: string;
bio?: string;
avatar?: string;
}) {
await socialBackbone.updateProfile(data);
}
}
// IPC handler
ipcMain.handle('social:getProfile', async (event, principal) => {
return socialBackbone.getProfile(principal);
});
Example 4: Backend/CLI (Node.js/Deno/Bun)
// scripts/export-profile.ts
import { socialBackbone } from '@dappjak/social-backbone-client';
import { writeFile } from 'fs/promises';
// CLI tool to export user data
async function exportUserData(principal: string) {
console.log(`Exporting data for ${principal}...`);
const [profile, posts, followers, following] = await Promise.all([
socialBackbone.getProfile(principal),
socialBackbone.getPosts(principal, 1000),
socialBackbone.getFollowers(principal),
socialBackbone.getFollowing(principal),
]);
const exportData = {
exportedAt: new Date().toISOString(),
principal,
profile,
posts,
followers,
following,
};
await writeFile(
`${principal}-export.json`,
JSON.stringify(exportData, null, 2)
);
console.log(`✅ Exported to ${principal}-export.json`);
}
// Run with: npx tsx export-profile.ts <principal>
exportUserData(process.argv[2]);
// Same client works in Node.js, Deno, Bun!
Example 5: Non-JavaScript Platforms (via JSON Bridge)
For platforms without JavaScript, you can create a simple bridge:
Python via subprocess:
# python/social_client.py
import json
import subprocess
class SocialBackboneClient:
"""Python wrapper using Node.js bridge"""
def get_profile(self, principal: str) -> dict:
result = subprocess.run(
['node', 'bridge.js', 'getProfile', principal],
capture_output=True,
text=True
)
return json.loads(result.stdout)
def get_posts(self, principal: str, limit: int = 50) -> list:
result = subprocess.run(
['node', 'bridge.js', 'getPosts', principal, str(limit)],
capture_output=True,
text=True
)
return json.loads(result.stdout)
# Usage
client = SocialBackboneClient()
profile = client.get_profile("abc123...")
posts = client.get_posts("abc123...", 20)
Swift/iOS via WebView bridge:
// iOS/SocialBackboneClient.swift
import WebKit
class SocialBackboneClient {
private let webView = WKWebView()
func getProfile(principal: String) async throws -> UserProfile {
let script = """
import { socialBackbone } from '@dappjak/social-backbone-client';
const profile = await socialBackbone.getProfile('(principal)');
return JSON.stringify(profile);
"""
let result = try await webView.evaluateJavaScript(script)
let data = (result as! String).data(using: .utf8)!
return try JSONDecoder().decode(UserProfile.self, from: data)
}
}
Media Storage: IPFS Integration
For images, videos, and large files, we store references to IPFS:
// When user uploads media
public shared(msg) func uploadMedia(
data: Blob,
contentType: Text
) : async Result<MediaReference, Error> {
let caller = msg.caller;
// Option 1: Store directly in canister (small files < 1MB)
if (data.size() < 1_000_000) {
let mediaId = generateId();
await storeInCanister(caller, mediaId, data, contentType);
return #ok({
url = "https://social-backbone.ic0.app/media/" # mediaId;
type_ = contentType;
});
}
// Option 2: Store in IPFS (large files)
else {
let ipfsHash = await uploadToIPFS(data);
// Store reference in CanDB
await storeMediaReference(caller, {
ipfsHash = ipfsHash;
contentType = contentType;
size = data.size();
});
return #ok({
url = "ipfs://" # ipfsHash;
type_ = contentType;
});
};
};
// Serve media via HTTP
public query func http_request_media(mediaId: Text) : async Http.Response {
switch (await getMediaFromCanister(mediaId)) {
case null { /* 404 */ };
case (?(data, contentType)) {
return {
status_code = 200;
headers = [
("Content-Type", contentType),
("Cache-Control", "public, max-age=31536000"), // 1 year
("Access-Control-Allow-Origin", "*")
];
body = data;
};
};
};
};
Complete Data Flow: Platform-Agnostic Architecture
┌────────────────────────────────────────────────────────┐
│ Any Platform/App │
│ │
│ • Web (React, Vue, Svelte, Next.js) │
│ • Mobile (React Native, iOS, Android) │
│ • Desktop (Electron, Tauri) │
│ • Backend (Node.js, Deno, Bun) │
│ • CLI tools, bots, AI agents │
└─────────────┬──────────────────────────────────────────┘
│
│ TypeScript Client Library
│ @dappjak/social-backbone-client
│
▼
┌────────────────────────────────────────────────────────┐
│ @dfinity/agent (ICP SDK) │
│ │
│ • Handles Principal conversion │
│ • Manages authentication (Internet Identity) │
│ • Candid interface decoding/encoding │
│ • Query calls (fast, no consensus) │
│ • Update calls (authenticated) │
└─────────────┬──────────────────────────────────────────┘
│
│ Candid Interface (Type-Safe API)
│ Returns JSON strings → Parsed to objects
│
▼
┌────────────────────────────────────────────────────────┐
│ Social Backbone Canister (ICP) │
│ │
│ Query Methods (read-only, fast): │
│ getProfile(Principal) → JSON string │
│ getPosts(Principal, limit) → JSON array string │
│ getFollowers(Principal) → JSON array string │
│ │
│ Update Methods (authenticated, writes): │
│ updateProfile(...) → Result │
│ createPost(...) → Result<postId> │
└─────────────┬──────────────────────────────────────────┘
│
│ CanDB Partition Routing
│ (by Principal hash)
│
▼
┌────────────────────────────────────────────────────────┐
│ CanDB Storage Layer │
│ (Auto-scaled across multiple canisters) │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Partition │ │ Partition │ │ Partition │ │
│ │ #0 │ │ #1 │ │ #N │ │
│ │ │ │ │ │ │ │
│ │ Users │ │ Users │ │ Users │ │
│ │ 0-999 │ │ 1000-1999 │ │ N*1000-... │ │
│ │ │ │ │ │ │ │
│ │ • Profiles │ │ • Profiles │ │ • Profiles │ │
│ │ • Posts │ │ • Posts │ │ • Posts │ │
│ │ • Social │ │ • Social │ │ • Social │ │
│ │ Graph │ │ Graph │ │ Graph │ │
│ │ • Activity │ │ • Activity │ │ • Activity │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ Each partition: ~8GB storage, ~1000 users │
│ Auto-creates new partitions when full │
└─────────────┬──────────────────────────────────────────┘
│
│ Large media references
▼
┌────────────────────────────────────────────────────────┐
│ IPFS (Distributed Storage) │
│ │
│ • Images, videos, large files │
│ • Content-addressed by hash │
│ • Referenced in CanDB as ipfs://{hash} │
│ • Small files (<1MB) stored directly in canister │
└────────────────────────────────────────────────────────┘
Benefits of This Architecture
1. True Platform Agnosticism
Your social data isn't locked to DappJak apps. Anyone can build on it:
- Third-party mobile apps
- Browser extensions
- Desktop clients
- Analytics dashboards
- AI agents that read your feed
You own the data. Anyone can build the interface.
2. Unlimited Scalability
Traditional blockchain social media:
├─ Single contract
├─ All users compete for gas
└─ Storage limits → 💥
DappJak CanDB approach:
├─ Auto-scaling partitions
├─ Per-user isolation
├─ Unlimited horizontal growth
└─ 1M+ users → ✅ No problem
3. Standard TypeScript/JavaScript API
Developers don't need to learn blockchain internals:
// Install the package
npm install @dappjak/social-backbone-client
// Import and use - that's it!
import { socialBackbone } from '@dappjak/social-backbone-client';
const profile = await socialBackbone.getProfile(principal);
const posts = await socialBackbone.getPosts(principal, 50);
No manual Candid encoding, no Principal management, no agent setup. Just npm install and import.
The wrapper handles all the ICP complexity:
- ✅ Principal conversion
- ✅ Candid interface encoding/decoding
- ✅ JSON parsing
- ✅ Type safety with TypeScript
- ✅ Works in browser, Node.js, React Native, Electron, Deno, Bun
4. Privacy + Portability
Each user's data lives in their partition:
// User can export ALL their data
public query(msg) func exportMyData() : async Blob {
let caller = msg.caller;
let pk = "user#" # Principal.toText(caller);
// Get all entities for this user
let allData = await db.scanAll(pk);
// Serialize to JSON
let json = entitiesToJSON(allData);
// Return as downloadable blob
Text.encodeUtf8(json)
};
Download once, import anywhere. True data sovereignty.
Example: Building a Third-Party App
Let's say you want to build "DappJak Reader" - a read-only mobile app for browsing DappJak content:
# Initialize your project
npm create vite@latest dappjak-reader -- --template react-ts
cd dappjak-reader
# Install the Social Backbone client - that's it!
npm install @dappjak/social-backbone-client
// src/App.tsx
import { useEffect, useState } from 'react';
import { socialBackbone } from '@dappjak/social-backbone-client';
// You DON'T need to:
// ❌ Deploy your own canisters
// ❌ Learn Motoko or Candid
// ❌ Set up Internet Identity (for read-only)
// ❌ Pay for cycles
// ❌ Understand blockchain internals
// You just:
// ✅ npm install the client
// ✅ Import and use
// ✅ Build your UI
// ✅ Ship your app
function App() {
const [profile, setProfile] = useState(null);
const [posts, setPosts] = useState([]);
useEffect(() => {
async function loadData() {
const principal = 'xyz123...'; // From URL params or search
// Simple async/await - just like any API!
const [profileData, postsData] = await Promise.all([
socialBackbone.getProfile(principal),
socialBackbone.getPosts(principal, 50),
]);
setProfile(profileData);
setPosts(postsData);
}
loadData();
}, []);
if (!profile) return <div>Loading...</div>;
return (
<div>
<header>
<img src={profile.avatar} alt={profile.displayName} />
<h1>{profile.displayName}</h1>
<p>{profile.bio}</p>
</header>
<main>
{posts.map(post => (
<article key={post.postId}>
<p>{post.text}</p>
{post.mediaUrl && <img src={post.mediaUrl} alt="" />}
<time>{new Date(post.createdAt).toLocaleString()}</time>
</article>
))}
</main>
</div>
);
}
export default App;
That's the power of platform-agnostic design.
- Internet Computer provides decentralization (no central servers to shut down)
- CanDB provides unlimited scalability (millions of users)
- TypeScript wrapper provides developer accessibility (standard npm package)
- JSON responses provide data portability (works with any framework)
Comparison to Traditional Approaches
| Feature | Centralized (Twitter) | Federated (Mastodon) | DappJak Social Backbone |
|---|---|---|---|
| Data Ownership | Platform owns | Server admin owns | You own via Principal |
| Portability | None | Server-dependent | Full export anytime |
| Identity | Username@platform | username@server | Internet Identity (universal) |
| Cross-app | Walled gardens | Some federation | Native integration |
| Reputation | Per-platform | Per-server | Cross-app universal |
| Cost | Free (you're product) | Server costs | Cycles (developer pays) |
| Censorship Resistance | Low | Medium | High (blockchain) |
| User Experience | Excellent | Good | Excellent (no gas fees) |
| Developer Control | None | Full (if admin) | Smart contract governance |
Privacy and Security
Privacy Layers
- Identity Privacy: Internet Identity uses WebAuthn, no email/phone required
- Profile Privacy: Granular per-app visibility settings
- Activity Privacy: Choose what activities get shared where
- Relationship Privacy: Private follows, hidden connections
- Data Privacy: Encrypted storage options for sensitive data
Security Model
┌─────────────────────────────────────────────────┐
│ Security Architecture │
├─────────────────────────────────────────────────┤
│ │
│ 1. Authentication │
│ └─ Internet Identity (WebAuthn) │
│ • Cryptographic device-based auth │
│ • No passwords to leak │
│ │
│ 2. Authorization │
│ └─ Principal-based access control │
│ • Each app requests permissions │
│ • User grants granular access │
│ │
│ 3. Data Integrity │
│ └─ Blockchain consensus │
│ • Immutable audit trail │
│ • Cryptographic signatures │
│ │
│ 4. Privacy Controls │
│ └─ User-defined policies │
│ • Per-app visibility rules │
│ • Encryption for sensitive data │
│ │
│ 5. App Isolation │
│ └─ Canister sandboxing │
│ • Apps can't access unauthorized data │
│ • Social Backbone enforces permissions │
└─────────────────────────────────────────────────┘
The Road Ahead
Phase 1: Foundation (Months 1-3)
- ✅ Deploy Social Backbone canister
- ✅ Implement core profile management
- ✅ Build social graph functionality
- ✅ Create SDK for app integration
Phase 2: First Apps (Months 4-6)
- 🚧 Launch Seachan with Social Backbone
- 🚧 Integrate Honk FM
- 🚧 Build unified notifications
- 🚧 Implement karma system
Phase 3: Expansion (Months 7-9)
- 📋 Launch Atlas Network
- 📋 Complete isOS integration
- 📋 Advanced privacy controls
- 📋 Data export/import tools
Phase 4: Ecosystem (Months 10-12)
- 📋 Third-party app SDK
- 📋 Federation with other ICP apps
- 📋 Advanced reputation algorithms
- 📋 Community governance
Why This Matters
The internet started as a decentralized network of connected computers. Then it got centralized into a handful of platforms that own your data, your audience, and your digital life.
We're building the internet that should have been.
With the Social Backbone architecture, DappJak Labs demonstrates that you can have:
- Decentralization without sacrificing user experience
- Data ownership without fragmentation
- Privacy without isolation
- Interoperability without compromise
Every app you use strengthens your presence in the others. Your reputation travels with you. Your followers are truly yours. Your data can't be held hostage.
This is social media you control.
Get Involved
The DappJak Social Backbone is open source and built on the Internet Computer. We're building in public and would love your contributions:
- Developers: Help build the Social Backbone SDK
- Creators: Join our alpha testing program
- Visionaries: Share ideas for new integrated apps
- Community: Follow our progress and give feedback
The future of social media isn't owned by corporations. It's owned by you.
Let's build it together.
References
- Abramov, D. (2024). "Open Social" - Overreacted.io - Philosophy of open social protocols and data ownership
- Gate.io Learn. "What is the Open Social Protocol? All You Need to Know About Open Social" - Overview of OpenSocial's modular architecture and SocialFi implementation
- OpenSocial Protocol. "Official Documentation" - Technical specifications, social as a layer philosophy, and implementation guide
- Internet Computer Protocol Documentation. DFINITY Foundation - ICP blockchain specifications and developer resources
- AT Protocol Specifications. Bluesky/ATProto - Decentralized social protocol specifications
This post is part of the DappJak Labs development blog. Follow our journey as we build unstoppable applications on the Internet Computer.