---
eip: 8444
title: Agentic Resource Discovery (ARD) Onchain
description: Address-rooted onchain value registry whose records are conformant ARD entries anchored to skill, tender and agent identity contracts
author: Gary Yang (@garyyang-finchip)
discussions-to: https://ethereum-magicians.org/t/erc-8444-agentic-resource-discovery-ard-onchain/29963
status: Draft
type: Standards Track
category: ERC
created: 2026-10-12
requires: 165, 712, 1271
---

## Abstract

This ERC defines an onchain registry of *value records* for agentic resources. A value record binds an ARD entry, as defined by the Agentic Resource Discovery (ARD) specification maintained by the ards-project working group, to an onchain *anchor* — a token-bound executable skill, a token-bound task tender, an address-anchored agent identity, or an ARD registry endpoint — and, through a *kind adapter*, to the address that controls that anchor. Every record is a valid ARD entry, and every resolver that serves records is a conformant ARD registry, so agents and agent users outside Web3 discover and consume onchain value through ordinary ARD search. Authority over a record belongs to an address, never to a domain; a domain is an endorsement that an address collects by claiming it onchain and being acknowledged in the domain's own ARD manifest. Records are identified by their anchor; ARD URNs are aliases.

## Motivation

ARD answers *what exists and who says so*. A publisher lists its MCP servers, A2A agents, skills and APIs in a manifest under its own domain; registries crawl, index and federate those manifests; a client asks in natural language and receives entries together with the publisher's identity. ARD deliberately defines nothing beyond discovery: no price, no payment, no settlement, no escrow, no credit. It has no demand side — only resources that are offered, never work that is wanted — and it roots trust in domain ownership.

An agent economy needs the complementary half: *what a resource is worth, who stands behind it, and how it settles*. Those questions already have onchain answers. A token-bound executable skill commits a package hash and sells calls, epochs, licences or copies. A token-bound task tender escrows a bounty inside the token and settles against verifiers, judges or collective decisions. An address-anchored agent identity turns any address into a dormant identity that becomes active when a live agent is bound to it, and credit assertions attach to it. What is missing is the layer that lets an ordinary ARD client see those answers without a wallet, an RPC endpoint or any knowledge of the chain.

This ERC is that layer. It makes three decisions.

**Value root = settlement root.** In the agent economy, any party that gets paid already holds an address: the payee of an HTTP 402 payment is an address, the owner of a token is an address, the recipient of an escrowed bounty is an address. Rooting value in the address therefore adds nothing a publisher does not already have, and it keeps the authority that moves money and the authority that describes the offer in the same hands. A domain is not a second root. It is an endorsement an address can collect, and a domain without a key can only publish dormant entries.

**Anchor is identity, URN is alias.** ARD identifies entries by domain-anchored URNs. Value records are identified by their anchor — a CAIP-19 token identifier or a CAIP-10 account identifier — because that is what the money, the escrow and the identity actually attach to. The controller's URN becomes the record's canonical name only while the controller holds a verified binding to that URN's publisher domain; every gateway can additionally serve the record under its own alias. Equality is decided by anchor, so the same record can be reached through many names and never confused with another.

**Kind is posture, type is format.** ARD's `type` says what format a payload has. A value record's `kind` says which side of a trade the entry occupies: an offer, a tender, a subject, or an index. The two are orthogonal. The same MCP server can be offered as a skill and sought by a tender; new payload formats never add kinds.

Two consequences are worth naming. Every existing ARD entry is already a dormant value record (level 0); nothing changes for domain-only publishers until an address anchors their entry. And tenders give ARD a second direction of discovery: a `task` entry is the first ARD entry whose reader is a supplier looking for work rather than a consumer looking for a tool.

## 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](https://www.rfc-editor.org/rfc/rfc2119) and [RFC 8174](https://www.rfc-editor.org/rfc/rfc8174).

### 1. Terms

