--- eip: 8325 title: Asset Anchor Registry description: A registry interface for registry-scoped token-to-anchor bindings for off-chain asset claims author: Chris Turner , David Hay (@david-hay), Reagan Simpson (@krumg111), Collins Musyimi (@Musyimi97) discussions-to: https://ethereum-magicians.org/t/erc-8325-asset-anchor-registry/28934 status: Last Call last-call-deadline: 2026-10-19 type: Standards Track category: ERC created: 2026-07-04 requires: 165 --- ## Abstract This Ethereum Request for Comments (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: - which registry record the token claims; - whether the registry records the same token-to-anchor relationship; - whether the binding is exclusive within that registry; - whether the binding applies to an entire token contract or one token ID; or - whether the anchor is active, expired, deactivated, or invalidated. 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 [Request for Comments (RFC) 2119](https://www.rfc-editor.org/rfc/rfc2119) and [RFC 8174](https://www.rfc-editor.org/rfc/rfc8174). ### 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 the optional `IAssetAnchorRegistryRecovery` interface, defined in the [Recovery Interface](#recovery-interface) section below. 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: ```solidity 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 application binary interface (ABI) encoded as the following ordered tuple: ```solidity struct AnchorMetadata { bytes32 assetClass; bytes32 jurisdiction; uint64 attestationDate; uint64 expiresAt; bytes uri; bytes extensions; } ``` The canonical encoding is: ```solidity 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 International Organization for Standardization (ISO) 3166-1 alpha-2 code. The Uniform Resource Identifier (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 ```solidity pragma solidity ^0.8.20; 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: ```solidity 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: - store the supplied hashes and derived `anchorId`; - store `registeredAt` as `uint64(block.timestamp)`; - initialize `boundToken` to `address(0)`; - initialize `bindingScope` to `bytes32(0)`; - initialize `boundTokenId` to `0`; - initialize `active` to `true`; and - emit `AnchorRegistered`. 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: ```solidity 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: ```solidity pragma solidity ^0.8.20; 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: ```solidity pragma solidity ^0.8.20; 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: - preserve `boundToken`, `bindingScope`, and `boundTokenId`; - make `isBindingValid(anchorId)` return `false`; - permanently deactivate the anchor; - emit `AnchorDeactivated` if the anchor was active; and - emit `TokenBindingInvalidated`. 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: ```solidity pragma solidity ^0.8.20; 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: ```solidity pragma solidity ^0.8.20; 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](./eip-165.md) 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](./eip-721.md) or [ERC-1155](./eip-1155.md) 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 non-fungible token (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 decentralized autonomous organization (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-3643](./eip-3643.md) and [ERC-7943](./eip-7943.md) 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: - deterministic anchor derivation and duplicate rejection; - rejection of zero hashes and malformed metadata; - contract scope with `tokenId == 0`; - rejection of contract scope with a nonzero token ID; - token-ID scope with token ID `0`; - separation of contract scope from token-ID-zero scope; - rejection of duplicate anchor and duplicate valid binding tuple use; - atomic registration and binding; - expiry at, before, and after the inclusive boundary; - permanent manual deactivation; - monotonic re-attestation; - token registry mismatch; - complete mutually declared binding verification across registry and token-side queries; - preservation of binding fields across deactivation and recovery invalidation; - lifecycle event coverage for registration, binding, re-attestation, deactivation, and recovery invalidation; - optional release of an invalidated binding tuple; and - positive and negative ERC-165 detection. ## 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. A replacement binding recorded for a token that does not expose a token-side interface is registry-side only. ### 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](../LICENSE.md).