본문으로 건너뛰기

Agent 신원

AI Agent에 ERC-8004 표준을 따르는, 독립적으로 검증 가능한 온체인 신원을 제공합니다.

한 문장으로 요약하면: 먼저 Agent 지갑을 생성한 뒤 신원 등록 API를 한 번 호출합니다. 몇 초 후 누구나 직접 검증할 수 있는 온체인 Agent ID가 생성됩니다. 지갑의 온체인 연결은 등록 완료 후 별도로 요청해야 합니다.

이 장은 자체 시스템을 BOT Chain Agent 신원 서비스와 연동하는 개발자를 위한 문서입니다. 인증 정보 발급, 신원 등록, 지갑 연결 요청, 준비 완료 확인, 설명 정보 관리, 상대방이 여러분을 신뢰하지 않고도 신원을 검증하는 방법을 설명합니다.

이 장의 모든 경로는 다음 기본 URL 중 하나를 기준으로 합니다.

환경URL
BOT 메인넷https://identity-api.botchain.ai
Bohr 테스트넷https://identity-api.bohr.life

1. 개요​

질문답변
무엇을 받나요?온체인 agentId와 공개 게시된 JSON 설명 파일
인증 방법은 무엇인가요?요청 헤더 X-API-Key
등록은 동기식인가요?아니요. 생성은 즉시 202를 반환하고 온체인 작업은 백그라운드에서 진행됩니다
완료 여부는 어떻게 아나요?신원 상태를 폴링하거나 상태 변경 webhook을 받습니다
얼마나 걸리나요?보통 몇 초이며 블록 생성 시간에 따라 달라집니다
가스비는 누가 내나요?서비스에서 부담합니다. 연동 시스템은 프라이빗 키를 보유하지 않고 계정에 입금할 필요도 없습니다
제삼자가 무엇을 검증할 수 있나요?owner, Agent 지갑, 설명 파일 URL을 모두 체인에서 직접 읽을 수 있습니다
지원하는 체인은 무엇인가요?BOT 메인넷(chain ID 677)과 Bohr 테스트넷(chain ID 968)

다음을 기억하세요.

API로 Agent를 설명하고, 체인은 소유자와 사용하는 지갑을 기록합니다. 후자는 다른 사람이 여러분에게 묻지 않고 직접 조회할 수 있습니다.

2. 인증 정보 받기​

각 연동 주체는 프로젝트이며 프로젝트 ID와 하나 이상의 API 키를 가집니다. 키는 서비스에서 발급하며 현재 셀프서비스 발급 절차는 없습니다. 원하는 프로젝트 이름을 알려주시면 다음을 반환합니다.

project_id  launchpad
key_id k_01J8XV…
api_key ak_live_k_01J8XV….<secret>

project_id는 기록용으로 프로젝트 개통 시 함께 안내합니다. 업무 요청에 project_id를 보낼 필요가 없으며, 보내지 마세요. 서버는 API 키로 테넌트를 조회하므로 요청 본문을 바꿔 다른 프로젝트에 리소스를 생성할 수 없습니다.

API 키는 점으로 구분된 두 부분으로 구성됩니다.

ak_<environment>_k_<key_id>.<secret>

<environment>는 발급 환경 레이블(예: live, test)이며 project_id가 아닙니다. 프로젝트 소속은 서버의 api_keys 테이블에 저장되며 key_id와 연결됩니다.

점을 포함한 전체 문자열을 X-API-Key에 넣으세요. secret은 생성 시 한 번만 반환되며 이후 다시 조회할 수 없습니다. 즉시 시크릿 관리 시스템에 저장하세요. 분실하면 새로 발급하고 이전 키를 폐기해야 하며 복구 방법은 없습니다.

키는 환경별로 구분됩니다. 테스트넷 키는 메인넷에서 사용할 수 없습니다.

3. 신원 등록​

먼저 지갑을 생성하세요

신원은 특정 Agent 지갑을 위해 등록합니다. wallet_id와 wallet_address 모두 필수이며 기존 지갑에서 가져와야 합니다. 이 API를 호출하기 전에 지갑을 만드세요. Agent 지갑을 참고하세요.

