ERC-8001 에이전트 조정 프레임워크 (Agent Coordination Framework)
Final · 기타 · 2025-08-02 작성
여러 자동화 프로그램(에이전트)이 믿을 만한 중개자 없이 하나의 작업에 합의하는 틀입니다. 제안자가 의도(인텐트)를 올리고 모든 참여자가 서명으로 수락하면 그때만 실행할 수 있습니다.
왜 필요한가
탈중앙 금융(DeFi)이나 블록체인 게임, 자동화 상거래에서는 여러 에이전트가 믿을 만한 조정자 없이 함께 움직여야 하는 상황이 자주 생깁니다. 그런데 기존 인텐트 표준인 ERC-7521과 ERC-7683은 한 주체가 시작하는 흐름만 정의하고 여러 당사자가 실제로 동의했는지 확인하는 방법은 정하지 않았습니다. 그래서 다자간 작업을 만들려는 프로젝트는 합의 확인 방식을 저마다 따로 설계해야 했고 서명이 다른 곳에서 재사용(리플레이)되거나 참여자 목록이 조작될 위험도 각자 막아야 했습니다. 이 표준은 그 빈틈을 메우는 가장 작은 온체인 장치를 정합니다. 제안자의 EIP-712 서명 인텐트와 참여자별 수락 서명을 모아 필요한 수락이 모두 유효하게 모였을 때만 실행 가능 상태가 되도록 규칙을 통일합니다.
구현해야 하는 것
event CoordinationProposed(bytes32 indexed intentHash, address indexed proposer, bytes32 coordinationType, uint256 participantCount, uint256 coordinationValue);: 새 조정 인텐트가 제안되었을 때 인텐트 해시, 제안자, 조정 유형, 참여자 수, 조정 가치를 알립니다.event CoordinationAccepted(bytes32 indexed intentHash, address indexed participant, bytes32 acceptanceHash, uint256 acceptedCount, uint256 requiredCount);: 참여자 한 명이 수락했을 때 현재까지 수락한 수와 필요한 수를 함께 알립니다.event CoordinationExecuted(bytes32 indexed intentHash, address indexed executor, bool success, uint256 gasUsed, bytes result);: 조정이 실행되었을 때 실행자, 성공 여부, 사용 가스, 결과를 알립니다.event CoordinationCancelled(bytes32 indexed intentHash, address indexed canceller, string reason, uint8 finalStatus);: 조정이 취소되었을 때 취소한 주소, 사유, 최종 상태를 알립니다.function proposeCoordination(AgentIntent calldata intent, bytes calldata signature, CoordinationPayload calldata payload) external returns (bytes32 intentHash);: 제안자가 서명한 인텐트와 페이로드를 제출해 조정을 제안하고 인텐트 해시를 돌려받습니다.function acceptCoordination(bytes32 intentHash, AcceptanceAttestation calldata attestation) external returns (bool allAccepted);: 참여자가 수락 증명을 제출합니다. 모든 참여자가 수락을 마치면 true를 반환합니다.function executeCoordination(bytes32 intentHash, CoordinationPayload calldata payload, bytes calldata executionData) external returns (bool success, bytes memory result);: 모든 수락이 모인 인텐트를 실행 데이터와 함께 실행하고 성공 여부와 결과를 반환합니다.function cancelCoordination(bytes32 intentHash, string calldata reason) external;: 조정을 취소합니다. 만료 전에는 제안자만, 만료 후에는 누구나 호출할 수 있습니다.function getCoordinationStatus(bytes32 intentHash) external view returns (Status status, address proposer, address[] memory participants, address[] memory acceptedBy, uint256 expiry);: 인텐트의 현재 상태와 제안자, 참여자 목록, 수락자 목록, 만료 시각을 조회합니다.function getRequiredAcceptances(bytes32 intentHash) external view returns (uint256);: 실행에 필요한 수락 수를 조회합니다. 제안 시점에 참여자 수와 같아야 합니다.function getAgentNonce(address agent) external view returns (uint64);: 에이전트의 현재 논스를 조회합니다. 새 인텐트는 이 값보다 큰 논스를 써야 합니다.
심층 설명
세 가지 타입 데이터 구조와 도메인
표준은 AgentIntent, CoordinationPayload, AcceptanceAttestation 세 구조를 정의합니다. AgentIntent는 payloadHash, expiry, nonce, agentId, coordinationType, coordinationValue, participants를 담고 participants에는 agentId 자신이 반드시 포함되어야 합니다. CoordinationPayload는 version, coordinationType, coordinationData, conditionsHash, timestamp, metadata를 담으며 coordinationData는 코어 입장에서 불투명한 바이트입니다. AcceptanceAttestation은 intentHash, participant, nonce, expiry, conditionsHash, signature를 담습니다. EIP-712 도메인은 {name: "ERC-8001", version: "1", chainId, verifyingContract}로 고정이고, ERC-5267로 도메인을 노출하는 것을 권장합니다.
해시 계산 규칙과 getIntentHash
participants 배열은 uint160(address) 기준으로 중복 없이 엄격한 오름차순이어야 하며 participantsHash는 keccak256(abi.encodePacked(participants))로 계산합니다. getIntentHash(intent)는 전체 서명 다이제스트가 아니라 구조체 해시(intentStructHash)를 반환해야 하고 AcceptanceAttestation.intentHash도 그 구조체 해시여야 합니다. 각 수락은 이 intentHash 필드를 포함한 자신만의 EIP-712 다이제스트에 서명합니다. ACCEPTANCE_TYPEHASH의 필드 이름은 Solidity 구조체와 정확히 일치해야 합니다.
여섯 가지 상태 코드
구현은 enum Status { None, Proposed, Ready, Executed, Cancelled, Expired }를 그대로 써야 합니다. None은 인텐트가 없는 기본값, Proposed는 제안되었지만 수락이 다 모이지 않은 상태, Ready는 모든 참여자가 수락해 실행 가능한 상태, Executed는 실행 완료, Cancelled는 명시적 취소, Expired는 실행이나 취소 없이 만료된 상태입니다. getCoordinationStatus는 각 상황에서 이 값을 정확히 반환해야 합니다.
proposeCoordination 검증 규칙
서명이 ERC-8001 도메인에서 인텐트를 검증하지 못하면, intent.expiry가 block.timestamp 이하이면, intent.nonce가 getAgentNonce(intent.agentId)보다 크지 않으면, participants가 오름차순 중복 없는 목록이 아니면, agentId가 participants에 없으면 되돌려야(revert) 합니다. 성공하면 CoordinationProposed 이벤트를 내고 상태는 Proposed, 저장된 논스는 제출한 논스와 같아지며 getRequiredAcceptances는 참여자 수와 같아야 합니다.
수락과 실행의 조건
acceptCoordination은 인텐트가 없거나 만료됐을 때, 호출자가 참여자가 아닐 때, 이미 수락했을 때, 서명이 유효하지 않을 때, attestation.expiry가 현재 시각 이하일 때 되돌립니다. executeCoordination은 상태가 Ready가 아니거나, 인텐트가 만료됐거나, 수락 중 하나라도 만료됐거나, 제출한 페이로드의 해시가 payloadHash와 다르면 되돌립니다. 실행이 유효하면 executionData가 나타내는 동작을 시도하고 (success, result)를 반환하며 상태가 Executed가 됩니다. 수락의 유효 기간은 수락 시점과 실행 시점 모두에서 검사됩니다.
취소 권한과 논스 규칙
만료 전에는 제안자만 취소할 수 있고 만료 후에는 누구나 취소할 수 있습니다. 취소되면 CoordinationCancelled 이벤트가 나고 상태가 Cancelled가 됩니다. 논스는 유효한 새 인텐트마다 증가해야 하고 저장된 값보다 크지 않은 논스는 거부해야 합니다. 수락 쪽 논스는 선택 사항이지만 구현한다면 참여자별로 엄격히 단조 증가해야 합니다. 오류는 ERC8001_ExpiredIntent, ERC8001_BadSignature, ERC8001_NotParticipant, ERC8001_DuplicateAcceptance, ERC8001_PayloadHashMismatch 같은 설명적인 커스텀 오류로 내는 것을 권장합니다.
의도적으로 뺀 것들
이 표준은 프라이버시, 평판, 임계값(threshold) 정책, 담보(bonding), 크로스체인 의미론을 다루지 않습니다. 이런 기능은 이 명세를 참조하는 선택적 모듈로 만들도록 남겨 두었습니다. 단일 체인으로 범위를 좁힌 것은 브리지 의미론과의 결합을 피해 감사하기 쉬운 기본 장치로 유지하기 위해서입니다. 참여자 목록을 정렬하는 것은 해시 가변성을 없애고 오프체인 중복 제거를 가능하게 하며 인텐트와 수락을 분리한 것은 서명을 오프체인에서 모아 온체인 검사를 한 번으로 줄이기 위해서입니다.
쓸 때 주의할 점
- 리플레이 공격: EIP-712 도메인 바인딩과 단조 증가 논스로 다른 컨트랙트에서 서명이 재사용되는 것을 막습니다.
- 서명 가변성: low-s 값 강제와 64·65바이트 서명 지원이 필수입니다.
- 이중 서명(equivocation): 참여자가 서로 충돌하는 인텐트에 서명할 수 있습니다. 모듈 수준의 슬래싱이나 평판으로 완화합니다.
- 라이브니스: 인텐트와 수락 모두에 유효 기간(TTL)을 두어야 하고 실행자는 남은 시간이 충분한지 확인해야 합니다.
- MEV 노출: coordinationData가 전략을 드러낸다면 커밋 리빌이나 암호화를 쓰는 프라이버시 모듈을 사용해야 합니다.