--- eip: 8323 title: Source-Token Agent Binding for ERC-8004 description: Derive an ERC-8004 agent identity from a pre-existing ERC-721, recording permanent provenance while exposing live ownership separately. author: Tiago Merlini (@TMerlini) , babyblueviper1 (@babyblueviper1) discussions-to: https://ethereum-magicians.org/t/erc-8323-source-token-agent-binding-for-erc-8004/28920 status: Draft type: Standards Track category: ERC created: 2026-06-12 requires: 165, 721, 8004 --- ## Abstract This standard defines a minimal interface for an [ERC-8004](./eip-8004.md) agent identity registry that is **bound to a single source [ERC-721](./eip-721.md) collection**. Holders of the bound collection bridge a token they own into an agent identity. The source token is recorded at registration time as **permanent provenance** and is never locked or transferred; ownership of the source token is checked once at mint via `ownerOf` and thereafter exposed as a **separate, live, re-checkable view**. This separates two facts that existing standards conflate: *which token an agent was derived from* (immutable) and *whether the current agent holder still owns that token* (mutable). ## Motivation [ERC-8004](./eip-8004.md) establishes agent identities but assumes an agent is minted fresh, with no relationship to a pre-existing asset. In practice the most natural agents are **derived from NFTs people already hold** — a PFP, a membership token, a game character. There is no standard way to say, verifiably and on-chain, "this agent *is* that NFT," nor to express the relationship without custody. [ERC-6551](./eip-6551.md) (token-bound accounts) gives a token an *account*, but inverts the direction this standard needs: TBA is "a wallet owned by a token," whereas an agent identity derived from a token is "an identity whose provenance is a token, owned by whoever the agent owner is." The two compose but solve different problems. Concretely, this standard answers: - **Provenance:** Which source token (collection + tokenId) was an agent minted from? This MUST be immutable for the life of the agent. - **Live ownership:** Does the current owner of the agent still own the source token? This MUST be queryable at any time without trusting stored state. - **Non-custody:** Binding MUST NOT lock, escrow, or transfer the source token. Eligibility is checked at mint via `ownerOf`; nothing on the source collection changes. - **Visual identity:** An agent bridged from a token SHOULD inherit that token's metadata, so it presents in wallets and marketplaces as the NFT it was derived from — making the binding legible to humans, not only to contracts. - **Discoverability:** Given a source collection, which registry holds its agents? (See the companion discovery standard.) Keeping provenance and live ownership distinct is the core contribution. A consumer evaluating an agent can ask "was this agent legitimately minted by a holder of collection X?" (permanent, answered by `getSourceNFT` + the `SourceNFTLinked` log) independently of "is the agent's source NFT still held by the agent owner today?" (live, answered by `isSourceNFTOwnershipValid`). Collapsing these — as a naïve "bound NFT" field would — loses provenance the moment the source token is sold, and loses freshness the moment it is cached. ## Specification The key words "MUST", "MUST NOT", "SHOULD", and "MAY" are to be interpreted as described in RFC 2119 and RFC 8174. A compliant registry MUST implement [ERC-721](./eip-721.md) (the agent identity is itself an NFT), [ERC-8004](./eip-8004.md), and the following interface. It MUST return `true` from [ERC-165](./eip-165.md) `supportsInterface(0x27eba962)`, the interface id of `IAgentSourceBinding` (the XOR of its five function selectors). ```solidity interface IAgentSourceBinding /* is IERC165 */ { /// @notice Emitted once, at registration, binding an agent to its source token. /// MUST be emitted exactly once per agentId and MUST NOT be emitted again. event SourceNFTLinked( uint256 indexed agentId, address indexed sourceContract, uint256 sourceTokenId ); /// @notice The source ERC-721 collection this registry is bound to. /// @dev MUST be immutable for the life of the registry. function boundCollection() external view returns (address); /// @notice Register an agent derived from `sourceTokenId` of `boundCollection`. /// @dev MUST revert unless `IERC721(boundCollection).ownerOf(sourceTokenId) == msg.sender`. /// MUST mint a new agent identity (ERC-721) to `msg.sender`. /// MUST record (boundCollection, sourceTokenId) as immutable provenance. /// MUST emit `SourceNFTLinked`. /// MUST NOT lock, escrow, or transfer the source token. /// @return agentId The newly minted agent identity. function registerWithSource(uint256 sourceTokenId) external payable returns (uint256 agentId); /// @notice The immutable source token an agent was derived from. /// @dev MUST revert if `agentId` does not exist or has no source binding. function getSourceNFT(uint256 agentId) external view returns (address sourceContract, uint256 sourceTokenId); /// @notice Whether `agentId` has a recorded source binding. function hasSourceNFT(uint256 agentId) external view returns (bool); /// @notice Whether the source token is still under the control of `agentId`. /// @dev Live check via `ownerOf`. Valid if `ownerOf(sourceToken)` is the agent /// holder, the agent's CANONICAL ERC-6551 token-bound account, OR the binding /// contract itself (sovereignty patterns). A bare `ownerOf == ownerOf(agentId)` /// check MUST NOT be used — it force-fails TBA/binding-custody bindings. /// The TBA in (b) is pinned: the account from the ERC-6551 registry /// 0x000000006551c19487814612e58FE06813775758 with the implementation and salt /// the registry declares — not "any TBA of the agent" (a token has many). /// MUST NOT rely on cached ownership. Reverts for a non-existent `agentId` /// (per ERC-721 `ownerOf`); returns false (not revert) when the source token /// no longer exists. function isSourceNFTOwnershipValid(uint256 agentId) external view returns (bool); } ``` ### Query-only source binding Not every agent that can prove source ownership implements source-binding *registration*. The canonical case is a **self-sourced** agent, whose source is the agent contract itself — `getSourceNFT(agentId)` returns `(address(this), agentId)` — so there is no external collection and `boundCollection` / `registerWithSource` have no meaning for it. Such an implementation MUST NOT advertise `IAgentSourceBinding` (`0x27eba962`): it does not implement all five functions, and advertising the full id is a false [ERC-165](./eip-165.md) positive that a consumer would trust. For these implementations this standard defines a query-only companion interface, `IAgentSourceBindingView` — the read side of source binding, with [ERC-165](./eip-165.md) id `0x8b3597c9` (the XOR of the `getSourceNFT`, `hasSourceNFT`, and `isSourceNFTOwnershipValid` selectors): ```solidity interface IAgentSourceBindingView /* is IERC165 */ { /// @notice The immutable source token an agent was derived from. /// @dev MUST revert if `agentId` does not exist or has no source binding. function getSourceNFT(uint256 agentId) external view returns (address sourceContract, uint256 sourceTokenId); /// @notice Whether `agentId` has a recorded source binding. function hasSourceNFT(uint256 agentId) external view returns (bool); /// @notice Whether the source token is still under the control of `agentId`. /// Same semantics as in `IAgentSourceBinding`. function isSourceNFTOwnershipValid(uint256 agentId) external view returns (bool); } ``` The three functions MUST implement semantics identical to their same-named counterparts in `IAgentSourceBinding` — this is a subsetting of interface surface, not a relaxation of behavior. `isSourceNFTOwnershipValid` in particular MUST implement the full three-case live-ownership rule defined under **Provenance vs. live ownership** below (direct holder, canonical [ERC-6551](./eip-6551.md) TBA, or binding-contract escrow) — a literal `ownerOf(sourceToken) == ownerOf(agentId)` check is non-conformant here for the same reason it is non-conformant in the full interface, and a self-sourced implementation is not exempt from it: a self-sourced agent can equally hold its source via a TBA or an escrowing binding contract rather than direct ownership, so a naïve equality check silently force-fails the sovereignty pattern this companion interface exists to serve. `IAgentSourceBindingView` is an independent interface, **not** a base of `IAgentSourceBinding`: the two ids stay fixed to the functions each declares. - An implementation that returns `true` from `supportsInterface(0x8b3597c9)` MUST implement all three functions with the semantics defined in this standard. - Such an implementation MUST return `false` from `supportsInterface(0x27eba962)` unless it also implements `boundCollection` and `registerWithSource`. - A full `IAgentSourceBinding` implementation MAY, and SHOULD, also return `true` from `supportsInterface(0x8b3597c9)` — the three functions are a subset it already implements — so a view-only consumer can detect it without probing the full interface. - A consumer that only needs to read and re-verify source ownership SHOULD accept either `0x8b3597c9` or `0x27eba962`. A consumer that requires source-binding registration MUST check `0x27eba962`. The boundary is drawn here deliberately: `IAgentSourceBindingView` is not "a smaller interface for lesser contracts" but the **honest limit of what a self-sourced agent can attest**. Whether a source exists and is still owned is universal; registration presumes an external collection. An implementation MUST advertise only the interface it implements. ### Registration - `registerWithSource` MUST verify live ownership of the source token by the caller and MUST revert otherwise. - Implementations MAY offer overloads accepting a custom agent URI and/or initial metadata, provided the source-ownership check and the `SourceNFTLinked` emission are preserved. - The source binding, once recorded, MUST be immutable. There MUST be no function that rebinds, clears, or overwrites the `(sourceContract, sourceTokenId)` of an existing agent. ### Provenance vs. live ownership - `getSourceNFT` and the `SourceNFTLinked` event express **permanent provenance** and MUST remain valid even after the source token is transferred away. - `isSourceNFTOwnershipValid` expresses **live ownership** and MUST be computed from a current `ownerOf` call at query time. - Consumers MUST NOT infer live ownership from `getSourceNFT` alone. - **Valid ownership is not bare owner-equality.** `isSourceNFTOwnershipValid` MUST return `true` when `ownerOf(sourceToken)` is any of: (a) the current owner of `agentId` — direct, non-custodial holding; (b) the agent's canonical [ERC-6551](./eip-6551.md) token-bound account — the agent holds its own source (sovereignty); or (c) the binding contract itself — source escrowed under the binding (sovereignty). A literal `ownerOf(sourceToken) == ownerOf(agentId)` check is **non-conformant**: it reports every TBA- or binding-custody binding as permanently lapsed, force-failing the sovereignty pattern. - **Case (b) MUST pin a single account.** A token-bound account is `CREATE2`-derived from `(registry, implementation, chainId, tokenContract, tokenId, salt)`, so one agent token is the base of unboundedly many candidate TBAs; "any TBA of the agent" is not checkable, and two implementations that pick differently disagree on identical chain state. The canonical account is the one computed via the [ERC-6551](./eip-6551.md) registry at `0x000000006551c19487814612e58FE06813775758` using the `implementation` and `salt` the binding registry declares. A conformant registry MUST make that `implementation` and `salt` determinable (e.g. a view returning the account, or values fixed in the implementation and documented). - **Nonexistent agent vs. nonexistent source.** `isSourceNFTOwnershipValid` MUST revert for a non-existent `agentId` (it inherits ERC-721 `ownerOf` semantics, consistent with `getSourceNFT`). The return-`false`-not-revert rule applies only to the *source* token: a burned or non-existent source token yields `false`, not a revert. ### Consumer requirements The provenance / live-ownership split exists so a consumer can ask "is this binding still true?" This subsection states when a consumer is *obligated* to ask. - **Re-check at action time, not approval time.** Any consumer gating an irreversible action on a source-bound identity — a settlement contract, an executor acting under a pre-action verdict, an escrow releasing on agent identity — MUST call `isSourceNFTOwnershipValid` at **action time**. An attestation, verdict, or approval references chain state as of its issuance timestamp; source-token ownership is mutable and can lapse between judgment and execution. - **Atomicity.** Where the consumer is a contract, the re-check MUST be atomic with the gated action — performed in the same transaction. A separate re-check transaction ahead of the action reintroduces the lapse window at smaller scale (a TOCTOU gap). An off-chain consumer MUST re-check as close to execution as possible and record the check timestamp alongside the action. - **Verdict semantics under a lapsed binding.** A pre-action verdict over a source-bound agent attests the identity premise *as of* the verdict's timestamp — the premise does not carry forward. If `isSourceNFTOwnershipValid` returns `false` at execution time, the executor MUST treat the failed re-check as a blocking condition: the verdict does not authorize execution under a broken binding. The verdict itself remains authentic and historically valid — what fails is the premise, not the signature. Consumers MUST distinguish the two when recording outcomes. - **No canonical agent per source token.** Because re-mint is permitted, no `agentId` is *the* agent for a source token; multiple simultaneously-valid bindings to one source token are possible (same holder, multiple mints). A consumer MUST treat the `agentId` as the identity and MUST NOT resolve "the agent for token X" by source token. The source token was never the identity — neither the accountability record nor any notion of canonicalness travels with it. - **Caching.** `getSourceNFT` is immutable and MAY be cached indefinitely. `isSourceNFTOwnershipValid` MUST NOT be cached across actions; each gated action performs its own check. Collapsing the two cache policies recreates exactly the conflation this standard exists to remove. Note (non-normative): this registry never signals validity changes — they occur as `Transfer` events on the *source* collection. A consumer indexing binding validity MUST watch both contracts. *Reference data:* the first governed action by a source-bound agent — `tokenId 14`, the "PGA #14" / Pixel Goblins #577 identity whose end-to-end provenance walk is given under Reference Implementation — was pre-action reviewed `approve_with_concerns`, top concern "identity-binding correctness": a validator asking, before this standard existed, precisely the question `isSourceNFTOwnershipValid` answers. Record: `api.babyblueviper.com/ledger/23` (the WYRIWE input-provenance proposal's §L4 triple-hash, ethereum/ERCs#1810; verdict timestamp strictly before execution timestamp). ### Binding scope - A registry MUST be bound to exactly one source collection (`boundCollection`), fixed at deployment and immutable thereafter. - A single deployment serves a single source collection. Multi-collection lookup is out of scope and is the responsibility of a separate registry-discovery standard (a factory mapping `sourceCollection => registry`). The reference implementation deploys one registry per collection via such a factory. ### Metadata inheritance - On registration, an implementation SHOULD set the agent's [ERC-721](./eip-721.md) `tokenURI` so that it resolves to metadata mirroring the source token's metadata — for example by reusing the source token's IPFS content — so that the bridged agent inherits the visual identity (image, name, attributes) of its source token. - The transport mechanism is implementation-defined: an implementation MAY reference the source token's content directly (same CID), or serve a derived metadata document that embeds the source media. The standard does not prescribe the document shape. - Metadata inheritance is RECOMMENDED rather than REQUIRED: an agent MAY carry distinct metadata where a separate visual identity is intended. Where an agent is meant to *be* its source NFT, mirroring the source metadata is the expected behavior and reinforces the provenance asserted by `SourceNFTLinked`. ## Rationale - **Live `ownerOf` over locking:** non-custodial binding keeps the source NFT liquid and usable elsewhere; the standard never competes with the source collection for custody. - **Immutable provenance:** provenance that disappears on transfer is not provenance. Recording it once and never mutating it makes "minted by a holder of X" a permanent, log-indexable fact. - **Separate live view:** consumers that *do* care about current ownership (e.g. gating an action on continued holding) get a trustless live check, decoupled from the historical fact. - **Agent identity as ERC-721:** makes agents themselves transferable, marketplace-compatible, and royalty-bearing ([ERC-2981](./eip-2981.md)), reusing existing infrastructure rather than inventing custody. - **Metadata inheritance preserves identity:** mirroring the source token's metadata means the agent presents as its source NFT, making the "this agent *is* that token" claim legible to wallets and marketplaces, not just to contracts reading `getSourceNFT`. - **Fees are non-normative.** `registerWithSource` is `payable` to accommodate implementations that charge a mint fee, but the standard does not define a fee, treasury, or pricing mechanism — these are implementation concerns. The reference implementation's per-registry `mintPrice` → `treasury` forwarding is illustrative only. A conformant registry MAY mint for free. - **Honest ERC-165 over convenient ERC-165.** A self-sourced or read-only-source agent implements the read side of binding but not registration. Rather than let it advertise the full `IAgentSourceBinding` and misreport `supportsInterface`, the standard gives that profile its own id (`0x8b3597c9`). Advertising exactly what is implemented is the purpose of [ERC-165](./eip-165.md); the companion view interface makes the honest choice expressible instead of forcing a false positive. Deriving the id via `type(IAgentSourceBindingView).interfaceId` (rather than a hardcoded constant) keeps it from drifting from the functions the interface declares. ## Backwards Compatibility This standard is **strictly additive to [ERC-8004](./eip-8004.md)** and changes no existing interface. A compliant registry is a valid ERC-721 and ERC-8004 identity registry; consumers unaware of `IAgentSourceBinding` treat agents as ordinary 8004 identities. ## Reference Implementation Deployed and live on Ethereum mainnet (chain id 1); `dinamic.eth` resolves through this stack. Every address below is verifiable with a single `eth_call`. **Deployment infrastructure** | Contract | Address | Role | |---|---|---| | `AgentIdentityRegistryFactory` | `0xc2bb6502a7d8ee3cdb2f96508d6cdf426aa2858f` | Deploys one `AgentIdentityRegistry` per source collection; `lookup(collection)`, `allCollections()` | | `FactoryFacade` | `0xC939b58F3B36293D3D55993ADD6EE0C79e7AAD2f` | Permissionless, fee-gated registry deployment | **Live registries** — each bound to a source ERC-721 collection, holding bridged agents: | Source collection | `boundCollection` | Agent registry (`IAgentSourceBinding`) | Agents minted | |---|---|---|---| | Pixel Goblins | `0x6559807FfD23965d3aF54ee454bC69F113Ed06Ef` | `0xe0454dFA17a57a84c3E0E2DBFdA5318CBBE91e2c` | 14 | | Goblinarinos | `0x616F2ac5dd4F760dB693c21E9Ca7a8Aa962CF93b` | `0xe61F5a6783AE09949B9a1b6821b68f89C0d7bB2D` | 5 | | Sproto Gremlins | `0xEeca64ea9fCf99A22806Cd99b3d29cf6e8D54925` | `0x608520f2725C312092cC837a05a5A1c504AB26f4` | 0 | | Radbro Webring V2 | `0xABCDB5710B88f456fED1e99025379e2969F29610` | `0x860FAb14deDCFBf5C928E4Af0A88c6Ce0Ba879CF` | 1 | **Verifiable cross-reference.** The Pixel Goblins agent registry (`0xe0454dFA…1e2c`) reads `totalSupply() == 14`, with agent `tokenId 14` derived from a Pixel Goblin held by its registrant — the same identity referenced as "PGA #14" in babyblueviper Ledger 23. Provenance is confirmable end to end: `getSourceNFT(14)` on the registry returns `(0x6559807F…, )`, and `ownerOf()` on the source collection can be compared against `ownerOf(14)` on the registry — exactly what `isSourceNFTOwnershipValid(14)` computes. Source: `AgentIdentityRegistry.sol` in the `ens-dynamic-kit` stack. The reference implementation additionally provides [EIP-712](./eip-712.md)-verified agent wallets (`setAgentWallet`), ERC-2981 royalties, source-metadata inheritance, and an `{agentId}` URI template — only the `IAgentSourceBinding` surface is normative. **Self-sourced profile (`IAgentSourceBindingView`).** The query-only companion is exercised by a self-sourced registry — an agent whose source is the registry itself. Reference: `GenesisAgentRegistry` at `0xe91934aB1f6A40cc1Bb4cD530FEFF56dFE524963` (mainnet), for which `getSourceNFT(agentId)` returns `(0xe91934aB1f6A40cc1Bb4cD530FEFF56dFE524963, agentId)`, `supportsInterface(0x8b3597c9)` returns `true`, and `supportsInterface(0x27eba962)` returns `false` — advertising the read side it implements without the registration side it does not. Source: `GenesisAgentRegistry.sol` in the same stack. ## Security Considerations - **Provenance ≠ current ownership.** Consumers MUST NOT treat `getSourceNFT` as proof the agent owner currently holds the source token; use `isSourceNFTOwnershipValid` for that. Conversely, a `false` from the live check does not retroactively invalidate provenance. - **Source-token transfer after mint.** Because binding is non-custodial, the source token can be sold while the agent persists. This is intended; provenance is historical. Applications gating on continued holding MUST re-check live. - **Re-mint is permitted by design.** A registry MUST NOT prevent the same source token from being bridged again. Eligibility is "the caller owns the source token now," so as a source token changes hands each successive holder MAY mint an agent from it. This is intentional and follows from the non-custodial model — forbidding it would require the standard to track historical mints and gate on them, recreating the custody it deliberately avoids. The resulting agents are distinct identities sharing a common provenance source. - **Sybil surface.** Because re-mint is allowed, the count of agents bound to a collection is not bounded by the collection's supply. Consumers MUST NOT treat "an agent exists for token X" as evidence of a unique or current relationship; combine `getSourceNFT` (provenance) with `isSourceNFTOwnershipValid` (live) and, where uniqueness matters, with application-level policy. - **Track record does not travel with the source token.** A re-minted agent inherits provenance, never track record. Accountability records (outcome-linked reputation: the WYRIWE proposal's §L4 `recordPointer` (ethereum/ERCs#1810), the AI-inference-proof-verification proposal's attestation/judgment history (ethereum/ERCs#1771)) bind to the `agentId`, not the source token. A consumer MUST NOT transfer trust across distinct `agentId`s sharing a source token: "this agent is Goblin #577" and "this agent has the predecessor's record" are different claims; only the first is made by this standard. A fresh agent re-earns its record from zero. This is a feature: it makes source-token resale Sybil-neutral at the trust layer (buying the token buys provenance and visual identity, not reputation) — the same boundary that proposal's aggregation guard draws: weight from settled outcomes and at-stake counterparties, never from inherited identity. - **`ownerOf` reentrancy / hostile source collection.** The source collection is arbitrary ERC-721 code; `ownerOf` is a `view` call but implementations MUST treat the bound collection as untrusted and avoid state assumptions across the call. The reference implementation reads `ownerOf` only. - **Payable registration and value handling.** `registerWithSource` is `payable`. An implementation that forwards or escrows value MUST revert the entire registration if that transfer fails, so no agent is minted without the associated value settling, and MUST NOT leave ETH stranded in the registry. The standard mandates no fee; a registry that mints for free SHOULD reject non-zero `msg.value` to avoid silent loss. - **Smart-contract agent wallets ([ERC-1271](./eip-1271.md)).** Implementations that add an agent-wallet binding beyond the normative interface (the reference implementation's `setAgentWallet`) and accept a contract wallet MUST verify the authorization via [ERC-1271](./eip-1271.md) `isValidSignature`, treating any return other than the magic value as failure, and MUST bind the signed payload to both the `agentId` and an expiry to prevent cross-agent and replay reuse. This surface is outside `IAgentSourceBinding` but is called out because agent identities are commonly operated by smart-contract wallets. ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md).