ERC-8327 Directional transfer domain registry

Final · Other · created 2026-07-05

Defines a shared registry interface for querying, for each ordered pair of source domain, destination domain, and asset class, whether a transfer path is currently permitted. Multiple tokens and contracts can share a single path policy.

Why it is needed

Rules restricting token transfers are typically embedded in the token's own code or in address-level logic. This works for individual conditions like holder status or balance, but there is no shared interface that multiple tokens can query for transferability between logical domains, such as jurisdictions, regulated venues, corporate networks, game economies, or DAO treasury boundaries. Such compatibility is directional, so a transfer allowed from A to B may not be allowed from B to A, and the same path may differ by asset class. Without a standard interface, each token or transfer controller must embed its own path table or attach to a proprietary registry, duplicating policy state and forcing integrators to learn a different lookup method for each implementation. This standard standardizes the narrow question of whether a transfer of a given asset class from a given source domain to a given destination domain is currently permitted. It does not answer whether a transfer will ultimately succeed; checks such as balance, holder eligibility, freezes, and sanctions remain separate.

In depth

Routes are fully distinguished by direction and asset class

A route is the ordered tuple (sourceDomain, destinationDomain, assetClass). Both domain and asset class are non-zero bytes32 identifiers, and the registry does not interpret their meaning. A route must be directional: even if (A, B, C) is permitted, (B, A, C) cannot be assumed permitted, and bidirectional permission requires two independently permitted routes. Routes are also distinguished by asset class: permitting (A, B, C) does not imply permitting (A, B, D). The registry must treat each ordered tuple independently of the others.

Query results are deterministic regardless of the caller

isRoutePermitted returns exactly the current permission status of that ordered tuple. It is false for a route that has never been permitted, a route that was immediately revoked, or a route whose graceful revocation effective time has passed. Within the same block the result must be deterministic and must not depend on msg.sender, tx.origin, or any caller-specific state. getRoute returns a Route struct containing permitted, effectiveAt, permissionEvidenceHash, and revocationEvidenceHash; for an unknown route, all fields default to zero. If permitted is true, effectiveAt is the time the current permission took effect; if permitted is false but effectiveAt is non-zero, it is the time the last revocation took effect.

State rules for setting and immediate revocation

setRoute and revokeRoute can only be called by an authorized registrar, and must reject a zero domain, asset class, or evidence hash. If setRoute succeeds, permitted becomes true, effectiveAt becomes uint64(block.timestamp), permissionEvidenceHash becomes the supplied value, revocationEvidenceHash becomes zero, and a RouteSet event is emitted. It may be called again on a route that is already permitted or already revoked; the new call replaces the state and evidence, and the prior action remains recorded in events. revokeRoute must not revert merely because the route is unknown or already revoked. Revoking an unknown route creates a non-permitted record with zero permission evidence, and repeated revocation replaces the latest timestamp and evidence while emitting a new event. Both functions must reject if block.timestamp exceeds the uint64 maximum.

Evidence hashes are unverified commitments

An evidence hash is a non-zero bytes32 commitment to application-defined material supporting a route action. The registry treats it opaquely and does not verify the original source, hashing method, authority, accuracy, or availability. permissionEvidenceHash is the evidence for the current permitted state, and revocationEvidenceHash is the evidence for the current revoked state; while a route is permitted, revocationEvidenceHash must be bytes32(0). Only the most recent evidence remains in state, so consumers that want to reconstruct the full history must index route events. Zero hashes are prohibited to remove the ambiguity between the absence of evidence and a meaningful commitment.

Batch queries must produce the same results as individual queries

isRoutePermittedBatch must revert if the three arrays differ in length; otherwise it must return an array where each element i matches the result of an individual isRoutePermitted call at the same index. Implementations may impose a documented maximum batch size. Consumers calling the batch function from a state-changing execution should limit input length, because even though it is a view function, an unbounded array called by another contract within a state-changing transaction can consume excessive gas and render that operation unusable.

The graceful revocation extension and lazy effectiveness

Graceful revocation is an optional extension (IGracefulRouteRevocation). initiateRevocation succeeds only when the route is currently permitted and no graceful revocation is in progress; it records initiatedAt as the current time and effectiveAt as a strictly later time, and emits RouteRevocationInitiated. The length and configuration of the grace period are left to the implementation but must be documented. Lazy effectiveness is the core mechanism: once block.timestamp reaches effectiveAt, isRoutePermitted returns false even without a finalizeRevocation transaction. This design ensures the route does not remain open indefinitely even if the finalizing transaction is missing or censored. cancelRevocation is only possible before effectiveAt, and the route remains permitted. finalizeRevocation stores the non-permitted state after effectiveAt and emits RouteRevoked exactly once; since it does not affect the effectiveness itself, it may be left callable by anyone. If setRoute or revokeRoute is called, the graceful revocation record is cleared before the new state is installed.

What the registry does not do, and consumer responsibilities

This standard does not map addresses or tokens to domains, nor does it define a classification scheme for domains and asset classes. How identifiers are constructed must be documented by the application, and the same bytes32 value in different registries must not be assumed to carry the same meaning. The method of registrar authorization is also left to the implementation, whether ownership, role-based access control, governance, or signatures, but unauthorized callers must not be able to successfully call setRoute, revokeRoute, initiateRevocation, or cancelRevocation. A consumer enforcing route decisions should determine the domain and asset class within the same transaction as the transfer, call isRoutePermitted, reject the transfer if false, and then separately apply independent checks such as balance, freeze, and sanctions. A compliant registry must implement ERC-165 and return true for the interfaceId of ITransferDomainRegistry, and if it implements graceful revocation, must also return true for the interfaceId of IGracefulRouteRevocation.

Things to watch out for

Original ERC · ← ERCs