The Complete Guide to Agent‑to‑Agent Marketplaces in 2026
Autonomous agents are no longer lab curiosities; they are programmable micro‑services that can discover, negotiate, and pay each other for compute, data, or domain‑specific work. This guide walks through the practical pieces you need to build or consume an agent‑to‑agent (A2A) marketplace today, with working code, clear trade‑offs, and no hype.
1. Why a Marketplace Layer?
Agents already talk over HTTP, gRPC, or WebSocket. What a marketplace adds is discoverability, standardised contracts, and trustless settlement. Without it you end up hard‑coding endpoints, managing API keys manually, and reinventing escrow for every partnership.
A marketplace solves three concrete problems:
| Problem | Marketplace solution |
|---|---|
| Finding the right capability | Registry + metadata schema (name, version, SLAs, price) |
| Negotiating terms | Structured offer/acceptance messages (JSON‑LD) that can be verified |
| Paying for usage | On‑chain escrow (ERC‑20) or off‑chain ledger with provable receipts |
2. Core Architectural Pieces
- Registry Service – a decentralised or federated index where agents publish their service descriptor.
-
Descriptor Schema – defines inputs, outputs, authentication, price, and SLA. We use JSON‑Schema extended with
@typefrom Schema.org. - Messaging Bus – agents exchange request and response envelopes over a reliable transport (HTTP/2 or QUIC).
- Settlement Layer – a thin wrapper around an ERC‑20 contract (USDC on Base) that locks funds before execution and releases them on proof‑of‑completion.
- Reputation & Dispute – optional off‑chain scoring (EigenTrust) plus an on‑chain arbitration contract for high‑value disputes.
All pieces can be hosted independently; the only hard coupling is the descriptor format and the settlement interface.
3. Service Descriptor Example
{
"@context": "https://schema.org/",
"@type": "Service",
"name": "SentimentAnalysis-v1",
"description": "Returns polarity score (-1..1) for English text.",
"version": "1.0.0",
"provider": {
"@type": "Organization",
"name": "NexusAI Labs",
"url": "https://nexusai.example"
},
"input": {
"type": "object",
"properties": {
"text": {"type": "string", "maxLength": 5000}
},
"required": ["text"]
},
"output": {
"type": "object",
"properties": {
"score": {"type": "number", "minimum": -1, "maximum": 1},
"model": {"type": "string"}
},
"required": ["score"]
},
"price": {
"currency": "USDC",
"value": 0.02,
"decimals": 6
},
"sla": {
"latencyMs": 500,
"availability": "99.9%"
},
"endpoint": "https://agent.nexusai.example/sentiment"
}
The descriptor is immutable once published (IPFS CID or on‑chain calldata). Consumers verify the hash before trusting the endpoint.
4. Minimal Agent SDK (Python)
Below is a self‑contained snippet that shows how an agent can:
- Look up a service via the registry (simple HTTP GET).
- Build and sign a request envelope.
- Escrow payment via USDC on Base (using
web3.py). - Call the service and release funds on success.
Note: This is illustrative; production code would add retry logic, circuit breakers, and proper key management.
# agent_client.py
import json, os, time, hashlib
import requests
from web3 import Web3
from eth_account.messages import encode_defunct
# -------------------------------------------------
# Config – replace with your own keys / endpoints
# -------------------------------------------------
REGISTRY_URL = "https://registry.nexusai.example/v1/services"
USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" # Base USDC
W3 = Web3(Web3.HTTPProvider("https://base.mainnet.rpc.io"))
ACCOUNT = W3.eth.account.from_key(os.getenv("PRIVATE_KEY"))
ESCROW_ABI = [...] # minimal ERC20 approve/transferFrom ABI
ESCROW_ADDR = Web3.to_checksum_address("0xEscrowContractAddress")
# -------------------------------------------------
# 1. Discovery
# -------------------------------------------------
def find_service(name: str, version: str = None):
params = {"name": name}
if version:
params["version"] = version
resp = requests.get(REGISTRY_URL, params=params, timeout=5)
resp.raise_for_status()
data = resp.json()
if not data["items"]:
raise LookupError("Service not found")
return data["items"][0] # assume first match
# -------------------------------------------------
# 2. Build signed request envelope
# -------------------------------------------------
def sign_envelope(payload: dict):
# canonical JSON (no whitespace) for deterministic hash
canon = json.dumps(payload, separators=(",", ":"), sort_keys=True)
message_hash = encode_defunct(text=canon)
signed = ACCOUNT.sign_message(message_hash)
return {
"payload": payload,
"sig": signed.signature.hex(),
"signer": ACCOUNT.address
}
# -------------------------------------------------
# 3. Escrow helper (approve + lock)
# -------------------------------------------------
def escrow_lock(amount_usdc: float, service_addr: str):
usdc = W3.eth.contract(address=USDC_ADDRESS, abi=ERC20_ABI)
# approve escrow to pull funds
approve_tx = usdc.functions.approve(
ESCROW_ADDR,
int(amount_usdc * 1e6) # 6 decimals
).build_transaction({
"from": ACCOUNT.address,
"nonce": W3.eth.get_transaction_count(ACCOUNT.address),
"gas": 80000,
"maxFeePerGas": W3.to_wei(2, "gwei"),
"maxPriorityFeePerGas": W3.to_wei(1, "gwei")
})
signed_approve = ACCOUNT.sign_transaction(approve_tx)
W3.eth.send_raw_transaction(signed_approve.rawTransaction)
# lock funds in escrow (simple escrow contract: lock(uint256 amount, address provider))
escrow = W3.eth.contract(address=ESCROW_ADDR, abi=ESCROW_ABI)
lock_tx = escrow.functions.lock(
int(amount_usdc * 1e6),
Web3.to_checksum_address(service_addr)
).build_transaction({
"from": ACCOUNT.address,
"nonce": W3.eth.get_transaction_count(ACCOUNT.address),
"gas": 120000,
"maxFeePerGas": W3.to_wei(2, "gwei"),
"maxPriorityFeePerGas": W3.to_wei(1, "gwei")
})
signed_lock = ACCOUNT.sign_transaction(lock_tx)
tx_hash = W3.eth.send_raw_transaction(signed_lock.rawTransaction)
receipt = W3.eth.wait_for_transaction_receipt(tx_hash)
return receipt
# -------------------------------------------------
# 4. Call the service & release on success
# -------------------------------------------------
def invoke_service(desc: dict, text: str):
# request envelope
request = {
"input": {"text": text},
"nonce": int(time.time()),
"client": ACCOUNT.address
}
envelope = sign_envelope(request)
# escrow payment (price from descriptor)
price_usdc = desc["price"]["value"]
escrow_lock(price_usdc, desc["endpoint"])
# actual HTTP call (could be gRPC, etc.)
headers = {"Content-Type": "application/json", "X-Agent-Sig": envelope["sig"]}
resp = requests.post(
desc["endpoint"],
json=envelope,
headers=headers,
timeout=desc["sla"]["latencyMs"] / 1000 + 2
)
resp.raise_for_status()
result = resp.json()
# release escrow (provider calls release; here we just verify receipt)
# In a real system the provider would call escrow.release()
# We simply check that the call succeeded; the provider will claim funds.
return result
# -------------------------------------------------
# Example usage
# -------------------------------------------------
if __name__ == "__main__":
svc = find_service("SentimentAnalysis-v1")
out = invoke_service(svc, "I love building agents on Base!")
print("Score:", out["score"])
What the snippet shows
- Discovery – a simple GET to a registry; you could swap this for an IPFS‑based DHT or a GraphQL endpoint.
- Signed envelope – guarantees request integrity and lets the provider verify the caller’s identity without sharing secrets.
- Escrow flow – approve → lock → provider releases
This article was originally published by DEV Community and written by Nikhil Ranka.
Read original article on DEV Community