DEV Community

Nikhil Ranka
Nikhil Ranka

Posted on

The Complete Guide to Agent-to-Agent Marketplaces in 2026

The Complete Guide to Agent‑to‑Agent Marketplaces in 2026

For developers who are building autonomous AI agents and need a realistic view of how agent‑to‑agent (A2A) markets actually work today.


1. What an A2A Marketplace Actually Is

An A2A marketplace is a decentralized or semi‑centralized platform that lets one autonomous agent discover, negotiate, pay for, and invoke services offered by another agent. Unlike a classic API marketplace, the participants are software agents that can:

  • Publish a service descriptor that includes capability signatures, pricing, SLA, and required credentials.
  • Consume descriptors, formulate a request, and automatically handle payment and attestation.
  • Operate without a human in the loop for the discovery‑to‑execution cycle.

In 2026 the dominant pattern is request‑response over HTTP/2 with JSON‑LD service descriptors, x402 HTTP‑based micropayment headers, and Verifiable Credentials for trust. The stack is deliberately minimal: no proprietary SDKs, just standards that any language can implement.


2. Core Building Blocks

Block Purpose Typical Spec (2026)
Service Descriptor Advertises what the agent can do JSON‑LD schema https://schema.org/Service + custom agent:capability field
Discovery Protocol Lets buyers find sellers Agent‑Discovery‑Protocol (ADP) over DHT or a lightweight federated index (e.g., IPFS‑pinning + signed CID list)
Payment Layer Enables trustless, sub‑cent transfers x402 – HTTP 402 Payment Required with Payload: header containing a signed USDC transfer on Base (ERC‑20)
Identity & Attestation Verifies that the caller is who it claims Verifiable Credentials (VC) – issuer = agent’s DID, verifier = marketplace or counterparty
Invocation Contract Defines retry, timeout, and SLA handling Simple JSON‑RPC over HTTP/2 with x-agent-retry and x-agent-timeout headers

All of these pieces can be assembled with under 200 lines of code; the rest is plumbing.


3. Service Descriptor Example

Below is a minimal descriptor for an agent that offers sentiment analysis on short text. It lives at https://agent.example.com/descriptor.jsonld.

{
  "@context": [
    "https://schema.org/",
    { "agent": "https://w3id.org/agent#" }
  ],
  "@type": "Service",
  "name": "SentimentAnalyzer",
  "description": "Returns a sentiment score (−1 to 1) for English sentences ≤ 200 tokens.",
  "agent:capability": {
    "input": { "type": "string", "maxLength": 200 },
    "output": { "type": "number", "minimum": -1, "maximum": 1 }
  },
  "offers": {
    "@type": "Offer",
    "priceSpecification": {
      "@type": "UnitPriceSpecification",
      "price": "0.02",
      "priceCurrency": "USDC",
      "valueAddedTaxIncluded": false
    },
    "eligibleRegion": { "@type": "Country", "name": "US" },
    "availability": "https://schema.org/AlwaysAvailable"
  },
  "agent:endpoint": "https://agent.example.com/invoke",
  "agent:did": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
}
Enter fullscreen mode Exit fullscreen mode

Why JSON‑LD? It lets any agent merge the descriptor with its own ontology without custom parsers. The agent:capability block is deliberately tiny; you can extend it with input/output schemas using JSON Schema if you need stricter validation.


4. Publishing to a Marketplace

A marketplace is simply a hosted index that accepts signed descriptors and makes them searchable. The following Python snippet shows how an agent can register itself with a marketplace that implements the ADP over IPFS (using ipfshttpclient).

import json, os, ipfshttpclient
from eth_account.messages import encode_defunct
from web3 import Web3

# ------------------------------------------------------------------
# Config – replace with your own values
# ------------------------------------------------------------------
IPFS_API = "/ip4/127.0.0.1/tcp/5001/http"
PRIVATE_KEY = os.getenv("AGENT_PRIVATE_KEY")  # 0x-prefixed hex
W3 = Web3(Web3.HTTPProvider("https://base-mainnet.infura.io/v3/<PROJECT_ID>"))

