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 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 above:
Implementations SHOULD expose the domain via ERC-5267.
Primary Types
structAgentIntent{bytes32payloadHash;// keccak256(CoordinationPayload)
uint64expiry;// unix seconds; MUST be > block.timestamp at propose
uint64nonce;// per-agent nonce; MUST be > agentNonces[agentId]
addressagentId;// initiator and signer of the intent
bytes32coordinationType;// domain-specific type id, e.g. keccak256("MEV_SANDWICH_COORD_V1")
uint256coordinationValue;// informational in Core; modules MAY bind value
address[]participants;// unique, ascending; MUST include agentId
}structCoordinationPayload{bytes32version;// payload format id
bytes32coordinationType;// MUST equal AgentIntent.coordinationType
bytescoordinationData;// opaque to Core
bytes32conditionsHash;// domain-specific
uint256timestamp;// creation time (informational)
bytesmetadata;// optional
}structAcceptanceAttestation{bytes32intentHash;// getIntentHash(intent)
addressparticipant;// signer
uint64nonce;// optional in Core; see Nonces
uint64expiry;// acceptance validity; MUST be > now at accept and execute
bytes32conditionsHash;// participant constraints
bytessignature;// ECDSA (65 or 64 bytes) or ERC-1271
}
Typed Data Hashes
bytes32constantAGENT_INTENT_TYPEHASH=keccak256("AgentIntent(bytes32 payloadHash,uint64 expiry,uint64 nonce,address agentId,bytes32 coordinationType,uint256 coordinationValue,address[] participants)");bytes32constantACCEPTANCE_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[]memoryps)internalpurereturns(bytes32){returnkeccak256(abi.encodePacked(ps));}function_agentIntentStructHash(AgentIntentcalldatai)internalpurereturns(bytes32){returnkeccak256(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(bytes32domainSeparator,AgentIntentcalldatai)internalpurereturns(bytes32){returnkeccak256(abi.encodePacked("\x19\x01",domainSeparator,_agentIntentStructHash(i)));}function_acceptanceStructHash(AcceptanceAttestationcalldataa)internalpurereturns(bytes32){// a.intentHash MUST be the AgentIntent struct hash, not the digest.
returnkeccak256(abi.encode(ACCEPTANCE_TYPEHASH,a.intentHash,a.participant,a.nonce,a.expiry,a.conditionsHash));}function_acceptanceDigest(bytes32domainSeparator,AcceptanceAttestationcalldataa)internalpurereturns(bytes32){returnkeccak256(abi.encodePacked("\x19\x01",domainSeparator,_acceptanceStructHash(a)));}
Participants MUST be unique and sorted ascending. Implementations MUST reject non-canonical arrays.
proposeCoordination:
Verifies EIP-712 signature by agentId using ECDSA for EOAs or ERC-1271 for contracts.
Requires intent.expiry > block.timestamp and intent.nonce > agentNonces[agentId].
Stores the canonicalised state and sets agentNonces[agentId] = intent.nonce.
Emits CoordinationProposed.
acceptCoordination:
Checks the intent exists and is not expired.
Verifies the participant is listed and has not already accepted.
Verifies the acceptance signature against the typed AcceptanceAttestation.
Records acceptance and stores the acceptance expiry for that participant.
Emits CoordinationAccepted with the typed acceptance hash.
Returns true when all required acceptances are present.
executeCoordination:
Requires the intent to be in an executable state. In ERC-8001 the policy is all participants have accepted.
Requires every stored acceptance to be unexpired at execution time.
Verifies payloadHash matches the stored hash.
Emits CoordinationExecuted.
cancelCoordination:
The proposer MAY cancel before execution. Anyone MAY cancel after expiry.
Emits CoordinationCancelled.
Status values are implementation-defined but MUST include Proposed, Ready, Executed, Cancelled, Expired.
executeCoordination MUST:
Verify status == Ready (i.e., every participant has accepted).
Verify block.timestamp < intent.expiry.
For each recorded acceptance: verify block.timestamp < acceptance.expiry.
Verify keccak256(abi.encode(payload)) equals the stored payloadHash.
Nonces
ERC-8001 defines a single intent nonce per agent: agentNonces[agentId]. Acceptance nonces are OPTIONAL in ERC-8001. If implemented, they MUST be strictly monotonic per agent.
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.