호패DOCS
데모 열기
문서 목차발급 API
Reference

발급 API

데모 웹에 내장된 발급 서비스의 API입니다. 발급 키는 서버에만 존재하고, 트랜잭션 가스는 서비스가 부담합니다.

이 API는 테스트넷 데모용입니다. 발급은 두 단계입니다 — /api/screen이 실제 심사를 돌려 케이스를 만들고, /api/issue는 통과한 케이스에만 붙습니다. 심사를 건너뛰는 경로는 없습니다.

POST /api/screen

발급 전 심사입니다. 검사 7종을 순서대로 돌리고 케이스를 만들어 케이스 번호를 돌려줍니다. 이름은 명단 대사에만 쓰고 저장하지 않습니다 — 케이스에는 솔티드 해시만 남습니다.

요청

json
{
  "address": "0x…",       // 필수 — 심사 대상 지갑
  "name": "Gildong Hong", // 필수 — 제재 명단 대사용. 저장하지 않음
  "dob": "1990-05-05",    // 선택 — 동명이인 판별에 쓰임
  "nationality": "KR",    // 선택 — ISO 3166-1 alpha-2
  "residency": "KR"
}

검사 순서

검사내용실패 시
OWNERSHIP지갑 소유권 서명 (발급 단계에서 검증)FAIL
IDENTITY선언 정보의 형식·범위 정합성FAIL
ADDRESSOFAC 디지털자산 주소 정확 일치 대사FAIL
SANCTIONSOFAC·UN 이름 대사 (95점 이상 차단 / 85점 이상 검토)FAIL 또는 HIT
EXPOSURE제재 지정 주소와의 온체인 접촉HIT
JURISDICTIONFATF 조치 촉구 관할FAIL
RISK위험평가 종합 — 등급이 만료·재심사 주기를 결정등급 4 이상이면 HIT

응답

json
{
  "caseNo": "ONB-20260729-0001",
  "decision": "ALLOW",         // ALLOW | REVIEW | BLOCK
  "status": "AUTO_CLEARED",
  "checks": [ { "kind": "SANCTIONS", "status": "PASS", "summary": "…", "ms": 107 }, … ],
  "risk": { "score": 22, "band": 1, "bandLabel": "낮음", "ttlDays": 365, "rescreenDays": 365, … },
  "screening": { "topScore": 0, "candidates": 84, "scanned": 20168, "listVersion": "0x…" },
  "screenedAt": "2026-07-29T…"
}

FAIL 하나면 BLOCK, HIT 하나면 REVIEW입니다. 자동 승인은 전 검사가 통과했을 때만이고, BLOCK·REVIEW 케이스로는 발급이 나가지 않습니다.

POST /api/issue

지정한 지갑에 KYC 어테스테이션을 발급(또는 승급)합니다.

요청

json
{
  "address": "0x…",     // 필수 — 발급 대상 지갑 주소
  "level": 1,           // 선택 — 1(본인 선언)만 허용. 벤더 등급은 연동 후 개방
  "force": false,       // 선택 — 이미 활성이어도 강제 재발급
  "caseNo": "ONB-…",    // 필수 — /api/screen이 준 통과 케이스. 15분 내, 1회만 사용 가능
  "claimsRoot": "0x…",  // 필수 — 대상자 브라우저가 만든 클레임 커밋먼트(bytes32).
                        //   신원 원본은 서버로 오지 않는다
  "issuedAt": "…",      // 필수 — 서명 시각(ISO 8601), 10분 내 유효
  "signature": "0x…"    // 필수 — 대상 지갑의 EIP-191 서명. 원문은 아래 참조
}

서명 원문 (EIP-191 personal_sign)

text
호패 발급 요청
이 서명은 지갑 소유권 증명이에요. 가스가 들지 않아요.
wallet: <address 소문자>
level: L<level>
claimsRoot: <claimsRoot 소문자>
issued-at: <issuedAt ISO 8601>

서버가 같은 원문을 재구성해 서명을 검증하고, 복구된 서명자가 address와 일치할 때만 발급합니다. 원문에 level·claimsRoot·시각이 바인딩되어 있어 다른 요청에 재사용할 수 없고, issuedAt이 10분을 지나면 거절됩니다.

동작

조건결과
요청 등급 이상으로 이미 활성트랜잭션 없이 { "alreadyActive": true } 반환 (force로 우회 가능).
force 재발급기존 어테스테이션을 같은 트랜잭션에서 폐기(supersede)하고 새로 발급.
가스 잔고가 낮은 지갑소량의 가스 지원(stipend)을 먼저 전송.

