Alert Source Discuss
⚠️ Draft Standards Track: ERC

ERC-8327: Directional Transfer Domain Registry

Defines token-agnostic directional transfer-route permissions between opaque domains by asset class

Authors Chris Turner <c.turner@kula.com>, David Hay (@david-hay), Reagan Simpson (@krumg111), Collins Musyimi (@Musyimi97)
Created 2026-07-05
Discussion Link https://ethereum-magicians.org/t/erc-8327-directional-transfer-domain-registry/28936
Requires EIP-165

Abstract

This ERC defines a token-agnostic registry interface for querying directional transfer-route permission between opaque domains by asset class. For an ordered triple of sourceDomain, destinationDomain, and assetClass, the registry reports whether that route is currently permitted and exposes the evidence commitments and effective timestamp associated with its latest state.

The core interface supports immediate route permission and revocation, state retrieval, and batch queries. An optional extension supports delayed revocation with explicit initiation, cancellation, lazy effectiveness, and finalization semantics.

This ERC does not assign addresses to domains, derive asset classes, validate evidence, or enforce token transfers. A token or transfer controller that relies on a route decision must resolve the applicable domains and asset class, query the registry, and enforce the result within its transfer path.

Motivation

Transfer restrictions are commonly expressed in token-local or address-level logic. That model is appropriate when eligibility depends on a particular holder, balance, token, or transaction amount. It does not provide a common lookup surface for policies that apply to transfers between logical domains across multiple tokens sharing an asset classification.

A domain can represent a jurisdiction, regulated venue, enterprise network, game economy, DAO treasury boundary, or another application-defined context. Transfer compatibility between such domains is often directional: permission from domain A to domain B does not imply permission from B to A. The same route can also differ by asset class.

Without a shared interface, each token or controller embeds its own route table or integrates with a proprietary registry. This duplicates policy state and requires integrations to understand implementation-specific query methods.

This ERC standardizes the narrow external question:

Is the route from this source domain to this destination domain currently permitted for this asset class?

It deliberately does not answer whether a complete transfer can succeed. Balances, holder eligibility, freezes, sanctions, settlement conditions, and token-specific rules remain separate checks.

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 domain is an opaque, nonzero bytes32 identifier for an application-defined logical boundary.

An asset class is an opaque, nonzero bytes32 identifier for a category of assets to which one route policy applies.

A route is the ordered triple (sourceDomain, destinationDomain, assetClass).

A registrar is an address authorized by the implementation to modify route state.

An evidence hash is a nonzero bytes32 commitment to application-defined material supporting a route lifecycle action.

A grace period is an implementation-defined delay between initiation and effectiveness of a graceful revocation.

Core Interface

A compliant registry MUST implement:

interface ITransferDomainRegistry {
   struct Route {
       bool permitted;
       uint64 effectiveAt;
       bytes32 permissionEvidenceHash;
       bytes32 revocationEvidenceHash;
   }

   event RouteSet(
       bytes32 indexed sourceDomain,
       bytes32 indexed destinationDomain,
       bytes32 indexed assetClass,
       bytes32 permissionEvidenceHash,
       uint64 effectiveAt
   );

   event RouteRevoked(
       bytes32 indexed sourceDomain,
       bytes32 indexed destinationDomain,
       bytes32 indexed assetClass,
       bytes32 revocationEvidenceHash,
       uint64 effectiveAt
   );

   function isRoutePermitted(
       bytes32 sourceDomain,
       bytes32 destinationDomain,
       bytes32 assetClass
   ) external view returns (bool);

   function getRoute(
       bytes32 sourceDomain,
       bytes32 destinationDomain,
       bytes32 assetClass
   ) external view returns (Route memory);

   function setRoute(
       bytes32 sourceDomain,
       bytes32 destinationDomain,
       bytes32 assetClass,
       bytes32 permissionEvidenceHash
   ) external;

   function revokeRoute(
       bytes32 sourceDomain,
       bytes32 destinationDomain,
       bytes32 assetClass,
       bytes32 revocationEvidenceHash
   ) external;

   function isRoutePermittedBatch(
       bytes32[] calldata sourceDomains,
       bytes32[] calldata destinationDomains,
       bytes32[] calldata assetClasses
   ) external view returns (bool[] memory permitted);
}

Route Direction and Scope

