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.
| Attestation | Schema | refUID | Data |
|---|---|---|---|
| Seed: the item's identity, created once | The model's schema, bytes32 <model> | none | The model schema's own UID |
| Version: a snapshot point | The bytes32 version base schema (0x13c0fd59…bdf6) | The Seed | The version schema's own UID |
| Property: one field's value | <EAS type> <property>, such as string title | The Version | The 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
patchpublish 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). Anew_versionpublish adds a Version and attests every property again under it. Nothing is edited in place. - Relations are Seed UIDs. A
Relationproperty holds the related item's Seed UID asbytes32. AListof relations holds abytes32[]of Seed UIDs. Related items in the same publish get their UIDs filled in by cross-references. - Files are items too. An
Image,FileorHtmlvalue is published as a separate item (modelsImage,File,Html) whose Version carries a property under thestring storage_transaction_idschema with the Arweave transaction ID. OP Sepolia also has a legacybytes32 storage_transaction_idschema, 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):
| UID | Schema | refUID | Attester | |
|---|---|---|---|---|
| Seed | 0x8899fe7c…369a | bytes32 image | none | the user's account |
| Version | 0xe8c17407…a3e2 | bytes32 version | the Seed | the same account |
| Property | 0x0b88bdbc…e268 | string storage_transaction_id | the Version | the 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.
| What | Definition | Example |
|---|---|---|
| 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 Html | bytes32 <easName(property)> | author: Relation → bytes32 author, coverImage: Image → bytes32 cover_image |
| List of relations | bytes32[] <singular>_<ref model>_ids | authors with ref: 'Identity' → bytes32[] author_identity_ids |
| Version | bytes32 version | Shared by every model |
| File-backed values | string storage_transaction_id | Shared 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.ioWho 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.
Related
- Contracts overview
- SeedProtocolExtension: what
multiPublishcreates - Base schemas and Cross-references
- ModelProperty: property types and their EAS types
@seedprotocol/easand@seedprotocol/query: reading Seed data- Permanence and Revocation
Executor and router extension
Reference for SeedProtocolExecutor, the ERC-7579 executor module session keys publish through, and SeedExecutorRouterExtension, which lets a thirdweb ManagedAccount host it: install, functions, events, errors and supported accounts.
Cross-references (publishIndex)
How one multiPublish batch writes a Seed UID it creates into a property or list element of any request in the batch, the rules the contracts enforce, how the SDK builds a batch, and how direct EAS differs.