ERC-8001 Agent coordination framework
Final · Other · created 2025-08-02
A framework in which multiple automated programs (agents) agree on a single task without a trusted intermediary. A proposer posts an intent, and it can be executed only when all participants accept it with signatures.
Why it is needed
In decentralized finance (DeFi), blockchain games, and automated commerce, situations often arise in which multiple agents must act together without a trusted coordinator. However, the existing intent standards ERC-7521 and ERC-7683 define only flows initiated by a single party and do not specify how to verify that multiple parties have actually agreed. As a result, projects building multi-party tasks had to design their own agreement verification methods, and each also had to guard against signatures being reused elsewhere (replay) and participant lists being manipulated. This standard fills that gap with the smallest possible onchain mechanism. It gathers the proposer's EIP-712 signed intent and each participant's acceptance signature, and unifies the rules so that the intent becomes executable only when all required acceptances have been collected and are valid.
What to implement
event CoordinationProposed(bytes32 indexed intentHash, address indexed proposer, bytes32 coordinationType, uint256 participantCount, uint256 coordinationValue);: Announces the intent hash, proposer, coordination type, number of participants, and coordination value when a new coordination intent is proposed.event CoordinationAccepted(bytes32 indexed intentHash, address indexed participant, bytes32 acceptanceHash, uint256 acceptedCount, uint256 requiredCount);: Announces, when a single participant accepts, the number accepted so far together with the number required.event CoordinationExecuted(bytes32 indexed intentHash, address indexed executor, bool success, uint256 gasUsed, bytes result);: Announces the executor, whether it succeeded, the gas used, and the result when a coordination is executed.event CoordinationCancelled(bytes32 indexed intentHash, address indexed canceller, string reason, uint8 finalStatus);: Announces the address that cancelled, the reason, and the final status when a coordination is cancelled.function proposeCoordination(AgentIntent calldata intent, bytes calldata signature, CoordinationPayload calldata payload) external returns (bytes32 intentHash);: The proposer submits a signed intent and payload to propose a coordination and receives back the intent hash.function acceptCoordination(bytes32 intentHash, AcceptanceAttestation calldata attestation) external returns (bool allAccepted);: A participant submits an acceptance attestation. Returns true when all participants have accepted.function executeCoordination(bytes32 intentHash, CoordinationPayload calldata payload, bytes calldata executionData) external returns (bool success, bytes memory result);: Executes an intent for which all acceptances have been collected, together with execution data, and returns whether it succeeded and the result.function cancelCoordination(bytes32 intentHash, string calldata reason) external;: Cancels the coordination. Before expiry only the proposer can call it, and after expiry anyone can.function getCoordinationStatus(bytes32 intentHash) external view returns (Status status, address proposer, address[] memory participants, address[] memory acceptedBy, uint256 expiry);: Looks up the intent's current status, the proposer, the participant list, the list of accepters, and the expiry time.function getRequiredAcceptances(bytes32 intentHash) external view returns (uint256);: Looks up the number of acceptances required for execution. It must equal the number of participants at the time of the proposal.function getAgentNonce(address agent) external view returns (uint64);: Looks up the agent's current nonce. A new intent must use a nonce greater than this value.
In depth
Three typed data structures and the domain
The standard defines three structures: AgentIntent, CoordinationPayload, and AcceptanceAttestation. AgentIntent holds payloadHash, expiry, nonce, agentId, coordinationType, coordinationValue, and participants, and participants MUST include agentId itself. CoordinationPayload holds version, coordinationType, coordinationData, conditionsHash, timestamp, and metadata, where coordinationData is opaque bytes from the core's point of view. AcceptanceAttestation holds intentHash, participant, nonce, expiry, conditionsHash, and signature. The EIP-712 domain is fixed as {name: "ERC-8001", version: "1", chainId, verifyingContract}, and exposing the domain through ERC-5267 is recommended.
Hash computation rules and getIntentHash
The participants array MUST be in strictly ascending order by uint160(address) with no duplicates, and participantsHash is computed as keccak256(abi.encodePacked(participants)). getIntentHash(intent) MUST return the struct hash (intentStructHash), not the full signing digest, and AcceptanceAttestation.intentHash MUST also be that struct hash. Each acceptance signs its own EIP-712 digest that includes this intentHash field. The field names of ACCEPTANCE_TYPEHASH must exactly match the Solidity struct.
Six status codes
The implementation must use enum Status { None, Proposed, Ready, Executed, Cancelled, Expired } as is. None is the default value when no intent exists, Proposed means the intent has been proposed but not all acceptances have been collected, Ready means all participants have accepted and it can be executed, Executed means execution is complete, Cancelled means explicit cancellation, and Expired means it expired without being executed or cancelled. getCoordinationStatus must return exactly this value in each situation.
proposeCoordination validation rules
It must revert if the signature does not verify the intent under the ERC-8001 domain, if intent.expiry is less than or equal to block.timestamp, if intent.nonce is not greater than getAgentNonce(intent.agentId), if participants is not a strictly ascending list without duplicates, or if agentId is not in participants. On success it emits a CoordinationProposed event, the status becomes Proposed, the stored nonce equals the submitted nonce, and getRequiredAcceptances must equal the number of participants.
Conditions for acceptance and execution
acceptCoordination reverts when the intent does not exist or has expired, when the caller is not a participant, when the caller has already accepted, when the signature is invalid, or when attestation.expiry is less than or equal to the current time. executeCoordination reverts when the status is not Ready, when the intent has expired, when any one of the acceptances has expired, or when the hash of the submitted payload differs from payloadHash. If the execution is valid, it attempts the action described by executionData, returns (success, result), and the status becomes Executed. The validity period of an acceptance is checked both at acceptance time and at execution time.
Cancellation authority and nonce rules
Before expiry only the proposer can cancel, and after expiry anyone can cancel. When cancelled, a CoordinationCancelled event is emitted and the status becomes Cancelled. The nonce must increase with each valid new intent, and a nonce that is not greater than the stored value must be rejected. The nonce on the acceptance side is optional, but if implemented it must be strictly monotonically increasing per participant. It is recommended to signal errors with descriptive custom errors such as ERC8001_ExpiredIntent, ERC8001_BadSignature, ERC8001_NotParticipant, ERC8001_DuplicateAcceptance, and ERC8001_PayloadHashMismatch.
Intentionally omitted
This standard does not cover privacy, reputation, threshold policies, bonding, or cross-chain semantics. These features are left to be built as optional modules that reference this specification. The scope is narrowed to a single chain to avoid coupling with bridge semantics and keep the base primitive easy to audit. Sorting the participant list removes hash malleability and enables off-chain deduplication, and separating the intent from the acceptance lets signatures be collected off-chain so that the on-chain check is reduced to a single pass.
Things to watch out for
- Replay attacks: EIP-712 domain binding and monotonically increasing nonces prevent a signature from being reused on another contract.
- Signature malleability: Enforcing low-s values and supporting 64-byte and 65-byte signatures are required.
- Equivocation: A participant can sign conflicting intents. This is mitigated by module-level slashing or reputation.
- Liveness: Both the intent and the acceptance must have a validity period (TTL), and the executor must check that enough time remains.
- MEV exposure: If coordinationData reveals a strategy, a privacy module that uses commit-reveal or encryption should be used.