Routes MUST be directional. A permitted route (A, B, C) MUST NOT imply that (B, A, C) is permitted. Bidirectional permission requires two independently permitted routes.

Routes MUST also be asset-class scoped. A permitted route (A, B, C) MUST NOT imply permission for (A, B, D).

The registry MUST treat each route triple independently.

Route Queries

isRoutePermitted MUST return the current permission state for the exact route triple. It MUST return false for a route that has never been permitted, has been revoked immediately, or has reached the effective time of a graceful revocation.

For a given block, the result MUST be deterministic and MUST NOT depend on msg.sender, tx.origin, or caller-specific state.

getRoute MUST return the current Route representation for the exact triple. For an unknown route, it MUST return the default record in which every field is zero.

When permitted is true, effectiveAt is the time at which the current permission state became effective. When permitted is false and effectiveAt is nonzero, it is the time at which the latest revocation became effective. An unknown route has effectiveAt == 0.

Setting a Route

setRoute MUST be restricted to authorized registrars. It MUST reject a zero sourceDomain, destinationDomain, assetClass, or permissionEvidenceHash.

On success, setRoute MUST:

  • set permitted to true;
  • set effectiveAt to uint64(block.timestamp);
  • store the supplied permissionEvidenceHash;
  • set revocationEvidenceHash to bytes32(0); and
  • emit RouteSet with the stored values.

Calling setRoute for an already permitted or previously revoked route is allowed. The new call replaces the route’s current state and evidence fields; prior lifecycle actions remain discoverable through events.

An implementation MUST reject the call if block.timestamp cannot be represented as uint64.

Immediate Revocation

revokeRoute MUST be restricted to authorized registrars. It MUST reject a zero sourceDomain, destinationDomain, assetClass, or revocationEvidenceHash.

On success, revokeRoute MUST:

  • set permitted to false;
  • set effectiveAt to uint64(block.timestamp);
  • preserve the current permissionEvidenceHash;
  • store the supplied revocationEvidenceHash; and
  • emit RouteRevoked with the stored values.

For an authorized caller supplying valid nonzero arguments, revokeRoute MUST NOT revert solely because the route was unknown or already revoked. Revoking an unknown route creates a non-permitted route state with zero permission evidence and the supplied revocation evidence. Repeated revocation replaces the latest revocation timestamp and evidence and emits a new event.

An implementation MUST reject the call if block.timestamp cannot be represented as uint64.

Evidence Semantics

All evidence hashes accepted by this ERC MUST be nonzero. The registry treats them as opaque commitments and does not validate their preimages, hashing scheme, authority, correctness, or availability.

permissionEvidenceHash represents the evidence supplied for the current permission state. revocationEvidenceHash represents the evidence supplied for the current revocation state and MUST be bytes32(0) while the route is permitted.

Route state contains only the latest evidence fields. Consumers reconstructing the complete lifecycle MUST index the route events.

Batch Queries

isRoutePermittedBatch MUST revert when its three arrays have different lengths. Otherwise, it MUST return an array of the same length in which output element i equals:

isRoutePermitted(
   sourceDomains[i],
   destinationDomains[i],
   assetClasses[i]
)

Implementations MAY impose a documented maximum batch size. Consumers calling the batch function from state-changing execution SHOULD bound the input length.

Graceful Revocation Extension

Graceful revocation is OPTIONAL. A registry implementing it MUST implement both the core interface and the following extension:

interface IGracefulRouteRevocation {
   struct Revocation {
       uint64 initiatedAt;
       uint64 effectiveAt;
       bytes32 revocationEvidenceHash;
       bool pending;
       bool finalized;
   }

   event RouteRevocationInitiated(
       bytes32 indexed sourceDomain,
       bytes32 indexed destinationDomain,
       bytes32 indexed assetClass,
       bytes32 revocationEvidenceHash,
       uint64 initiatedAt,
       uint64 effectiveAt
   );

   event RouteRevocationCancelled(
       bytes32 indexed sourceDomain,
       bytes32 indexed destinationDomain,
       bytes32 indexed assetClass,
       bytes32 cancellationEvidenceHash
   );

   function getRevocation(
       bytes32 sourceDomain,
       bytes32 destinationDomain,
       bytes32 assetClass
   ) external view returns (Revocation memory);