def sign_descriptor(descriptor: dict) -> str:
    """Return a hex‑encoded ECDSA signature over the canonical JSON."""
    payload = json.dumps(descriptor, sort_keys=True, separators=(",", ":")).encode()
    message = encode_defunct(text=payload.decode())
    signed = W3.eth.account.sign_message(message, private_key=PRIVATE_KEY)
    return signed.signature.hex()

def publish(descriptor_path: str):
    with open(descriptor_path, "r") as f:
        desc = json.load(f)

    desc["agent:signature"] = sign_descriptor(desc)

    client = ipfshttpclient.connect(IPFS_API)
    res = client.add_json(desc)
    cid = res["cid"]
    print(f"Published descriptor → ipfs://{cid}")

    # OPTIONAL: push CID to a federated index (e.g., a simple HTTP POST)
    # requests.post("https://marketplace.example.com/index", json={"cid": cid})

if __name__ == "__main__":
    publish("descriptor.jsonld")
Enter fullscreen mode Exit fullscreen mode

Trade‑offs

Aspect Benefit Cost / Risk
IPFS storage Immutable, censorship‑resistant, no vendor lock‑in Retrieval latency (seconds) if not pinned; you must run a pinning service or rely on the marketplace to do it.
ECDSA signature Simple, works with any Ethereum‑compatible key No built‑in revocation; if the key is compromised you must issue a new DID and update all descriptors.
Federated index Enables keyword search without scanning all CIDs Requires trust in the index operator; you can mitigate with multi‑signature validation of posted CIDs.

5. Discovering and Invoking a Service

Discovery is usually a two‑step process: (1) query the index for matching CIDs, (2) fetch the descriptor, verify its signature, then compose an x402‑paid request.

5.1 Querying the Index (pseudo‑REST)

GET https://marketplace.example.com/search?q=sentiment&capability.output.type=number
Enter fullscreen mode Exit fullscreen mode

Response (trimmed):

{
  "results": [
    {
      "cid": "bafybeigdyrzt5wfp7ud7g2u2v6bheswtcq6g7namr4vj5v3j5tbkaef4e",
      "score": 0.92
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

5.2 Fetching & Verifying the Descriptor

import json, ipfshttpclient
from eth_account import Account
from eth_account.messages import encode_defunct

def fetch_and_verify(cid: str) -> dict:
    client = ipfshttpclient.connect("/ip4/127.0.0.1/tcp/5001/http")
    desc = client.get_json(cid)

    # Verify signature
    sig = desc.pop("agent:signature")
    payload = json.dumps(desc, sort_keys=True, separators=(",", ":")).encode()
    message = encode_defunct(text=payload.decode())
    recovered = Account.recover_message(message, signature=bytes.fromhex(sig))
    expected = desc["agent:did"].split(":")[-1]  # simplified for did:key
    assert recovered.lower() == expected.lower(), "Invalid signature"
    return desc
Enter fullscreen mode Exit fullscreen mode

5.3 Making the x402‑Paid Call

When the descriptor indicates a price, the client must include an x402 header. The server replies with 402 Payment Required and a Payload: header containing the signed USDC transfer. The client then resends the request with the payment proof.


python
import requests
from eth_account import Account
from eth_account.messages import encode_defunct

BASE_URL = "https://agent.example.com/invoke"
USDC_CONTRACT = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"  # Base USDC
CHAIN_ID = 8453

def make_payment(amount_usdc: str) -> str:
    """Create a signed ERC‑20 transfer (USDC) and return hex‑encoded data."""
    from web3 import Web3
    w3 = Web3(Web3.HTTPProvider("https://base-mainnet.infura.io/v3/<PROJECT_ID>"))
    usdc = w3.eth.contract(address=USDC_CONTRACT, abi=[
        {"constant":False,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],
         "name":"transfer","outputs":[{"name":"","type":"bool"}],"type":"function"}
    ])
    nonce = w3.eth.get_transaction_count(Account.from_key(PRIVATE_KEY).address)
    tx = usdc.functions.transfer(
        Web3.toChecksumAddress("0xAgentServiceAddress"),  # replace with service’s wallet
        int(float(amount_usdc) * 1e6)  # USDC has 6 decimals
    ).buildTransaction({
        "chainId": CHAIN_ID,
        "gas
Enter fullscreen mode Exit fullscreen mode

Top comments (0)