ERC-8325 Asset Anchor Registry

Final · Tokens · created 2026-07-04

A registry interface that binds a token contract or token ID to an off-chain asset claim record (anchor). It lets anyone query, in a common way, which token is bound to which record and whether that binding is still valid.

Why it is needed

A token can state in its metadata that it represents an off-chain asset, but there is no common interface for other contracts to verify that claim. There is no way to query which record a token claims to be bound to, whether the registry records the same relationship, whether that binding is exclusive within the registry, whether it applies to the whole contract or only to a specific token ID, or whether the record is active, expired, deactivated, or invalidated. As a result, the same asset claim is represented differently by different issuers, and consumers must rely on metadata and registries that vary by implementation. Institutional or regulated deployments need a durable answer to which token was recorded against which asset claim, when, and whether it is still valid, but assertion-only tokenization gives independent consumers no common interface to verify that relationship.

What to implement

In depth

Anchors and the two hash commitments

An anchor is a registry record holding a commitment and metadata for an off-chain asset claim. At registration it takes a separate legalHash (legal basis) and evidenceHash (evidence), and must reject the case where both are bytes32(0). The anchor ID is derived as anchorId = keccak256(abi.encode(legalHash, evidenceHash)), so the same hash pair produces the same ID across different implementations, and re-registering an already-registered anchorId must be rejected. On successful registration, registeredAt is stored as uint64(block.timestamp), and boundToken is initialized to address(0), bindingScope to bytes32(0), boundTokenId to 0, and active to true, after which AnchorRegistered is emitted. The implementation determines how registration authorization is granted, but that policy must be documented.

Binding scope and the token ID 0 problem

Binding scope is distinguished by two constants. BINDING_SCOPE_CONTRACT is keccak256("ERC-8325:BINDING_SCOPE:CONTRACT"), and BINDING_SCOPE_TOKEN_ID is keccak256("ERC-8325:BINDING_SCOPE:TOKEN_ID"). Under contract scope, tokenId must be the unused canonical value 0, while under token ID scope any uint256 value, including 0, is valid. Token ID 0 must not be interpreted as a signal value for contract binding. The reason for having an explicit scope is to distinguish binding an actual ERC-721 or ERC-1155 token with token ID 0 from a contract-wide binding.

Metadata encoding and validation rules

Registration metadata must be the ordered abi.encode of the AnchorMetadata struct (assetClass, jurisdiction, attestationDate, expiresAt, uri, extensions). assetClass and jurisdiction must not be bytes32(0), and attestationDate must be nonzero and not later than block.timestamp. expiresAt must be later than attestationDate and must not be earlier than block.timestamp at registration time. uri must not be empty, while extensions may be empty. It is recommended that assetClass use a domain-separated identifier from a documented classification scheme, and that jurisdiction, when the primary jurisdiction is a single country, be derived from an uppercase ISO 3166-1 alpha-2 code. The URI only points to the location of material corresponding to the commitment and does not guarantee any particular scheme or availability.

Exclusivity of binding and immutability of history

bindToken must reject an unknown anchor, an inactive or expired anchor, a zero token address, an unsupported scope, or an anchor whose binding fields are already set. Before binding, it must confirm that no valid anchor is already linked to the same binding tuple (token, bindingScope, tokenId); it is recommended that the uniqueness key be derived as keccak256(abi.encode(token, bindingScope, tokenId)). On successful binding, boundToken, bindingScope, and boundTokenId are set and TokenBound is emitted; these three history fields can never be modified again, including after deactivation or invalidation. registerAndBind applies the same requirements atomically. If a token exposes anchorRegistry(), the address it returns must match the registry performing the binding; tokens without a token-side interface can still be bound, but that record is a registry-only binding, not a mutually declared binding.

Lifecycle: active, expired, reattestation

Every compliant registry must implement the lifecycle interface. isActive returns false if AnchorRecord.active is false or block.timestamp > expiresAt, and the anchor is active up to and including the exact expiresAt timestamp. deactivateAnchor can only be called by an authorized caller and sets active to false; manual deactivation is permanent and cannot be reversed by reattest. reattest must reject an anchor that has been manually deactivated, and newAttestationDate must be nonzero, not later than block.timestamp, and not earlier than the existing attestationDate. newExpiresAt must be later than both block.timestamp and newAttestationDate, and must not be earlier than the existing expiresAt. In other words, reattestation cannot bring expiration forward.

Optional recovery interface

A registry capable of invalidating a disputed binding must implement IAssetAnchorRegistryRecovery, though recovery itself is optional. invalidateTokenBinding is restricted to an authorized recovery role and must reject an unknown, unbound, or already invalidated anchor, as well as a zero reasonHash. On successful invalidation, boundToken, bindingScope, and boundTokenId are preserved while isBindingValid becomes false, the anchor is permanently deactivated, and AnchorDeactivated is emitted if it was active, followed by TokenBindingInvalidated. An implementation may release that binding tuple so another anchor can be bound, but an invalidated record must remain queryable and must never be rebound itself. Recovery requires substantial administrative trust, which is why it was not included in the minimal registry interface.

Six checks for a mutually declared binding

For a consumer to treat a binding as mutually declared, all six of the following must be confirmed: that getAnchor returns the expected token, scope, and token ID; that isBound is true; that isActive is true; that isBindingValid is true when recovery is implemented; that the token supports the corresponding token-side interface; and that the token reports the same registry and anchor. If even one of these fails, it must not be treated as a currently mutually declared binding. A compliant registry must implement ERC-165 and return true for the registry and lifecycle interface IDs, and must also return true for the recovery interface ID if recovery is implemented. ERC-165 only indicates interface support and does not prove correct behavior or the validity of the off-chain claim.

What this standard does not guarantee

The scope of guarantee is deliberately limited to a single registry instance. Independent registries or deployments on other chains may each register claims about the same off-chain asset, and no global uniqueness or cross-registry conflict resolution is provided. Since anchorId does not include a chain identifier or registry address, consumers that need global identification must use the combination (chainId, registry, anchorId). Governance is also left open, so a single issuer, a regulated registrar, a multisig, a DAO, or a permissionless protocol can all operate a registry, and which registry to trust is left to the consumer. Allocation constraints such as issuance volume or share ratios are also not standardized and remain the responsibility of tokens and application contracts. This ERC does not establish the existence, ownership, legal validity, or value of an off-chain asset.

Things to watch out for

Original ERC · ← ERCs