하나의 wallet_id는 서비스 전체에서 하나의 신원에만 연결할 수 있습니다. 다른 신원에 재사용하면 409를 반환합니다.

POST /v1/agents/identities
Content-Type: application/json
X-API-Key: ak_…
Idempotency-Key: <멱등성-키>
{
"external_agent_id": "자체-agent-id",
"wallet_id": "wal_de43139cef869dc9c4…",
"chain_id": 677,
"owner_address": "0x5f5802fA78d5eE2A3B4fD67F49CD502430C3E88C",
"wallet_address": "0xd9DC4F46d9da603A2A077A7190BAa85f4e2f05fe",
"metadata": {
"name": "예제 Agent",
"description": "이 Agent가 하는 일"
},
"callback_url": "https://your-service.example/webhooks/identity"
}
필드필수의미
external_agent_id예자체 시스템의 Agent 식별자. 프로젝트와 체인 범위에서 고유해야 함
wallet_id예이 신원이 속한 Agent 지갑(서비스 전체에서 고유)
chain_id예메인넷 677, 테스트넷 968. 다른 값은 거부
owner_address예신원을 제어하는 주소. 유효한 EVM 주소여야 함
wallet_address예Agent가 거래에 사용하는 지갑 주소. 유효한 EVM 주소여야 함
metadata예자유 형식 설명. name이 존재하고 비어 있지 않아야 함
callback_url아니요상태 변경을 받을 절대 http/https URL. 생략하면 폴링 사용
idempotency_key예요청 본문 또는 Idempotency-Key 헤더로 전달 가능

요청 본문에는 프로젝트 ID나 키 자료가 없습니다. 프로젝트는 API 키에서 추출하므로, 다른 값을 전달해 다른 프로젝트에 신원을 생성할 수 없습니다.

external_agent_id는 (프로젝트, 체인) 범위에서 고유합니다. 같은 (프로젝트, 체인, external_agent_id)로 다시 생성하면 요청 내용이 달라도 기존 신원과 "replayed": true를 반환하며 두 번째 레코드를 만들지 않습니다. 다른 신원이 이미 사용 중인 wallet_id와 같은 영구 충돌만 409를 반환합니다.

202 Accepted를 반환합니다. 신원 레코드는 생성되었지만 온체인 작업은 아직 수행되지 않았습니다.

{
"identity": {
"identity_id": "idn_c39e39dd-1030-4c33-b888-5beeb876247d",
"project_id": "launchpad",
"external_agent_id": "자체-agent-id",
"wallet_id": "wal_de43139cef869dc9c4…",
"chain_id": 677,
"owner_address": "0x5f5802fa78d5ee2a3b4fd67f49cd502430c3e88c",
"wallet_address": "0xd9dc4f46d9da603a2a077a7190baa85f4e2f05fe",
"registry_address": "0xb43edfb9c7609cf645e932b2ff20f26f0d4488de",
"status": "PENDING",
"wallet_binding_status": "PENDING",
"created_at": "2026-09-08T06:12:44Z",
"updated_at": "2026-09-08T06:12:44Z"
},
"replayed": false
}

이후 모든 API에서 사용하므로 identity_id를 저장하세요. 생성 시에는 owner 가스비 충전과 이후의 register() / 최종 메타데이터 게시 작업만 대기열에 등록합니다. 지갑 연결은 자동으로 대기열에 등록하지 않습니다.

읽기와 쓰기 응답 구조가 다릅니다

쓰기 API는 신원 객체를 identity로 감싸지만 읽기 API는 객체 자체를 반환합니다. 생성 후에는 response.identity.status, GET 후에는 response.status를 사용하세요. 두 응답을 같은 방식으로 읽는 것이 가장 흔한 연동 오류입니다.

멱등성​

논리적인 생성 작업마다 고유한 Idempotency-Key를 사용하세요. 같은 키로 재요청하면 202가 아니라 200과 "replayed": true를 반환하므로 타임아웃 후 재시도해도 다른 신원이 생성되지 않습니다. 키는 자체 Agent ID에서 도출하고 HTTP 요청마다 무작위로 만들지 마세요. 매번 바꾸면 멱등성이 무의미해집니다.

