Seed Protocol
Infrastructure

EAS indexing

Where Seed reads attestations from (the easscan GraphQL indexer by default), how to point the SDK and @seedprotocol/query at another indexer, and the pinned EAS indexer in infra/eas-indexer that the local twin runs.

Published Seed data is a set of attestations on EAS. The chain only stores them; finding "every Post this account published" needs an indexer, a service that follows the EAS contracts and makes the attestations searchable. Seed reads published data through an EAS indexer's GraphQL API, the one easscan.org runs.

What reads go through the indexer

  • The SDK's EAS sync (client.syncFromEas, and the automatic sync when addresses change) pulls the Seeds, Versions and property attestations of the owned and watched addresses into the local database.
  • Schema lookups find the EAS schema UID for each model and property.
  • @seedprotocol/query's remote adapter and @seedprotocol/feed read published items straight from the indexer.
  • @seedprotocol/eas is the client all of these use.

Attestations are written on-chain and show up in reads once the indexer has picked them up. A publish that attests straight to EAS (EOA mode) then reads the Version's property attestations back from the indexer to check them, retrying for about 25 seconds before it fails with METADATA_PROPERTIES_MISSING, so that route needs the indexer up and current.

By default, every read goes to OP Sepolia's easscan indexer:

https://optimism-sepolia.easscan.org/graphql

Choose the indexer

The SDK's config.eas.indexerUrl wins, then the EAS_ENDPOINT or NEXT_PUBLIC_EAS_ENDPOINT environment variable, then the known easscan indexer for the chain Seed reads from. On a chain with no known indexer, such as a local chain, reads with none of these set throw No EAS indexer is known for chain <id>. Set SeedConfig.eas.indexerUrl (or the EAS_ENDPOINT env var). @seedprotocol/eas has the full order, how the chain is chosen, and the chains with a known indexer.

In an app, set the indexer in the SDK's config:

import type { SeedConfig } from '@seedprotocol/sdk'
import schema from '../seed.schema.json'

export const config: SeedConfig = {
  schema,
  eas: { indexerUrl: 'https://eas-indexer.example.com/graphql' },
}

In a script or server without the SDK, such as one using @seedprotocol/query, use the environment variables:

EAS_ENDPOINT=https://eas-indexer.example.com/graphql node read.mjs

An EAS_ENDPOINT that is a known easscan indexer for a different chain throws Seed Protocol chain mismatch: … on the first read, so a leftover variable can't make reads follow the wrong chain. An endpoint the SDK doesn't recognise isn't checked: make sure it indexes the chain you publish to.

Client and configuration lists the SDK's eas settings.

infra/eas-indexer

The seed-protocol repository has a Docker setup for running an EAS indexer yourself. It's what the local twin, a local fork of OP Sepolia, uses for reads. It isn't a hosted Seed service: production reads go to easscan.

What it is. The official eas-indexing-service, the same software easscan runs, so its GraphQL API matches what Seed already queries. The Dockerfile clones it at a pinned commit (9490bfac) and adds a small overlay:

  • customChain.ts adds one chain from the EAS_CUSTOM_CHAIN environment variable, so a local chain can be indexed without editing upstream's chain list. The value is the JSON of one upstream chain config: chainId, chainName, subdomain, version, contractAddress, schemaRegistryAddress, etherscanURL, contractStartBlock and rpcProvider.
  • A vendored yarn.lock and Node 20. Upstream has no lockfile, and its floating dependencies no longer install on its own Node 18 image. The Dockerfile explains how to move to a newer upstream commit.

How it runs. infra/eas-indexer/docker-compose.yml starts two services:

ServiceDetails
postgresPostgres 16, database eas-index, user and password postgres, data in the pgdata volume
indexerThe indexer, built from the Dockerfile. GraphQL at http://localhost:4000/graphql
VariableDefaultDescription
CHAIN_ID31337Which chain config the indexer follows
EAS_CUSTOM_CHAINemptyThe extra chain config described above
POLLING_INTERVAL5000Fallback polling interval in milliseconds. The indexer also listens for new blocks
EAS_INDEXER_PORT4000Host port for GraphQL

The indexer can reach services on the host as host.docker.internal, which is how it reaches the twin's node.

With the local twin

bun run twin:up in the seed-protocol repository runs this indexer for the twin and seeds it from OP Sepolia's easscan with the schemas, schema names and attestations up to the fork block. Local twin covers the steps and flags, and seedTwinConfig returns the SDK's EAS config for it as twin.eas (Point an app at it).

The indexer stops on any attestation whose schema it hasn't seen, and retries the same batch forever. Seed's first publish on the twin uses schemas registered on OP Sepolia long before the fork, so without the seeded schemas the indexer would get stuck there.

For @seedprotocol/query or a feed server in another process, set the chain and endpoint:

EAS_CHAIN_ID=31337 EAS_ENDPOINT=http://localhost:4000/graphql node read.mjs

Beyond the twin

Seed hasn't used or tested this setup against a public chain, and the repository has no instructions for it. If you try:

  • Upstream picks the chain from its built-in list by CHAIN_ID. Check that list and its RPC settings at the pinned commit, or describe the chain in EAS_CUSTOM_CHAIN.
  • The indexer reads from the chain config's contractStartBlock and resumes from the last block recorded in Postgres. Starting later than the EAS deployment hits the unknown-schema problem above unless you load the earlier schemas first.
  • The Compose file's Postgres credentials are fixed and meant for throwaway local data.

On this page