ERC-8325 - Asset Anchor Registry

Created 2026-07-04
Status Draft
Category ERC
Type Standards Track
Authors
Requires

Abstract

This ERC defines interfaces for registries that bind token contracts or token IDs to anchor records representing claims about off-chain assets. Each anchor contains separate commitments to a claimed legal basis and supporting evidence. Bindings distinguish whole-contract scope from token-ID scope, enforce registry-scoped exclusivity, and preserve immutable binding history.

Token-side interfaces allow contracts to declare the same registry and anchor, enabling consumers to verify both sides of a binding. A lifecycle interface defines structured metadata, expiry, re-attestation, and permanent deactivation. An optional recovery interface permits disputed bindings to be invalidated without deleting their historical records.

The resulting records provide durable, registry-scoped binding provenance for consumers that require an auditable lifecycle history.

This ERC does not establish the existence, ownership, legal validity, or value of an off-chain asset.

Motivation

A token can claim in metadata that it represents an off-chain asset, but that claim does not provide a common interface for another contract to determine:

Deployments subject to institutional or regulatory oversight often need a durable answer to which token tuple was recorded against an asset claim, when the binding was established, and whether it remains current. This ERC makes registry-scoped exclusivity and immutable binding history explicit without asserting that the underlying claim is legally valid or factually correct.

Without a mutually queryable structure, the relationship remains assertion-only tokenization: the token can describe an off-chain asset, but an independent consumer cannot verify the claimed token-to-record relationship through a common interface.

Applications consequently rely on implementation-specific metadata and registries. The same asset claim can be represented differently by each issuer, and consumers cannot inspect a binding through a common interface.

This ERC standardizes the structural relationship between a registry anchor and a token contract or token ID. The guarantee is intentionally limited to one registry instance. Registry operators remain responsible for deciding which claims they accept, and consumers remain responsible for deciding which registries they trust.

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

An anchor is a registry record containing commitments and metadata for an off-chain asset claim.

A contract binding binds an anchor to an entire token contract. This is appropriate when one contract represents one claimed asset or instrument.

A token-ID binding binds an anchor to one token ID within a token contract.

A binding tuple is (token, bindingScope, tokenId).

A valid binding is a recorded binding that has not been invalidated through IAssetAnchorRegistryRecovery. Binding validity is distinct from lifecycle activity.

An active anchor is an anchor that has not been permanently deactivated and whose metadata has not expired.

Binding Scopes

Implementations MUST use the following scope identifiers:

bytes32 constant BINDING_SCOPE_CONTRACT =
    keccak256("ERC-8325:BINDING_SCOPE:CONTRACT");

bytes32 constant BINDING_SCOPE_TOKEN_ID =
    keccak256("ERC-8325:BINDING_SCOPE:TOKEN_ID");

For BINDING_SCOPE_CONTRACT, tokenId MUST equal 0 as a canonical unused value.

For BINDING_SCOPE_TOKEN_ID, every uint256 value is valid, including token ID 0. Token ID 0 MUST NOT be interpreted as a contract-binding sentinel.

Metadata Encoding

Registration metadata MUST be ABI encoded as the following ordered tuple:

struct AnchorMetadata {
    bytes32 assetClass;
    bytes32 jurisdiction;
    uint64 attestationDate;
    uint64 expiresAt;
    bytes uri;
    bytes extensions;
}

The canonical encoding is:

abi.encode(
    metadata.assetClass,
    metadata.jurisdiction,
    metadata.attestationDate,
    metadata.expiresAt,
    metadata.uri,
    metadata.extensions
)

assetClass and jurisdiction MUST NOT be bytes32(0). attestationDate MUST NOT be 0 and MUST NOT be later than block.timestamp. expiresAt MUST be later than attestationDate and MUST NOT be earlier than block.timestamp at registration. uri MUST NOT be empty. extensions MAY be empty.

assetClass SHOULD be a domain-separated identifier from a documented taxonomy. When an anchor has one primary country jurisdiction, jurisdiction SHOULD be a domain-separated identifier derived from its uppercase ISO 3166-1 alpha-2 code.