4. 등록 완료 대기​

등록은 백그라운드에서 여러 단계를 거칩니다. 각 단계를 따로 처리할 필요 없이 status가 REGISTERED가 되는지만 확인하면 됩니다.

상태의미
PENDING접수 완료, 대기열에 있음
GAS_FUNDINGowner 계정에 가스비 충전 중
REGISTERINGregister() 제출 완료, 온체인 반영 대기
METADATA_PENDING최종 등록 파일 게시 또는 업데이트 중. 최초 등록 후와 이후 메타데이터 업데이트 시 모두 이 상태에 진입
REGISTERED등록 완료. erc8004_agent_id와 metadata_uri가 채워짐
WALLET_BINDING지갑 연결 트랜잭션 제출 완료, 온체인 확정 대기. 확정 후 REGISTERED로 복귀

wallet_binding_status는 독립적으로 진행됩니다. 온체인 연결이 완료되면 PENDING에서 CONFIRMED로 바뀝니다.

신원이 REGISTERED여도 연결을 아직 요청하지 않았거나 요청 후 트랜잭션이 아직 제출되지 않았다면 wallet_binding_status는 PENDING에 머무를 수 있습니다. 연결 트랜잭션이 온체인 확정을 기다리는 동안 status는 일시적으로 WALLET_BINDING이 됩니다.

4.1 폴링​

GET /v1/agents/identities/{identity_id}
X-API-Key: ak_…

읽기 API는 래퍼 없이 신원 객체를 직접 반환합니다.

{
"identity_id": "idn_c39e39dd-1030-4c33-b888-5beeb876247d",
"project_id": "launchpad",
"wallet_id": "wal_de43139cef869dc9c4…",
"status": "REGISTERED",
"wallet_binding_status": "CONFIRMED",
"erc8004_agent_id": "45",
"registry_address": "0xb43edfb9c7609cf645e932b2ff20f26f0d4488de",
"metadata_uri": "https://…/v2-….json",
"owner_address": "0x5f5802fa78d5ee2a3b4fd67f49cd502430c3e88c",
"wallet_address": "0xd9dc4f46d9da603a2a077a7190baa85f4e2f05fe",
"updated_at": "2026-09-08T06:12:51Z"
}

자체 식별자로 조회하면 서비스의 ID를 따로 저장하지 않아도 됩니다.

GET /v1/agents/by-external-id/{chain_id}/{external_agent_id}
GET /v1/agents/by-erc8004/{chain_id}/{registry_address}/{erc8004_agent_id}

세 API 모두 프로젝트에 속하지 않는 신원에는 404를 반환합니다. '내 것이 아님'과 '존재하지 않음'이 같은 응답인 것은 의도된 설계입니다.

4.2 콜백(Webhook)​

callback_url을 지정하면 상태가 바뀔 때마다 POST로 전송합니다.

POST <지정한 callback_url>
Content-Type: application/json
X-Callback-Token: <프로젝트 개통 시 합의한 토큰>

X-Callback-Token은 프로젝트 개통 시 API 키와 함께 합의하며 신원 서비스 worker에 설정됩니다. 신원을 생성할 때마다 새로 발급하는 토큰이 아닙니다. 수신자는 개통 시 받은 동일한 값으로 검증해야 합니다.

{
"identity_id": "idn_c39e39dd-1030-4c33-b888-5beeb876247d",
"external_agent_id": "자체-agent-id",
"chain_id": 677,
"status": "REGISTERED",
"wallet_binding_status": "CONFIRMED",
"erc8004_agent_id": "45",
"registry_address": "0xb43edfb9c7609cf645e932b2ff20f26f0d4488de",
"metadata_uri": "https://…/v2-….json",
"updated_at": "2026-09-08T06:12:51Z"
}

