ERC-8328 Actor-linked compliance event log (Subject-Linked Compliance Event Log)
Last Call · Other · created 2026-07-05
Defines an append-only shared log that records regulation-related actions, such as token issuance, freezes, and KYC (know-your-customer) changes, per actor. Records cannot be deleted, and corrections are preserved as a linked history.
Why it is needed
Systems that handle regulated assets perform actions such as issuance, redemption, freezes, KYC status changes, regulatory holds, and forced transfers. Today these records are scattered across app-specific events, per-token state changes, general-purpose attestations, and off-chain databases. Because the formats differ, it is hard for contracts, indexers, auditors, and reporting systems to retrieve comparable records across different implementations. Existing token and compliance standards only define token behavior, holder eligibility, transfer validation, and institutional classification; they do not provide a shared storage record for actor-level actions. This standard defines a single query interface that distinguishes the address that technically recorded an action from the authority basis that record claims, and that preserves the evidence hash and correction history. However, this is a reporting interface, and it is not proof that a record is true or lawful.
In depth
Record structure and recording rules
Each event contains a subject (subjectId, subjectType), event type, outcome, recorder (actor), authority claim, party array, evidence hash, optional evidence URI, payload profile and payload, operation reference (operationRef), occurred time and recorded time, and a correction pointer. The index starts at 0 per subject, and the returned eventIndex must equal that subject's event count immediately before the append. Implementations set actor to msg.sender, recordedAt to uint64(block.timestamp), and initialize correctedByIndex to NO_CORRECTED_BY. recordEvent must be callable only by authorized recorders, and the authorization scheme is implementation-defined but must be documented. evidenceHash cannot be bytes32(0), but evidenceURI may be left empty. The party array holds at most 10 entries, and the payload is at most 2048 bytes.
Separation of occurred time and recorded time
occurredAt is the time at which the reported action is claimed to have actually happened, and recordedAt is the time it was recorded on-chain. If occurredAt is greater than block.timestamp, recordEvent must revert. The maximum allowed backdating period for retroactive records is recommended (SHOULD) to be implementation-defined and documented, but the standard does not fix a period. The reference implementation rejects backdated records older than 30 days, but this is a deployment choice, not a standard requirement. The log does not independently verify occurredAt; it is merely the recorder's claim.
Append-only and correction chains
All fields of a recorded event are immutable except for correctedByIndex, and event deletion is prohibited. A normal event sets correctsIndex to NO_CORRECTION (type(uint256).max). A correction event has its type set to EVT_CORRECTION, with correctsIndex set to the index of the event being corrected. The target of a correction must be an earlier event of the same subject, and its correctedByIndex must be NO_CORRECTED_BY (0). Since a correction's index is always greater than its target's, 0 is a safe sentinel. Each event can be corrected only once, so no branching occurs, and correcting a correction event itself produces a linear chain. A correction policy must not allow a recorder to correct another recorder's event merely because it holds recording authority; it may permit the original recorder, a designated corrector, or an administrator, and this must be documented. A correction does not rewrite the original event; it records the fix in a new event.
Exact behavior of query functions
getEvent, currentEventIndex, and isEventCurrent must revert if eventIndex is greater than or equal to eventCount(subjectId). currentEventIndex follows correctedByIndex to return the index of the terminal event that reaches NO_CORRECTED_BY, and calling it on a terminal event returns the input index unchanged. lastRecordedEventByType returns the largest event index recorded for that type; this is based on recording order, not on selecting the event with the largest occurredAt, nor does it interpret the correction chain. Correction events are indexed under EVT_CORRECTION rather than their original type, so consumers must use currentEventIndex rather than assuming that the last event of a given type represents current state.
Standard identifier scheme
All identifiers are keccak256 hashes of namespaced strings. Four subject types are defined: SUBJECT_TOKEN, SUBJECT_ADDRESS, SUBJECT_ASSET, SUBJECT_CASE. Fifteen event types are defined, covering issuance, transfer, redemption, freeze/unfreeze, forced transfer, KYC approval/revocation/renewal, regulatory hold/release, allowlist addition/removal, policy change, and correction. Six outcomes are defined: APPROVED, DENIED, PENDING, EXECUTED, EXPIRED, REVOKED. Six party roles are defined: SENDER, RECEIVER, TARGET, BENEFICIARY, CONTROLLER, SUBJECT. Three authority identifiers are defined: INTERNAL_POLICY, COURT_ORDER, REGULATOR. The authority field merely records a claim and does not authenticate that authority. The standard does not validate combinations of event type and outcome, so acceptance by the log must not be taken to mean the combination is valid. EVT_TRANSFER is intended for recording compliance-significant transfers and is not meant to replace routine token transfer events.
Versioned payload profiles
Additional per-event data is encoded according to the schema declared by payloadProfileId. The standard defines abi.encode encodings for PAYLOAD_TRANSFER_V1 (from, to, amount, routeRef), PAYLOAD_FREEZE_V1 (target, amount, expiresAt, reason), PAYLOAD_KYC_V1 (subject, jurisdiction, riskTier, expiresAt), and PAYLOAD_FORCED_TRANSFER_V1 (from, to, amount, legalBasis). A recorder that declares one of these profiles must follow exactly that encoding, and consumers must check payloadProfileId before decoding. The log itself has no obligation to decode the payload or verify compatibility between the profile and the event type. Unknown profile identifiers must be treated as opaque bytes.
Interface detection and the limits of this standard
A compliant log must implement ERC-165 and return true for type(IComplianceEventLog).interfaceId. However, ERC-165 only indicates interface support and does not guarantee the recorder's trustworthiness, evidence validity, authority, or legal compliance. This standard is a reporting interface; it does not define compliance policy, identity verification, transfer restrictions, or legal authority. A stored record is merely a traceable claim, not proof that the reported action actually occurred or was lawful. The source document is currently in Last Call status with a deadline of October 19, 2026, and its content may change before final approval.
Things to watch out for
- Recorder trust: The log only proves that an authorized address recorded specific bytes; it is not proof that the record is true. A compromised, malicious, or improperly authorized recorder can post false events.
- Self-declared authority claims: The authority field is self-asserted. It may claim a court order, regulator, or internal policy that does not exist or did not authorize the action in question, so consumers must independently verify evidence of authority.
- Privacy and data protection: All stored fields, including party addresses and URIs, are permanently observable on the public chain. Personal, confidential, investigative, or legally restricted information must not be posted merely because the interface permits it. Use opaque or salted subject identifiers. Hashing low-entropy personal data without a secret salt is not meaningful protection.
- Gas cost of long correction chains: currentEventIndex follows correction pointers on-chain. If the chain grows long, this can consume substantial gas or make on-chain calls impractical, so unnecessary repeated corrections should be avoided, and long histories can instead be interpreted off-chain.
- Payload confusion: The log may store malformed known payloads or arbitrary unknown profiles. Decoding without checking payloadProfileId can misinterpret attacker-controlled bytes, so the profile must be checked and malformed known profiles must be rejected.