The URI identifies where a consumer can retrieve material corresponding to the anchor commitments. This ERC does not require a particular URI scheme or guarantee availability.

Registry Interface

interface IAssetAnchorRegistry {
    struct AnchorRecord {
        bytes32 anchorId;
        bytes32 legalHash;
        bytes32 evidenceHash;
        address boundToken;
        bytes32 bindingScope;
        uint256 boundTokenId;
        uint64 registeredAt;
        bool active;
    }

    event AnchorRegistered(
        bytes32 indexed anchorId,
        bytes32 legalHash,
        bytes32 evidenceHash
    );

    event TokenBound(
        bytes32 indexed anchorId,
        address indexed token,
        bytes32 indexed bindingScope,
        uint256 tokenId
    );

    event AnchorDeactivated(bytes32 indexed anchorId, string reason);

    event AnchorReattested(
        bytes32 indexed anchorId,
        uint64 oldExpiresAt,
        uint64 newExpiresAt,
        uint64 newAttestationDate
    );

    function registerAnchor(
        bytes32 legalHash,
        bytes32 evidenceHash,
        bytes calldata metadata
    ) external returns (bytes32 anchorId);

    function bindToken(
        bytes32 anchorId,
        address token,
        bytes32 bindingScope,
        uint256 tokenId
    ) external;

    function registerAndBind(
        bytes32 legalHash,
        bytes32 evidenceHash,
        bytes calldata metadata,
        address token,
        bytes32 bindingScope,
        uint256 tokenId
    ) external returns (bytes32 anchorId);

    function getAnchor(bytes32 anchorId)
        external
        view
        returns (AnchorRecord memory);

    function isBound(bytes32 anchorId) external view returns (bool);
}

Registration

registerAnchor and registerAndBind MUST reject bytes32(0) for legalHash or evidenceHash.

The anchor identifier MUST be derived as:

anchorId = keccak256(abi.encode(legalHash, evidenceHash));

The same (legalHash, evidenceHash) pair therefore produces the same anchorId within and across implementations of this ERC. A registry MUST reject an anchorId that it has already registered.

On successful registration, the registry MUST:

The mechanism for authorizing registration is implementation-defined. Implementations MUST document their authorization policy.

Binding

bindToken MUST reject an unknown anchor, an inactive or expired anchor, a zero token address, an unsupported binding scope, and an anchor whose binding fields have already been set.

Before recording a binding, the registry MUST ensure that no valid anchor is already associated with the same binding tuple. The uniqueness key SHOULD be derived as:

keccak256(abi.encode(token, bindingScope, tokenId))

On successful binding, the registry MUST set boundToken, bindingScope, and boundTokenId and emit TokenBound. These three historical fields MUST NOT be modified after they are set, including after deactivation or invalidation.

registerAndBind MUST apply the same registration and binding requirements atomically.

The mechanism for authorizing binding is implementation-defined. It MUST prevent an unrelated caller from binding another registrar's unbound anchor.

If token exposes anchorRegistry(), the returned address MUST equal the registry performing the binding. A registry MAY bind a token that does not implement a token-side interface. Such a record is a registry-side binding only and does not constitute mutually declared binding under this ERC.

Registry Queries

getAnchor MUST return the complete stored record and MUST revert for an unknown anchorId.

isBound MUST return true when boundToken is not address(0), regardless of lifecycle activity or recovery invalidation. It MUST return false for a known but unbound anchor and MUST revert for an unknown anchorId.

Lifecycle Interface

Every compliant registry MUST implement the lifecycle interface because anchor activity and expiry are part of the common verification model:

interface IAssetAnchorRegistryLifecycle {
    function getMetadata(bytes32 anchorId)
        external
        view
        returns (AnchorMetadata memory);

    function registeredBy(bytes32 anchorId)
        external
        view
        returns (address);

    function isActive(bytes32 anchorId) external view returns (bool);