수신자는 네 가지 규칙을 따라야 합니다.

  1. 먼저 X-Callback-Token을 검증하고 본문을 처리하세요. 상수 시간 비교를 사용합니다. 검증을 생략하면 URL을 아는 누구나 상태 변경을 위조할 수 있습니다.
  2. 신속하게 2xx를 반환하세요. 다른 응답은 실패로 간주해 재시도합니다. 실제 처리는 비동기로 수행하세요.
  3. 중복 전달을 처리해야 합니다. 같은 상태가 여러 번 도착할 수 있으며, identity_id와 updated_at을 멱등성 키로 사용하면 충분합니다.
  4. 재시도로 순서가 바뀔 수 있습니다. 저장된 레코드보다 updated_at이 오래된 페이로드는 무시하세요.

재시도 간격은 15초, 1분, 5분이며 이후에는 15분마다 재시도합니다. 전달을 포기하지 않습니다. 장기간 실패하는 엔드포인트는 상태 변경이 버려지는 대신 재시도가 계속 쌓입니다. 콜백은 폴링을 최적화하며 대체하지 않습니다. 엔드포인트가 한 시간 동안 중단되어도 폴링하면 현재 상태를 즉시 받습니다.

5. 설명 정보 업데이트​

PUT /v1/agents/identities/{identity_id}/metadata
X-API-Key: ak_…
{
"metadata": { "name": "예제 Agent", "description": "업데이트된 설명" },
"idempotency_key": "update-2026-09-08-1"
}

새 버전의 등록 파일을 게시하고 온체인 URI를 갱신합니다. 202와 {"identity": …, "replayed": false}를 반환하며, 같은 멱등성 키로 재요청하면 200과 "replayed": true를 반환합니다.

접수 후 신원의 status는 METADATA_PENDING으로 돌아가며, setAgentURI가 확정되면 REGISTERED가 됩니다. metadata_uri의 변경을 확인하거나 status가 REGISTERED로 돌아올 때까지 기다리면 반영 여부를 알 수 있습니다.

여기서는 agentId, owner, walletAddress, registrations를 입력할 수 없습니다. 이 값들은 온체인에서 결정됩니다. 서비스가 현재 온체인 값을 새 버전마다 병합하므로, 임의의 owner나 agentId를 소유한다고 주장하는 파일을 게시할 수 없습니다.

지갑 연결 요청​

POST /v1/agents/identities/{identity_id}/wallet-binding
Content-Type: application/json
X-API-Key: ak_…

{ "idempotency_key": "bind-2026-09-08-1" }

요청 본문에는 멱등성 키만 넣습니다. 주소나 서명은 필요 없습니다. 지갑은 신원 생성 시 이미 정해졌으며, 온체인 승인 서명은 지갑 키를 보유한 서비스 측에서 생성합니다.

신원을 생성해도 연결 작업은 자동으로 대기열에 등록되지 않습니다. 신원의 status가 REGISTERED가 된 뒤 이 API를 호출하세요. 너무 일찍 호출하면 425를 반환합니다. 최초 연결과 PENDING에 머무는 연결 작업을 다시 대기열에 등록할 때 모두 사용합니다. 202 또는 200과 "replayed": true를 반환합니다.

wallet_binding_status가 CONFIRMED로 바뀌면 완료입니다.

일시 중지​

POST /v1/agents/identities/{identity_id}/suspend
X-API-Key: ak_…

200과 {"identity": …}를 반환합니다. 일시 중지된 신원은 새로운 등록 단계, 메타데이터 업데이트, 지갑 연결 등 새 작업을 받지 않으며 suspended_at이 설정됩니다. 이미 브로드캐스트된 트랜잭션은 계속 확정 절차를 진행합니다.

온체인 신원에는 영향이 없습니다. 일시 중지는 플랫폼 측 상태이며 온체인 작업이 아니므로, 제삼자가 레지스트리를 읽으면 여전히 유효한 신원으로 보입니다.

6. 오류 처리​

오류 본문에는 error 문자열이 포함됩니다. 재시도 여부는 HTTP 상태 코드로 판단하세요.

