Home / Solutions / SocialFi
SocialFi

SocialFi data: the onchain half of the social graph

A social protocol splits at the chain boundary: identity and trades settle onchain, casts and DMs do not. Farcaster registers accounts and signer keys on OP Mainnet, Lens wrote follows and posts to Polygon, friend.tech settled every share trade on Base, and SQD serves those events decoded from one endpoint across 225+ networks. What lives in offchain hubs is out of scope, and the page says which half is which.

225+ Networks
3 Farcaster registries
Genesis Full history
Registries Identity onchain
The problem

A social protocol splits at the chain boundary, and only one half of it is onchain

Identity and trades settle onchain; casts, follows, and reactions live in offchain hubs, so an indexer serves exactly one slice of a social app and it is worth being precise about which. That slice is small but it is the protocol's root of trust: Farcaster registers accounts and signer keys through three OP Mainnet contracts, Lens wrote its follow and post graph to Polygon, and friend.tech settled every creator-share trade on Base. Reading it the naive way is harder than it looks, because sign-ups route through a bundler so a transaction filter on the registry returns nothing, and a trade contract that indexes only its own signature forces full eth_getLogs pagination against per-call caps. The events are all public; the cost is in how you read them.

How it works

The onchain slice is small, but it reconciles exactly

Farcaster's architecture is explicitly hybrid: identity settles onchain, content lives in offchain hubs. The onchain half is three contracts on OP Mainnet: the IdRegistry assigns account ids, the KeyRegistry adds the app keys that let a client publish for an account, and the StorageRegistry rents storage. Casts, follows, and reactions never touch the chain. An indexer that promises Farcaster data from the chain serves exactly this slice, and it is worth being precise about which slice that is.

The slice is small, but it is the protocol's root of trust, and it reconciles exactly. Over one 24-hour window (OP Mainnet blocks 153,960,000 to 154,003,199, 2026-07-08 13:52 to 2026-07-09 13:52 UTC) the IdRegistry emitted 163 Register events carrying ids 3,341,237 through 3,341,399: 163 consecutive ids, no gaps. Ids are assigned sequentially by this one contract, so the event stream is the complete account-creation record, not a sample of it.

Where a protocol does write the graph onchain, the same query shape reads it. Lens Protocol recorded follows and posts as Polygon events: in blocks 40,000,000 to 40,010,000 (about six hours of 2023-03-05) the LensHub contract 0xDb46…5a1d emitted 10,277 Followed and 3,006 PostCreated logs, still queryable at that pinned range from the polygon-mainnet dataset.

One registration, pinned to the block it landed in:

your terminal
curl -s -X POST https://portal.sqd.dev/datasets/optimism-mainnet/stream \
-H 'content-type: application/json' \
-d '{
"type": "evm",
"fromBlock": 153960058, "toBlock": 153960058,
"logs": [{
"address": ["0x00000000fc6c5f01fc30151999387bb99a9f489b"],
"topic0": ["0xf2e19a901b0748d8b08e428d0468896a039ac751ec4fec49b44b7b9c28097e45"]
}],
"fields": {
"log": { "topics": true, "data": true, "transactionHash": true },
"block": { "number": true, "timestamp": true }
}
}'

The row it returns:

one Register event, fid 3,341,237
{
"header": { "number": 153960058, "timestamp": 1783518893 },
"logs": [{
"transactionHash": "0xda2bcfb7a458aa7edd47946e41bbb4522f8cafca8c7d3258aff4e34d5c3e48e9",
"data": "0x00000000000000000000000000000000fcb080a4d6c39a9354da9eb9bc104cd7",
"topics": [
"0xf2e19a901b0748d8b08e428d0468896a039ac751ec4fec49b44b7b9c28097e45",
"0x0000000000000000000000004d5bd4232350357f0db685580cfa8ab9c98fccb2",
"0x000000000000000000000000000000000000000000000000000000000032fbb5"
]
}]
}

