diff --git a/docs/ensnode.io/astro.config.mjs b/docs/ensnode.io/astro.config.mjs
index 9ed94d704..32cd25578 100644
--- a/docs/ensnode.io/astro.config.mjs
+++ b/docs/ensnode.io/astro.config.mjs
@@ -35,6 +35,8 @@ export default defineConfig({
redirects: {
"/docs": "/docs/integrate",
"/ensnode": "/docs/integrate",
+ // EFP page moved under the Complementary Protocols group
+ "/docs/integrate/omnigraph/efp": "/docs/integrate/omnigraph/complementary/efp",
"/examples": "/docs/integrate/omnigraph/examples",
"/ensnode/deploying/railway": "/docs/services/ensrainbow/deploying/railway",
// ENS Subgraph docs moved from /docs/integrate/subgraph to /docs/integrate/ens-subgraph
diff --git a/docs/ensnode.io/config/integrations/starlight/sidebar-topics/integrate.ts b/docs/ensnode.io/config/integrations/starlight/sidebar-topics/integrate.ts
index 0fc00d46e..c1fe2ebdf 100644
--- a/docs/ensnode.io/config/integrations/starlight/sidebar-topics/integrate.ts
+++ b/docs/ensnode.io/config/integrations/starlight/sidebar-topics/integrate.ts
@@ -100,6 +100,20 @@ export const integrateSidebarTopic = {
label: "Protocol Acceleration",
link: "/docs/integrate/omnigraph/protocol-acceleration",
},
+ {
+ label: "Complementary Protocols",
+ collapsed: true,
+ items: [
+ {
+ label: "EFP (Social Graph)",
+ link: "/docs/integrate/omnigraph/complementary/efp",
+ },
+ {
+ label: "AI Agents / ERC-8004",
+ link: "/docs/integrate/omnigraph/complementary/erc-8004",
+ },
+ ],
+ },
{
label: "FAQ",
link: "/docs/integrate/omnigraph/faq",
diff --git a/docs/ensnode.io/src/components/molecules/omnigraph-static-example/ExampleRequirementAside.astro b/docs/ensnode.io/src/components/molecules/omnigraph-static-example/ExampleRequirementAside.astro
new file mode 100644
index 000000000..0221e78e0
--- /dev/null
+++ b/docs/ensnode.io/src/components/molecules/omnigraph-static-example/ExampleRequirementAside.astro
@@ -0,0 +1,32 @@
+---
+// Renders a plugin-requirement Aside for an Omnigraph example, driven by the example's category.
+// Example pages pass their `id`; the component looks the example up and renders the matching aside
+// (or nothing) so requirement notices stay consistent and live next to the example definition.
+import { Aside } from "@astrojs/starlight/components";
+
+import { getOmnigraphExampleConfigById } from "@data/omnigraph-examples/config";
+
+interface Props {
+ id: string;
+}
+
+const { id } = Astro.props;
+const category = getOmnigraphExampleConfigById(id)?.category;
+---
+
+{
+ category === "EFP" && (
+
+ )
+}
diff --git a/docs/ensnode.io/src/components/organisms/OmnigraphStaticExampleSet.astro b/docs/ensnode.io/src/components/organisms/OmnigraphStaticExampleSet.astro
index 885e2a6b3..1e0a5d4a1 100644
--- a/docs/ensnode.io/src/components/organisms/OmnigraphStaticExampleSet.astro
+++ b/docs/ensnode.io/src/components/organisms/OmnigraphStaticExampleSet.astro
@@ -6,6 +6,7 @@ import StaticExampleCard from "@components/molecules/omnigraph-static-example/St
import CurlStaticCodeExample from "@components/molecules/omnigraph-static-example/CurlStaticCodeExample.astro";
import SdkStaticCodeExample from "@components/molecules/omnigraph-static-example/SdkStaticCodeExample.astro";
import GraphqlStaticQueryExample from "@components/molecules/omnigraph-static-example/GraphqlStaticQueryExample.astro";
+import ExampleRequirementAside from "@components/molecules/omnigraph-static-example/ExampleRequirementAside.astro";
import { exampleTabClass } from "@components/molecules/omnigraph-static-example/constants";
import { resolveOmnigraphStaticExample } from "@lib/examples/omnigraph/resolve-static-example";
@@ -37,6 +38,8 @@ const { description } = getOmnigraphExampleById(id);
const data = resolveOmnigraphStaticExample(id);
---
+
following, and followers — and resolves each followed account's ENS primary name. Requires the efp plugin.",
+ category: "EFP",
+ namespace: ENSNamespaceIds.Mainnet,
+ hostSeparatePage: true,
+ },
+ {
+ id: "efp-who-follows",
+ title: "EFP Who Follows",
+ description:
+ 'This query finds the raw EFP list records pointing at an address (any list, including block/mute tags), to answer "which lists follow this address?". Requires the efp plugin.',
+ category: "EFP",
+ namespace: ENSNamespaceIds.Mainnet,
+ hostSeparatePage: true,
+ },
+ {
+ id: "efp-list",
+ title: "EFP List",
+ description:
+ "This query loads an EFP list by tokenId — its roles, storage location, and raw records with tags. Requires the efp plugin.",
+ category: "EFP",
+ namespace: ENSNamespaceIds.Mainnet,
+ hostSeparatePage: true,
+ },
];
export const OMNIGRAPH_EXAMPLES_INDEX_PATH = "/docs/integrate/omnigraph/examples" as const;
diff --git a/docs/ensnode.io/src/data/omnigraph-examples/examples.json b/docs/ensnode.io/src/data/omnigraph-examples/examples.json
index 6b8b4bef5..614439f4b 100644
--- a/docs/ensnode.io/src/data/omnigraph-examples/examples.json
+++ b/docs/ensnode.io/src/data/omnigraph-examples/examples.json
@@ -109,7 +109,7 @@
"variables": {
"registry": {
"chainId": 11155111,
- "address": "0x64c81210d0e580cfc7746f3fb910bf0e8f6378e1"
+ "address": "0xdedb92913a25abe1f7bcdd85d8a344a43b398b67"
}
}
},
@@ -119,7 +119,7 @@
"variables": {
"contract": {
"chainId": 11155111,
- "address": "0x64c81210d0e580cfc7746f3fb910bf0e8f6378e1"
+ "address": "0xdedb92913a25abe1f7bcdd85d8a344a43b398b67"
}
}
},
@@ -167,5 +167,26 @@
"variables": {
"address": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045"
}
+ },
+ {
+ "id": "efp-follow-graph",
+ "query": "query EfpFollowGraph($address: Address!) {\n account(by: { address: $address }) {\n efp {\n # The validated primary list, or null if unset/unvalidated.\n primaryList { tokenId }\n # following/followers are validated and block/mute-filtered, so the\n # edges are full Accounts you can walk straight into ENS names.\n following(first: 10) {\n totalCount\n edges {\n node {\n address\n resolve {\n primaryName(by: { chainName: ETHEREUM }) {\n name { beautified }\n }\n }\n }\n }\n }\n followers(first: 10) {\n totalCount\n edges { node { address } }\n }\n }\n }\n}",
+ "variables": {
+ "address": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045"
+ }
+ },
+ {
+ "id": "efp-who-follows",
+ "query": "# Raw record view: includes block/mute and non-primary lists — inspect the tags.\n# For the validated social graph, prefer Account.efp.followers instead.\nquery EfpWhoFollows($address: Address!) {\n efp {\n listRecords(where: { recordData: $address }, first: 25) {\n totalCount\n edges {\n node {\n tags\n list { tokenId user }\n }\n }\n }\n }\n}",
+ "variables": {
+ "address": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045"
+ }
+ },
+ {
+ "id": "efp-list",
+ "query": "query EfpList($tokenId: TokenId!) {\n efp {\n list(by: { tokenId: $tokenId }) {\n tokenId\n owner\n user\n manager\n storageLocation { chainId address slot }\n # Raw records (any tag, no primary-list validation).\n records(first: 25) {\n totalCount\n edges { node { recordData tags } }\n }\n }\n }\n}",
+ "variables": {
+ "tokenId": "1"
+ }
}
]
diff --git a/docs/ensnode.io/src/data/omnigraph-examples/responses.json b/docs/ensnode.io/src/data/omnigraph-examples/responses.json
index febc201df..44b8a295b 100644
--- a/docs/ensnode.io/src/data/omnigraph-examples/responses.json
+++ b/docs/ensnode.io/src/data/omnigraph-examples/responses.json
@@ -6470,5 +6470,569 @@
}
}
}
+ },
+ "efp-follow-graph": {
+ "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
+ }
+ }
+ }
+ }
+ },
+ "efp-who-follows": {
+ "data": {
+ "efp": {
+ "listRecords": {
+ "totalCount": 6085,
+ "edges": [
+ {
+ "node": {
+ "tags": [],
+ "list": null
+ }
+ },
+ {
+ "node": {
+ "tags": [],
+ "list": null
+ }
+ },
+ {
+ "node": {
+ "tags": [],
+ "list": null
+ }
+ },
+ {
+ "node": {
+ "tags": [],
+ "list": null
+ }
+ },
+ {
+ "node": {
+ "tags": ["top8"],
+ "list": {
+ "tokenId": "2400",
+ "user": "0x9760721a78fcff74a61440a06dbcb2a0ab2e4f89"
+ }
+ }
+ },
+ {
+ "node": {
+ "tags": [],
+ "list": {
+ "tokenId": "31294",
+ "user": "0x3a4bbb42c18e1bb24edbd6e00aef4ea023da960c"
+ }
+ }
+ },
+ {
+ "node": {
+ "tags": [],
+ "list": null
+ }
+ },
+ {
+ "node": {
+ "tags": [],
+ "list": null
+ }
+ },
+ {
+ "node": {
+ "tags": [],
+ "list": {
+ "tokenId": "39897",
+ "user": "0x8e79ef9e545fa14e205d89970d50e7caa3456683"
+ }
+ }
+ },
+ {
+ "node": {
+ "tags": [],
+ "list": {
+ "tokenId": "388",
+ "user": "0xbe4f0cdf3834bd876813a1037137dcfad79acd99"
+ }
+ }
+ },
+ {
+ "node": {
+ "tags": [],
+ "list": {
+ "tokenId": "55439",
+ "user": "0xe4ab754481e555fdd76d93d85b42dcbf56a8ab1e"
+ }
+ }
+ },
+ {
+ "node": {
+ "tags": [],
+ "list": {
+ "tokenId": "39191",
+ "user": "0xff2d23446f7726da0512c19525262612c4e83d27"
+ }
+ }
+ },
+ {
+ "node": {
+ "tags": [],
+ "list": null
+ }
+ },
+ {
+ "node": {
+ "tags": [],
+ "list": null
+ }
+ },
+ {
+ "node": {
+ "tags": [],
+ "list": {
+ "tokenId": "16149",
+ "user": "0x225f137127d9067788314bc7fcc1f36746a3c3b5"
+ }
+ }
+ },
+ {
+ "node": {
+ "tags": [],
+ "list": {
+ "tokenId": "33148",
+ "user": "0x3ce7241a8c5c291e50a0d027dd9badff9db1fd7c"
+ }
+ }
+ },
+ {
+ "node": {
+ "tags": [],
+ "list": {
+ "tokenId": "5056",
+ "user": "0x52fc2a810b96b333c303e48f95f6f88a2acf5d69"
+ }
+ }
+ },
+ {
+ "node": {
+ "tags": [],
+ "list": null
+ }
+ },
+ {
+ "node": {
+ "tags": [],
+ "list": {
+ "tokenId": "52365",
+ "user": "0xb5a9b9e2aec901d1b857138fc93c932f350a60ed"
+ }
+ }
+ },
+ {
+ "node": {
+ "tags": [],
+ "list": {
+ "tokenId": "55011",
+ "user": "0x2a811f1e11636c144a2a062d3d402245a43d4074"
+ }
+ }
+ },
+ {
+ "node": {
+ "tags": [],
+ "list": null
+ }
+ },
+ {
+ "node": {
+ "tags": [],
+ "list": {
+ "tokenId": "45610",
+ "user": "0x7f7720bdb2cb5c13dd30a0c8ab8d0dd553b31caa"
+ }
+ }
+ },
+ {
+ "node": {
+ "tags": [],
+ "list": {
+ "tokenId": "43738",
+ "user": "0xdf095ca41af452ed9ed390d8fac260fbdad20976"
+ }
+ }
+ },
+ {
+ "node": {
+ "tags": [],
+ "list": null
+ }
+ },
+ {
+ "node": {
+ "tags": [],
+ "list": {
+ "tokenId": "45079",
+ "user": "0x2e6728f76d949753f2e7dcee33f8136cf7e4e5f0"
+ }
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "efp-list": {
+ "data": {
+ "efp": {
+ "list": {
+ "tokenId": "1",
+ "owner": "0xc983ebc9db969782d994627bdffec0ae6efee1b3",
+ "user": "0xc983ebc9db969782d994627bdffec0ae6efee1b3",
+ "manager": "0xc983ebc9db969782d994627bdffec0ae6efee1b3",
+ "storageLocation": {
+ "chainId": 8453,
+ "address": "0x41aa48ef3c0446b46a5b1cc6337ff3d3716e2a33",
+ "slot": "0x5659892f4e1f565ebd9f7504a28177ca927325e4104d0c6787c7e8a00dcda60c"
+ },
+ "records": {
+ "totalCount": 452,
+ "edges": [
+ {
+ "node": {
+ "recordData": "0x0000ce08fa224696a819877070bf378e8b131acf",
+ "tags": []
+ }
+ },
+ {
+ "node": {
+ "recordData": "0x000f16ca1e73a0b6b10e1ed4cc5f8232336c1482",
+ "tags": []
+ }
+ },
+ {
+ "node": {
+ "recordData": "0x009ae2d1a7240cfc89095fab06ed24f1641d9169",
+ "tags": []
+ }
+ },
+ {
+ "node": {
+ "recordData": "0x00c7ecc4187f239e09d4cfea5abe6d3c18f923bc",
+ "tags": []
+ }
+ },
+ {
+ "node": {
+ "recordData": "0x021021ccee934b346160342f8d7f59f514c08c56",
+ "tags": []
+ }
+ },
+ {
+ "node": {
+ "recordData": "0x025376e7e7f161a198fb5fc90a220a553836d11a",
+ "tags": []
+ }
+ },
+ {
+ "node": {
+ "recordData": "0x027fc636db3ced7342581baed6eee79f7d1123c3",
+ "tags": []
+ }
+ },
+ {
+ "node": {
+ "recordData": "0x02c5b7c40daaaf3aac3dd2ff39013cdd0f1fb4ae",
+ "tags": []
+ }
+ },
+ {
+ "node": {
+ "recordData": "0x0331969e189d63fbc31d771bb04ab44227d748d8",
+ "tags": []
+ }
+ },
+ {
+ "node": {
+ "recordData": "0x035ebd096afa6b98372494c7f08f3402324117d3",
+ "tags": []
+ }
+ },
+ {
+ "node": {
+ "recordData": "0x041d311ac26e478ea6e1753ec354676c452db9d8",
+ "tags": []
+ }
+ },
+ {
+ "node": {
+ "recordData": "0x0446bc2beabb16aa542cd99eaff3e63af37125b4",
+ "tags": []
+ }
+ },
+ {
+ "node": {
+ "recordData": "0x049569adb8a1e8a9349e9f1111c7b7993a4612eb",
+ "tags": []
+ }
+ },
+ {
+ "node": {
+ "recordData": "0x054becf10c79afa4e60f75b187837be9fb83867d",
+ "tags": []
+ }
+ },
+ {
+ "node": {
+ "recordData": "0x05977b2fb9b7ab3f3733b34350044a01a388579c",
+ "tags": []
+ }
+ },
+ {
+ "node": {
+ "recordData": "0x05a1ff0a32bc24265bcb39499d0c5d9a6cb2011c",
+ "tags": []
+ }
+ },
+ {
+ "node": {
+ "recordData": "0x06ce84f051ce93c2fe99344bc8c9d2bf92e64409",
+ "tags": []
+ }
+ },
+ {
+ "node": {
+ "recordData": "0x074470b9a32f68de86fac393a10d5cea01c54269",
+ "tags": []
+ }
+ },
+ {
+ "node": {
+ "recordData": "0x0865c5de7fe349aa5b7f6ef721f8c2b9cfe5e40d",
+ "tags": []
+ }
+ },
+ {
+ "node": {
+ "recordData": "0x092ad3842dced68b5b6ef9cc602be66924baecdd",
+ "tags": []
+ }
+ },
+ {
+ "node": {
+ "recordData": "0x09a78e1abe7a893c43986dbf7864c79d03860bde",
+ "tags": []
+ }
+ },
+ {
+ "node": {
+ "recordData": "0x0c077910e1dc0b5bea1054cce14143e0971f95ce",
+ "tags": []
+ }
+ },
+ {
+ "node": {
+ "recordData": "0x0d1f2c041b7f9702903b89057c1f57f1f01a79af",
+ "tags": []
+ }
+ },
+ {
+ "node": {
+ "recordData": "0x0d3f5a7a1ee78e743e25c18e66942fcbcd84ccad",
+ "tags": []
+ }
+ },
+ {
+ "node": {
+ "recordData": "0x0de5a5d50453e702ad66d3fcf7c986f408019ffd",
+ "tags": []
+ }
+ }
+ ]
+ }
+ }
+ }
+ }
}
}
diff --git a/docs/ensnode.io/src/data/omnigraph-examples/schema.graphql b/docs/ensnode.io/src/data/omnigraph-examples/schema.graphql
index 9fbe48746..ed4321927 100644
--- a/docs/ensnode.io/src/data/omnigraph-examples/schema.graphql
+++ b/docs/ensnode.io/src/data/omnigraph-examples/schema.graphql
@@ -34,6 +34,11 @@ type Account {
where: AccountDomainsWhereInput
): AccountDomainsConnection
+ """
+ This Account's Ethereum Follow Protocol (EFP) presence: its lists, validated primary list, and account metadata. Null when the connected ENSIndexer does not have the `efp` plugin enabled.
+ """
+ efp: AccountEfp
+
"""
All Events for which this Account is the HCA-aware `sender` (i.e. `Event.sender`).
"""
@@ -50,6 +55,17 @@ type Account {
"""
id: Address!
+ """
+ The Names whose indexed `addr()` record points at this Account, optionally scoped to a single CoinType. Reflects literally-indexed, Canonical Domains only: records whose node has no Canonical Domain are omitted.
+ """
+ nameReferences(
+ after: String
+ before: String
+ first: Int
+ last: Int
+ where: AccountNameReferencesWhereInput
+ ): AccountNameReferencesConnection
+
"""
The Permissions granted to this Account, optionally filtered to Permissions in a specific contract.
"""
@@ -132,6 +148,85 @@ input AccountDomainsWhereInput {
version: ENSProtocolVersion
}
+"""
+An account's Ethereum Follow Protocol (EFP) presence.
+"""
+type AccountEfp {
+ """
+ The accounts that follow this account: those whose validated primary EFP list holds this account as a non-`block`/`mute` record.
+ """
+ followers(after: String, before: String, first: Int, last: Int): AccountEfpFollowersConnection
+
+ """
+ The accounts this account follows: the address records in its validated primary EFP list, excluding `block`/`mute`-tagged records. Empty when the account has no validated primary list.
+ """
+ following(after: String, before: String, first: Int, last: Int): AccountEfpFollowingConnection
+
+ """
+ The EFP lists this account is the `user` of (the lists representing it).
+ """
+ lists(after: String, before: String, first: Int, last: Int): AccountEfpListsConnection
+
+ """
+ Get one of this account's EFP account-metadata values by key (e.g. "primary-list").
+ """
+ metadata(key: String!): EfpAccountMetadata
+
+ """
+ All of this account's EFP account-metadata entries.
+ """
+ metadatas(after: String, before: String, first: Int, last: Int): AccountEfpMetadatasConnection
+
+ """
+ The account's validated primary EFP list: the list named by its `primary-list` metadata, returned only if that list's `user` role matches the account (the EFP two-step Primary List validation). Null if unset or unvalidated.
+ """
+ primaryList: EfpList
+}
+
+type AccountEfpFollowersConnection {
+ edges: [AccountEfpFollowersConnectionEdge!]!
+ pageInfo: PageInfo!
+ totalCount: Int!
+}
+
+type AccountEfpFollowersConnectionEdge {
+ cursor: String!
+ node: Account!
+}
+
+type AccountEfpFollowingConnection {
+ edges: [AccountEfpFollowingConnectionEdge!]!
+ pageInfo: PageInfo!
+ totalCount: Int!
+}
+
+type AccountEfpFollowingConnectionEdge {
+ cursor: String!
+ node: Account!
+}
+
+type AccountEfpListsConnection {
+ edges: [AccountEfpListsConnectionEdge!]!
+ pageInfo: PageInfo!
+ totalCount: Int!
+}
+
+type AccountEfpListsConnectionEdge {
+ cursor: String!
+ node: EfpList!
+}
+
+type AccountEfpMetadatasConnection {
+ edges: [AccountEfpMetadatasConnectionEdge!]!
+ pageInfo: PageInfo!
+ totalCount: Int!
+}
+
+type AccountEfpMetadatasConnectionEdge {
+ cursor: String!
+ node: EfpAccountMetadata!
+}
+
type AccountEventsConnection {
edges: [AccountEventsConnectionEdge!]!
pageInfo: PageInfo!
@@ -179,6 +274,27 @@ input AccountIdInput {
chainId: ChainId!
}
+type AccountNameReferencesConnection {
+ edges: [AccountNameReferencesConnectionEdge!]!
+ pageInfo: PageInfo!
+ totalCount: Int!
+}
+
+type AccountNameReferencesConnectionEdge {
+ cursor: String!
+ node: NameReference!
+}
+
+"""
+Filter for Account.nameReferences.
+"""
+input AccountNameReferencesWhereInput {
+ """
+ If set, scopes matches to a single CoinType. When omitted, returns matches across all CoinTypes.
+ """
+ coinType: CoinType
+}
+
type AccountPermissionsConnection {
edges: [AccountPermissionsConnectionEdge!]!
pageInfo: PageInfo!
@@ -379,6 +495,19 @@ CoinType represents an enssdk#CoinType.
"""
scalar CoinType
+"""
+The ENSIP-7 contenthash protocol type (e.g. IPFS, IPNS, Swarm, Arweave).
+"""
+enum ContenthashProtocol {
+ ARWEAVE
+ IPFS
+ IPNS
+ ONION
+ ONION3
+ SKYNET
+ SWARM
+}
+
"""
DogecoinAddress represents a Base58Check-encoded Dogecoin address (coin type 3).
"""
@@ -561,6 +690,11 @@ type DomainProfile {
"""
avatar: ProfileAvatar
+ """
+ The interpreted ENSIP-7 contenthash on the profile of an ENS name. Returns null when the raw contenthash record is unset, empty, or cannot be decoded (unknown codec or malformed bytes). See https://docs.ens.domains/ensip/7.
+ """
+ contenthash: ProfileContenthash
+
"""
The interpreted description on the profile of an ENS name, or null when unset.
"""
@@ -1166,6 +1300,269 @@ type ENSv2RegistryReservation implements Registration {
unregistrant: Account
}
+"""
+An EFP `(address, key) -> value` account-metadata entry (e.g. "primary-list").
+"""
+type EfpAccountMetadata {
+ """
+ The account this metadata belongs to.
+ """
+ address: Address!
+
+ """
+ The CAIP-10 account id of the AccountMetadata contract.
+ """
+ contract: AccountId!
+
+ """
+ When this metadata entry was first indexed (Unix timestamp, seconds).
+ """
+ createdAt: BigInt!
+ id: ID!
+
+ """
+ The metadata key (UTF-8 string).
+ """
+ key: String!
+
+ """
+ When this metadata entry was last updated (Unix timestamp, seconds).
+ """
+ updatedAt: BigInt!
+
+ """
+ The metadata value (raw bytes).
+ """
+ value: Hex!
+}
+
+"""
+An EFP list NFT (a ListRegistry token) and the records it holds.
+"""
+type EfpList {
+ """
+ When this list was first indexed (Unix timestamp, seconds).
+ """
+ createdAt: BigInt!
+
+ """
+ The address allowed to administer this list.
+ """
+ manager: Address
+
+ """
+ The list NFT's AccountId.
+ """
+ nft: AccountId!
+
+ """
+ The current ERC-721 owner of the list NFT.
+ """
+ owner: Address!
+
+ """
+ The records currently in this list (the addresses it follows).
+ """
+ records(after: String, before: String, first: Int, last: Int): EfpListRecordsConnection
+
+ """
+ The decoded onchain location of this list's records, or null if not known.
+ """
+ storageLocation: EfpListStorageLocation
+
+ """
+ The ERC-721 TokenId of the list NFT.
+ """
+ tokenId: TokenId!
+
+ """
+ When this list was last updated (Unix timestamp, seconds).
+ """
+ updatedAt: BigInt!
+
+ """
+ The address allowed to post records to this list.
+ """
+ user: Address
+}
+
+"""
+Identify an EFP list by its NFT token id.
+"""
+input EfpListByInput {
+ """
+ The ERC-721 token id of the list NFT.
+ """
+ tokenId: TokenId!
+}
+
+"""
+A single record within an EFP list (an address it follows), with its tags.
+"""
+type EfpListRecord {
+ """
+ The Account this record points to (its `recordData`). Always resolvable: an Account exists for any address (see `query.account`), so a record's target can always be walked into its ENS names and own EFP presence.
+ """
+ account: Account!
+
+ """
+ The CAIP-10 account id of the ListRecords contract holding this record.
+ """
+ contract: AccountId!
+
+ """
+ When this record was first indexed (Unix timestamp, seconds).
+ """
+ createdAt: BigInt!
+ id: ID!
+
+ """
+ The EFP list this record belongs to.
+ """
+ list: EfpList
+
+ """
+ Canonical record bytes (version | type | address), 0x-prefixed (exactly 22 bytes), with any trailing junk after the 20-byte address truncated.
+ """
+ record: Hex!
+
+ """
+ The followed/target address (the record's 20-byte payload). EFP indexes only address records (recordType 1).
+ """
+ recordData: Address!
+
+ """
+ The EFP record type (1 = address).
+ """
+ recordType: Int!
+
+ """
+ The list's storage slot (bytes32) within the ListRecords contract.
+ """
+ slot: Hex!
+
+ """
+ UTF-8 tags attached to this record (e.g. "close-friend", "block").
+ """
+ tags: [String!]!
+}
+
+type EfpListRecordsConnection {
+ edges: [EfpListRecordsConnectionEdge!]!
+ pageInfo: PageInfo!
+ totalCount: Int!
+}
+
+type EfpListRecordsConnectionEdge {
+ cursor: String!
+ node: EfpListRecord!
+}
+
+"""
+Filter EFP list records.
+"""
+input EfpListRecordsWhereInput {
+ """
+ The target address of an address record (recordType 1). Filtering by this answers 'which lists follow this address?'.
+ """
+ recordData: Address
+
+ """
+ The EFP record type (1 = address).
+ """
+ recordType: Int
+}
+
+"""
+The decoded onchain location of a list's records: a (chainId, ListRecords contract address, slot) tuple.
+"""
+type EfpListStorageLocation {
+ """
+ The ListRecords contract address holding the list's records.
+ """
+ address: Address!
+ chainId: ChainId!
+
+ """
+ The list's storage slot (bytes32) within the ListRecords contract.
+ """
+ slot: Hex!
+}
+
+"""
+Filter EFP lists by their owner, user, or manager address.
+"""
+input EfpListsWhereInput {
+ """
+ The address allowed to administer the list.
+ """
+ manager: Address
+
+ """
+ The ERC-721 owner of the list NFT.
+ """
+ owner: Address
+
+ """
+ The address allowed to post records.
+ """
+ user: Address
+}
+
+"""
+Queries for Ethereum Follow Protocol (EFP) data.
+"""
+type EfpQuery {
+ """
+ Get an EFP list by its NFT token id.
+ """
+ list(by: EfpListByInput!): EfpList
+
+ """
+ Find EFP list records. Filter by `recordData` to answer 'which lists follow this address?'.
+ """
+ listRecords(
+ after: String
+ before: String
+ first: Int
+ last: Int
+ where: EfpListRecordsWhereInput
+ ): EfpQueryListRecordsConnection
+
+ """
+ Find EFP lists, optionally filtered by owner / user / manager.
+ """
+ lists(
+ after: String
+ before: String
+ first: Int
+ last: Int
+ where: EfpListsWhereInput
+ ): EfpQueryListsConnection
+}
+
+type EfpQueryListRecordsConnection {
+ edges: [EfpQueryListRecordsConnectionEdge!]!
+ pageInfo: PageInfo!
+ totalCount: Int!
+}
+
+type EfpQueryListRecordsConnectionEdge {
+ cursor: String!
+ node: EfpListRecord!
+}
+
+type EfpQueryListsConnection {
+ edges: [EfpQueryListsConnectionEdge!]!
+ pageInfo: PageInfo!
+ totalCount: Int!
+}
+
+type EfpQueryListsConnectionEdge {
+ cursor: String!
+ node: EfpList!
+}
+
"""
Email represents a validated contact email address.
"""
@@ -1432,6 +1829,31 @@ input NameOrNodeInput @oneOf {
node: Node
}
+"""
+A Name whose indexed `addr()` record points at an Account.
+"""
+type NameReference {
+ """
+ The CoinType of the matching `addr()` record.
+ """
+ coinType: CoinType!
+
+ """
+ The canonical Domain whose `addr(coinType)` record points at this Account.
+ """
+ domain: Domain!
+
+ """
+ Whether this name is the ENSIP-19 Primary Name of this Account for this `coinType`. i.e. whether reverse resolution of `(address, coinType)` resolves to this exact name. Protocol Accelerated when supported.
+ """
+ match: Boolean!
+
+ """
+ The Resolver holding the matching `addr()` record.
+ """
+ resolver: Resolver!
+}
+
"""
A NameWrapperRegistration represents a Registration initiated by the ENSv1 NameWrapper.
"""
@@ -1816,6 +2238,31 @@ type ProfileAvatar {
httpUrl: String
}
+"""
+The interpreted ENSIP-7 contenthash on the profile of an ENS name.
+"""
+type ProfileContenthash {
+ """
+ The decoded, human-readable content identifier (e.g. a CID for IPFS, transaction ID for Arweave).
+ """
+ decoded: String!
+
+ """
+ The default public HTTP gateway URL for fetching this content in a browser (e.g. https://ipfs.io/ipfs/…). Null for protocols with no well-known public gateway (onion, skynet).
+ """
+ httpUrl: String
+
+ """
+ The ENSIP-7 contenthash protocol type (e.g. IPFS, IPNS, Swarm, Arweave).
+ """
+ protocolType: ContenthashProtocol!
+
+ """
+ The canonical protocol-native URI (e.g. "ipfs://bafy…", "ar://…", "bzz://…").
+ """
+ uri: String!
+}
+
"""
The interpreted header image on the profile of an ENS name.
"""
@@ -1904,6 +2351,11 @@ type Query {
where: DomainsWhereInput!
): QueryDomainsConnection
+ """
+ Ethereum Follow Protocol (EFP) queries. Null when the connected ENSIndexer does not have the `efp` plugin enabled.
+ """
+ efp: EfpQuery
+
"""
Identify Permissions by ID or AccountId.
"""
@@ -2558,6 +3010,11 @@ type ThreeDNSRegistration implements Registration {
unregistrant: Account
}
+"""
+TokenId represents an enssdk#TokenId.
+"""
+scalar TokenId
+
"""
A resolvable-but-unindexed Domain: not present in the index, but resolvable because an ancestor in its namegraph path has an ENSIP-10 wildcard Resolver (e.g. off-chain / CCIP-Read names, unindexed 3DNS names, wildcard subnames).
"""
diff --git a/docs/ensnode.io/src/data/omnigraph-examples/snapshot.json b/docs/ensnode.io/src/data/omnigraph-examples/snapshot.json
index dae7fee69..7667419bf 100644
--- a/docs/ensnode.io/src/data/omnigraph-examples/snapshot.json
+++ b/docs/ensnode.io/src/data/omnigraph-examples/snapshot.json
@@ -1,7 +1,7 @@
{
- "version": "v1.15.2",
- "commit": "97b5093a6",
- "sdkVersion": "1.15.2",
- "schemaTag": "v1.15.2",
- "snapshottedAt": "2026-06-10"
+ "version": "v1.16.0",
+ "commit": "ad03dc2af",
+ "sdkVersion": "1.16.0",
+ "schemaTag": "v1.16.0",
+ "snapshottedAt": "2026-06-19"
}
diff --git a/packages/ensnode-sdk/src/omnigraph-api/example-queries.ts b/packages/ensnode-sdk/src/omnigraph-api/example-queries.ts
index 1dee7a614..2d0636a7d 100644
--- a/packages/ensnode-sdk/src/omnigraph-api/example-queries.ts
+++ b/packages/ensnode-sdk/src/omnigraph-api/example-queries.ts
@@ -3,6 +3,7 @@ import { asInterpretedName, toNormalizedAddress } from "enssdk";
import { DatasourceNames, ENSNamespaceIds } from "@ensnode/datasources";
import { accounts } from "@ensnode/integration-test-env/devnet";
+import { PluginName } from "../ensindexer/config/types";
import { getDatasourceContract } from "../shared/datasource-contract";
import type { NamespaceSpecificValue } from "../shared/namespace-specific-value";
@@ -63,6 +64,8 @@ const SEPOLIA_V2_RESOLVER_WITH_RECORDS = {
export type GraphqlApiExampleQuery = {
id: string;
+ /** The plugin that must be enabled on the connected ENSIndexer for this query to return data. */
+ plugin: PluginName;
query: string;
variables: NamespaceSpecificValue