Seed Protocol
Contracts

Working with EAS

How Seed Protocol data maps onto EAS attestations: Seed, Version and property attestations, refUID chains, schema naming, reading attestations on easscan, and what revoking does.

Everything Seed Protocol publishes is an ordinary EAS attestation. There's no Seed registry to query: if you can read EAS, you can read Seed data, with the publish and query packages or with any EAS tool. Seed's attestations are in the EAS predeploy on OP Sepolia (0x4200000000000000000000000000000000000021); see Deployments.

How an item maps onto attestations

A published item is a small tree of attestations, linked by each attestation's refUID (EAS's "this attestation is about that one" field). All of them are made by the same account, the item's author.

AttestationSchemarefUIDData
Seed: the item's identity, created onceThe model's schema, bytes32 <model>noneThe model schema's own UID
Version: a snapshot pointThe bytes32 version base schema (0x13c0fd59…bdf6)The SeedThe version schema's own UID
Property: one field's value<EAS type> <property>, such as string titleThe VersionThe value, ABI-encoded for the schema

A few rules follow from the contracts and the publish package:

  • The item's UID is its Seed's UID. It never changes. Versions and properties hang off it.
  • Edits add attestations. A patch publish adds property attestations to the current Version, so a Version can have several attestations for the same property; the newest one by the Seed's attester that isn't revoked is the current value (see Who to trust). A new_version publish adds a Version and attests every property again under it. Nothing is edited in place.
  • Relations are Seed UIDs. A Relation property holds the related item's Seed UID as bytes32. A List of relations holds a bytes32[] of Seed UIDs. Related items in the same publish get their UIDs filled in by cross-references.
  • Files are items too. An Image, File or Html value is published as a separate item (models Image, File, Html) whose Version carries a property under the string storage_transaction_id schema with the Arweave transaction ID. OP Sepolia also has a legacy bytes32 storage_transaction_id schema, which isn't the one to read; see Base schemas. The parent's property holds that item's Seed UID. Permanence explains what lives on Arweave.
  • Everything is revocable, with no recipient and no expiry on the Seed and Version. The Seed contracts force revocable: true.

A real example

This Image item was published through an earlier Seed executor on OP Sepolia. Reading it with EAS.getAttestation(uid):

UIDSchemarefUIDAttester
Seed0x8899fe7c…369abytes32 imagenonethe user's account
Version0xe8c17407…a3e2bytes32 versionthe Seedthe same account
Property0x0b88bdbc…e268string storage_transaction_idthe Versionthe same account

The attester is the account, not the executor, even though the executor sent the calls.

Schema naming

EAS identifies a schema by a UID derived from its definition: keccak256(abi.encodePacked(schema, resolver, revocable)). The same definition, resolver and revocability give the same UID on every chain and for every app. The publish package always registers schemas with no resolver and revocable, so two apps whose Post models both have a Text property title publish into the same bytes32 post and string title schemas, and can read each other's items.

WhatDefinitionExample
Model (Seed schema)bytes32 <easName(model)>Post → bytes32 post (0x8fad85bc…e7d7 on OP Sepolia), HTMLPage → bytes32 html_page
Property<EAS type> <easName(property)>title: Text → string title (0x1ea9eab5…9ed7)
Single Relation, Image, File or Htmlbytes32 <easName(property)>author: Relation → bytes32 author, coverImage: Image → bytes32 cover_image
List of relationsbytes32[] <singular>_<ref model>_idsauthors with ref: 'Identity' → bytes32[] author_identity_ids
Versionbytes32 versionShared by every model
File-backed valuesstring storage_transaction_idShared by every Image, File and Html item. The SDK picks the schema by this exact definition, not by name

Every name comes from easName (exported by @seedprotocol/eas and the SDK): BlogPost → blog_post, HTMLPage → html_page. What's published where has its rules and the EAS type for each property type.

Before a publish, the publish package registers the missing schemas that publish attests under from the owner's account, and names each one with the same easName (that's how easscan shows names, and how the SDK finds a model's schema by name). Session keys can't register schemas, so before automation publishes, the owner's wallet runs ensureEasSchemasForModels for the models it will publish. A publish by the owner doesn't do it, since it registers no schema for a property the item leaves empty. See Who registers them.

A single Relation, Image, File or Html property is published under its own name: author: Relation is bytes32 author, never author_id. When it syncs, the SDK matches names on EAS against your model's properties as loosely as nameMatch allows.

Reading attestations

On easscan

optimism-sepolia.easscan.org shows any attestation, schema or address:

  • an attestation: https://optimism-sepolia.easscan.org/attestation/view/<uid>
  • a schema and its attestations: https://optimism-sepolia.easscan.org/schema/view/<schema uid>
  • everything one account attested: https://optimism-sepolia.easscan.org/address/<address>

Start from an item's seedUid: the Seed's page lists the attestations that reference it (its Versions), and each Version's page lists its properties.

With GraphQL

easscan also serves a GraphQL API at https://optimism-sepolia.easscan.org/graphql, which is what @seedprotocol/eas and @seedprotocol/query read by default. To list a Seed's Versions, query by refUID:

query Versions {
  attestations(
    where: {
      refUID: { equals: "0x8899fe7ca23579129031f270f0b79be9a629a3336b04754c5018d6e7a242369a" }
      schemaId: { equals: "0x13c0fd59d69dbce40501a41f8b37768d26dd2e2bb0cad64615334d84f7b9bdf6" }
    }
    orderBy: [{ timeCreated: desc }]
  ) {
    id
    attester
    revoked
    timeCreated
    decodedDataJson
  }
}

Query the properties the same way, with a Version's UID as refUID. To list one author's items of a model, filter on schemaId (the model's schema UID) and attester. @seedprotocol/eas wraps these queries, and EAS indexing covers running your own indexer.

Straight from the chain

An indexer is a convenience; the attestations themselves are in the EAS contract. EAS.getAttestation(uid) returns any attestation's schema, refUID, attester, revocation time and raw data, and SchemaRegistry.getSchema(uid) returns a schema's definition:

cast call 0x4200000000000000000000000000000000000021 \
  "getAttestation(bytes32)((bytes32,bytes32,uint64,uint64,uint64,bytes32,address,address,bool,bytes))" \
  0x8899fe7ca23579129031f270f0b79be9a629a3336b04754c5018d6e7a242369a \
  --rpc-url https://sepolia.optimism.io

Who to trust

EAS accepts any attestation that matches a registered schema, from anyone. Anyone can attest under bytes32 post, and anyone can make an attestation whose refUID points at your Seed: EAS checks that the referenced attestation exists, not who made it. So a reader should:

  • treat the Seed's attester as the item's author;
  • only accept Versions and properties made by that same attester;
  • skip anything revoked.

getCanonicalItemPropertiesFromEas in @seedprotocol/eas does all three by default; checkAttesters: false turns off the attester checks.

An indexer of your own should read Attested events from a known EAS address, not the executor's events, which any contract can trigger with arbitrary UIDs.

Revocation

Revoking is an EAS operation, not a Seed contract function. EAS sets the attestation's revocationTime and revoked flag; nothing is deleted, the data stays readable, and EAS has no un-revoke.

Revoking doesn't cascade: revoking a Seed doesn't revoke its Versions or properties on-chain, and revoking a parent doesn't revoke related items such as its images. Seed's readers hide an item whose Seed is revoked; a reader querying EAS directly should do the same.

Only the account that published can revoke, never a session key; see Revoking is owner-only. Revocation covers how item.unpublish() does it, and Permanence what revoking means for your users.

On this page