ERC-8001 defines a minimal, single-chain primitive for multi-party agent coordination. An initiator posts an intent and each participant provides a verifiable acceptance attestation. Once the required set of acceptances is present and fresh, the intent is executable. The standard specifies typed data, lifecycle, mandatory events, and verification rules compatible with EIP-712, ERC-1271, EIP-2098, and EIP-5267.
ERC-8001 omits privacy, reputation, threshold policies, bonding, and cross-chain semantics. Those are expected as optional modules that reference this specification.
Motivation
Agents in DeFi/MEV/Web3 Gaming and Agentic Commerce often need to act together without a trusted coordinator. Existing intent standards (e.g., ERC-7521, ERC-7683) define single-initiator flows and do not specify multi-party agreement.
ERC-8001 specifies the smallest on-chain primitive for that gap: an initiator's EIP-712 intent plus per-participant EIP-712/EIP-1271 acceptances. The intent becomes executable only when the required set of acceptances is present and unexpired. Canonical (sorted-unique) participant lists and standard typed data provide replay safety and wallet compatibility. Privacy, thresholds, bonding, and cross-chain are left to modules.
Specification
The keywords “MUST”, “SHOULD”, and “MAY” are to be interpreted as described in RFC 2119 and RFC 8174.
Implementations MUST expose the following canonical status codes for getCoordinationStatus:
Status Codes
Implementations MUST use the canonical enum defined below:
enum Status { None, Proposed, Ready, Executed, Cancelled, Expired }
None = default zero state (intent not found)
Proposed = intent proposed, not all acceptances yet
Ready = all participants have accepted, intent executable
Executed = intent successfully executed
Cancelled = intent explicitly cancelled
Expired = intent expired before execution
Overview
This ERC specifies:
A canonicalised EIP-712 domain for agent coordination,
Typed data structures (AgentIntent, CoordinationPayload, AcceptanceAttestation),
Implementations SHOULD expose the domain via ERC-5267.
Primary Types
struct AgentIntent { bytes32 payloadHash; // keccak256(CoordinationPayload) uint64 expiry; // unix seconds; MUST be > block.timestamp at propose uint64 nonce; // per-agent nonce; MUST be > agentNonces[agentId] address agentId; // initiator and signer of the intent bytes32 coordinationType; // domain-specific type id, e.g. keccak256("MEV_SANDWICH_COORD_V1") uint256 coordinationValue; // informational in Core; modules MAY bind value address[] participants; // unique, ascending; MUST include agentId}struct CoordinationPayload { bytes32 version; // payload format id bytes32 coordinationType; // MUST equal AgentIntent.coordinationType bytes coordinationData; // opaque to Core bytes32 conditionsHash; // domain-specific uint256 timestamp; // creation time (informational) bytes metadata; // optional}struct AcceptanceAttestation { bytes32 intentHash; // getIntentHash(intent) address participant; // signer uint64 nonce; // optional in Core; see Nonces uint64 expiry; // acceptance validity; MUST be > now at accept and execute bytes32 conditionsHash; // participant constraints bytes signature; // ECDSA (65 or 64 bytes) or ERC-1271}
Typed Data Hashes
bytes32 constant AGENT_INTENT_TYPEHASH = keccak256( "AgentIntent(bytes32 payloadHash,uint64 expiry,uint64 nonce,address agentId,bytes32 coordinationType,uint256 coordinationValue,address[] participants)");bytes32 constant ACCEPTANCE_TYPEHASH = keccak256(// Field names MUST exactly match the Solidity struct. "AcceptanceAttestation(bytes32 intentHash,address participant,uint64 nonce,uint64 expiry,bytes32 conditionsHash)");// participants MUST be unique and strictly ascending by uint160(address). function _participantsHash(address[] memory ps) internal pure returns (bytes32) { return keccak256(abi.encodePacked(ps)); } function _agentIntentStructHash(AgentIntent calldata i) internal pure returns (bytes32) { return keccak256(abi.encode( AGENT_INTENT_TYPEHASH, i.payloadHash, i.expiry, i.nonce, i.agentId, i.coordinationType, i.coordinationValue, _participantsHash(i.participants) )); }// Full EIP-712 digest for the initiator’s signature. function _agentIntentDigest(bytes32 domainSeparator, AgentIntent calldata i) internal pure returns (bytes32) { return keccak256(abi.encodePacked("\x19\x01", domainSeparator, _agentIntentStructHash(i))); } function _acceptanceStructHash(AcceptanceAttestation calldata a) internal pure returns (bytes32) { // a.intentHash MUST be the AgentIntent struct hash, not the digest. return keccak256(abi.encode( ACCEPTANCE_TYPEHASH, a.intentHash, a.participant, a.nonce, a.expiry, a.conditionsHash )); } function _acceptanceDigest(bytes32 domainSeparator, AcceptanceAttestation calldata a) internal pure returns (bytes32) { return keccak256(abi.encodePacked("\x19\x01", domainSeparator, _acceptanceStructHash(a))); }
The functions defined in this specification MUST exhibit the following externally observable behaviours.
This standard does NOT prescribe storage layout, execution model, or internal mechanisms.
proposeCoordination
proposeCoordination MUST revert if:
the signature does not validate the supplied AgentIntent under the ERC-8001 EIP-712 domain;
intent.expiry <= block.timestamp;
intent.nonce is not strictly greater than getAgentNonce(intent.agentId);
participants is not strictly ascending and unique;
intent.agentId is not included in the participants list.
If valid:
CoordinationProposed MUST be emitted;
getCoordinationStatus MUST report Proposed;
getAgentNonce(intent.agentId) MUST equal the supplied nonce;
getRequiredAcceptances(intentHash) MUST equal the number of participants.
acceptCoordination
acceptCoordination MUST revert if:
the intent does not exist or has expired;
the caller is not listed as a participant;
the participant has already accepted;
the attestation signature does not validate under the ERC-8001 domain;
attestation.expiry <= block.timestamp.
If valid:
CoordinationAccepted MUST be emitted;
the participant MUST appear in the acceptedBy list returned by getCoordinationStatus;
if all participants have accepted:
the function MUST return true;
status MUST be Ready.
Otherwise the function MUST return false.
executeCoordination
executeCoordination MUST revert if:
the intent is not in Ready state;
intent.expiry <= block.timestamp;
any acceptance has expired;
the supplied payload does not hash to payloadHash.
If valid:
the implementation MUST attempt execution of the behaviour represented by executionData;
the function MUST return (success, result);
CoordinationExecuted MUST be emitted;
getCoordinationStatus MUST report Executed.
cancelCoordination
If the intent has not expired, only the proposer MUST be permitted to cancel.
After expiry, any caller MUST be permitted to cancel.
On success:
CoordinationCancelled MUST be emitted;
status MUST be Cancelled.
getCoordinationStatus
getCoordinationStatus(intentHash) MUST return:
None if the intent does not exist;
Proposed if not all participants have accepted and the intent has not expired;
Ready if all participants have accepted and expiries have not elapsed;
Executed if execution has occurred;
Cancelled if cancellation has occurred;
Expired if the intent has expired and was not executed or cancelled.
Nonces
getAgentNonce(agent) MUST increase for every valid new intent.
proposeCoordination MUST reject nonces not strictly greater than the stored nonce.
Acceptance-level nonces MAY be implemented; if so, they MUST be strictly monotonic per participant.
Errors
Implementations SHOULD revert with descriptive custom errors (or equivalent revert strings) for the following baseline conditions, and MAY define additional errors for domain-specific modules (e.g. slashing, reputation, or privacy conditions):
Sorted participant lists remove hash malleability and allow off-chain deduplication.
Separation of intent and acceptance allows off-chain collation and a single on-chain check.
Keeping ERC-8001 single-chain avoids coupling to bridge semantics and keeps the primitive audit-friendly.
Wallet friendliness: EIP-712 arrays let signers see actual participant addresses.
Backwards Compatibility
ERC-8001 introduces a new interface. It is compatible with EOA and contract wallets via ECDSA and ERC-1271. It does not modify existing standards.
Reference Implementation
A permissive reference implementation is provided in contracts/AgentCoordination.sol. It uses a minimal ECDSA helper and supports ERC-1271 signers. It enforces participant canonicalisation, intent nonces, acceptance freshness, and all-participants policy.
Security Considerations
Replay: EIP-712 domain binding and monotonic nonces prevent cross-contract replay.
Malleability: Low-s enforcement and 64/65-byte signature support are required.
Equivocation: A participant can sign conflicting intents. Mitigate with module-level slashing or reputation.
Liveness: Enforce TTL on both intent and acceptances. Executors should ensure enough time remains.
MEV: If coordinationData reveals strategy, use a Privacy module with commit-reveal or encryption.