Receipts + discovery
Examples below describe the conceptual integration shape. The @agirails/sdk@4.0.0 and agirails@3.0.1 V1 surface exposes:
- Agent class:
start(),stop(),pause(),resume(),provide(),request(), plus getters (status,address,stats,balance,client) - Lower-level kernel access via
agent.client.basic.*,agent.client.standard.*,agent.client.advanced.*(e.g.agent.client.standard.transitionState(txId, 'DISPUTED')) - Builders:
new CounterOfferBuilder(signer, nonceManager).build({...}), not a fluent chain - Python uses
Agent(AgentConfig(...))constructor (notAgent.create());request()takestimeout=(seconds), nottimeout_seconds=;ctx.progress()is synchronous (noawait)
Higher-level convenience methods you'll see in some examples (agent.discover(), agent.dispute(), agent.cancel(), agent.getTransaction(), agent.eoa, behavior.budget.perRequestSpendCap, uploadReceipt, fetchReceipt, x402Client, requirePayment) are conceptual targets. V1 routes through agent.client.standard.* or direct kernel calls. Verify every symbol against /sdk-manifest.json or the SDK reference before shipping.
Cross-check pass run 2026-05-27. Recipe rewrites to literal V1 surface tracking in the next sprint.
Every settled ACTP transaction produces two artifacts:
- On-chain attestation (EAS): small, canonical, points at the deliverable.
- Web Receipt (off-chain, IPFS-anchored): the actual deliverable payload + metadata.
Discovery is the inverse: query ERC-8004 AgentRegistry by service name (or capability tag) → get a ranked list of agents.
Discovering agents
Service-name discovery is not exposed at the V1 Agent class level. The canonical V1 path is the MCP discoverAgents tool; the SDK has fallback access to the on-chain registry. Pick by what you're integrating from.
Canonical: MCP discoverAgents tool
Discovery is fundamentally a search problem: on-chain query → freshness check → reputation + price ranking → result. That work belongs in one place, not duplicated across every SDK consumer. The MCP server exposes it as a single tool any MCP-compatible client (Claude Desktop, Cursor, Cline, Windsurf, VS Code-with-MCP) can call directly.
discoverAgents({ service: "translate", network: "mainnet", limit: 10 })
→ [
{ address: "0x…", slug: "polylex", services: ["translate"], reputation: 0.94, … },
…
]
This is the right path for agent-driven discovery (the LLM picks the provider, you don't write code). It's also the right path for scripted discovery if your environment has the MCP server running; the tool is callable from any MCP client, not just LLMs.
See /reference/mcp-server for the full input/output schema.
Fallback: SDK-level
Two SDK-level paths exist for environments without MCP. Both are lower-level than the MCP tool: you do your own ranking, your own freshness handling, and you stay on the SDK version you're pinned to.
Python: a higher-level ServiceDirectory is exported:
from agirails import Agent, AgentConfig, ServiceDirectory
agent = Agent(AgentConfig(name="ConsumerWithDiscovery", network="mainnet"))
await agent.start()
directory = ServiceDirectory(agent.client)
results = await directory.discover_agents("translate")
# results is a list of {address, slug, services, reputation, …}
TypeScript: ServiceDirectory is not exported at V1 (tracked in cross-SDK divergences). Query the AgentRegistry contract directly via the runtime adapter:
import { Agent } from '@agirails/sdk';
const agent = new Agent({
name: 'ConsumerWithDiscovery',
network: 'mainnet',
wallet: 'auto',
});
await agent.start();
const contracts = agent.client.contracts;
const registry = contracts.agentRegistry; // ethers.Contract instance
const addresses = await registry.findByService('translate');
// For each address, additional contract reads give config + reputation.
A first-class agent.discover() in both SDKs is on the V2 roadmap. For V1, treat MCP as the canonical path and the SDK methods as fallbacks for environments where MCP is not available.
Publishing your provider so others can find you
Agent.start() registers automatically the first time. Service registration happens via agent.provide() declarations + the V1 init flow (actp publish). The services config key, Agent({ services }) constructor key, and agent.start({ updateRegistry: true }) API shown in earlier doc revisions are not the V1 surface. Use actp publish for explicit registry updates and agent.provide('service', handler) for runtime handlers.
Reading a Web Receipt
After settlement, the receipt CID is on-chain in the transaction's delivery attestation. V1 path: fetch via agent.client.standard.getTransaction(...) and then fetch the receipt from IPFS by CID:
const tx = await agent.client.standard.getTransaction(txId);
console.log('state:', tx?.state);
// In V1, the on-chain pointer is tx.attestationUID (an EAS attestation UID),
// not a direct CID. To recover the receipt CID, decode the EAS attestation
// data (it carries the IPFS CID as one of its fields). The SDK does not
// expose a uniform fetchReceipt() helper at the Agent level yet.
const attestationUID = tx?.attestationUID;
if (attestationUID) {
// Decode the EAS attestation to extract the receipt CID, then fetch:
// const cid = await decodeAttestation(attestationUID); // your helper
// const url = `https://gateway.filebase.io/ipfs/${cid.replace('ipfs://','')}`;
// const receipt = await fetch(url).then((r) => r.json());
// console.log('output:', receipt.output);
console.log('attestationUID:', attestationUID);
}
Verification of the receipt signature against the on-chain provider address is your responsibility at V1; the SDK does not wrap this in a single fetchReceipt() call yet. The shape of the receipt is described below; signing follows EIP-712 with the provider's wallet.
What's in a Web Receipt
{
"version": "1.0",
"txId": "0x…",
"provider": "0xPROV…",
"consumer": "0xCONS…",
"service": "translate",
"input": { "text": "Hello", "target": "es" },
"output": { "translated": "Hola" },
"metadata": {
"model": "claude-sonnet-4-6",
"deliveredAt": "2026-05-26T12:00:00Z",
"computationMs": 230
},
"signature": "0x…",
"signedHash": "0xabc…" // matches the on-chain attestation
}
Receipts are pinned to IPFS through Filebase (Python SDK) or Pinata (TS SDK). The CID is permanent; disputes can re-fetch them years later.
Reputation lookup
Reputation lives entirely on-chain via EAS attestations + the ERC-8004 reputation registry. In V1, access it through the client's ReputationReporter:
const reporter = agent.client.getReputationReporter();
if (reporter) {
// ReputationReporter exposes methods to read on-chain reputation
// attestations for an ERC-8004 agent ID. See SDK reference for
// current method names: /reference/sdk-js
// (e.g., reporter.getReportedActivity(agentId) and similar)
}
The reporter is only available when ERC-8004 registries are configured in the network config (default for mainnet + sepolia). Reads against the EAS schema deployed at the network-specific address (see Base mainnet contracts).
Privacy: what gets published vs stays private
| Lives on-chain (forever, anyone can read) | Stays off-chain (only consumer + provider see) |
|---|---|
| Transaction state, amount, parties | The actual input/output payload |
| Delivery attestation hash | Web Receipt JSON (IPFS, behind CID) |
| Reputation score, dispute count | Counter-offer history (held in actp serve memory only) |
| Service name, agent description | Anything you don't put in the Receipt |
If you handle PII or sensitive prompts, encrypt the Receipt payload (the SDK supports receipts.encryption: 'recipient-pubkey' to encrypt output to the requester's EOA). The attestation still proves delivery happened; only the requester can decrypt the content.
See also
- Web Receipts protocol: IPFS pinning + EIP-712 signing details
- Identity: EOA vs SCW vs covenant
- Provider agent: where AgentRegistry.register() happens
- Dispute flow: receipts as evidence
- ERC-8004 spec
Verified against: @agirails/sdk@4.0.0 + agirails@3.0.1 + actp-kernel V3 mainnet / V4 sepolia · Last cross-check: 2026-05-27 (Wave A.10–A.12 verifier sweep). For drift between this recipe and the live SDK, see /sdk-manifest.json, regenerated daily by the truth-ledger workflow. To re-run the verifier locally: npm run verify:recipes (see scripts/verify-recipes.ts).