   function initiateRevocation(
       bytes32 sourceDomain,
       bytes32 destinationDomain,
       bytes32 assetClass,
       bytes32 revocationEvidenceHash
   ) external;

   function cancelRevocation(
       bytes32 sourceDomain,
       bytes32 destinationDomain,
       bytes32 assetClass,
       bytes32 cancellationEvidenceHash
   ) external;

   function finalizeRevocation(
       bytes32 sourceDomain,
       bytes32 destinationDomain,
       bytes32 assetClass
   ) external;
}

The grace-period duration and its configuration mechanism are implementation defined. The implementation MUST document that policy. Only the resulting timestamps and state transitions are standardized.

Graceful Revocation Initiation

initiateRevocation MUST be restricted to authorized registrars. It MUST reject a zero route identifier or zero revocationEvidenceHash.

It MUST revert unless isRoutePermitted currently returns true for the route and no graceful revocation is pending.

On success, it MUST:

  • set initiatedAt to uint64(block.timestamp);
  • set effectiveAt to a representable uint64 value strictly later than initiatedAt;
  • store the supplied revocationEvidenceHash;
  • set pending to true;
  • set finalized to false; and
  • emit RouteRevocationInitiated.

The route MUST remain permitted while block.timestamp < effectiveAt.

Lazy Effectiveness

When block.timestamp >= effectiveAt for a pending graceful revocation, isRoutePermitted MUST return false without requiring a finalization transaction.

During that interval, getRoute MUST return an effective route representation with:

  • permitted == false;
  • effectiveAt equal to the graceful revocation’s effectiveAt;
  • the existing permissionEvidenceHash; and
  • revocationEvidenceHash equal to the graceful revocation evidence.

The stored revocation remains pending until finalized, but the route is already non-permitted. Therefore, consumers MUST NOT infer route permission from whether RouteRevoked has been emitted.

Graceful Revocation Cancellation

cancelRevocation MUST be restricted to authorized registrars. It MUST reject a zero route identifier or zero cancellationEvidenceHash.

It MUST revert unless a revocation is pending and block.timestamp < effectiveAt.

On success, it MUST clear the revocation state and emit RouteRevocationCancelled. The route remains permitted. The cancellation evidence is emitted but is not retained in the Revocation struct; consumers requiring it MUST index the event.

Graceful Revocation Finalization

finalizeRevocation MUST reject a zero route identifier. It MUST revert unless a revocation is pending and block.timestamp >= effectiveAt.

On success, it MUST:

  • set pending to false;
  • set finalized to true;
  • persist the route as non-permitted;
  • set the route’s effectiveAt to the graceful revocation effective time;
  • store the graceful revocationEvidenceHash on the route; and
  • emit RouteRevoked exactly once for that graceful revocation.

Finalization MAY be permissionless because route effectiveness does not depend on it. Repeated or nonexistent finalization MUST NOT emit a duplicate RouteRevoked event.

The finalized Revocation record MUST remain queryable until another route lifecycle action clears it.

Interaction With Core Lifecycle Functions

In a registry implementing the graceful extension, setRoute MUST clear any pending or finalized graceful-revocation record before installing the new permission state.

revokeRoute MUST clear any pending or finalized graceful-revocation record before installing the immediate revocation state.

These actions do not emit RouteRevocationCancelled. Their respective RouteSet or RouteRevoked event records the state transition.

Domain and Asset-Class Identification

This ERC does not map accounts or tokens to domains and does not define domain or asset-class taxonomies. Applications MUST document how they derive each identifier supplied to the registry.

Identifiers SHOULD be domain separated by an application or registry namespace. The same bytes32 value in two independent registries MUST NOT be assumed to have the same meaning without an explicit coordination agreement.

An asset class MAY represent a regulatory category, asset type, product class, or a single asset when per-asset routing is required. The mapping from an individual token or asset identifier to assetClass is application defined.

Authorization

Registrar authorization is implementation defined. A registry MAY use ownership, role-based access control, governance, signatures, or another documented mechanism.

Whatever mechanism is selected, unauthorized callers MUST NOT be able to call setRoute, revokeRoute, initiateRevocation, or cancelRevocation successfully.

Interface Detection

Compliant registries MUST implement ERC-165 and return true for type(ITransferDomainRegistry).interfaceId.