- **ARD entry**: an entry object as defined by the ARD specification, v0.91: `identifier` (a URN of the form `urn:air:<publisher>:<namespace>:<name>`, where `<publisher>` is a fully qualified domain name), `displayName`, `type` (a media type), exactly one of `url` or `data`, and optional members including `description`, `tags`, `capabilities`, `representativeQueries`, `version`, `updatedAt`, `metadata` and `trustManifest`. This ERC does not restate that specification; the entry schema it relies on is vendored as [`ard-entry.schema.json`](../assets/eip-8444/vendor/ard/ard-entry.schema.json), together with its JSON-LD context and licence.
- **Record**: an onchain value record: kind, anchor, descriptor hash, descriptor URI, canonical URN and status.
- **Anchor**: the identity of a record, `(chainId, target, id)`. Rendered as a CAIP-19 asset identifier for token anchors (`eip155:<chainId>/erc721:<target>/<id>`) and as a CAIP-10 account identifier for address anchors (`eip155:<chainId>:<target>`, with `#<id in hex>` appended for a registry anchor).
- **Controller**: the address with authority over an anchor, derived by the kind adapter at call time.
- **Descriptor**: the publisher-authored ARD entry carrying the value members of section 4. Its *content members* are hashed into the record.
- **Resolver**: software that reads records and domain manifests, computes the resolved members of section 4.3, and serves records. A resolver that exposes the ARD registry interface is a **gateway**.
- **Binding**: the relation between a domain and an address (section 5).
- **Level**: `L0` dormant, `L1` anchored, `L2` backed (section 6).

### 2. Kinds

A kind is a value posture. Its identifier is `bytes32 kindId = keccak256("ard-onchain.kind.<name>")`.

| kind | posture | anchor | controller |
|---|---|---|---|
| `skill` | offer (supply side) | token-bound executable skill token (CAIP-19) | the token owner |
| `task` | tender (demand side) | token-bound task tender token (CAIP-19) | the token owner |
| `agent` | subject | an address (CAIP-10), `id` = 0 | the address itself |
| `registry` | index | an operator address (CAIP-10) with a nonzero `id` discriminator | the address itself |

A registry instance MUST support these four kinds. A new kind MUST correspond to a new value posture; a new payload format MUST NOT introduce a kind. The names `assertion`, `policy` and `market` are reserved for future postures and are not defined by this ERC.

### 3. Records, anchors and identifiers

- `recordId = keccak256(abi.encode(block.chainid, target, id))`. An anchor has at most one record; registering an anchor that already has a record MUST revert, whatever kind is given.
- Anchors MUST be on the chain of the registry instance. The same token on another chain is another anchor and another record.
- Records are equal if and only if their anchors are equal. URNs never decide equality.
- **Canonical URN.** The controller MAY supply a URN at registration and on every descriptor update. The URN is the record's canonical identifier only while (a) the binding between the URN's `<publisher>` domain and the current controller is `verified` (section 5) and (b) that domain's manifest contains an entry whose `identifier` equals the URN. Otherwise the record has aliases only, and a resolver that previously served the URN as canonical MUST report it as stale.
- **Gateway alias.** A gateway serving the record under its own domain `G` MUST use the identifier `urn:air:G:onchain:<anchor-encoding>`, where `<anchor-encoding>` is the anchor's CAIP string, lowercased, with every `:`, `/` and `#` replaced by `-`. Aliases are per gateway; two gateways serve the same record under two aliases, and clients MUST compare records by `value:anchor`, not by identifier.
- A record MAY have an empty URN. Such a record is served under aliases only.
- The `descriptorURI` of a record is a hint for fetching the descriptor. It is not a trust source: a fetched descriptor is accepted only when its content hash equals the record's `descriptorHash`.

### 4. Descriptor

A descriptor is an ARD entry with the additional members below. Its members fall into three classes.

#### 4.1 Member classes and hash

- **Publication members** — `@context`, `@id`, `identifier`, `trustManifest`. They belong to whoever publishes the entry: the controller's own manifest or a gateway serving an alias. They are not hashed; identification is committed by the record's `urn` and by the controller address.
- **Resolved members** — the `value:` members listed in section 4.3. They are computed by resolvers and are never hashed.
- **Content members** — every other member, including the ARD base members (`displayName`, `type`, `url` or `data`, `description`, `tags`, `capabilities`, `representativeQueries`, `version`, `updatedAt`, `metadata`) and the authored `value:` members of section 4.2.