응답 (성공)

json
{
  "ok": true,
  "txHash": "0x…",            // 발급 트랜잭션
  "uid": "0x…",               // EAS 어테스테이션 UID
  "level": 1,
  "supersededUid": "0x…",     // 재발급으로 폐기된 구 UID (없으면 null)
  "evidence": { … },          // 증적 레코드 원문 (심사 체크·케이스 ID·타임스탬프)
  "evidenceHash": "0x…",      // 위 레코드의 keccak256 — 온체인 기록과 일치
  "stipendTxHash": "0x…",     // 가스 지원 tx (없으면 null)
  "claimsRoot": "0x…",        // 온체인에 기록된 커밋먼트 루트 (스키마 v2)
  "auditRef": "0x…"           // 감사 봉투 해시 (스키마 v2)
}

evidence는 앞 레코드의 해시(prevHash)를 품은 증적 체인의 한 항목입니다. 키 순서를 정렬한 정규 JSON을 keccak256하면 온체인 evidenceHash와 일치하며, POST /api/evidence가 그 계산을 대신 해 줍니다.

POST /api/revoke

연결한 지갑이 자기 호패를 철회하는 경로입니다. 발급과 동일한 EIP-191 소유권 서명을 요구하고, 사유 코드는 USER_REQUEST로 기록됩니다. 제재 적중에 의한 폐기는 이 경로가 아니라 재대사 데몬이 발급 키로 실행합니다.

요청

json
{
  "address": "0x…",       // 필수 — 폐기 대상 지갑 (서명 주체와 동일해야 함)
  "requestedAt": "…",     // 필수 — ISO 8601, 서명 원문에 포함 (10분 내 유효)
  "signature": "0x…"      // 필수 — revokeMessage()의 EIP-191 서명
}

응답 (성공)

json
{
  "ok": true,
  "txHash": "0x…",      // 폐기 트랜잭션
  "record": { … },      // 폐기 사유 레코드 원문
  "reasonHash": "0x…"   // 레코드의 keccak256 — 온체인 기록과 일치
}

공통 사항

항목내용
레이트리밋클라이언트당 5분에 10회 (엔드포인트별). 초과 시 429.
에러 응답{ "error": "메시지" } — 400(형식·주소·등급·서명 만료 오류), 401(소유권 서명 불일치), 404(폐기 대상 없음), 429(레이트리밋), 500(트랜잭션 실패).
트랜잭션 직렬화발급 키의 nonce 경합을 막기 위해 서버에서 발급·폐기 tx를 직렬 처리합니다.
증적 영속화증적 체인은 현재 서버 인스턴스 메모리에 있습니다. 사본이 발급 응답으로 대상자에게 전달되므로 저장소 없이도 검증은 성립합니다. WORM 저장소는 다음 단계입니다.

발급 권한 모델: 온체인 발급은 레지스트리에 등록된 issuer 키만 가능하고, 그 키는 이 서버에만 존재합니다. SDK는 읽기 전용이며 발급 능력이 없습니다.

POST /api/evidence · GET /api/evidence?hash=

증적 검증기입니다. GET은 저장소에서 레코드를 찾아 주고, POST는 건네받은 사본의 해시를 다시 계산합니다. 후자는 저장소가 없어도, 우리를 믿지 않아도 성립하는 경로입니다 — 발급 때 받은 사본과 온체인 evidenceHash만 있으면 됩니다.

json
// 요청
{ "record": { … }, "expected": "0x…" }

// 응답
{ "computed": "0x…", "expected": "0x…", "match": true }

GET /api/cron/rescreen · POST

일일 재대사입니다. 활성 로스터를 온체인 이벤트에서 재구성해 제재 지정 주소·온체인 접촉으로 다시 대사하고, 적중하면 증적을 만들어 폐기 트랜잭션을 냅니다. GET은 Vercel Cron이 매일 00:00 KST에 호출하며 CRON_SECRET으로 보호됩니다. POST는 콘솔의 즉시 실행 버튼이 쓰는 같은 코드입니다.

json
{
  "runId": "RSC-…",
  "scanned": 12,                       // 대사한 활성 어테스테이션 수
  "hits": [ { "subject": "0x…", "reason": "OFAC 제재 지정 주소 — …" } ],
  "revoked": [ "0x…" ],
  "listVersion": "0x…",
  "trigger": "cron"
}

GET /api/aml

콘솔이 읽는 운영 상태입니다. 명단 소스·신선도, 재대사 이력, 케이스, 증적 체인 무결성, KPI가 들어 있습니다. 화면의 숫자는 전부 이 응답에서 나옵니다.