topics[2] is the new account id: 0x32fbb5 is fid 3,341,237, registered to the address in topics[1] with its recovery address in data. The gotcha sits in the transaction underneath: its to is the Bundler (0x00000000fc04…b0aa, which registers the id, adds a key, and rents storage in one call), not the IdRegistry. A transaction filter on the registry address returns nothing; you filter logs by the emitting contract. The same transaction also emits the KeyRegistry Add event carrying the account's new app key.

The onchain record

friend.tech went quiet; the tape is still there

friend.tech priced creator shares on a bonding curve on Base, and every buy and sell is one Trade event from a single contract, 0xCF20…A4d4. The app's activity collapsed after 2023; the contract record did not. Two same-width windows, 43,200 blocks (24 hours) each, tell it directly:

BlocksUTC windowTrade events
  • 4,000,000 to 4,043,1992023-09-15 to 09-16147,587
  • 48,300,000 to 48,343,1992026-07-07 to 07-0813
Trade events from the friend.tech shares contract on Base, counted from the same filtered stream

Reading it needs nothing from the app. The pinned query returns the same three trades in block 4,000,000 today as when they settled in September 2023:

your terminal
curl -s -X POST https://portal.sqd.dev/datasets/base-mainnet/stream \
-H 'content-type: application/json' \
-d '{
"type": "evm",
"fromBlock": 4000000, "toBlock": 4000000,
"logs": [{
"address": ["0xcf205808ed36593aa40a44f10c7f7c2f67d4a4d4"],
"topic0": ["0x2c76e7a47fd53e2854856ac3f0a5f3ee40d15cfaa82266357ea9779c486ab9c3"]
}],
"fields": {
"log": { "data": true, "transactionHash": true },
"block": { "number": true, "timestamp": true }
}
}'

The 512-hex-character data blob is the whole trade. Decoded against the Trade ABI, word by word:

FieldDecoded value
  • trader0x348f3cc2cc63ef98c9b86f58406b555193cda308
  • subject0x5f7f9c1f498eabc6887f47894a2770be19f82887
  • isBuytrue
  • shareAmount2
  • ethAmount0.3188125 ETH
  • protocolEthAmount0.015940625 ETH (exactly 5%)
  • subjectEthAmount0.015940625 ETH (exactly 5%)
  • supply52
The same log, decoded. The two fee legs reconcile: each is ethAmount x 0.05.

The first of the three logs it returns, from transaction 0xee09…7d7c:

one Trade event, raw
{
"header": { "number": 4000000, "timestamp": 1694789347 },
"logs": [{
"transactionHash": "0xee09f9160be0ef58ee3e5b5cb79f810126bd9e5f1b9cd0043cf0bfd3c7567d7c",
"data": "0x00000000…00000034"
}]
}

The data value is shown elided at its middle. In full it is 512 hex characters (514 with the 0x prefix), the eight 32-byte ABI words the decoded table lists; the curl above returns it complete.

Show the full raw row
one Trade event, full data
{
"header": { "number": 4000000, "timestamp": 1694789347 },
"logs": [{
"transactionHash": "0xee09f9160be0ef58ee3e5b5cb79f810126bd9e5f1b9cd0043cf0bfd3c7567d7c",
"data": "0x000000000000000000000000348f3cc2cc63ef98c9b86f58406b555193cda3080000000000000000000000005f7f9c1f498eabc6887f47894a2770be19f8288700000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000046ca647947448000000000000000000000000000000000000000000000000000038a1e9fa9f6a000000000000000000000000000000000000000000000000000038a1e9fa9f6a000000000000000000000000000000000000000000000000000000000000000034"
}]
}

This is also why the tape is awkward to pull anywhere else: the Trade event indexes nothing but its own signature (one topic), so no node-side filter can narrow it by trader or subject. Over eth_getLogs that means paginating the full event stream through per-call block-range and result caps against an archive node, for a contract that emitted 147,587 events in a single day (the eth_getLogs limits guide measures those caps on live RPCs). From the Portal it is one streamed range request: NDJSON from the contract's first block to the block you pin, decode downstream, filter in your own store.

Capabilities

Built for SocialFi at scale

Registry events, filtered by emitter

