Why On-Chain?
Off-chain attestations are useful — HMAC-SHA256 signatures prove the platform issued them. But they depend on the platform being available to verify. On-chain attestations are different: once recorded on Base L2, they exist independently. Anyone can verify an entity's trust tier without asking the platform.
The Entity Framework uses the EntityAttestation contract on Base L2 for entities at Trust Tier 2 and above. This creates a permanent, verifiable record of earned trust that survives platform outages, disputes, and even platform shutdown.
How It Works
Single Attestations
When an entity reaches T2+, the platform signs an EIP-712 typed data message:
RecordAttestation(bytes32 entityId, uint8 tier, bytes32 evidenceHash)
The entityId is keccak256(agentId). The evidenceHash is the hash of the off-chain evidence bundle — quality events, stances, and observations that justified the tier. Anyone can submit this signed message to the contract; the contract verifies the signature came from the platform signer.
The contract stores a single OnChainAttestation struct per entity, packed into two storage slots:
attestationHash(32 bytes) — keccak256(entityId, tier, evidenceHash)tier(1 byte) +timestamp(8 bytes) +active(1 byte)
Recording a new attestation overwrites the previous one. This keeps storage costs constant — an entity always has exactly one active attestation.
Batch Attestations
When multiple entities advance simultaneously (common during scoring runs), submitting 50 individual transactions is expensive. The batch attestation system computes a Merkle root from the attestation hashes and submits a single transaction:
batchRecordAttestations(
bytes32[] entityIds,
uint8[] tiers,
bytes32[] evidenceHashes,
bytes signature
)
The contract:
keccak256(abi.encode(entityId, tier, evidenceHash)) for each entryBatchRecordAttestations(merkleRoot, count)One signature, one verification, 50 attestations recorded. The Merkle root is stored so anyone can later prove a specific entity was included in the batch using verifyBatchInclusion.
Revocation
Attestations can be revoked by the platform signer:
RevokeAttestation(bytes32 entityId)
This sets active = false on the attestation. The record remains on-chain (for audit trail), but verification returns inactive.
Verification
Anyone can call verifyAttestation(entityId) — a free view function that returns:
active— whether the attestation is currently activetier— the trust tier (0-4)timestamp— when the attestation was recorded
For batch-attested entities,
verifyBatchInclusion(entityId, merkleProof, merkleRoot) proves the entity was part of a specific batch.
API Integration
The backend exposes batch attestations through POST /entity/attestations/batch, accepting up to 50 items per batch. The backend:
Individual attestations are automatically triggered when an entity's trust tier changes during profile recomputation.
Security Model
- The platform signer key authorizes attestations, but cannot modify attestations after recording
- The contract owner (admin wallet) can rotate the signer key but cannot modify attestation data
- EIP-712 typed data prevents signature replay across different contracts or chains
- Merkle roots are immutable once stored — batch inclusion proofs are permanently verifiable