Registries implementing graceful revocation MUST also return true for type(IGracefulRouteRevocation).interfaceId.

ERC-165 indicates interface support only. It does not prove that route data is correct, that the registry is governed appropriately, or that a token or controller enforces registry decisions.

Consumer Enforcement

A consumer enforcing a route decision SHOULD perform the following within the same transaction as the governed transfer:

  1. Resolve the sender and receiver to the applicable source and destination domains using an authoritative application-defined mechanism.
  2. Resolve the token or asset to the applicable asset class.
  3. Call isRoutePermitted(sourceDomain, destinationDomain, assetClass).
  4. Reject the transfer when the result is false.
  5. Apply all independent token, identity, balance, freeze, sanctions, and settlement checks required by the application.

Checking route permission off-chain before submitting a later transfer does not provide atomic enforcement because route state can change between the check and execution.

Rationale

Why Directional Routes?

Compatibility can be asymmetric. A domain may allow outbound transfers to another domain without accepting inbound transfers from it. Encoding each direction separately avoids an unsafe assumption of reciprocity.

Why Opaque Domains?

Standardizing one universal domain taxonomy would couple the interface to a particular legal, organizational, or application model. Opaque identifiers let independent systems use the same route interface while defining their own meaning and resolution mechanism.

Why Scope Routes by Asset Class?

The same pair of domains can permit one category of assets and prohibit another. Asset-class scoping allows multiple assets governed by the same route policy to share one entry without requiring per-token route storage.

Why an External Registry?

An external registry allows multiple tokens and controllers to consult one route-policy surface. It also separates route administration from token implementation and avoids requiring every supported token standard to adopt the same storage model.

Why isRoutePermitted Instead of canTransfer?

The registry evaluates only the supplied route triple. A name such as canTransfer would imply checks that the interface does not perform, including balances, account eligibility, freezes, sanctions, and token-specific rules.

Why Require Nonzero Evidence Commitments?

A zero hash is ambiguous between absent evidence and a meaningful commitment. Requiring a nonzero value makes absence explicit at the application layer and prevents route records from silently appearing documented when no commitment was supplied.

Why Is Immediate Revocation Idempotent Over Route Existence?

An authorized registrar should be able to establish a route as non-permitted without first proving that it was previously enabled. This supports defensive revocation and repeated emergency actions while retaining each action in the event history.

Why Graceful Revocation as an Extension?

Some systems need immediate emergency closure. Others have in-flight settlement or notice obligations that require a future effective time. Keeping delayed revocation optional preserves a small core interface while standardizing the additional lifecycle only for deployments that need it.

Why Lazy Effectiveness?

If route closure depended on a later finalization transaction, a missing or censored transaction could leave the route permitted indefinitely. Lazy effectiveness makes the announced timestamp authoritative. Finalization exists to persist state and emit the terminal event, not to activate the revocation.

Why Keep Grace-Period Configuration Implementation Defined?

Appropriate delay depends on the applicable settlement cycle and policy. The interface exposes the resulting effectiveAt timestamp, which consumers need for interoperability, without prescribing one configuration mechanism or duration.

Prior Art

ERC-1592 defines reusable address and ERC-20 transfer rules based on sender, destination, and amount. This ERC instead standardizes an external lookup keyed by an ordered domain pair and asset class.

ERC-1462 defines transfer-checking and document-reference functions for security tokens. This ERC is token agnostic and does not define a security-token extension.

ERC-3643 defines a regulated-token architecture with identity registries, compliance modules, and token lifecycle functions. This ERC does not define holder eligibility or token behavior; it provides one route-policy input that such systems may optionally consume.

ERC-7943 defines a universal real-world asset (RWA) token interface including transfer eligibility, freezing, and forced transfer behavior. This ERC is an external registry keyed by domains and asset classes rather than a token behavior interface.

Certificate revocation and time-to-live systems provide analogous delayed transition patterns, but they do not define an EVM transfer-domain registry.

Backwards Compatibility

This ERC introduces new interfaces and does not modify existing token standards. Existing ERC-20, ERC-721, ERC-1155, ERC-3643, ERC-7943, and custom tokens remain unaffected unless their transfer path is explicitly integrated with a registry.

The registry does not require a token to expose a new interface. A token, compliance module, transfer controller, bridge, or settlement contract can call the registry as an external dependency.