`descriptorHash` MUST equal SHA-256 over the UTF-8 bytes of the JSON Canonicalization Scheme ([RFC 8785](https://www.rfc-editor.org/rfc/rfc8785)) serialization of the content members. Because publication and resolved members are excluded, a gateway alias entry and the controller's own entry hash to the same value, and a resolver can verify any served copy against the chain.

#### 4.2 Authored value members

The prefix `value` denotes the IRI

```
https://eips.ethereum.org/assets/eip-8444/ns#
```

and the context document is [`value-context.jsonld`](../assets/eip-8444/schemas/value-context.jsonld). All authored members are OPTIONAL unless stated.

| member | value | meaning |
|---|---|---|
| `value:kind` | `skill`, `task`, `agent` or `registry` | REQUIRED. The posture; MUST equal the record's kind. |
| `value:controller` | `did:pkh:eip155:<chainId>:<address>` | The controller the publisher declares. In a domain's manifest this is the acknowledgment of section 5.2. |
| `value:terms` | object `{ unit, price: { amount, currency, asset? }, cadence?, supply? }` | Offer terms. `unit` is one of `call`, `epoch`, `license`, `copy`; `amount` is a decimal string; `currency` is a display code; `asset` is a CAIP-19 identifier when the price is denominated in an onchain asset. |
| `value:settlement` | array of `{ rail, payee, asset?, endpoint? }` | Settlement rails. `rail` is one of `onchain`, `x402`, `declared`; `payee` is a CAIP-10 account identifier. `declared` carries no guarantee and is meaningful only at level 0. |
| `value:provenance` | object `{ packageDigest, cardDigest? }` | Digests of the anchored package and of the published card, as `sha256:<hex>`. For `skill` and `task` records, `packageDigest` MUST equal the anchor hint (section 8) for the record to reach level 2. |
| `value:credit` | array of `{ scheme, level, registry }` | References to credit assertions about the subject. Referenced, never copied. |
| `value:dispute` | object `{ policyId, registry }` | The collective decision policy that governs disputes about the record. Referenced, never copied. |
| `value:eligibility` | object `{ credit?: { scheme, minLevel }, binding?: "verified" }` | `task` only: conditions a fulfiller must meet. |
| `value:registration` | URI | `agent` only: the [ERC-8004](./eip-8004.md) registration file of the bound agent. |
| `value:indexes` | array of CAIP-10 identifiers | `registry` only: the registry instances the endpoint indexes. |

The payload of a `task` descriptor MUST have `type` `application/ai-task+json` and MUST carry the tender description in `data` (or at `url`) as defined by [`ai-task.schema.json`](../assets/eip-8444/schemas/ai-task.schema.json): `wants` (payload media types accepted as fulfilment), `bounty` (`amount`, `currency`, optional CAIP-19 `asset`), and optional `specDigest`, `specURI`, `cadence`, `cardinality`, `copies`, `acceptance` (`mode` one of `verifier`, `judge`, `collective`, with `ref` and `policyId`), `deadlines` and `eligibility`. The payload MUST be valid without any onchain member, so that a tender published by a domain without a key is a legal ARD entry.

#### 4.3 Resolved members

Resolvers add the following members to served entries and MUST NOT include them when computing `descriptorHash`: `value:anchor` (the anchor's CAIP string), `value:binding` (section 5.3), `value:level` and `value:status` (section 6), `value:aliases` (the gateway aliases the resolver serves), `value:canonical` (the canonical URN, when valid), `value:stale` (URNs the record carried that are no longer canonical), `value:claimedUrn` (the record's URN when it is not canonical), `value:registry` (the CAIP-10 identifier of the registry instance) and `value:descriptorHash` (`sha256:<hex>` of the content members).

### 5. Domain claims and binding

#### 5.1 Claims (onchain)

An address claims a domain by calling `claimDomain`, or by having anyone submit `claimDomainBySig` with an [EIP-712](./eip-712.md) signature of the claim; contract accounts are verified through [ERC-1271](./eip-1271.md). The typed data is

```
EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)
  name = "ARD Onchain Registry", version = "1"
DomainClaim(string domain,address controller,uint256 nonce,uint256 deadline)
```

with `nonce` equal to `nonces(controller)` at submission; a successful signed claim increments the nonce. The registry records claims and never adjudicates them: several addresses MAY claim one domain. A domain string MUST be in normalized form — lowercase ASCII letters, digits, `-` and `.`, 1 to 253 bytes, no leading or trailing `.`, no empty label — and `domainHash = keccak256(bytes(domain))`. Internationalized names MUST be given in their IDNA ASCII (punycode) form.

#### 5.2 Acknowledgment (off-chain)

A domain acknowledges an address in its ARD manifest at `/.well-known/ard.json`: either a top-level member `value:controllers` (an array of `did:pkh` identifiers) containing the address, or an entry whose `value:controller` is the address. ARD registries ignore both members; resolvers conforming to this ERC read them.

#### 5.3 Binding states

Resolvers compute the binding of a (domain, address) pair as:

| state | condition |
|---|---|
| `unbound` | the address has no claim on the domain |
| `claimed` | the address has a claim and the domain does not acknowledge it; no other address has a claim |
| `verified` | the address has a claim and the domain acknowledges it |
| `contested` | several addresses have claims; an acknowledged one is `verified`, every other one is `contested` |

A record's `value:binding` is the state of (publisher domain of the record's URN, current controller), except that `unbound` is reported as `third-party`, and a record without a URN reports `none`. Loss of acknowledgment demotes `verified` to `claimed` or `contested`; the canonical URN then becomes stale. A binding that returns does not retroactively validate anything served while it was absent.

The registry does not verify manifests, and the chain cannot. Where an onchain consumer needs a verified binding — for example a tender whose `value:eligibility` requires one — the verified state is mirrored onchain as an assertion issued by a party that consumer trusts; this ERC defines no such assertion registry.

### 6. Levels and status

Record status is one of `Active`, `Suspended`, `Retired`. The controller moves a record between `Active` and `Suspended` and from either to `Retired`; `Retired` is terminal and binds every later holder of the anchor.

| level | condition |
|---|---|
| `L0` dormant | no record exists; value members, if present, are declared and unverifiable |
| `L1` anchored | a record exists with status `Active` |
| `L2` backed | `L1`, the kind adapter reports the anchor as backed, and for kinds with an anchor hint the descriptor's `value:provenance.packageDigest` equals the hint |

Levels and binding states are orthogonal. A client sees a record as the triple (level, binding, credit).

### 7. Registry interface

```solidity
interface IARDOnchainRegistry {
    enum Status { None, Active, Suspended, Retired }

    struct Record {
        bytes32 kind;
        address target;
        uint256 id;
        bytes32 descriptorHash;
        string  descriptorURI;
        string  urn;
        Status  status;
        address registrant;
        uint64  updatedAt;
    }

    event Registered(bytes32 indexed recordId, bytes32 indexed kind, address indexed target, uint256 id, address registrant, bytes32 descriptorHash, string descriptorURI, string urn);
    event DescriptorUpdated(bytes32 indexed recordId, bytes32 descriptorHash, string descriptorURI, string urn);
    event StatusChanged(bytes32 indexed recordId, Status status);
    event DomainClaimed(bytes32 indexed domainHash, address indexed controller, string domain);
    event DomainReleased(bytes32 indexed domainHash, address indexed controller);
    event KindRegistered(bytes32 indexed kind, address adapter, string uri);
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    function register(bytes32 kind, address target, uint256 id, bytes32 descriptorHash, string calldata descriptorURI, string calldata urn) external returns (bytes32 recordId);
    function updateDescriptor(bytes32 recordId, bytes32 descriptorHash, string calldata descriptorURI, string calldata urn) external;
    function setStatus(bytes32 recordId, Status status) external;

    function claimDomain(string calldata domain) external;
    function claimDomainBySig(string calldata domain, address controller, uint256 nonce, uint256 deadline, bytes calldata signature) external;
    function releaseDomain(string calldata domain) external;

    function registerKind(bytes32 kind, address adapter, string calldata uri) external;
    function transferOwnership(address newOwner) external;

    function owner() external view returns (address);
    function recordIdOf(address target, uint256 id) external view returns (bytes32);
    function recordOf(bytes32 recordId) external view returns (Record memory);
    function controllerOf(bytes32 recordId) external view returns (address);
    function backed(bytes32 recordId) external view returns (bool);
    function anchorHintOf(bytes32 recordId) external view returns (bytes32);
    function claimsOf(bytes32 domainHash) external view returns (address[] memory);
    function hasClaim(bytes32 domainHash, address controller) external view returns (bool);
    function domainHashOf(string calldata domain) external pure returns (bytes32);
    function kindAdapter(bytes32 kind) external view returns (address);
    function nonces(address controller) external view returns (uint256);
    function DOMAIN_SEPARATOR() external view returns (bytes32);

    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
```

- `register` MUST revert unless `kind` has an adapter, `descriptorHash` is nonzero, `adapter.exists(target, id)` is true, `msg.sender == adapter.controllerOf(target, id)`, and no record exists for the anchor. On success the record is `Active`, `registrant` is `msg.sender`, and `Registered` is emitted.
- `updateDescriptor` and `setStatus` MUST revert unless the record exists, is not `Retired`, and `msg.sender` equals the controller the adapter reports *at that moment*. The registry MUST NOT store or cache controllers.
- `setStatus` MUST accept only `Active` → `Suspended`, `Suspended` → `Active`, and either → `Retired`.
- `claimDomain` and `claimDomainBySig` MUST validate the domain as in section 5.1, MUST revert on a duplicate claim by the same address, and MAY bound the number of claimants per domain. `claimDomainBySig` MUST revert when `deadline` has passed, when `nonce` is not the controller's current nonce, or when the signature does not verify for `controller`.
- `registerKind` MUST be restricted to the instance owner, MUST revert for a kind that already has an adapter, and MUST revert unless `adapter.kind()` equals `kind`. The kind table is therefore write-once; transferring ownership to the zero address makes it final.
- `controllerOf`, `backed` and `anchorHintOf` MUST be forwarded to the kind adapter and MUST revert for an unknown record.
- The registry MUST implement [ERC-165](./eip-165.md) and return true for the interface above (id `0x881908a5`).

### 8. Kind adapter interface

```solidity
interface IARDKindAdapter {
    function kind() external view returns (bytes32);
    function exists(address target, uint256 id) external view returns (bool);
    function controllerOf(address target, uint256 id) external view returns (address);
    function backed(address target, uint256 id) external view returns (bool);
    function anchorHint(address target, uint256 id) external view returns (bytes32);
}
```

Adapters MUST NOT revert from these views; a missing or destroyed anchor is reported as `exists == false`, `controllerOf == address(0)`, `backed == false`, `anchorHint == 0`. The four kinds behave as follows.

| kind | `exists` | `controllerOf` | `backed` | `anchorHint` |
|---|---|---|---|---|
| `skill` | `target` declares the skill kernel interface (id `0x734553a6`) through ERC-165 and `ownerOf(id)` succeeds | `ownerOf(id)` | the skill binding's package hash is nonzero | the package hash |
| `task` | `target` declares the tender kernel interface (id `0xc319d532`) through ERC-165 and `ownerOf(id)` succeeds | `ownerOf(id)` | the tender is not cancelled, its escrow balance is nonzero, and `settleBy` is zero or in the future | the task hash |
| `agent` | `target != 0` and `id == 0` | `target` | the configured agent identity registry reports the address as active | the digest of the identity's self-declared document, if any |
| `registry` | `target != 0` and `id != 0` | `target` | never | 0 |

For `registry` records the RECOMMENDED discriminator is `uint256(keccak256(bytes(endpointURL)))`; it keeps an operator's registry records distinct from its own `agent` record. The skill owner, not the skill's update authority, is the controller: a value record is an offer, and offers belong to holders. The tender owner, not its acceptance authority, is the controller for the same reason.

### 9. Resolvers and gateways

A resolver builds the served view of a record as follows: it reads the record and the adapter views; it fetches the descriptor; it rejects the descriptor unless the content-member hash equals `descriptorHash`; it derives the anchor string, the gateway alias, the binding state, the canonical URN, the level and the status; and it emits the content members verbatim with the publication members it owns and the resolved members of section 4.3. A resolver MUST NOT serve a record whose descriptor it could not fetch or whose hash does not match as if it were consistent, and MUST NOT serve `Retired` records in search results.

A gateway MUST be a conformant ARD registry: it MUST implement the search endpoint with the federation modes `auto`, `referrals` and `none`, SHOULD implement the explore endpoint, MAY implement the listing endpoint, and MUST publish `/.well-known/ard.json` containing at least one entry of type `application/ai-registry+json` that describes itself. Every served record MUST carry `value:anchor` and `value:descriptorHash`, so that any client can verify it against the chain or against a second gateway. A gateway SHOULD list other gateways of the same instance, gateways of other instances, and ordinary ARD registries as referrals, and MAY register itself as a `registry` record so that chain-native agents find it.

## Rationale

**One root.** Every earlier design that gave domains and addresses equal standing produced a split authority: a domain could publish a price its key holder had not signed, and a key holder could not prove it owned what a domain said. Making the address the only root, and the domain an endorsement the address collects, removes the split without excluding anyone — a domain that acknowledges an address in its manifest costs the publisher one JSON member, and a publisher with no key stays discoverable at level 0.

**Claims onchain, acknowledgment off-chain.** The chain cannot read a manifest over HTTPS, so a binding cannot be verified by the registry. Recording claims only, and letting resolvers compute the state from claims plus the manifest, keeps the kernel honest about what it knows; the same split is used for the agent identity's liveness states, where the chain stores facts and resolvers derive states.

**Anchor as identity.** ARD's URN grammar requires a domain, and a record rooted in an address has none until a binding is verified. Treating the URN as an alias instead of fighting the grammar lets every gateway serve every record under a grammar-conformant name, lets a controller's canonical name appear and disappear with its binding, and makes transfers harmless: a token changes hands, the controller changes, the canonical URN goes stale, the anchor does not move.

**Content hash without the envelope.** Hashing the identifier would make the commitment depend on the publishing domain, which is exactly what the alias mechanism must be free of. Hashing the content members only means the controller commits to what is offered or wanted, the record commits to the name, and any copy anywhere can be checked.

**Controllers derived, never stored.** A stored controller is a stale controller after the first transfer. Asking the adapter at every call costs one external view and removes an entire class of bugs. The same reasoning puts the owner, not the update authority or the acceptance authority, in control: the party who can sell or fund the anchor is the party whose offer or tender it is.

**A thin kernel.** Prices, escrow balances, credit levels and identity states already live in the anchored contracts; copying them into the registry would create a second source of truth that drifts. The registry references them, and adapters read them live. The kernel is not upgradeable and the kind table is write-once, so the rule that was registered is the rule that runs.

**Same-chain anchors.** A registry can verify an anchor on its own chain and nothing else. Cross-chain duplicates are separate records; linking them is left to a future profile.

**Kinds as postures.** Tying kinds to payload formats would have doubled the kind table every time a new card format appeared. Four postures cover the trade: what is offered, what is wanted, who trades, and where to look. The reserved names leave room for credit schemes, decision policies and venues to become discoverable in their own right.

**The ARD side.** The profile depends on ARD's open entry schema (`additionalProperties: true`), its JSON-LD extension mechanism and its registry interface, and on nothing else; field names of the upstream specification are vendored and pinned, so that upstream drift is absorbed in the profile rather than in the kernel. The `application/ai-task+json` payload is designed to be adoptable upstream unchanged.

Contributions to specific parts of this proposal will be credited here by name.

## Backwards Compatibility

Every record is a valid ARD entry: an ARD registry that knows nothing of the `value` namespace indexes it as an ordinary entry with ordinary members. Existing ARD manifests are dormant records; nothing changes for them until an address anchors an entry. No change is required to skill, tender or identity contracts: adapters read their existing views, and the registry writes nothing to them. A registry instance on a chain without an agent identity registry runs the `agent` kind with an adapter whose `backed` is always false.

## Test Cases

All values below are regenerated by `tools/vectors.js` in the reference repository and re-derived in Solidity by `test/ARDVectors.t.sol`; they are reproduced here so that an implementation can be checked without either.

**Kind identifiers.** `keccak256("ard-onchain.kind.skill")` = `0x75357da1ea166b9845548f84f7855b7f122cfc7dc349e5edbdca01bd8ca7b0bb`; `task` = `0x119c021f95332a1e2d06adbfe8e298dc2484ac0daa16e4cd837d8de82fbba87c`; `agent` = `0x283cdd5ece2ad33dc6be3a32a7a140a653c611b0296c9b54878ae0439bbef3a2`; `registry` = `0x455d1d9d2b151bdfaa173c1cee0ee26ece8e7bdb98830e8e3ca1e180989cf3c6`.

**Record identifiers.** `keccak256(abi.encode(chainId, target, id))`:

| chainId | target | id | recordId |
|---|---|---|---|
| 1 | `0x1111111111111111111111111111111111111111` | 42 | `0x0d08c6fa2c6a5ae5c13bc57518a49b424023de0ef0cd02b9ea602037047a6db1` |
| 8453 | `0x1111111111111111111111111111111111111111` | 42 | `0xc245c99190db0ed5821479618eb82b3becdc274dca6acddaf3078000f199f22f` |
| 11155111 | `0x2222222222222222222222222222222222222222` | 0 | `0xa88c79ac8e1d8dbae0dbc845c75506143ba90b04f2a66da265038f84c19cc37a` |

**Domain claim.** Registry at `0x1111111111111111111111111111111111111111` on chain 11155111; private key `0x00000000000000000000000000000000000000000000000000000000000a11ce` (controller `0xe05fcC23807536bEe418f142D19fa0d21BB0cfF7`); claim `domain = "alice.example"`, `nonce = 0`, `deadline = 1800000000`. `domainHash` = `0xd94dcba65ee46b0c774ec85ff2be239f8804d9baabc4179270b1bd4ce2b0f7a7`; domain separator = `0xec5831d04515ab1995eebc8bbec08d8148a53409f71b9ecf675314f4984a77f3`; `DomainClaim` type hash = `0x71105fff4878e24d184c15b1ff52740495db3e91040fe5acf0f667c43b3b7859`; struct hash = `0xd5fc7c7976d4c81c60f792a00d8b43a9c1ffa3d7eda855af2bab488b6d8f8ba0`; digest = `0x238311c40bc07599d7364142ccc7591b5a8d00af5da9575d2260595a6ef385a4`; signature = `0x24e212617b31874c2cd8cc233f3fafbec367c7c0f88c9de5312ec7e78f0287181035daf9fd426a0b7dbf82d0b8ccec72c06329e70ede835e54e4f8aba640870a1c`. `claimDomainBySig("alice.example", controller, 0, 1800000000, signature)` MUST succeed and leave `nonces(controller) == 1`.

**Descriptor hash.** For the `registry` example descriptor the content members canonicalize to

```
{"description":"ARD registry endpoint indexing the ARD Onchain registry instance on Base. Results carry onchain anchors and descriptor hashes for independent verification.","displayName":"ARD Onchain gateway for Base","representativeQueries":["onchain skills with escrowed bounties","agent tasks paying in USDC on Base"],"type":"application/ai-registry+json","updatedAt":"2026-10-12T00:00:00Z","url":"https://registry.finchip.ai/api/v1/","value:controller":"did:pkh:eip155:8453:0x6666666666666666666666666666666666666666","value:indexes":["eip155:8453:0x7777777777777777777777777777777777777777"],"value:kind":"registry"}
```

whose SHA-256 is `0xb5a387663102892a0d2bc1679191d4dd35ff4b5e21179fb61a73150b432445b6`. The `skill` example descriptor hashes to `0x8df640a84dbff351a1d67490b42a8d696f5d1232210863e2b469137fe105a6ac`, and the gateway alias entry that mirrors it in the example manifest hashes to the same value although its `identifier` and `trustManifest` differ.

**Gateway alias.** Anchor `eip155:8453/erc721:0x1111111111111111111111111111111111111111/42` served by `registry.finchip.ai` has the alias `urn:air:registry.finchip.ai:onchain:eip155-8453-erc721-0x1111111111111111111111111111111111111111-42`; address anchor `eip155:8453:0x2222222222222222222222222222222222222222` has the alias `urn:air:registry.finchip.ai:onchain:eip155-8453-0x2222222222222222222222222222222222222222`.

**Interface identifiers.** `IARDOnchainRegistry` = `0x881908a5`; `IARDKindAdapter` = `0x67bde273`; skill kernel interface = `0x734553a6`; tender kernel interface = `0xc319d532`.

**Behaviour.** The reference test suite covers, among others: registration of each kind; rejection of an unknown kind, an empty descriptor hash, a missing anchor, a non-controller and a second record for an anchor; the agent and registry anchors of one address not colliding; a controller that follows a token transfer while the registrant stays; a destroyed anchor reporting a zero controller and blocking mutation; the status transitions and the terminality of `Retired`; write-once kind registration; signed claims by an externally owned account and by an ERC-1271 account, with expiry, nonce and replay checks; domain validation; a funded, cancelled and expired tender; an identity moving through active, stale and retired; and the vectors above.

## Reference Implementation

The kernel [`ARDOnchainRegistry.sol`](../assets/eip-8444/contracts/ARDOnchainRegistry.sol) with its interface [`IARDOnchainRegistry.sol`](../assets/eip-8444/contracts/interfaces/IARDOnchainRegistry.sol) and [`SignatureChecker.sol`](../assets/eip-8444/contracts/libraries/SignatureChecker.sol); the adapter interface [`IARDKindAdapter.sol`](../assets/eip-8444/contracts/interfaces/IARDKindAdapter.sol) and the four adapters [`SkillAdapter.sol`](../assets/eip-8444/contracts/adapters/SkillAdapter.sol), [`TaskAdapter.sol`](../assets/eip-8444/contracts/adapters/TaskAdapter.sol), [`AgentAdapter.sol`](../assets/eip-8444/contracts/adapters/AgentAdapter.sol) and [`RegistryAdapter.sol`](../assets/eip-8444/contracts/adapters/RegistryAdapter.sol), compiled against the vendored kernel interfaces [`ISkillTokenKernel.sol`](../assets/eip-8444/contracts/adapters/interfaces/ISkillTokenKernel.sol), [`ITaskTenderKernel.sol`](../assets/eip-8444/contracts/adapters/interfaces/ITaskTenderKernel.sol) and [`IAgentIdentityRegistry.sol`](../assets/eip-8444/contracts/adapters/interfaces/IAgentIdentityRegistry.sol). Authored descriptors for each kind — [`skill.json`](../assets/eip-8444/examples/skill.json), [`task.json`](../assets/eip-8444/examples/task.json), [`agent.json`](../assets/eip-8444/examples/agent.json), [`registry.json`](../assets/eip-8444/examples/registry.json) — and a gateway manifest [`well-known-ard.json`](../assets/eip-8444/examples/well-known-ard.json). Vectors: [`kind-ids.json`](../assets/eip-8444/vectors/kind-ids.json), [`record-id.json`](../assets/eip-8444/vectors/record-id.json), [`domain-claim.json`](../assets/eip-8444/vectors/domain-claim.json), [`descriptor-hash.json`](../assets/eip-8444/vectors/descriptor-hash.json), [`alias.json`](../assets/eip-8444/vectors/alias.json), [`interface-ids.json`](../assets/eip-8444/vectors/interface-ids.json). A resolver and gateway that passes the ARD conformance tooling accompanies the reference repository; it is not part of these assets.

## Security Considerations

**Canonical URN squatting.** A canonical URN is valid only under a verified binding, so an address cannot take a name under a domain that does not acknowledge it. Gateway aliases are scoped to the gateway's own domain and cannot collide with anyone's canonical name.

**Domain hijack and expiry.** When a domain changes hands or its manifest changes, acknowledgment disappears and the binding drops to claimed or contested; the canonical URN turns stale and resolvers stop serving it as canonical. Nothing served during a gap is retroactively validated when the binding returns.

**Descriptor substitution.** The descriptor URI is a convenience. A resolver accepts a descriptor only when its content hash matches the record, so neither a hostile host nor a compromised gateway can change what a controller committed to without the mismatch being visible.

**Dishonest resolvers.** Every served record carries its anchor and descriptor hash, so a client can reproduce the resolver's work against the chain or cross-check a second resolver. Score values are relevance, never trust.

**Display-name phishing.** Names, descriptions and even trust manifests are publisher-authored. Clients should present the anchor, the binding state and the level, and treat a verified binding as the only domain-level trust signal.

**Foreign-chain anchors.** The registry cannot verify an anchor on another chain, so it refuses them. A record that claims to describe a foreign asset is simply a record about a local one.

**Signature replay.** Signed claims are bound to the registry's chain and address through the EIP-712 domain, to the signer through the nonce, and to a time window through the deadline. A claim transaction front-run by a stranger changes nothing: claims are per address and carry no meaning without acknowledgment.

**Spam and squatting of anchors.** Registration costs gas and requires control of the anchor; only the owner of a token, the address itself or an operator with a chosen discriminator can register, so nobody can register a record about someone else's anchor.

**Stale trust after transfer.** A transferred skill keeps its record but changes controller; its canonical URN goes stale, and credit facets that referred to the former controller no longer describe the current one. Resolvers re-derive binding and level on every sync, and clients should read the controller rather than remember it.

**Terminal retirement.** Retiring a record is irreversible and binds every later holder of the anchor. A controller that wants to pause an offer should suspend it; a buyer should read the record's status before acquiring an anchor.

**Adapter trust.** Adapters decide existence, control and backing for a kind, and they are registered write-once by the instance owner. A deployment should publish its adapter addresses with the instance, and an owner that has finished registering kinds should renounce ownership so that the kind table cannot change.

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).