상태 코드의미재시도?
400요청 형식 오류 또는 필수 필드 누락아니요. 요청 수정
401API 키 누락 또는 무효아니요
404신원이 없거나 다른 프로젝트 소속아니요
409다른 신원이 이미 wallet_id를 사용 중인 경우 등 영구 충돌아니요
425신원이 필요한 상태에 아직 도달하지 않음예. 나중에 재시도
5xx서비스 측 오류예. 백오프 후 재시도

425 Too Early에 특히 주의하세요. 작업 자체는 유효하지만 신원이 실행 가능한 단계에 도달하지 않았다는 의미입니다. 예를 들어 등록 완료 전에 지갑 연결을 요청한 경우입니다. 409와 비슷해 보여도 의미는 반대입니다. 409는 중단, 425는 나중에 재시도하라는 뜻입니다. 425를 영구 실패로 처리하면 성공할 수 있는 신원을 미리 포기하게 됩니다.

7. 온체인 신원 검증​

지금까지는 서비스 API로 서비스의 기록을 설명했습니다. ERC-8004의 가치는 제삼자가 서비스를 신뢰하지 않고도 핵심 정보를 검증할 수 있다는 데 있습니다. Agent가 자신의 것임을 증명해 달라는 요청을 받으면 이 절을 전달하세요.

아래 호출은 모두 읽기 전용입니다. 프라이빗 키, 가스, 계정이 필요하지 않습니다.

7.1 세 가지 핵심 조회​

확인할 내용호출반환
Agent를 제어하는 주체ownerOf(uint256 agentId)address
사용하는 지갑getAgentWallet(uint256 agentId)address
설명 정보 위치tokenURI(uint256 agentId)string
import { ethers } from 'ethers'

const provider = new ethers.JsonRpcProvider('https://rpc.botchain.ai')
const registry = new ethers.Contract(
'0xB43Edfb9C7609cF645e932B2fF20f26F0d4488dE',
[
'function ownerOf(uint256) view returns (address)',
'function getAgentWallet(uint256) view returns (address)',
'function tokenURI(uint256) view returns (string)',
],
provider,
)

const agentId = 45n
console.log(await registry.ownerOf(agentId)) // 0x5f5802fA78d5eE2A3B4fD67F49CD502430C3E88C
console.log(await registry.getAgentWallet(agentId)) // 0xd9DC4F46d9da603A2A077A7190BAa85f4e2f05fe
console.log(await registry.tokenURI(agentId)) // https://…/v2-….json

의존성을 추가하지 않으려면 JSON-RPC를 직접 사용해도 됩니다.

# ownerOf(45), 선택자 0x6352211e, 인수를 32바이트로 패딩
curl -s https://rpc.botchain.ai \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{
"to":"0xB43Edfb9C7609cF645e932B2fF20f26F0d4488dE",
"data":"0x6352211e000000000000000000000000000000000000000000000000000000000000002d"
},"latest"]}'

예제는 메인넷에 연결합니다. 테스트넷에서는 https://rpc.bohr.life와 8절의 IdentityRegistry 주소로 변경하세요. 나머지는 동일합니다.

두 가지 동작을 알아두세요.

  • 등록되지 않은 ID에 대해 ownerOf는 제로 주소를 반환하지 않고 revert합니다. 이 revert는 'Agent가 없음'으로 처리하되 네트워크 오류와 구분하세요.
  • getAgentWallet은 ID 존재 여부를 검증하지 않습니다. 미등록 ID와 등록되었지만 지갑이 연결되지 않은 ID 모두에 제로 주소를 반환합니다. 존재 여부는 getAgentWallet이 아닌 ownerOf로 확인하세요.

7.2 등록 파일​

tokenURI는 ERC-8004 등록 파일 형식의 JSON을 가리킵니다.

{
"type": "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
"name": "예제 Agent",
"description": "이 Agent가 하는 일",
"owner": "0x5f5802fa78d5ee2a3b4fd67f49cd502430c3e88c",
"walletAddress": "0xd9dc4f46d9da603a2a077a7190baa85f4e2f05fe",
"registrations": [
{
"agentId": "45",
"agentRegistry": "eip155:677:0xb43edfb9c7609cf645e932b2ff20f26f0d4488de"
}
]
}