    function deactivateAnchor(
        bytes32 anchorId,
        string calldata reason
    ) external;

    function reattest(
        bytes32 anchorId,
        uint64 newExpiresAt,
        uint64 newAttestationDate
    ) external;
}

getMetadata, registeredBy, and isActive MUST revert for an unknown anchor.

registeredBy MUST return the address that successfully registered the anchor and MUST NOT change after registration.

isActive MUST return false if AnchorRecord.active is false or if block.timestamp > expiresAt. An anchor remains active at the exact expiresAt timestamp.

deactivateAnchor MUST be restricted to authorized callers, set active to false, and emit AnchorDeactivated. Manual deactivation is permanent and MUST NOT be reversed by reattest.

reattest MUST be restricted to an authorized caller and MUST reject a manually deactivated anchor. newAttestationDate MUST NOT be 0, later than block.timestamp, or earlier than the existing attestationDate. newExpiresAt MUST be later than block.timestamp, later than newAttestationDate, and not earlier than the existing expiresAt. Successful re-attestation MUST emit AnchorReattested.

Recovery Interface

Binding recovery is OPTIONAL. A registry that permits disputed bindings to be invalidated MUST implement:

interface IAssetAnchorRegistryRecovery {
    event TokenBindingInvalidated(
        bytes32 indexed anchorId,
        address indexed token,
        bytes32 indexed bindingScope,
        uint256 tokenId,
        bytes32 reasonHash
    );

    function invalidateTokenBinding(
        bytes32 anchorId,
        bytes32 reasonHash
    ) external;

    function isBindingValid(bytes32 anchorId)
        external
        view
        returns (bool);
}

invalidateTokenBinding MUST be restricted to an authorized recovery role. It MUST reject an unknown, unbound, or previously invalidated anchor and a zero reasonHash.

Successful invalidation MUST:

An implementation MAY free the binding tuple so that a different anchor can be bound to it. If it does so, the invalidated record MUST remain historically queryable and MUST NOT itself be rebound.

isBindingValid MUST return true only when an anchor is bound and has not been invalidated. It MUST revert for an unknown anchor.

Token Interfaces

Whole-contract bindings use:

interface IAssetBoundToken is IERC165 {
    function anchorId() external view returns (bytes32);
    function anchorRegistry() external view returns (address);
    function isAnchorActive() external view returns (bool);
}

Token-ID bindings use:

interface IAssetBoundTokenId is IERC165 {
    function anchorIdOf(uint256 tokenId)
        external
        view
        returns (bytes32);

    function anchorRegistry() external view returns (address);

    function isAnchorActiveFor(uint256 tokenId)
        external
        view
        returns (bool);
}

anchorRegistry MUST remain unchanged after deployment.

For IAssetBoundToken, anchorId MUST remain unchanged after deployment.

For IAssetBoundTokenId, anchorIdOf(tokenId) MUST revert when the token ID is not bound. Once a nonzero anchor is declared for a token ID, that declaration MUST NOT change.

isAnchorActive and isAnchorActiveFor MUST reflect lifecycle activity from the declared registry. Consumers MUST query isBindingValid separately when the registry implements the recovery interface.

Interface Detection

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

Compliant registries MUST return true for type(IAssetAnchorRegistryLifecycle).interfaceId. Registries implementing recovery MUST also return true for type(IAssetAnchorRegistryRecovery).interfaceId.

Compliant tokens MUST return true for the applicable token-side interface ID.

ERC-165 detects interface support. It does not prove correct behavior, transfer enforcement, registrar trustworthiness, or the validity of an off-chain claim.

Complete Binding Verification

A consumer treating a binding as mutually declared MUST verify all of the following:

  1. getAnchor(anchorId) returns the expected token, scope, and token ID.
  2. isBound(anchorId) returns true.
  3. isActive(anchorId) returns true.
  4. isBindingValid(anchorId) returns true when recovery is implemented.
  5. The token supports the applicable token-side interface.
  6. The token reports the same registry and anchor.

