Resolver
FidemarkResolver is a custom EAS SchemaResolver registered with both Fidemark schemas. EAS calls it on every attestation and revocation; the resolver decides whether to accept.
Validation rules
Section titled “Validation rules”Both schemas
Section titled “Both schemas”contentHash != bytes32(0): empty hashes are rejected.- All required string fields must be non-empty.
- String fields are length-bounded:
proofMethod ≤ 64 bytes,contentType ≤ 128 bytes,modelIdandprovider(AI) ≤ 128 bytes. - The attestation’s schema UID must be one of the two Fidemark schemas. Unknown schemas revert.
- When the resolver is paused (
pause()called by owner), all new attestations revert withEnforcedPause. Verifying existing attestations is unaffected.
Human Content Attestation
Section titled “Human Content Attestation”creator == attestation.attester: you can’t attest authorship on behalf of a different wallet. EAS’s delegated attestation flow still works because EAS setsattesterto the original signer when relayed.createdAt != 0.createdAt <= block.timestamp + 1 day: protects against far-future timestamps while tolerating client clock skew.proofMethodmust be on the resolver’s allowlist.wallet-signedis bootstrapped at deploy time; the owner can add more (e.g.ens-verified).
AI Output Attestation
Section titled “AI Output Attestation”modelIdnon-empty.providernon-empty.promptHashmay be zero: not every agent flow has a discrete prompt.
Revocation
Section titled “Revocation”onRevoke accepts every revocation and emits a Revoked event. EAS already enforces that only the original attester can revoke a revocable attestation, so the resolver doesn’t need to re-check. Revocation is permitted even when the resolver is paused (revocation only ever removes a claim; pause blocks adding new ones).
Admin surface
Section titled “Admin surface”The resolver uses OpenZeppelin’s Ownable2Step (two-step ownership transfers). Owner-only functions:
| Function | Purpose |
|---|---|
setSchemas(humanUID, aiUID) | Bind the schema UIDs to the resolver. Callable once. Reverts if humanUID == aiUID. |
addProofMethod(string method) | Add a value to the proofMethod allowlist (≤ 64 bytes). |
removeProofMethod(string method) | Remove a value from the allowlist. Cannot remove wallet-signed (the bootstrap method) so an owner-key compromise cannot brick L0. |
pause() / unpause() | Emergency switch. While paused, onAttest reverts; revocations still work. |
transferOwnership(newOwner) / acceptOwnership() | Two-step ownership transfer. Bad-address transfers do not brick admin. |
There is no upgrade path: the resolver is non-upgradeable by design. EAS schema bindings are immutable in the registry, and silently changing validation rules under existing attestations would break their trust guarantee.
If new validation logic is needed (e.g. verifying a TEE attestation signature), Fidemark ships a new resolver version paired with a new schema: old attestations stay verifiable forever, new attestations get the new rules.
Multi-party + PoP resolvers
Section titled “Multi-party + PoP resolvers”The same admin pattern applies to FidemarkMultiResolver and FidemarkPoPResolver:
- Two-step ownership.
pause()/unpause()on the attest path.- Bootstrap proofMethod (
multi-partyandpop-verified-worldidrespectively) cannot be removed.
The PoP resolver additionally exposes:
| Function | Purpose |
|---|---|
proposeWorldIdVerifier(addr) | Stage a swap of the World ID verifier address. Subject to a 24-hour timelock. |
finalizeWorldIdVerifier() | Apply the previously-proposed verifier swap once the timelock has elapsed. |
cancelWorldIdVerifierProposal() | Cancel a pending proposal. |
setWorldIdAppId(string) / lockAppId() | Update or permanently freeze the Worldcoin app id. |
setWorldIdGroupId(uint256) / lockGroupId() | Update or permanently freeze the verification level (1 = Orb, 0 = Phone). |
In production deployments the appId and groupId are locked immediately after the resolver is bound to the production Worldcoin app, so they cannot drift even with a compromised owner key.
Events
Section titled “Events”| Event | When |
|---|---|
HumanAttestation(uid, creator, contentHash, proofMethod) | A Human Proof attestation passed validation. |
AIAttestation(uid, attester, contentHash, modelId, provider) | An AI Proof attestation passed validation. |
MultiAttestation(uid, contentHash, submitter, attesterCount) | A multi-party attestation passed validation (multi resolver). |
PoPAttestation(uid, creator, contentHash, nullifierHash, proofMethod) | A PoP attestation passed validation (PoP resolver). |
Revoked(uid, attester) | An attestation was revoked. |
SchemasSet(humanUID, aiUID) / SchemaSet(uid) | A resolver was bound to its schema(s). |
ProofMethodAdded(method) / ProofMethodRemoved(method) | The allowlist changed. |
Paused(account) / Unpaused(account) | The resolver was paused or unpaused. |
OwnershipTransferStarted(previousOwner, newOwner) / OwnershipTransferred(...) | Two-step ownership flow events. |
WorldIdVerifierProposed(verifier, effectiveAt) / WorldIdVerifierFinalized(verifier) / WorldIdVerifierProposalCancelled() | PoP timelock-gated verifier swap events. |
WorldIdAppIdLocked() / WorldIdGroupIdLocked() | PoP one-shot freeze events. |
These events are stable indexer integration points.