마루에서 EAS로 신원 검증하기

intermediate integration 30 min

스키마 등록, attestation 발급, 표준 EVM JSON-RPC를 통한 검증까지 전체 흐름을 단계별로 따라가 봅니다.

학습 목표

  • ✓마루 EAS Schema Registry에 스키마 등록 방법
  • ✓온체인 attestation 발급 방법
  • ✓표준 EVM 도구 (cast / viem / eas-sdk)로 attestation 다시 조회하는 방법

사전 요구사항

  • OKRW가 있는 마루 테스트넷 계정
  • 기본적인 Solidity/Ethers.js 지식

필요 도구

Hardhat (or Foundry)Node.js 20+Foundry's cast (optional, for shell verification)
이 튜토리얼에서는 KYC 발급자가 사용자에게 'IsVerified' attestation을 발급하는 상황을 시뮬레이션합니다. 그런 다음 표준 EVM JSON-RPC로 attestation을 다시 읽습니다. 프론트엔드 dApp이나 백엔드 서비스가 사용자의 상태를 검증할 때 사용하는 것과 동일한 경로입니다. 마루의 EAS는 Solidity 컨트랙트 preinstall이므로 어떤 EVM 도구(viem, ethers, cast, eas-sdk)로도 직접 읽을 수 있습니다.
1

1. 스키마 정의

먼저 스키마를 등록해야 합니다. 이 튜토리얼의 스키마는 간단한 bool isVerified입니다. 스크립트를 사용하여 글로벌 EAS 스키마 레지스트리에 이를 등록합니다. 스키마 UID는 스키마 문자열, 리졸버, revocable 값으로 정해지는 해시이므로, 테스트넷의 bool isVerified처럼 이미 등록된 스키마라면 스크립트가 그 UID를 그대로 사용합니다.
scripts/register.js javascript
const { SchemaRegistry } = require("@ethereum-attestation-service/eas-sdk");

async function main() {
  // ... 프로바이더/사이너 설정 ...

  const schemaRegistry = new SchemaRegistry(SCHEMA_REGISTRY_ADDRESS);
  schemaRegistry.connect(signer); // register()에는 서명자가 필요합니다
  const schema = "bool isVerified";
  const resolverAddress = "0x0000000000000000000000000000000000000000"; // 리졸버 없음
  const revocable = true;

  // 스키마 UID는 (스키마, 리졸버, revocable)로 결정됩니다. 이미 등록된 스키마라면(테스트넷에는
  // `bool isVerified`가 이미 있습니다) 다시 등록하지 않고 그 UID를 씁니다.
  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. 증명 생성

이제 발급자 역할을 수행합니다. 특정 수신자 주소가 검증되었음을 증명합니다. PCL의 EAS_POLICY가 이 수신자를 통과시키게 하려면 attestation을 인덱싱도 해야 합니다. PCL은 EAS Indexer를 통해서만 attestation을 찾고 일반 attest만으로는 인덱싱되지 않으므로, 마지막 줄에서 indexAttestation을 제출합니다(누구나 호출할 수 있으며, 스키마의 resolver가 attestation을 만들 때 인덱싱한다면 생략해도 됩니다).
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", // attestation 대상 계정으로 바꾸세요
      expirationTime: 0,
      revocable: true,
      data: encodedData,
    },
  });

  const newAttestationUID = await tx.wait();
  console.log("Attestation UID:", newAttestationUID);

  // PCL의 EAS_POLICY는 EAS Indexer를 통해서만 attestation을 찾으므로 이 attestation을 인덱싱합니다
  // (누구나 호출할 수 있으며, 스키마의 resolver가 attestation을 만들 때 인덱싱한다면 생략합니다).
  const indexer = new ethers.Contract("0x1000000000000000000000000000000000000008", [ // EAS Indexer 프리인스톨
    "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. JSON-RPC를 통한 검증

EAS 컨트랙트의 getAttestation(uid)로 attestation을 다시 읽습니다. 쉘에서는 cast로, 앱 코드에서는 viem/ethers로 호출합니다. 둘 다 동일한 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)));
팁: revocationTime(0이어야 함)과 expirationTime(0 = 만료 없음, 또는 미래 시각)을 확인합니다. PCL의 EAS_POLICY가 전역 정책으로 설정되든 컨트랙트 정책으로 설정되든 실행하는 것과 정확히 동일한 검사입니다. PCL은 발신자가 해당 스키마로 받은 attestation 중 가장 최근에 인덱싱된 것에 이 검사를 실행하므로, 2단계에서 attestation을 인덱싱합니다.

마무리

표준 eas-sdk로 attestation을 발급하고 표준 EVM 도구로 검증했습니다. 특수 CLI는 필요하지 않습니다. 마루의 EAS는 다른 EVM 체인의 EAS와 동일하게 동작합니다. 역방향 조회(예: "이 사용자가 스키마 X로 보유한 attestation은?")는 querying-eas-data 가이드와 Indexer 메서드 문서에서 다룹니다.
ESC
검색어를 입력하세요