This ERC defines an append-only interface for subject-linked compliance event
records. Each record includes a subject, event type, outcome, technical actor,
claimed authority, involved parties, evidence commitment, optional evidence
location, versioned payload profile, operation reference, occurrence time, and
recording time.
Events are indexed per subject and by event type. Corrections are recorded as
new events linked to earlier records. A forward pointer on the corrected record
prevents correction forks and allows consumers to resolve the terminal event in
a correction chain.
This ERC is a reporting interface. It does not define compliance policy,
identity verification, transfer restrictions, legal authority, or regulatory
compliance. Stored records are attributable assertions, not proof that the
reported action occurred or was lawful.
Motivation
Compliance-relevant lifecycle actions are currently represented through
application-specific events, token-local state changes, generic attestations,
and off-chain databases. This fragmentation makes it difficult for contracts,
indexers, auditors, and reporting systems to query comparable records across
implementations.
Existing token and compliance standards primarily define token behavior,
holder eligibility, transfer validation, or entity classification. Those
capabilities do not provide a common stored record for subject-level lifecycle
actions such as issuance, redemption, freezing, know-your-customer (KYC)
status changes, regulatory holds, policy changes, and forced transfers.
A shared event-log interface provides:
one query surface for records attached to an application-defined subject;
explicit separation between the recorder and the claimed authority;
structured party roles rather than untyped address arrays;
evidence commitments and optional retrieval references;
versioned payload profiles for interoperable event-specific data;
per-type indexing without scanning an entire subject history; and
append-only, fork-free correction provenance.
The log can be called by a token, compliance module, governance executor,
multisig, or other authorized recorder. It does not require any particular
token standard or enforcement architecture.
Specification
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”,
“SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and
“OPTIONAL” in this document are to be interpreted as described in
RFC 2119 and
RFC 8174.
Definitions
A subject is an application-defined entity identified by subjectId and,
optionally, contextualized by subjectType.
A compliance event is a stored assertion about a compliance-relevant action
or state transition concerning a subject.
An actor is msg.sender at the time recordEvent is called. It identifies
the technical recorder, not necessarily the human decision-maker or legal
authority.
An authority is the recorder’s claimed legal, regulatory, contractual, or
governance basis for the event. The claim is not verified by the log.
A party is an EVM address associated with an explicit role in the event.
A payload profile is a versioned schema declaring how payload is encoded.
A correction chain is a linear sequence of events linked by
correctsIndex and correctedByIndex.
A terminal event is an event whose correctedByIndex equals
NO_CORRECTED_BY.
NO_CORRECTION indicates that an event does not correct an earlier event.
NO_CORRECTED_BY indicates that an event has no successor correction. Event
index zero is safe for this sentinel because a correction’s index is always
greater than the index it corrects and therefore can never be zero.
Event indices MUST be zero-based and scoped per subjectId. The returned
eventIndex MUST equal the subject’s event count immediately before the new
event is appended.
recordEvent MUST be restricted to authorized recorders. The authorization
mechanism is implementation defined and MUST be documented.
For every accepted event, the implementation MUST:
store every supplied field without changing its value, except as explicitly
specified by this ERC;
set actor to msg.sender;
set recordedAt to uint64(block.timestamp);
initialize correctedByIndex to NO_CORRECTED_BY;
append the event index to the subject and event-type index;
emit ComplianceEventRecorded; and
return the new event index.
The implementation MUST reject the call if block.timestamp cannot be
represented as uint64.
evidenceHash MUST NOT be bytes32(0). evidenceURI MAY be empty when the
evidence location is private, unavailable on-chain, or exchanged out of band.
An empty URI does not weaken the requirement for a nonzero commitment.
The parties array MUST contain no more than 10 entries. payload MUST contain
no more than 2048 bytes. Empty party arrays and empty payloads are allowed.
This ERC does not require nonzero values for subjectId, subjectType,
eventType, outcome, authority, operationRef, party addresses, party
roles, or payloadProfileId. Applications requiring stricter semantics MUST
enforce and document them before calling recordEvent.
Temporal Semantics
occurredAt represents when the reported action occurred. recordedAt
represents when the record was appended on-chain.
recordEvent MUST revert when occurredAt > block.timestamp.
Implementations SHOULD impose and document a maximum backdating interval.
Different deployments can require different intervals, so the duration is not
standardized by this ERC.
The log does not independently verify occurredAt. It is an assertion by the
recorder.
Append-Only Semantics
Once recorded, every event field MUST remain immutable except
correctedByIndex. Events MUST NOT be deleted.
Updating correctedByIndex is permitted only when accepting a valid correction
under the correction rules below.
Correction Semantics
An original or non-correction event MUST use NO_CORRECTION and MUST NOT use
EVT_CORRECTION, which is defined in the Event Types section below:
correctsIndex = index of the corrected event
eventType = EVT_CORRECTION
For a correction, recordEvent MUST:
require correctsIndex to identify an earlier event under the same
subjectId;
require the target event’s correctedByIndex to equal
NO_CORRECTED_BY;
authorize the correction under the implementation’s documented correction
policy;
set the target’s correctedByIndex to the new correction event index; and
append the correction as a new event.
The correction policy MUST NOT permit an ordinary recorder to correct another
actor’s event merely because both addresses can record events. It MAY authorize
the original actor, a designated corrector, or an administrator. The policy
MUST be documented.
Each event can be corrected at most once, preventing forks. A correction event
can itself be corrected later, producing a linear chain.
The correcting record does not rewrite the original event’s event type or
payload. Recorders MUST place the corrected assertion in the new event’s fields
or an application-defined correction payload. Consumers MUST interpret the
terminal event under the applicable profile and application policy.
Current-State Queries
currentEventIndex MUST revert when eventIndex >= eventCount(subjectId).
Otherwise, it MUST follow correctedByIndex until reaching
NO_CORRECTED_BY and return the terminal event index. Calling it on a terminal
event MUST return the supplied index.
isEventCurrent MUST revert when eventIndex >= eventCount(subjectId) and
otherwise return whether correctedByIndex == NO_CORRECTED_BY.
Because corrections always point to earlier events and each event has at most
one successor, conforming correction chains cannot contain cycles or branches.
Event Retrieval and Counting
getEvent MUST return the complete stored event and MUST revert when
eventIndex >= eventCount(subjectId).
eventCount MUST return the number of events stored under a subject.
eventCountByType MUST return the number of events recorded with the exact
eventType under a subject.
eventByTypeAt MUST return the event index at the specified zero-based ordinal
and MUST revert when the ordinal is outside the type-specific index.
lastRecordedEventByType MUST return the greatest event index recorded with
the exact event type and MUST revert when no matching event exists.
lastRecordedEventByType describes recording order. It does not select the
event with the greatest occurredAt and does not resolve correction chains.
Correction events are indexed under EVT_CORRECTION, not under the event type
they correct. Consumers resolving an earlier event MUST use
currentEventIndex rather than assuming the last event of the original type is
its current state.
Subject Identifiers
subjectId and subjectType are opaque to the log. Applications SHOULD use a
documented, domain-separated derivation and MUST NOT assume that equal subject
identifiers from independent logs have equal meaning without an explicit
coordination agreement.
The following subject-type identifiers are defined:
EVT_TRANSFER is intended for compliance-significant transfer records, not as
a replacement for a token’s ordinary transfer event. Applications SHOULD avoid
duplicating every routine token transfer unless the additional compliance
record is required by their reporting policy.
Custom event types SHOULD use a domain-separated namespace and explicit
version.
Custom party roles SHOULD use a documented namespace and version.
The base Party type contains an EVM address. It cannot carry an arbitrary
hashed identity without changing the interface. Applications needing private
or non-address party identifiers require a separate extension or SHOULD omit
those parties from the public record.
This ERC does not define or enforce an event-type and outcome compatibility
matrix. Applications MAY constrain combinations before recording. Consumers
MUST NOT infer that a combination was validated merely because the log accepted
it.
Authority Identifiers
The following common authority identifiers are defined:
A recorder declaring one of these profiles MUST encode the payload exactly as
specified. Consumers MUST inspect payloadProfileId before decoding.
The log is not required to decode payloads or validate compatibility between a
payload profile, event type, parties, and outcome. Consumers SHOULD reject a
malformed known profile. Unknown profile identifiers MUST be treated as opaque
bytes.
Custom payload profiles SHOULD use a documented namespace and version and MUST
define an exact encoding.
Operation References
operationRef is an application-defined correlation identifier linking the
record to an underlying action or workflow. It MAY be bytes32(0) when no such
reference is available.
A contract cannot access its transaction hash or final log index while
executing. Therefore, this ERC does not require a transaction-hash and log-index
derivation. Applications SHOULD compute a correlation identifier before the
underlying action and recordEvent calls when both occur in one transaction.
Recording an event in the same transaction as the underlying action provides
stronger linkage than recording it later, but the log still does not prove that
the record accurately describes that action.
Interface Detection
Compliant logs MUST implement ERC-165 and return true for
type(IComplianceEventLog).interfaceId.
ERC-165 indicates interface support only. It does not establish recorder
trustworthiness, evidence validity, authority, policy correctness, or legal
compliance.
Rationale
Why Subject-Linked Records?
An address-only or token-only key would exclude projects, assets, cases,
policies, and application-defined entities. An opaque subject identifier allows
one query model while leaving identity and namespace semantics to the
application.
Why Separate Actor and Authority?
The technical account recording an event and the claimed basis for the action
are different facts. A module can execute several actions under different
mandates, while multiple modules can act under the same mandate.
Why Structured Parties?
An untyped address list cannot distinguish a sender, receiver, target,
beneficiary, or controller. Explicit roles improve machine interpretation while
keeping the set extensible.
Why Require an Evidence Commitment but Permit an Empty URI?
Every record should commit to the evidence representation used by the recorder,
but public retrieval can be inappropriate for confidential or regulated data.
A nonzero hash preserves the commitment while an optional URI permits private
distribution.
Why Versioned Payload Profiles?
Opaque bytes without a declared schema prevent interoperable decoding.
Versioned profile identifiers let consumers recognize stable base encodings and
safely preserve unknown custom payloads.
Why Correction Events Instead of Mutable Records?
Replacing an event would erase the prior assertion. A correction chain retains
the full history and identifies the current terminal record. The forward
pointer and single-successor rule prevent competing corrections to the same
event.
Why Index Corrections Under Their Own Type?
A correction is a distinct lifecycle action. Indexing it under
EVT_CORRECTION preserves the original event-type history and lets consumers
query correction activity directly. Chain-resolution helpers provide current
state when needed.
Why Store Records Rather Than Emit Events Only?
EVM contracts cannot read historical logs. Storing records allows on-chain
consumers to retrieve event details, traverse correction chains, and iterate by
event type. Emitted events remain useful for off-chain indexing.
Why Keep Policy Validation Outside the Log?
The same event and outcome identifiers can be used under different legal and
application policies. The log standardizes representation and provenance, not
the rule engine deciding which combinations are valid.
Prior Art
ERC-8106 defines ERC-20 value-flow
observations, Compliance Entity and Decentralized Entity (CE/DE)
classification, compliance flags, and bizId correlation. This ERC instead
stores subject-linked lifecycle records with authority and evidence fields,
versioned payloads, type indexing, and correction chains. It does not define
CE/DE classification or require each record to correspond to an ERC-20 balance
change.
ERC-3643 defines regulated-token behavior, identity
registries, compliance modules, and lifecycle operations. This ERC is a
reporting layer that such a system may call; it does not replace enforcement or
identity checks.
ERC-7943 defines real-world asset (RWA) token behavior
including transfer eligibility, freezing, and forced transfers. This ERC
records attributed lifecycle assertions independently of any token interface.
ERC-7512 defines an on-chain representation of audit reports.
This ERC instead defines a subject-indexed compliance-event timeline and
correction model.
ERC-5851 defines on-chain verifiable credentials. Credentials
can authorize or identify a recorder, but they do not define this event-log
schema.
Generic attestation systems can represent compliance assertions through custom
schemas. This ERC defines a dedicated stored interface, base identifiers,
payload profiles, indexing behavior, and correction provenance.
Backwards Compatibility
This ERC introduces a new interface and does not modify existing token,
identity, attestation, or compliance standards.
An existing token or compliance module can call a companion
IComplianceEventLog without changing its base token interface. Systems that do
not integrate with the log are unaffected.
The subject identifier is application defined, so adoption does not require a
particular asset registry or token standard.
Test Cases
Implementations should test at least:
zero-based, per-subject event indexing;
subject isolation and event-type indexing;
actor assignment to msg.sender;
recording-time assignment and future-event rejection;
the configured backdating policy;
rejection of zero evidence commitments;
empty and non-empty evidence URIs;
party and payload size boundaries;
original-event and correction-event guards;
correction target bounds and same-subject behavior;
original-actor, administrator, and unauthorized correction paths;
correction fork prevention and multi-step linear chains;
currentEventIndex from original, intermediate, and terminal events;
isEventCurrent for corrected and terminal events;
invalid event and ordinal queries;
recording-order behavior of lastRecordedEventByType;
correction indexing under EVT_CORRECTION;
exact base payload-profile encodings;
opaque handling of unknown payload profiles;
acceptance of unconstrained event-type and outcome combinations; and
positive and negative ERC-165 detection.
Reference Implementation
A Solidity reference implementation, constants library, unit tests, Medusa
property tests, and independent audit are linked from the official discussion
thread.
The reference implementation:
uses recorder and administrator roles;
permits the original actor or an administrator with recorder authority to
correct an event;
limits party arrays to 10 entries;
limits payloads to 2048 bytes;
rejects events backdated by more than 30 days;
stores unknown payload profiles without decoding them; and
does not validate event-type and outcome combinations.
Role design and the 30-day backdating window are reference deployment choices.
The size limits and externally observable interface behavior are requirements
of this ERC.
Security Considerations
Recorder Trust
The log proves that an authorized address recorded particular bytes. It does
not prove that the record is true. A compromised, malicious, or incorrectly
authorized recorder can submit false or misleading events.
Claimed Authority
authority is self-asserted. A recorder can claim a court order, regulator, or
internal policy that does not exist or does not authorize the action. Consumers
must verify authority evidence independently.
Underlying Action Verification
A compliance event is not proof that the underlying issuance, transfer,
freeze, KYC decision, or other action occurred. Consumers requiring that proof
must verify the referenced operation and its relationship to the record.
Correction Authorization
Fork prevention does not determine who is entitled to correct a record. A weak
correction policy can let one recorder supersede another recorder’s assertions.
Implementations must document and enforce correction authority.
Long Correction Chains
currentEventIndex traverses on-chain correction pointers. Although chains are
linear and acyclic, a long chain can consume substantial gas or make an on-chain
call impractical. Applications should avoid unnecessary repeated corrections
and may resolve long histories off-chain.
Backdating
occurredAt is supplied by the recorder. Rejecting future timestamps prevents
one class of invalid input but does not establish historical accuracy. A
documented backdating limit reduces, but does not eliminate, fabricated history.
Privacy and Data Protection
All event fields, dynamic payloads, party addresses, and URIs stored on a public
chain are permanently observable. Implementations must not place personal,
confidential, investigative, or legally restricted information on-chain merely
because the interface permits it.
Public-chain deployments should use opaque or salted subject identifiers,
minimal party arrays, generalized outcomes, redacted payloads, and evidence
commitments whose preimages are distributed through appropriate access
controls. Hashing low-entropy personal data without a secret salt does not
provide meaningful privacy.
Evidence Availability and Ambiguity
A nonzero evidence hash does not make evidence available or identify the
hashing and document scheme by itself. Implementations must document how
evidence commitments are derived and how authorized consumers obtain the
preimage.
Payload Confusion
The log can store malformed known payloads and arbitrary unknown profiles. A
consumer that decodes without first checking payloadProfileId can
misinterpret attacker-controlled bytes. Consumers must use profile-aware
decoding and reject malformed known profiles.
Event-Type and Outcome Confusion
The base log does not validate event-type and outcome combinations. Consumers
must not treat a stored combination as policy-approved unless the recorder’s
application enforces the applicable matrix.
Storage Growth and Retrieval Costs
The log is append-only and grows monotonically. Authorization, bounded dynamic
fields, reporting-frequency policy, and operational monitoring are necessary
to control storage costs and spam.
Event and Storage Interpretation
Off-chain indexers can reconstruct timelines from events, but on-chain
contracts cannot read historical logs. On-chain consumers must use storage
getters. Indexers should reconcile events with storage when resolving correction
chains and current state.