ERC-8278: Service Objects

I’d like feedback on a small ERC-721 extension for service objects.

Abstract

This proposal defines a minimal ERC-721-compatible interface for service objects. A service object is a transferable token that represents control over an offchain service. The interface exposes a service manifest, service operator, and payment route so that wallets, indexers, marketplaces, and clients can resolve the current operational state of a service before interacting with it.

The proposal does not define service discovery, reputation, execution, payment settlement, smart-account behavior, MCP semantics, or x402 semantics.

Problem

ERC-721 provides ownership and transfer semantics, but it does not provide a standard way to answer operational questions about a service represented by a token.

Today, service marketplaces, API providers, agent registries, and payment middleware each define their own metadata formats and payment-route conventions.

As a result, wallets, clients, and indexers cannot reliably answer basic questions:

  • Which manifest currently describes this service?

  • Which operator is currently authorized to operate it?

  • Which payment route should paid endpoints use?

  • Has the payment route changed since the client last inspected it?

The absence of a common interface creates fragmentation across service-oriented applications and makes it difficult for generic infrastructure to support them.

Proposed Primitive

The proposed interface is intentionally small:

interface IERCServiceObject is IERC165 {
    event ServiceManifestUpdated(
        uint256 indexed serviceId,
        string uri,
        bytes32 indexed manifestHash
    );

    event ServiceOperatorUpdated(
        uint256 indexed serviceId,
        address indexed operator,
        uint64 expiresAt
    );

    event ServicePaymentRouteUpdated(
        uint256 indexed serviceId,
        address indexed revenueRecipient,
        string paymentURI,
        bytes32 indexed paymentManifestHash,
        uint64 routeNonce
    );

    function serviceManifest(uint256 serviceId)
        external
        view
        returns (string memory uri, bytes32 manifestHash);

    function serviceOperator(uint256 serviceId)
        external
        view
        returns (address operator, uint64 expiresAt);

    function servicePaymentRoute(uint256 serviceId)
        external
        view
        returns (
            address revenueRecipient,
            string memory paymentURI,
            bytes32 paymentManifestHash,
            uint64 routeNonce
        );
}

Current reduced interface ID:

0xf94c99e5

Non-Goals

This proposal does not define:

  • service discovery

  • reputation systems

  • validation systems

  • payment settlement

  • endpoint execution

  • escrow

  • revenue splitting

  • subscriptions

  • smart-account modules

  • MCP runtime behavior

  • x402 payment semantics

  • service quality guarantees

MCP and x402 may be referenced through manifests, but neither is required by the ERC.

Relationship To Existing Standards

ERC-721

This proposal reuses ERC-721 ownership and transfer semantics. It does not redefine ownership.

ERC-7656

ERC-7656 addresses generalized contract-linked services and linked infrastructure.

This proposal focuses on service object state:

  • service manifest

  • service operator

  • payment route

A future ERC-7656-linked implementation could expose the same interface.

ERC-8004

ERC-8004 focuses on agent identity, validation, and reputation.

This proposal does not define identity, trust, reputation, or registration. It only defines operational service state.

ERC-6551, ERC-4337, ERC-7579, ERC-6900

These standards address token-bound accounts, account abstraction, and smart-account execution.

Service accounts may be useful extensions, but they are intentionally excluded from the minimal interface.

Repository

Repository:
https://github.com/MeltedMindz/erc-service-object

Minimal Draft:
https://github.com/MeltedMindz/erc-service-object/blob/main/docs/ERC-draft-minimal.md

Hardening Review:
https://github.com/MeltedMindz/erc-service-object/blob/main/docs/final-hardening-review.md

Reference Implementation:
https://github.com/MeltedMindz/erc-service-object/tree/main/src

Questions For Review

  1. Is this best framed as:

    • an ERC-721 extension,

    • an ERC-7656 profile,

    • or both?

  2. Is serviceOperator necessary in the base interface, or should operator state be handled through a separate extension?

  3. Should payment route information include the revenue recipient, or should route discovery and recipient discovery be separated?

  4. Is routeNonce the correct primitive for detecting stale payment routes and cached payment offers?

  5. Should ERC-1155 support remain out of scope for the base proposal?

  6. Should signed usage receipts be standardized separately as a companion ERC rather than included in this proposal?

I am particularly interested in feedback from implementers working on ERC-7656, ERC-8004, wallets, marketplaces, indexers, smart accounts, x402 infrastructure, and MCP tooling.

ERC PR: Add ERC: Service Objects by MeltedMindz · Pull Request #1777 · ethereum/ERCs · GitHub

1 Like

The minimal scope is the right call, service operational state is its own layer and conflating it with identity or settlement makes both worse.

The composition we’re running at dinamic.eth maps cleanly onto this:

  • serviceManifest → agent.json served at gateway.ensub.org/agent/{registry}/{agentId}/.well-known/agent.json

  • serviceOperator → gateway attestor key resolved via ERC-8004 getAgentWallet(agentId)

  • servicePaymentRoute → BountySettlement on Base Sepolia (0x57fe09a6...) — clients fund a job, settlement is triggered on outcome

