Skip to content

Ethereum Follow Protocol (EFP)

The Ethereum Follow Protocol (EFP) is an onchain social graph for Ethereum accounts — a decentralized “who follows whom” primitive. Because EFP composes directly with ENS identity, the ENS Omnigraph API exposes it alongside ENS data, so you can read an account’s follow graph and resolve each followed account’s ENS name in a single query.

EFP is indexed by the efp plugin. The Omnigraph’s Query.efp and Account.efp fields return data only when the connected ENSIndexer has this plugin enabled; otherwise they resolve to null — treat that like any other unsupported-instance case, not a query error.

To enable it on your own instance, add efp to the ENSIndexer PLUGINS environment variable (a comma-separated list), for example:

Terminal window
PLUGINS=unigraph,efp

The efp plugin’s datasources are defined for the mainnet namespace (the EFP contracts on Base, OP Mainnet, and Ethereum Mainnet) and the local ens-test-env devnet.

The plugin indexes the full EFP onchain state into ENSDb:

  • Lists — the ERC-721 NFTs that hold follow relationships, with their owner / manager / user roles and storage location.
  • List records — the individual follows (address records), each with its tags (block, mute, top8, and custom tags).
  • Account metadata — the onchain (address, key) → value store, including the primary-list key used to validate an account’s primary list.

EFP is exposed through two entry points:

  • Account.efp — an account’s EFP presence: validated, block/mute-filtered following / followers connections (whose edges are full Accounts you can resolve straight into ENS names), the validated primaryList, and raw account metadata.
  • Query.efp — list-centric queries: list(by: { tokenId }), lists(where:) by role address, and listRecords(where:) (the raw record set, including block/mute).

Prefer Account.efp.following / followers for the social-graph answer — they apply EFP’s primary-list validation and block/mute filtering for you. Drop to the raw Query.efp.listRecords / EfpList.records only when you specifically need tags, storage location, or non-primary lists.

This query loads an account's validated EFP social graph — its primary list, following, and followers — and resolves each followed account's ENS primary name. Requires the efp plugin.

Run in ENSAdmin
GraphQL
query EfpFollowGraph($address: Address!) {
account(by: { address: $address }) {
efp {
# The validated primary list, or null if unset/unvalidated.
primaryList { tokenId }
# following/followers are validated and block/mute-filtered, so the
# edges are full Accounts you can walk straight into ENS names.
following(first: 10) {
totalCount
edges {
node {
address
resolve {
primaryName(by: { chainName: ETHEREUM }) {
name { beautified }
}
}
}
}
}
followers(first: 10) {
totalCount
edges { node { address } }
}
}
}
}
Variables
{
"address": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045"
}
Output
{
"data": {
"account": {
"efp": {
"primaryList": {
"tokenId": "6509"
},
"following": {
"totalCount": 10,
"edges": [
{
"node": {
"address": "0x1b63142628311395ceafeea5667e7c9026c862ca",
"resolve": {
"primaryName": {
"name": {
"beautified": "tgerring.eth"
}
}
}
}
},
{
"node": {
"address": "0x2b888954421b424c5d3d9ce9bb67c9bd47537d12",
"resolve": {
"primaryName": {
"name": {
"beautified": "lefteris.eth"
}
}
}
}
},
{
"node": {
"address": "0x50ec05ade8280758e2077fcbc08d878d4aef79c3",
"resolve": {
"primaryName": {
"name": {
"beautified": "hayden.eth"
}
}
}
}
},
{
"node": {
"address": "0x54becc7560a7be76d72ed76a1f5fee6c5a2a7ab6",
"resolve": {
"primaryName": {
"name": {
"beautified": "simona.eth"
}
}
}
}
},
{
"node": {
"address": "0x5b76f5b8fc9d700624f78208132f91ad4e61a1f0",
"resolve": {
"primaryName": {
"name": {
"beautified": "barmstrong.eth"
}
}
}
}
},
{
"node": {
"address": "0x648aa14e4424e0825a5ce739c8c68610e143fb79",
"resolve": {
"primaryName": {
"name": {
"beautified": "sassal.eth"
}
}
}
}
},
{
"node": {
"address": "0x983110309620d911731ac0932219af06091b6744",
"resolve": {
"primaryName": {
"name": {
"beautified": "brantly.eth"
}
}
}
}
},
{
"node": {
"address": "0xb8c2c29ee19d8307cb7255e1cd9cbde883a267d5",
"resolve": {
"primaryName": {
"name": {
"beautified": "nick.eth"
}
}
}
}
},
{
"node": {
"address": "0xd7029bdea1c17493893aafe29aad69ef892b8ff2",
"resolve": {
"primaryName": {
"name": null
}
}
}
},
{
"node": {
"address": "0xf56345338cb4cddaf915ebef3bfde63e70fe3053",
"resolve": {
"primaryName": {
"name": {
"beautified": "bored.eth"
}
}
}
}
}
]
},
"followers": {
"edges": [
{
"node": {
"address": "0x00000066ced4c62a0f740de74eb08bc2a1552764"
}
},
{
"node": {
"address": "0x000000dcf1190af44f7149b85299f18ce7221024"
}
},
{
"node": {
"address": "0x000066b5a8e1e35b7e2f64e24715829647f80000"
}
},
{
"node": {
"address": "0x00076993122809055293d8210a60d8265a7e17f3"
}
},
{
"node": {
"address": "0x000f16ca1e73a0b6b10e1ed4cc5f8232336c1482"
}
},
{
"node": {
"address": "0x0011e9582ac18d9b80204817eaa2e3b544e727aa"
}
},
{
"node": {
"address": "0x00173c4cb23e6d876fcb036ba954a2f9cfcafa19"
}
},
{
"node": {
"address": "0x00197ff7abdac8ad42152ff9d5bd9e521701a944"
}
},
{
"node": {
"address": "0x002153708f11f2651215059eea30820ee4d49ff3"
}
},
{
"node": {
"address": "0x003e7b75ff09615ad4a6f763dc7d28e48ed513d9"
}
}
],
"totalCount": 5338
}
}
}
}
}

Output matches a point in time snapshot GraphQL response from our alpha ENSNode instance. Live output depends on the configuration of your ENSNode instance and ENS state updates.