Base schemas
The EAS schemas Seed Protocol relies on beyond your own models: their definitions and UIDs, which ones must exist before anyone can publish, and which the SDK registers on demand.
Every Seed attestation is made under an EAS schema. Most of those schemas come from your models and properties, and the publish package registers them as it needs them. A few belong to the protocol itself: every publish uses them, whatever the app.
Schema UIDs are the same on every chain
An EAS schema's UID is keccak256(abi.encodePacked(schema, resolver, revocable)). It doesn't depend on the chain or on who registered it. Every schema on this page is registered with no resolver (the zero address) and as revocable, so each one has the same UID on every chain where it's registered. Registering a schema is permissionless, and a schema can't be changed or removed once it's registered.
Revocable matters: the Seed contracts make every attestation revocable, and EAS rejects a revocable attestation under an irrevocable schema. See Security and session keys.
Protocol schemas
These three are listed in schemas/base-schemas.json in the contracts repo. The OP Sepolia status on this page was read on 2026-10-18 from the SchemaRegistry (getSchema) and OP Sepolia's EAS Scan.
| Key | Schema | UID | OP Sepolia |
|---|---|---|---|
version | bytes32 version | 0x13c0fd59d69dbce40501a41f8b37768d26dd2e2bb0cad64615334d84f7b9bdf6 | Registered, named version |
schemaName | bytes32 schemaId,string name | 0x44d562ac1d7cd77e232978687fea027ace48f719cf1d58c7888e509663bb87fc | Registered (EAS schema #1, "Name a Schema") |
storageTransactionId | string storage_transaction_id | 0x55fdefb36fcbbaebeb7d6b41dc3a1a9666e4e42154267c889de064faa7ede517 | Registered, named storage_transaction_id |
versionis the schema of every Version attestation.multiPublishcreates one per publish withrefUIDset to the Seed, and its data is the Version schema's own UID (abi.encode(versionSchemaUid)). The SDK exports the UID asVERSION_SCHEMA_UID.schemaNameis EAS's schema for naming schemas. The SDK names every schema it registers with an attestation under it (schemaId,name), and finds model schemas by those names.storageTransactionIdlinks a property to the Arweave transaction that stores its file. ItsrefUIDis the Version.
OP Sepolia also has a legacy bytes32 storage_transaction_id schema (0xa1a5bee9e88ea4bac8e7a37a65ce735d47e3a9ff3e428308d40be499b4944817, EAS schema #132, no resolver, revocable, unnamed on EAS Scan). It isn't a protocol schema and isn't the one to use. Its 8 attestations, all from February 2024, hold a string encoded under a bytes32 schema, so a reader decodes each value as 0x…0020 instead of a transaction ID. SDKs before 0.7.6 picked a property's schema by name and could attest uploads under it; the SDK now picks by the exact definition, doesn't read a bytes32 value as an Arweave transaction ID, and doesn't recover those 8.
Who registers them
On a new chain, the protocol operator registers them with npx hardhat seed:ensure-schemas --network <name> from the contracts repo, which registers any that are missing and leaves the rest. --check-only only reports. See Deploying to a new chain.
The SDK only checks version and schemaName. ensureEasSchemasForItem and ensureEasSchemasForModels look both up before the first publish on a chain (once per chain, per process), and if either is missing it fails before anything is sent, with Seed base schema "<definition>" (<uid>) not registered on chain <id> (schemas … and … when both are). string storage_transaction_id is different: if it's missing when a publish uploads a file, the publish package registers and names it like any other property schema.
Model and property schemas
A model becomes an EAS schema when an item of it is first published, and a property when it's first published with a value. Before every publish, ensureEasSchemasForItem (from @seedprotocol/publish) checks every schema that publish attests under and then registers the missing ones, each with no resolver, as revocable, followed by a schemaName attestation that names it with its EAS name. Schema naming has the definition and name for each model and property.
The built-in storage models are model schemas too, and they're registered on OP Sepolia:
| Model | Schema | UID |
|---|---|---|
Image | bytes32 image | 0x5442a1004fe4152a06ed806367a989942f7051bad21bbed5b98d855509f79aed |
File | bytes32 file | 0x9215fa8182c35e94a9201127f1067822ff2bc55323f0e889dba0bf39b0eda89e |
Html | bytes32 html | 0x745173b1044be69150b650aeffc2f5c479f8695b0d260e7a2a3b6ece77798996 |
Registering costs a transaction per schema plus one per name, sent from the publishing wallet. EAS schemas are shared: if another app already registered string title, your app uses the same one. Change a schema after you've shipped explains why names and types are a contract.
A session key can't register schemas: it can only call the Seed executor. Have the owner's wallet register them first with ensureEasSchemasForModels(modelNames, wallet), which covers every property of those models. An ordinary publish registers only what that item attests, so it leaves out the properties the item leaves empty. A session-key publish that needs a missing schema fails while it checks the item, before anything is uploaded, with EasSchemasNotRegisteredError (code: 'EAS_SCHEMAS_NOT_REGISTERED', missing lists every missing definition) and the message schema "<definition>" is not registered; automation keys can't register schemas. Call ensureEasSchemasForModels with the owner wallet first. (schemas "<definition>", "<definition>" are not registered; … when several are missing).
Tool schemas registered by the publish package
@seedprotocol/publish writes three kinds of tool attestations, each under its own schema. Each attest… function registers its schema the first time it's used, unless you pass ensureSchema: false, and the ensure…Schema(wallet) functions do it on their own.
| Name | Schema | UID | OP Sepolia |
|---|---|---|---|
seedprotocol.publishAuthorization | address identity,address sessionKey,address app,string scopes,uint64 grantedAt,uint64 expiresAt,bytes32 permissionsHash | 0x04d3bf31bb5c07640f222023ee63b37769a966fc1500e96543ea72fb45b28a19 | Registered and named |
seedprotocol.domainOwnership | string domain,address claimer,string method,bytes32 challengeHash,uint64 verifiedAt,string registryCreationDate,string registryExpirationDate,bytes32 registryFingerprint,string scope | 0x3c78a221b5a06b61155981a4cdd50556ddf71452dd26d59512163219c18f1e2a | Not registered yet |
seedprotocol.publishedBy | bytes32 seedUid,bytes32 versionUid,bytes32[] attestationUids,bytes32 batchHash,string toolName,string toolVersion | 0x8856401efbc464e86c8c4077ded3c6dc4a9d4f8d398a7c1047c2bde9d8f40818 | Not registered yet |
getPublishAuthorizationSchemaUid(), getDomainOwnershipSchemaUid() and getPublishedBySchemaUid() compute these UIDs locally. The attestations themselves are covered in Security and session keys, Domain ownership and Tool attestations.
Checking a schema yourself
Read the SchemaRegistry: on OP Stack chains it's the predeploy at 0x4200000000000000000000000000000000000020. An unregistered UID comes back with a zero uid.
import { createPublicClient, http, parseAbi, zeroHash } from 'viem'
import { optimismSepolia } from 'viem/chains'
const registry = '0x4200000000000000000000000000000000000020'
const abi = parseAbi([
'struct SchemaRecord { bytes32 uid; address resolver; bool revocable; string schema; }',
'function getSchema(bytes32 uid) view returns (SchemaRecord)',
])
const publicClient = createPublicClient({ chain: optimismSepolia, transport: http() })
export async function isRegistered(uid: `0x${string}`): Promise<boolean> {
const record = await publicClient.readContract({ address: registry, abi, functionName: 'getSchema', args: [uid] })
return record.uid !== zeroHash
}To get a UID from a definition, SchemaRegistry.getSchemaUID(schema, resolver, revocable) from @ethereum-attestation-service/eas-sdk computes it offline.
Related
- Working with EAS: reading and writing Seed attestations directly
- Deploying to a new chain: registering the protocol schemas on a chain
- Define a schema and add to it
- @seedprotocol/eas: schema lookups by name and UID
Security and session keys
Who can publish and revoke through the Seed contracts, why everything is revocable, how session keys are scoped, what a publishAuthorization attestation records, and what a leaked session key can and can't do.
Deploying to a new chain
The procedure the contracts repo's scripts implement for putting Seed Protocol on another EVM chain: prerequisites, the Hardhat commands in order, rollback, and the SDK settings apps need afterwards.