Implementations that do not need delayed revocation implement only ITransferDomainRegistry. Implementations that need delayed revocation additionally implement IGracefulRouteRevocation.

Test Cases

Implementations should test at least:

  • directional independence of (A, B, C) and (B, A, C);
  • asset-class independence for the same domain pair;
  • caller-independent route queries;
  • default false and zero state for unknown routes;
  • nonzero validation for route identifiers and every evidence field;
  • setting, repeated setting, immediate revocation, repeated revocation, and re-enablement;
  • immediate revocation of an unknown route;
  • route evidence retrieval and event history;
  • batch output equivalence with individual queries;
  • mismatched batch lengths and implementation batch limits;
  • graceful initiation only for a currently permitted route;
  • permission before, and non-permission at, graceful effectiveAt;
  • lazy getRoute behavior before finalization;
  • cancellation before expiry and rejection at or after expiry;
  • finalization persistence and duplicate-event prevention;
  • immediate revocation and re-enablement while graceful state exists;
  • positive and negative ERC-165 detection; and
  • unauthorized lifecycle calls.

Reference Implementation

A Solidity reference implementation includes immediate and graceful registry contracts, a canonical route-key library, unit and fuzz tests, Medusa property tests, deployment scripts, and an independent audit. These materials are linked from the official discussion thread.

The reference implementation uses role-based registrar authorization, a deployment-time fixed grace period, a maximum batch size of 256, and:

keccak256(
   abi.encodePacked(sourceDomain, destinationDomain, assetClass)
)

as its internal route key. Those storage and administration choices are not required for conforming implementations.

Security Considerations

No Enforcement Guarantee

The registry is advisory. A token or controller that does not query and enforce the result can transfer regardless of route state. ERC-165 support by either contract does not prove that enforcement occurs.

Domain and Asset-Class Resolution

The registry evaluates caller-supplied identifiers. Incorrect, stale, or malicious resolution of an address or token to a domain or asset class can bypass the intended policy even when the registry itself is correct. Consumers must secure and document their resolution mechanism.

Registry and Registrar Trust

A malicious or compromised registrar can permit prohibited routes, revoke valid routes, replace evidence commitments, or repeatedly change state. Users must evaluate the registry’s authorization, governance, upgrade, and key management policies.

Evidence Limitations

An evidence hash proves only commitment to unknown bytes if the preimage is available. It does not establish authenticity, legal effect, correctness, authority, or continued availability. Consumers relying on evidence must obtain and verify the preimage under an agreed hashing and document scheme.

Transaction Atomicity and Races

An off-chain route query can become stale before a transfer executes. Enforcing consumers should query the registry and complete or reject the transfer within the same transaction.

Graceful Revocation Risk

The route remains permitted before effectiveAt, so the grace period creates a known window in which transfers can continue. Applications must choose a delay appropriate to their threat and settlement models. Emergency closure should use immediate revocation.

An authorized registrar can clear graceful state by calling setRoute or revokeRoute. Consumers requiring governance constraints around reinstatement must enforce them in the registry’s authorization policy.

Lazy Revocation and Event-Only Indexers

A graceful revocation becomes effective without a transaction at effectiveAt. An indexer that waits only for RouteRevoked can report stale permission until finalization. Consumers must evaluate timestamps or call the view interface.

Batch Gas Consumption

Although batch lookup is a view function, another contract can invoke it from a state-changing transaction. Unbounded arrays can consume excessive gas or make the calling operation unavailable. Implementations and callers should apply appropriate limits.

Identifier Collisions and Cross-Registry Meaning

Opaque identifiers have no global namespace. Two applications or registries can assign the same value to different domains or asset classes. Consumers must scope interpretation to the selected registry and its documented namespace.

Timestamp Dependence

Graceful revocation depends on block.timestamp. Block producers can influence timestamps within protocol bounds. Applications requiring exact wall-clock cutoffs must account for this uncertainty.

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

Chris Turner <c.turner@kula.com>, David Hay (@david-hay), Reagan Simpson (@krumg111), Collins Musyimi (@Musyimi97), "ERC-8327: Directional Transfer Domain Registry [DRAFT]," Ethereum Improvement Proposals, no. 8327, July 2026. Available: https://eips.ethereum.org/EIPS/eip-8327.