Failure of any applicable check means the consumer MUST NOT treat the binding as a current mutually declared binding.

Rationale

Why Use a Registry?

A separate registry allows the same binding interface to compose with fungible, non-fungible, multi-token, permissioned, and future token standards. It also provides one inspection surface for applications that do not control the token implementation.

The registry does not create global truth. Consumers select which registry operators and authorization policies they trust.

Why Two Hashes?

Off-chain asset structures often distinguish the instrument or legal basis from evidence about the referenced asset. Separate commitments preserve that distinction without assigning universal semantics to either document set.

Deployments that do not need the distinction can commit to two separately defined records. A zero hash is not available as an omission sentinel.

Why a Deterministic Anchor Identifier?

Deriving anchorId from both commitments gives implementations the same identifier for the same pair of bytes and makes duplicate registration within a registry unambiguous. It does not deduplicate semantically equivalent documents with different byte representations.

Why Explicit Binding Scope?

Using token ID 0 to mean whole-contract binding prevents token ID 0 from being bound as an actual ERC-721 or ERC-1155 token. Including an explicit scope makes contract binding and token-ID-zero binding distinct.

Why Split the Token Interfaces?

A whole-contract token has no meaningful anchorIdOf query, while a collection with independently anchored token IDs has no meaningful contract-wide anchorId. Separate interfaces avoid mandatory functions with misleading or implementation-specific failure behavior.

Why Separate Activity from Binding?

A binding is historical identity data. Activity is lifecycle status. Expiry or deactivation can make an anchor operationally inactive without changing which token was bound to it. Accordingly, isBound is not an activity check.

Why an Optional Recovery Interface?

Permanent bindings are vulnerable to registrar compromise, key loss, and binding-key squatting. Recovery allows an explicitly trusted administrator to invalidate an operational binding while preserving its history. Deployments that prefer absolute immutability can omit the recovery interface.

Recovery introduces substantial administrative trust and is therefore not part of the minimum registry interface.

Why Not Enforce Allocation Integrity?

Allocation constraints, such as ensuring that fractional token supply does not represent more than a defined share of an asset, depend on the economic and legal structure of the instrument. They are meaningful for some fungible fractional claims but not for a single NFT representing one object or for a registry record that does not express ownership percentages.

The registry therefore standardizes binding identity rather than issuance or allocation rules. Tokens and application-specific contracts remain responsible for enforcing any supply, fraction, or entitlement constraints.

Why Leave Registry Governance Open?

Different deployments require different trust models. A registry may be operated by one accountable issuer, a regulated registrar, a multisignature, a DAO, or a permissionless protocol. Requiring one governance model would exclude otherwise interoperable implementations without making their off-chain claims more truthful.

Consumers select which registries and governance policies they trust. The common interface makes those registries technically inspectable; it does not make them equally trustworthy.

Why Are Historical Binding Fields Immutable?

Changing a binding's token, scope, or token ID in place would erase the relationship that consumers previously inspected. This ERC therefore preserves those fields after deactivation and recovery invalidation, allowing auditors and other consumers to determine which tuple was recorded and how its status changed over time.

Field immutability does not mean that a binding remains active or valid. Consumers must evaluate lifecycle activity and, when implemented, recovery validity separately. If an implementation releases an invalidated tuple, the invalidated record remains queryable and a replacement is stored as a separate anchor. The guarantee remains scoped to one registry and does not establish global one-token-to-one-asset uniqueness.

Prior Art

ERC-6956 defines ERC-721 tokens bound one-to-one to physical or digital assets, with operations authorized by oracle attestations of control. This ERC is token-standard-neutral and standardizes a registry record binding, not proof-of-control authorization.

ERC-7929 permanently binds one on-chain token to another and mirrors ownership behavior. This ERC binds token contracts or token IDs to records representing off-chain claims and does not define token-to-token ownership hierarchies.

ERC-6065 defines an ERC-721 extension for tokenized real estate with property identifiers and operating-agreement data. This ERC is not limited to real estate or ERC-721 and does not prescribe asset-specific operations.

