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.
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.
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.
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.
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);
}
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.
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.
setRoute MUST be restricted to authorized registrars. It MUST reject a zero
sourceDomain, destinationDomain, assetClass, or
permissionEvidenceHash.
On success, setRoute MUST:
permitted to true;effectiveAt to uint64(block.timestamp);permissionEvidenceHash;revocationEvidenceHash to bytes32(0); andRouteSet 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.
revokeRoute MUST be restricted to authorized registrars. It MUST reject a
zero sourceDomain, destinationDomain, assetClass, or
revocationEvidenceHash.
On success, revokeRoute MUST:
permitted to false;effectiveAt to uint64(block.timestamp);permissionEvidenceHash;revocationEvidenceHash; andRouteRevoked 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.
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.
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 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.
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:
initiatedAt to uint64(block.timestamp);effectiveAt to a representable uint64 value strictly later than
initiatedAt;revocationEvidenceHash;pending to true;finalized to false; andRouteRevocationInitiated.The route MUST remain permitted while block.timestamp < effectiveAt.
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;permissionEvidenceHash; andrevocationEvidenceHash 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.
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.
finalizeRevocation MUST reject a zero route identifier. It MUST revert unless
a revocation is pending and block.timestamp >= effectiveAt.
On success, it MUST:
pending to false;finalized to true;effectiveAt to the graceful revocation effective time;revocationEvidenceHash on the route; andRouteRevoked 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.
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.
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.
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.
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.
A consumer enforcing a route decision SHOULD perform the following within the same transaction as the governed transfer:
isRoutePermitted(sourceDomain, destinationDomain, assetClass).false.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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Implementations should test at least:
(A, B, C) and (B, A, C);effectiveAt;getRoute behavior before finalization;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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.