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
event AnchorDeactivated(bytes32 indexed anchorId, string reason);: Emitted along with a reason when an anchor is permanently deactivated.function getAnchor(bytes32 anchorId) external view returns (AnchorRecord memory);: Returns the full stored anchor record and reverts for an unknown anchor ID.function isBound(bytes32 anchorId) external view returns (bool);: Returns whether a token is bound to the anchor. This only looks at whether a binding exists, independent of lifecycle or invalidation status.function getMetadata(bytes32 anchorId) external view returns (AnchorMetadata memory);: Returns the anchor's metadata (asset class, jurisdiction, attestation date, expiration time, URI, etc.).function registeredBy(bytes32 anchorId) external view returns (address);: Returns the address that registered the anchor, which does not change after registration.function isActive(bytes32 anchorId) external view returns (bool);: Returns whether the anchor has not been deactivated and is before expiration.function isBindingValid(bytes32 anchorId) external view returns (bool);: Returns true only if the token is bound and the binding has not been invalidated.function anchorId() external view returns (bytes32);: A contract-wide bound token declares the anchor ID it is bound to. This does not change after deployment.function anchorRegistry() external view returns (address);: Returns the registry address that the token declares it is bound to. This does not change after deployment.function isAnchorActive() external view returns (bool);: Reflects whether the anchor is active, based on the declared registry.function anchorIdOf(uint256 tokenId) external view returns (bytes32);: Returns the anchor ID that a specific token ID is bound to, and reverts for a token ID that is not bound.function isAnchorActiveFor(uint256 tokenId) external view returns (bool);: Reflects whether the anchor for a specific token ID is active, based on the declared registry.
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
- Registry trust: A registry only proves that its own state satisfies the structural rules. A malicious or compromised registrant can register a false claim with formally valid hashes and metadata, so consumers must evaluate the operator, authorization policy, upgrade authority, and legal context.
- No guarantee of legal or physical fact: A hash or a mutually declared binding does not prove that an off-chain asset exists, that a document is authentic, or that a token confers legal rights. Such determinations require external verification.
- Race between registration and binding: If registration and binding are separate, a window opens in between in which an unauthorized caller could attempt the binding. Implementations must restrict binding to authorized parties, and registrants requiring atomicity should use registerAndBind.
- Malicious tokens and interface impersonation: A token can return arbitrary values from the token-side interface or falsely claim ERC-165 support. Consumers must independently verify the registry record, and a registry that calls into a token during binding should use static calls and handle abnormal returns and reverts.
- Recovery authority: A recovery administrator can invalidate even a legitimate binding, and in implementations that release the tuple, an alternative anchor binding becomes possible. Production deployments should protect recovery authority with a multisig, governance, or a timelock, and consumers should monitor the TokenBindingInvalidated and AnchorDeactivated events.