Verifying Identity with EAS on Maroo
A complete walkthrough of registering a schema, issuing an attestation, and verifying it via standard EVM JSON-RPC.
What You Will Learn
- ✓How to register a schema with the Maroo EAS Schema Registry
- ✓How to issue an on-chain attestation
- ✓How to query the attestation back via standard EVM tooling (cast / viem / eas-sdk)
Prerequisites
- Maroo Testnet Account with OKRW
- Basic Solidity/Ethers.js knowledge
Tools Needed
Hardhat (or Foundry)Node.js 20+Foundry's cast (optional, for shell verification)
In this tutorial, we will simulate a KYC provider issuing an 'IsVerified' attestation to a user. We will then read the attestation back over standard EVM JSON-RPC — the same path a frontend dApp or backend service would use to verify a user's status. EAS on Maroo is a Solidity contract preinstall, so any EVM tooling (viem, ethers, cast, eas-sdk) reads it directly.
1
1. Define the Schema
First, we need to register a schema. For this tutorial, our schema is simple:
bool isVerified. We will use a script to register this with the global EAS Schema Registry. A schema's UID is deterministic — the hash of the schema string, the resolver and the revocable flag — so the script reuses it when the schema is already registered, as bool isVerified is on the testnet. scripts/register.js javascript
const { SchemaRegistry } = require("@ethereum-attestation-service/eas-sdk");
async function main() {
// ... setup provider/signer ...
const schemaRegistry = new SchemaRegistry(SCHEMA_REGISTRY_ADDRESS);
schemaRegistry.connect(signer); // register() needs a signer
const schema = "bool isVerified";
const resolverAddress = "0x0000000000000000000000000000000000000000"; // No resolver
const revocable = true;
// A schema UID is deterministic (schema, resolver, revocable): if this schema is already
// registered — `bool isVerified` is, on the testnet — reuse its UID instead of registering again.
const uid = SchemaRegistry.getSchemaUID(schema, resolverAddress, revocable);
let registered = true;
try {
await schemaRegistry.getSchema({ uid });
} catch (err) {
if (err.message === "Schema not found") registered = false;
else throw err; // a network error, not a missing schema
}
if (!registered) {
const tx = await schemaRegistry.register({ schema, resolverAddress, revocable });
await tx.wait();
}
console.log("Schema UID:", uid);
}
main().catch((err) => {
console.error(err);
process.exit(1);
}); 2
2. Create an Attestation
Now, act as the issuer. We will attest that a specific recipient address is verified. If PCL's
EAS_POLICY should accept that recipient, index the attestation too: PCL finds attestations only through the EAS Indexer, and a plain attest does not index itself — so the last lines submit indexAttestation (any account may; skip it if your schema's resolver indexes attestations as they are made). scripts/attest.js javascript
const { EAS, SchemaEncoder } = require("@ethereum-attestation-service/eas-sdk");
const { ethers } = require("ethers");
async function main() {
const eas = new EAS(EAS_CONTRACT_ADDRESS);
eas.connect(signer);
const schemaEncoder = new SchemaEncoder("bool isVerified");
const encodedData = schemaEncoder.encodeData([{ name: "isVerified", value: true, type: "bool" }]);
const tx = await eas.attest({
schema: SCHEMA_UID,
data: {
recipient: "0x8f3ac2b1d9e74c05a6b18fe27dc4913e5a0f7b62", // replace with the account you attest to
expirationTime: 0,
revocable: true,
data: encodedData,
},
});
const newAttestationUID = await tx.wait();
console.log("Attestation UID:", newAttestationUID);
// PCL's EAS_POLICY finds attestations only through the EAS Indexer — index this one
// (any account may; skip it if your schema's resolver indexes attestations as they are made).
const indexer = new ethers.Contract("0x1000000000000000000000000000000000000008", [ // EAS Indexer preinstall
"function indexAttestation(bytes32 attestationUID)",
"function isAttestationIndexed(bytes32 attestationUID) view returns (bool)",
], signer);
await (await indexer.indexAttestation(newAttestationUID)).wait();
console.log("Indexed:", await indexer.isAttestationIndexed(newAttestationUID));
}
main().catch((err) => {
console.error(err);
process.exit(1);
}); 3
3. Verify via JSON-RPC
Read the attestation back with
getAttestation(uid) against the EAS contract. Either cast from a shell or a viem/ethers call from app code — both hit the same EVM view.cast (Foundry)
terminal bash
cast call $EAS_CONTRACT \
"getAttestation(bytes32)" \
$ATTESTATION_UID \
--rpc-url $MAROO_RPC_URL viem typescript
import { parseAbi } from "viem";
const easAbi = parseAbi([
"struct Attestation { bytes32 uid; bytes32 schema; uint64 time; uint64 expirationTime; uint64 revocationTime; bytes32 refUID; address recipient; address attester; bool revocable; bytes data; }",
"function getAttestation(bytes32 uid) view returns (Attestation)",
]);
const att = await publicClient.readContract({
address: EAS_CONTRACT,
abi: easAbi,
functionName: "getAttestation",
args: [attestationUID],
});
const valid =
att.revocationTime === 0n &&
(att.expirationTime === 0n || att.expirationTime > BigInt(Math.floor(Date.now()/1000))); Tip: Check
revocationTime (must be 0) and expirationTime (must be 0 = never, or in the future) — those are exactly the same checks PCL's EAS_POLICY runs, whether it is bound as a global policy or as a contract policy. PCL runs them on the most recently indexed attestation the sender has received under the schema, which is why Step 2 indexes it. Conclusion
You issued an attestation with the standard eas-sdk and verified it with standard EVM tools. No specialized CLI needed — EAS on Maroo behaves like EAS on any EVM chain. For reverse lookups ("what attestations does this user have under schema X?") see
querying-eas-data and the Indexer methods.