ERC-3643 and ERC-7943 define token behavior and compliance-related interfaces. They do not define the registry-scoped token-to-anchor relationship specified here. Tokens implementing either can also implement a token-side interface from this ERC.

Backwards Compatibility

This ERC introduces new interfaces and does not change existing token standards. Existing token contracts can be recorded in a registry without modification, but this produces only a registry-side binding.

An existing upgradeable token can add the applicable token-side interface. An immutable token that lacks the interface requires a wrapper, adapter, or new deployment to provide mutually declared binding. Consumers should distinguish a registry-only record from a binding confirmed by both registry and token.

Test Cases

Implementations should test at least the following cases:

Reference Implementation

A Solidity reference implementation, unit tests, fuzz tests, invariants, and an independent audit are linked from the discussion referenced in the preamble. The implementation is illustrative; its access-control roles and deployment model are not required by this ERC.

Security Considerations

Registry Trust

The registry proves only that its own state satisfies the specified structural rules. A malicious or compromised registrar can register false claims with validly formed hashes and metadata. Consumers should evaluate the registry's operator, authorization policy, upgrade authority, and legal context.

No Legal or Physical Truth Guarantee

Neither a hash nor a mutually declared token binding proves that an off-chain asset exists, that documents are authentic, or that a token conveys a legal right. Those determinations require external verification.

Registration-to-Binding Races

Separate registration and binding create an interval in which an unauthorized caller may attempt to bind an anchor. Implementations should restrict binding to authorized parties. Registrars should use registerAndBind when atomicity is required.

Malicious Tokens and Interface Spoofing

A token can return arbitrary values from the token-side interfaces or falsely claim ERC-165 support. Consumers should verify the registry record independently. Registries that call token contracts during binding should use static calls and handle malformed return data and reverts.

Recovery Authority

A recovery administrator can invalidate legitimate bindings and, when the implementation releases binding tuples, enable replacement anchors. Production deployments should protect recovery authority with a multisignature, governance, timelock, or equivalent controls. Consumers should monitor TokenBindingInvalidated and AnchorDeactivated events.

Releasing an invalidated tuple does not change an immutable token-side anchor declaration. A replacement can be mutually declared only when the token already declares the replacement anchor or does not expose a token-side interface.

Document Canonicalization

The registry treats legalHash and evidenceHash as opaque commitments. Two representations of the same document can produce different hashes. Deployments requiring interoperable document commitments should define a deterministic normalization and bundle-hashing procedure.

Data Availability

An on-chain commitment is not useful for verification if the committed material cannot be retrieved. The registry does not guarantee URI persistence or data availability. Deployments should use durable storage and availability policies.

Multi-Registry and Cross-Chain Duplication

Independent registries or deployments on different chains can register claims about the same off-chain asset. This ERC provides no global uniqueness or cross-registry conflict resolution.

Because anchorId does not include a chain identifier or registry address, consumers requiring globally scoped identity should use (chainId, registry, anchorId) rather than anchorId alone. A companion system that accepts only a bare anchorId can otherwise conflate records from different registry domains.

Expiry and Timestamp Dependence

Lifecycle status depends on block.timestamp, which block producers can vary within protocol constraints. Applications should avoid relying on second-level precision around an expiry boundary.

Upgradeability

An upgradeable registry can alter binding behavior after consumers begin relying on it. Upgrades must preserve historical binding fields to remain compliant. Consumers should inspect proxy administration and upgrade policies.

Institutional and Regulated Deployments

Deployments operating under institutional or regulatory controls should document registration and binding authority, protect recovery and upgrade authority with appropriate multisignature, timelock, governance, or equivalent controls, and monitor all binding and lifecycle events.

Such deployments should also define how contested invalidations are reviewed and how committed records are made available to authorized auditors. Conformance with this ERC provides a common technical interface; it does not establish regulatory status, legal sufficiency, or compliance with any jurisdiction's requirements.

Copyright

Copyright and related rights waived via CC0.