Farcaster identity is three contracts on OP Mainnet: the IdRegistry assigns account ids, the KeyRegistry adds signer keys, the StorageRegistry rents storage. Sign-ups route through the Bundler contract, so you filter logs by the emitting registry; a transaction-level filter on the registry address returns nothing.

Graph events where the graph is onchain

Lens wrote follows and posts to Polygon as LensHub events; friend.tech priced creator shares as Trade events on Base. Both read as decoded logs through the same query shape as any EVM contract, pinned to the per-chain contract address.

History that outlives the app

Every event a social contract ever emitted stays queryable from genesis, with no dependency on the app or its API. A pinned block range returns the same rows today as the day they settled, which is how a wound-down app stays auditable.

The offchain half, stated plainly

Casts, DMs, and post bodies never touch the chain: Farcaster keeps them in its hub network, and a Lens PostCreated event carries a contentURI pointer, not the content. SQD serves logs, transactions, traces, and state diffs; what a protocol does not emit onchain is out of scope by design.

Why SQD

What you would build yourself

Every event is public, but a social contract emits only its own signature, often on a chain of its own, so the entire cost is in the reading.

  • Farcaster sign-ups
    Yourself Sign-ups route through the Bundler contract, so a transaction-level filter on the IdRegistry address returns nothing.
    With SQD Filter logs by the emitting registry contract and its Register and Add events stream back regardless of the transaction recipient.
  • friend.tech trades
    Yourself The Trade event indexes only its own signature, so narrowing by trader or subject means paginating the full stream over eth_getLogs against per-call caps, for a contract that emitted 147,587 events in a single day.
    With SQD One streamed range request returns the contract's whole Trade history as NDJSON, to decode and filter in your own store.
  • A wound-down app
    Yourself When the app shuts down its API dies with it, taking the read path to its history.
    With SQD Every event the contract emitted stays served from the dataset, and a pinned block range returns the same rows today as the day they settled.
  • Three chains, one graph
    Yourself Farcaster on OP Mainnet, Lens on Polygon, and friend.tech on Base are three chains and three contract sets to read.
    With SQD The same query shape reads all three, with the dataset name and contract address swapped per chain.

See this on your own contracts

Tell us the chains and contracts you care about, and we will show you the query that returns them.

Use cases

What teams build with SQD

  • Farcaster registration and signer-key feeds
  • Social graph analytics from onchain follow and post events
  • Creator-share and social-token trade dashboards
  • Historical archives of wound-down social apps
  • Sybil screening inputs from registration and key events
  • Storage-rent and protocol fee tracking

Frequently asked questions

What SocialFi data is actually onchain?
Less than the apps suggest, and it differs per protocol. Farcaster stores identity onchain (account registrations, signer keys, and storage rent on OP Mainnet) and keeps casts and follows in its offchain hub network. Lens wrote the graph itself to Polygon: in blocks 40,000,000 to 40,010,000, about six hours of 2023-03-05, the LensHub contract emitted 10,277 Followed and 3,006 PostCreated events. friend.tech creator-share trades settled entirely onchain on Base. SQD serves what the contracts actually emit; it does not backfill the offchain half.
How do I index Farcaster's onchain activity?
Filter logs by the registry contracts on the optimism-mainnet dataset: the IdRegistry for account registrations and transfers, the KeyRegistry for signer keys, the StorageRegistry for storage rent. Filter by the emitting contract, not the transaction recipient: sign-ups route through the Bundler contract, so a transaction-level filter on the IdRegistry address returns nothing. Casts, follows, and reactions never touch the chain; those come from a Farcaster hub, not a chain indexer. See how EVM indexing works for the query model.
Can I still query data from a SocialFi app that shut down?
Yes. The API dies with the app; the events do not. Every friend.tech Trade event ever emitted is still served from the base-mainnet dataset: a pinned block range returns the same rows today as when the app was live, 147,587 of them in a single September 2023 day. A wound-down social app leaves a complete, timestamped record, reproducible from genesis with no dependency on the app itself.
Get started

Your blockchain data infrastructure, handled.

Private Portal. Dedicated. Validated. Managed. Tell us what you're building, we'll show you what it looks like on SQD.