이 파일을 신뢰하지 말고 검증하세요. tokenURI로 얻지 않은 URL에서 제공하는 파일은 증거가 되지 않습니다. 올바른 절차는 다음과 같습니다.

  1. 레지스트리에서 tokenURI(agentId)를 읽습니다.
  2. 다른 URL로 바꾸지 말고 해당 URL을 요청합니다.
  3. registrations[0].agentId가 조회 대상 ID와 같고 registrations[0].agentRegistry가 호출한 레지스트리를 가리키는지 확인합니다.
  4. owner가 ownerOf와, walletAddress가 getAgentWallet과 일치하는지 확인합니다.

파일과 체인이 다르면 체인을 우선합니다. 레지스트리는 소유권과 지갑 연결의 권위 있는 정보원이며, 파일은 그와 일치해야 하는 설명일 뿐입니다.

7.3 시간에 따라 바뀌는 값​

변경온체인 메서드다시 읽을 값
설명 정보 업데이트setAgentURItokenURI
지갑 연결 또는 교체setAgentWalletgetAgentWallet
지갑 연결 해제unsetAgentWalletgetAgentWallet
소유권 이전transferFromownerOf

URI가 바뀔 수 있으므로 등록 파일을 무기한 캐시하지 마세요. URI를 캐시 키로 사용하고 최신 데이터가 필요하면 tokenURI를 다시 읽으세요.

지갑 연결은 두 가지 서명 검증 방식을 지원하므로 다음 두 계정 유형 모두 사용할 수 있습니다.

지갑 유형검증 방식
EIP-7702 위임 EOA를 포함한 EOAECDSA
컨트랙트 지갑(ERC-4337 계정, Safe 등)ERC-1271

Agent 지갑은 보통 EOA가 아닌 스마트 계정이므로 이 점이 중요합니다.

8. 환경 및 컨트랙트 주소​

항상 프록시(proxy) 주소를 호출하세요. 구현 컨트랙트 주소는 업그레이드 시 변경됩니다.

신원 서비스는 현재 IdentityRegistry의 등록, URI 업데이트, 지갑 연결만 처리합니다. 아래 Reputation / Validation 주소는 온체인에 배포된 ERC-8004 관련 컨트랙트이지만 이 서비스는 현재 해당 컨트랙트를 호출하지 않습니다. 제삼자가 자체 평판 또는 검증 절차를 연동할 때 참고할 수 있으며, 신원 등록 자체에는 IdentityRegistry만 필요합니다.

BOT 메인넷(chain ID 677)​

항목주소
RPChttps://rpc.botchain.ai
블록 탐색기https://scan.botchain.ai
IdentityRegistry0xB43Edfb9C7609cF645e932B2fF20f26F0d4488dE
ReputationRegistry0xB3f7061354193a424856Cc63028cE3556262dAc9
ValidationRegistry0x85Bf081D1492393C3Fb2742b379b6ADbC7dbC79b

Bohr 테스트넷(chain ID 968)​

항목주소
RPChttps://rpc.bohr.life
블록 탐색기https://scan.bohr.life
IdentityRegistry0xec8fFbC3c9A34AdDCbB3A14F91db2bd26A8b99c0
ReputationRegistry0xD4084c9397adca7fD0c24617681a2eA4C286402A
ValidationRegistry0x0ff64E68C0e5fB2c57E6B3e1D7F3c65FBD0eEA33

agentId는 레지스트리별로 부여됩니다. 메인넷 Agent 45와 테스트넷 Agent 45는 서로 무관한 신원입니다. agentId를 전달할 때는 반드시 chain ID를 함께 보내세요. 등록 파일의 agentRegistry가 이를 위해 존재합니다.

레지스트리는 완전한 ERC-721 구현이며 이름은 AgentIdentity(AGENT)입니다. 버전은 일반적인 version()이 아니라 컨트랙트의 getVersion()으로 조회하세요. 현재 두 체인 모두 2.0.0입니다. 따라서 NFT 표시를 지원하는 지갑과 탐색기는 별도 수정 없이 Agent 신원을 표시할 수 있습니다.