The three properties you’ve isolated are exactly the three things a client needs to know before interacting: what does this service do, who operates it, and where does payment go. Everything above that (identity, reputation, proof verification, settlement) is a separate concern that composes on top.

Happy to share more on the BountySettlement + ERC-8004 pattern if useful as a reference composition.

Tiago (dinamic.eth)

1 Like

One of the things I’ve been wrestling with throughout this process is where the boundary should actually sit. The original draft was much larger and included service accounts, receipts, payment semantics, and a lot of things that started drifting into identity and settlement territory. The more review it got, the more it felt like those concerns should compose on top rather than live in the base standard.

The mapping you described is helpful because it reinforces that separation:

  • manifest = what the service is

  • operator = who is currently operating it

  • payment route = where value should flow

Everything else can build on top of those primitives.

I’m especially interested in the ERC-8004 composition you mentioned. One of the questions I’ve been trying to answer is whether this should ultimately remain a standalone ERC-721 extension or whether it naturally becomes a profile that ERC-8004, ERC-7656, and similar systems can expose when they need service state.

Would definitely be interested in seeing more details on the BountySettlement + ERC-8004 pattern and how clients are resolving and validating state in practice today. Real-world compositions are probably more valuable than any amount of theoretical design work at this stage.

Thanks for taking the time to look through it.

1 Like

On standalone vs profile I’d keep it standalone. ERC-8004 is already handling identity, reputation, and the agent wallet binding; absorbing service state into it would expand scope in a direction that makes both standards harder to implement independently. The cleaner path is ERC-8004 optionally referencing ERC-8278 as “for service operational state, implement this” composable, not merged.

Here’s how the resolution actually works in practice at gateway.ensub.org today:

  1. Client queries serviceManifest() → gets the agent.json URL

  2. Client fetches agent.json → capabilities, model info, endpoint

  3. Client queries serviceOperator() → gets the attestor address

  4. Client validates attestor: ERC8004Registry.getAgentWallet(agentId) → confirms it matches the operator on-chain

  5. Client queries servicePaymentRoute() → gets BountySettlement address (0x57fe09a6... on Base Sepolia)

  6. Client funds a job on BountySettlement with inputHash (the WYRIWE commitment — keccak256 of the sanitized input)

  7. Agent executes, gateway produces a WYRIWE attestation signed under the operator key

  8. Settlement triggered: IProofVerifier.verify(inputHash, outputHash, metadata, proof) evaluated on-chain

  9. BountySettlement releases or rejects based on the proof result

Steps 3-4 are where ERC-8278 + ERC-8004 do real work together the client doesn’t need to trust the manifest, it can verify the operator independently on-chain. Without that binding, serviceOperator is just an address with no provenance.

The ERC-7656 question is worth tracking separately. If ERC-7656 handles generalized contract-linked services, ERC-8278 could become the profile for services that have operational state (manifest + operator + payment route) rather than just linked contract infrastructure. But that’s an additive relationship, not a conflict.

Happy to share the gateway code or BountySettlement source if it helps ground the design decisions.

Tiago (dinamic.eth)

1 Like

This is extremely helpful, especially the distinction between operator provenance and service state.

serviceOperator() becomes significantly more useful when there is an independent system like ERC-8004 that can attest to who that operator actually represents. Without that, it’s just an address. With it, clients can verify both the operational state and the identity layer separately.

I think that’s strengthening my view that ERC-8278 should stay focused on operational state and let identity, reputation, verification, settlement, and execution remain composable layers.

One thing I’m curious about: in your implementation, have you found the payment route to be relatively stable compared to the operator, or do they tend to change together? Part of the reason routeNonce ended up in the draft was to give clients and indexers a simple way to detect stale payment offers without having to understand the settlement system itself.

Also, I’d definitely be interested in looking through the gateway and BountySettlement code if you’re willing to share it. Real implementations are exactly what I was hoping to find before moving the draft further.

1 Like

In practice they’re very different in stability, the payment route is essentially static once deployed, the operator is the volatile one.

BountySettlement is a deployed contract with a fixed address. Clients can fund jobs against it indefinitely without any coordination. The payment route only changes if you migrate to a new settlement contract, which is a significant event with migration overhead. In our setup it hasn’t changed since deployment.

The operator key is a different story. Gateway key rotation is a realistic operational event — hot wallet compromise, key upgrade, infrastructure migration. ERC-8004 getAgentWallet() handles the identity binding, but serviceOperator would need to update on rotation. These two don’t move together at all.

So routeNonce is actually most useful as a signal for the payment route side rather than the operator side, clients caching a funded payment route need to know when it’s been superseded. That’s the right place for the staleness signal. Operator staleness is better detected through ERC-8004 key rotation events than through a nonce.

Code links:

Gateway source is not public yet, will share once it’s ready for external review. BountySettlement is the most relevant piece for servicePaymentRoute and is fully available.

Tiago