EAS (Ethereum Attestation Service)
On-chain attestation layer for Green Goods. Every work submission, approval, and assessment is stored as an EAS attestation, creating permanent, verifiable records of regenerative work.
Why We Love EAS
EAS provides the verifiable data backbone for Green Goods. Unlike traditional databases, attestations are:
- Permanent — stored on-chain and immutable. Work records cannot be altered or deleted.
- Portable — attestation history travels with users across gardens, platforms, and protocols.
- Resolver-validated — each attestation type has a dedicated resolver contract that enforces role checks and action validity before the attestation is committed. This means invalid data never reaches the chain.
- Composable — other protocols can read and build on Green Goods attestations without permission.
Product Use Cases
Green Goods registers three attestation schemas on EAS, each backed by a resolver contract:
| Schema | Resolver | Purpose |
|---|---|---|
| Work | WorkResolver | Gardener submits evidence of completed work (title, feedback, media CIDs, metadata CID) |
| WorkApproval | WorkApprovalResolver | Operator reviews and approves/rejects submitted work (confidence score, verification method) |
| Assessment | AssessmentResolver | Evaluator creates an assessment of a garden's impact (strategy kernel, domain, reporting period) |
The attestation flow:
- Client encodes attestation data using
SchemaEncoderfrom@ethereum-attestation-service/eas-sdk - Data is submitted to the EAS contract via
EAS.attest() - EAS calls the resolver's
onAttest()hook, which validates role membership (via HatsModule) and action validity (via ActionRegistry) - If validation passes, the attestation is stored permanently on-chain
Submission paths depend on the user's authentication mode:
- Wallet submission -- direct
EAS.attest()call from the connected wallet - Passkey submission -- bundled as a UserOperation via smart account (ERC-4337), submitted through a bundler
Both paths use the same encoding logic (encodeWorkData, encodeWorkApprovalData, encodeAssessmentData in packages/shared/src/utils/eas/encoders.ts).
Two data paths exist for querying attestations:
- EAS GraphQL (easscan.org) -- the frontend queries work, approval, and assessment attestations directly from EAS's hosted indexer
- Envio indexer -- indexes core protocol state (gardens, roles, actions, vaults) but does NOT re-index EAS attestations
How We Integrate (Technical)
Configuration
EAS addresses and schema UIDs are stored in deployment artifacts per chain:
import { getEASConfig } from '@green-goods/shared';
const easConfig = getEASConfig(chainId);
// easConfig.WORK.uid -- Schema UID for Work attestations
// easConfig.WORK.schema -- Schema string (field types)
// easConfig.WORK_APPROVAL.uid / .schema
// easConfig.ASSESSMENT.uid / .schema
// easConfig.EAS.address -- EAS contract address
// easConfig.SCHEMA_REGISTRY.address
EAS GraphQL endpoints per chain:
- Arbitrum:
https://arbitrum.easscan.org/graphql - Celo:
https://celo.easscan.org/graphql - Sepolia:
https://sepolia.easscan.org/graphql
Encoding attestation data
The client uses encodeAbiParameters / SchemaEncoder to encode attestation payloads. The Work schema includes:
// Work attestation fields
{ name: "actionUID", type: "uint256" }
{ name: "title", type: "string" }
{ name: "feedback", type: "string" }
{ name: "metadata", type: "string" } // IPFS CID to metadata JSON
{ name: "media", type: "string[]" } // IPFS CIDs for photos/videos
Media files and metadata JSON are uploaded to IPFS (Storacha) before encoding. The encoder produces the hex-encoded bytes payload that EAS stores on-chain.
Struct vs tuple encoding (critical)
This is the most important encoding detail in the codebase:
abi.encode(struct)andabi.encode(field1, field2, ...)produce different bytes when the struct contains dynamic types (string, bytes, arrays)- Struct encoding wraps data with an outer 32-byte offset pointer; tuple encoding is flat
- EAS stores attestation data as flat tuples (the client uses
encodeAbiParameters) - All resolver contracts MUST use tuple decode:
(f1, f2) = abi.decode(data, (type1, type2)) - All tests MUST use tuple encode:
abi.encode(val1, val2)notabi.encode(struct)
Getting this wrong causes silent reverts in resolver validation.
Querying attestations
The shared package provides typed fetch functions that query the EAS GraphQL API:
// packages/shared/src/modules/data/eas.ts
getWorks(gardenAddress, chainId) // Work attestations for a garden
getWorksByGardener(gardenerAddress, chainId) // Works by a specific gardener
getWorkApprovals(gardenerAddress, chainId) // Approval attestations
getGardenAssessments(gardenAddress, chainId) // Assessment attestations
Each function handles schema UID lookup, GraphQL query construction, and response parsing into typed domain objects (EASWork, EASWorkApproval, EASGardenAssessment).
Resources
- EAS Documentation -- official EAS docs
- Contracts Package -- resolver contract source and deployment
- Architecture -- system diagrams showing EAS data flow
- Entity Matrix -- cross-protocol entity mapping
Next best action
Understand how Garden accounts use tokenbound accounts to hold assets and execute transactions.
Tokenbound integration