Verifying Identity with EAS on Maroo

intermediate integration 30 min

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.
ESC
Type to search