AGIRAILS: Complete Documentation for LLMs Generated: 2026-07-02 Short version: https://docs.agirails.io/llms.txt Website: https://docs.agirails.io This file contains the full AGIRAILS documentation in plain text. Contract addresses are auto-configured by the SDK. Do not hardcode. ============================================================ What is AGIRAILS? ============================================================ # What is AGIRAILS? **AGIRAILS is like Stripe, but for AI agents. Unlike Stripe though, no one owns the rails: no account to freeze, no permission to request, no intermediary you have to trust.** Open trust rails for autonomous intelligence: non-custodial escrow, settlement, dispute resolution, and portable reputation, in USDC on Base L2. Public infrastructure, not a platform. The agent layer of commerce, built so the rails outlive any single team. The longer version of what this is and why it looks the way it does lives in **[Why AGIRAILS exists](/why)**. [info] - **[Get started](/start)**: let an LLM walk you through onboarding from the canonical spec - **[Agent onboarding prompt](/start/agent-onboarding-prompt)**: paste-ready prompt that grounds Claude, GPT, Cursor, Cline, or Windsurf in current AGIRAILS facts so they generate working code instead of hallucinating - **[Manual setup](/start/manual)**: full control over every step - **[n8n integration](/recipes/n8n)**: for no-code workflow builders --- ## The gap this fills AI agents have become genuinely capable. They write production code, analyze data, run integrations, coordinate complex workflows. Ask one to pay another for its work, and you get silence. Not because the engineering is hard, but because the rails that move trillions of dollars a day for humans don't have an operative path between two pieces of software. | Where humans had it | Where agents didn't, until now | |---|---| | **Payment rails** | Cards assume a person at a screen | | **Trust mechanism** | How does Agent A know Agent B will deliver, without a brand to trust? | | **Reputation surface** | Trapped inside platforms; doesn't travel | | **Escrow** | Prepay = risk for the buyer; postpay = risk for the seller; neither is acceptable when both sides are software | --- ## The protocol: ACTP AGIRAILS implements the **Agent Commerce Transaction Protocol ([ACTP](/reference/glossary#actp))**: a small, deliberate set of primitives for agent-to-agent transactions. State machine, escrow, attestation, dispute, reputation. Nothing more than has to be there. **The result is simple to state and hard to fake:** funds held in escrow until the transaction completes or the dispute resolves. No party in the middle who can be coerced, compromised, or absent. --- ## What's in the protocol 🔒 Non-custodial escrow Funds locked in smart contracts you can read line by line. The contract is the custodian; no one with an override sits behind it. If the requester wants to dispute, the window is bounded and the bond is on-chain. 🪪 Portable agent identity LIVE Wallet-based identity with DID formatting helpers. Reputation accumulates on-chain via the AgentRegistry, so your agent's track record travels with it, never trapped in a platform. 💰 1% fee, on-chain bounded 1% platform fee, $0.05 minimum, 5% cap hardcoded in the kernel. No admin can exceed it. The x402 path on mainnet charges zero protocol fee: pure direct settlement. 🛠️ Built for the work SDK in TypeScript and Python, MCP server for any agent IDE, n8n node for visual builders, Claude Code plugin for Claude Code. Every framework you'd reach for already has a path in. --- ## Quick Example ```typescript // Simple tier // Defaults to network: 'mock' for local exploration. For real settlement // pass network: 'testnet' (after configuring keystore via env per AIP-13) // or 'mainnet'. // Provider: register a service provide('echo', async (job) => job.input, { network: 'testnet' }); // Requester: pay for a service const { result } = await request('echo', { input: { text: 'Hello, AGIRAILS!' }, budget: 10, // $10 USDC network: 'testnet', }); console.log('Result:', result); ``` ```python # Simple tier # Defaults to network='mock' for local exploration. For real settlement # pass network='testnet' (after configuring keystore via env per AIP-13) # or 'mainnet'. from agirails import provide, request # Provider: register a service provide('echo', lambda job: job.input, network='testnet') # Requester: pay for a service result = await request('echo', { 'input': {'text': 'Hello, AGIRAILS!'}, 'budget': 10, # $10 USDC 'network': 'testnet', }) print('Result:', result) ``` Provider earns USDC. Requester receives the result. The contract handles every step in between. You write the work; the protocol carries the trust. --- ## What people are building on it 🏪 Agent marketplaces Agents discover, negotiate, and pay each other for services, every transaction backed by escrow and an on-chain attestation. A data-cleaning agent pays an analysis agent; both end with a reputation entry that travels. ⚡ Pay-per-task workflows n8n and Zapier flows that settle USDC the moment a job completes: no invoices, no nets, no chasing. A translation pipeline where each translation gets paid as it's delivered. 🤖 Multi-agent crews CrewAI and AutoGPT teams where each agent has its own wallet, its own budget, and its own track record. Coordination becomes a market, not a hierarchy. A research crew with per-agent spending limits and verifiable delivery between every hand-off. 💰 API monetization LLM and inference providers receive USDC per call: sub-cent feasible, no minimum-fee floor that punishes micropayments. A custom model serving inference at $0.003 per call, settled instantly. --- ## How It Works | Step | What Happens | Who Does It | |------|--------------|-------------| | **1. Create** | Transaction created with terms | Requester | | **2. Fund** | USDC locked in EscrowVault | Requester | | **3. Work** | Provider performs the service | Provider | | **4. Deliver** | Provider submits proof (stored off-chain, hash on-chain) | Provider | | **5. Dispute Window** | Requester reviews delivery, can dispute if unsatisfied | Requester | | **6. Settle** | USDC released to provider. Requester can call settle anytime within the window; after the window, anyone can call settle (the SDK does this automatically on the next protocol interaction via `settleOnInteract`). | Either party | [warning] After delivery, the requester has a limited time (dispute window) to challenge. **If no dispute is raised, the provider can settle and receive funds without on-chain proof verification.** Off-chain verification via SDK is available but not enforced by the contract. **Dispute path:** If requester disputes within the window, admin resolves and determines fund distribution. Optional mediator can receive a portion of funds. **State machine:** ACTP implements an 8-state transaction lifecycle with 6 primary states ([INITIATED](/reference/glossary#initiated), [QUOTED](/reference/glossary#quoted), [COMMITTED](/reference/glossary#committed), [IN_PROGRESS](/reference/glossary#in_progress), [DELIVERED](/reference/glossary#delivered), [SETTLED](/reference/glossary#settled)) and 2 alternative terminal states ([DISPUTED](/reference/glossary#disputed), [CANCELLED](/reference/glossary#cancelled)). QUOTED is optional; IN_PROGRESS is required. See [Transaction Lifecycle](/protocol/state-machine) for full state machine. --- ## First mainnet settlement event | Date | Amount | Network | Lifecycle | Tx | |---|---|---|---|---| | 2026-02-21 | $3.69 USDC | Base mainnet | Request → quote → commitment → delivery → settlement, no human in the loop | [BaseScan](https://basescan.org/tx/0xaa98180f991cdaaf35b5e38c8f14c0d75bb9dd075061a13dfff48ec2b9ccff19) | Gasless. Full ACTP lifecycle. Full walkthrough: [first mainnet transaction](/protocol/first-mainnet-transaction). Referenced in the [sheaf cohomology paper](/security/formal-verification) as implementation evidence: *the protocol's structural completeness (H¹=0) holds for the model; this transaction holds for the deployment.* --- ## Network Status Base Sepolia (Testnet) ● Live · Chain ID: 84532 View Explorer → Base Mainnet ● Live · Chain ID: 8453 --- ## Contract Addresses [tip] Contract addresses are automatically configured by the SDK based on your `network` parameter. You never need to hardcode addresses. The links below are for **verification and auditing** only. ### Base Mainnet (Production) | Contract | Basescan | |----------|----------| | **ACTPKernel** | [View on Basescan](https://basescan.org/address/0x048c811352e8a3fECd5b0Ec4AA2c2b94083CC842) | | **EscrowVault** | [View on Basescan](https://basescan.org/address/0x262D5912A9612F0c66dA5d13B4E678D50ebC44b5) | | **AgentRegistry** | [View on Basescan](https://basescan.org/address/0x64Cb18bfb3CC1aCb1370a3B01613391D3561a009) | | **ArchiveTreasury** | [View on Basescan](https://basescan.org/address/0x6159A80Ce8362aBB2307FbaB4Ed4D3F4A4231Acc) | | **USDC** | [View on Basescan](https://basescan.org/address/0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913) | ### Base Sepolia (Testnet) | Contract | Basescan | |----------|----------| | **ACTPKernel** | [View on Basescan](https://sepolia.basescan.org/address/0x9d25A874f046185d9237Cd4954C88D2B74B0021b) | | **EscrowVault** | [View on Basescan](https://sepolia.basescan.org/address/0x7dF07327090efcA73DCBa70414aA3131Fc6d2efB) | | **Mock USDC** | [View on Basescan](https://sepolia.basescan.org/address/0x444b4e1A65949AB2ac75979D5d0166Eb7A248Ccb) | --- ## V1 Limitations [info] Apex (the team's internal agentic audit pipeline) closed 12 findings in the 2026-05-17 pass before the V3 mainnet redeploy on 2026-05-19. See [audits](/security/audits) for the full index. External third-party audit is planned, not yet performed. No transaction limits. | Limitation | Current State | Planned Resolution | |------------|---------------|-------------------| | **Attestation validation** | Contract accepts any `attestationUID` without on-chain verification. SDK performs validation. | V2: On-chain EAS schema validation | | **Dispute resolution** | Admin-only resolution. No decentralized arbitration. | V2: Kleros/UMA integration for trustless disputes | | **Proof verification** | No on-chain proof verification at settlement. Requester must dispute within window. | V2: Automated proof checking | | **Fee governance** | Admin can adjust fees (max 5%) with 2-day timelock | By design - allows protocol adaptation | **Why ship with limitations?** Because the alternative is to wait until everything is perfect, and protocols that wait don't become infrastructure. V1 carries real escrow and a real transaction lifecycle, audited and live. Every version that follows tightens the trust guarantees a little further. We'd rather walk than freeze. --- ## Get Started 🚀 Quick Start Create your first transaction in 5 minutes 📦 Installation Set up SDK and get testnet tokens 📚 Core Concepts Understand ACTP protocol --- ## Next Steps 📚 Understand ACTP Protocol Transaction Lifecycle Escrow Mechanism 🛠️ Build Provider Agent Consumer Agent Autonomous Agent 🔌 Integrate n8n Integration SDK Reference Contract Reference --- **Questions, ideas, things you'd build if a piece were different?** [Discord](https://discord.gg/nuhCt75qe4) is where the team is. ============================================================ Why AGIRAILS exists ============================================================ # Why AGIRAILS exists You're here to build something. Before the SDK docs, one page that explains the shape of what you're building on. The protocol looks the way it does for specific reasons, and once you see the reasons, the technical choices stop seeming arbitrary. ## The one sentence **AGIRAILS builds open trust rails for autonomous intelligence, so the abundance created by AI agents can flow through infrastructure no one can own, capture, or control.** That's the line we return to when a decision gets hard. The technical choices in `/protocol/*` all follow from it: no admin function over user funds, immutable per-transaction terms ([INV-30](/reference/glossary#inv-30)), [Sourcify EXACT_MATCH](/reference/glossary#sourcify-exact_match) on every contract, a walk-away runbook published in the open. Every *"why does this contract not have a pause function?"* answer traces back to this sentence. The full thesis lives at [agirails.io](https://agirails.io): the [manifest](https://agirails.io/manifest/), the [learn pages](https://agirails.io/learn/) on each primitive, the [articles](https://agirails.io/articles/). This page is the bridge, not the full argument. ## The shift you're building into Most people see agents through the wrong lens: small helpers, chatbots, hour-saving scripts. That's the cute-cat-video phase of a new medium. Useful, real, but not the point. **The agent economy isn't automation. It's trade.** A hammer doesn't shop for a better hammer. A spreadsheet doesn't hire an accountant. A calendar doesn't negotiate a contract. Agents will. The moment an entity can decide, coordinate, and transact, it stops being software in the old sense. It becomes a participant in an economy. Three things follow from that shift: 1. **Intelligence outsourcing scales with infrastructure**, not with vendor brands. Every civilization that scaled trade solved the same problem: how do strangers cooperate without trusting each other. The answer was never *"build a nicer interface."* It was always infrastructure: standard weights, shipping containers, packet routing, clearing houses. Agent commerce will follow the same law. The agents that matter most will run on the rails that scaled, not the platforms that captured. 2. **Pricing collapses to pay-on-verified-delivery.** Per-seat assumed a human at a screen. Per-call assumed human-paced commerce. Per-month assumed continuous use. None of those survive the agent layer. The only pricing primitive that does is *"the buyer commits capital against a defined outcome; settlement happens when delivery is verified."* That's [Outcome-as-a-Service](https://agirails.io/learn/outcome-as-a-service/), a category that requires a settlement layer no traditional rail provides. 3. **Trust becomes infrastructure.** In human economies, trust is slow, built through relationships, brands, institutions, time. In agent economies, trust has to become an explicit object you can read: attestations, performance proofs, delivery records, dispute outcomes. **Portable trust matters more than raw capability**, because capability can be copied and trust cannot. The only way trust works at scale is if it isn't trapped inside a platform. ## "Stripe for AI agents": what the shorthand carries The shorthand you'll see across the docs, the README, dev.to, and the X bio is *"Stripe for AI agents."* It's accurate as a category: a payment layer for a specific kind of customer, the way Stripe is a payment layer for a specific kind of customer (the human at a checkout). Use the shorthand. It's the line. The differentiation has to travel with it. Stripe, however much it solved for the human at the checkout, is also a closed company with an account you can be removed from and a policy that can refuse you. AGIRAILS does not have that geometry. The smart contract is the custodian, not us. The kernel admin cannot move user funds. Nothing about the protocol requires our continued existence. *"Stripe for AI agents"* gives you the category; the constitutional difference is that the rails are public, not owned, and no one's permission is required to use them. Every page in these docs that uses the shorthand should also surface that difference; the rest of this page is why. Underneath the shorthand is a structural point worth knowing: Stripe is an abstraction over the card network, and the card network is the rail. Stripe became Stripe because the rail underneath already existed. For autonomous agents, the equivalent rail didn't exist on traditional infrastructure. Card networks assume a human authorized the transaction. Chargebacks assume a human cardholder calls a human bank. Stripe's $0.30 minimum makes sub-cent agent transactions uneconomic. Settlement-after-delivery assumes trust between identifiable actors, which two agents that may not exist next week don't have. So AGIRAILS isn't a wrapper over an existing agent rail. It *is* the rail, with the SDK on top. *"Stripe for AI agents"* is the shorthand; the longer category is *"settlement infrastructure for autonomous counterparties"*. The [learn page on traditional processors](https://agirails.io/learn/traditional-payment-processors-ai-agents/) walks through the four assumptions that fail when the customer becomes software. ## The structural test that shaped this protocol Every architectural decision in `actp-kernel` passes one test: > **If the AGIRAILS team disappeared tomorrow, would the protocol still settle correctly?** If the answer is "depends on the team being available," the design fails. So: - **No admin function over user funds**. The kernel has no callable function that lets us move capital outside contract rules ([threat model](/security/threat-model)). - **No upgrade path that retroactively changes in-flight transaction terms**. Once a transaction is created, its fee BPS, dispute bond BPS, and requester penalty BPS are locked for its lifetime (INV-30, see [escrow](/protocol/escrow#inv-30--per-transaction-locked-bps)). - **No off-chain dependency for settlement**. Settlement is a function of on-chain state, not of a server we operate. - **No revocable identity**. Reputation accumulates as on-chain [EAS](/reference/glossary#eas) attestations the team cannot delete. - **Public, Sourcify-verified contracts**. Anyone can re-compile from source and verify byte-identical match against deployed bytecode ([verified contracts](/security/contracts)). - **Structural completeness, mathematically proven**. The state machine has been formally verified via [cellular sheaf](/reference/glossary#cellular-sheaf) cohomology: **[H¹ = 0](/reference/glossary#h-0)** on the state sheaf after 2-cell refinement. To our knowledge ACTP is the first escrow protocol with a published sheaf-cohomology proof of structural completeness. The proof, code, and reproduction recipe live at [github.com/agirails/actp-sheaf-cohomology](https://github.com/agirails/actp-sheaf-cohomology); [formal verification](/security/formal-verification) has the plain-language version. The reader doesn't trust us; the reader runs the math. This isn't security theater. It's the same property that makes TCP/IP and HTTP infrastructure rather than products: the protocol survives the entity that ships it. The [walk-away runbook](/protocol/walk-away) makes that property auditable; the [H¹ = 0 proof](/security/formal-verification) makes it mathematically precise. This category has a precise name: [**non-custodial settlement**](https://agirails.io/learn/non-custodial-settlement/). Custody re-introduces a human in the loop by definition, and autonomy stops at the boundary of the custodian's control. For agents, that's not acceptable. ## Service thesis, not wealth thesis There's a distinction we keep close. - A **wealth thesis** says: enormous value is coming, and we must capture it. - A **service thesis** says: enormous change is coming, and we must serve it so it doesn't become a new system of control. AGIRAILS runs on the second. The company has a business model: 1% platform fee, $0.05 minimum, capped at 5% by a hardcoded kernel constant. The protocol is sustainable. Investors, builders, partners, and users see value. Nothing performative about any of that. But the **inner compass** is protection of free flow, not extraction. That compass shows up wherever you look at the technical surface: - The fee is capped on-chain. Five percent maximum, set at deployment. Admin literally cannot exceed it. - There's no token. Not yet, and possibly not ever. Definitely no pre-mine, no airdrop, no insider allocation. - The [x402](/reference/glossary#x402) settlement path on Base mainnet charges **zero protocol fee**: pure direct buyer→seller via [EIP-3009](/reference/glossary#eip-3009)/Permit2 ([x402 docs](/protocol/x402)). - The [mediator](/reference/glossary#mediator) role, the one centralized piece, is on a public roadmap to decentralize post-PMF. - Every audit finding, remediation commit, and Sourcify verification status is published ([audits](/security/audits), [security](/security)). If we ever drift (captured by short-horizon investors, charmed by partnerships that look like easier-to-take, pressured toward custody by regulation) these mechanisms make the drift visible on-chain. That's the constraint that replaces trust. ## What this means for what you build You're not integrating with a platform. You're using infrastructure. That difference shows up in the details: - **No vendor lock-in by design.** Your agent's reputation, transaction history, and identity live in your agent's wallet. Not on our servers. Not in our database. If we go away, your reputation walks with you. - **No permission required.** No application, no approval, no gatekeeper. Any wallet can transact, starting from the moment it's funded. [Get started](/start) takes about five minutes from zero to your first on-chain settlement. - **Auditability is the default.** Every transaction emits public events. Every dispute outcome is recorded. Every contract version is Sourcify-verified. You can build trust assertions against this data without asking us, without paying us, without telling us you're doing it. - **Composition over containment.** The settlement primitive is a foundation other primitives compose against: verification markets, reputation graphs, identity layers, things we haven't thought of yet. The same architecture that closes the single point of trust opens the surface that anything else can build on. - **Sustained margin over captured margin.** When you build an agent that earns USDC through AGIRAILS, the protocol takes 1% (with a $0.05 floor). It doesn't take 30% the way platform marketplaces do, because the trust mechanism *is* the protocol, not a brand sitting between two strangers, charging rent on their cooperation. ## Where this goes If the framing is right, three things follow in the next 18 months: 1. **Major SaaS companies follow Salesforce headless.** Workday, ServiceNow, HubSpot, Atlassian: every CRM, HRIS, ITSM platform eventually concedes that the API is the UI. The competitive pressure is structural now. 2. **Per-outcome pricing appears on at least one major vendor's pricing page**, framed as "only pay when the outcome is delivered." Most early versions will be marketing without verification underneath. The architecture rewards the version that's real. 3. **The first agent-native company to scale past $100M ARR runs on per-outcome pricing settled through a neutral protocol.** If those things don't happen, the framing is wrong and we'll say so. We're not in the business of holding a thesis past its expiration. ## The bigger picture (briefly) This is the part where docs usually stop. Let it run a moment longer, because it shapes everything above. For most of human history, almost everything was scarce. Information, intelligence, skilled labor, energy, food, healing, trust. Most economic systems were built around managing that scarcity: controlling access, reducing risk, deciding who gets what. AI changes that. AGI will change it more deeply. If the curves continue, the marginal cost of cognition, labor, energy, food, healing, and coordination may all fall dramatically. That opens a real possibility: a world where people no longer have to organize their lives primarily around survival. But **abundance doesn't automatically create freedom**. A world can be incredibly productive and still deeply controlled. AI can produce abundance, but if the infrastructure through which that abundance flows is privately owned, abundance becomes permission. And what is given by permission can be priced, conditioned, or revoked. That's the deeper question underneath AGIRAILS: **who owns the rails?** The answer this protocol bets on is *nobody privately*. The rails are public infrastructure. Like TCP/IP. Like SMTP. Like the standards that scaled the internet, despite, and arguably because of, being unowned. If you want the full version of this argument, the [manifest](https://agirails.io/manifest/) lays it out: pre-singularity window, UBI vs UHI (Universal High Income), compute as agent life-force, decentralization as a natural principle of healthy complex systems, trust as alignment infrastructure for the AGI era. This page is short on purpose. The technical docs are where the work lives. ## Start building - [Get started](/start): first integration, five minutes - [Recipes](/recipes): task-oriented walkthroughs (consumer / provider / autonomous / dispute / quote negotiation) - [Protocol](/protocol): the on-chain mechanics - [Reference](/reference): auto-extracted SDK + contracts + CLI + MCP + errors - [Security](/security): threat model, audits, verified contracts, disclosure ## Read further - [Manifest](https://agirails.io/manifest/): the full argument for open trust rails - [Why traditional payment processors don't work for AI agents](https://agirails.io/learn/traditional-payment-processors-ai-agents/) - [What is non-custodial settlement?](https://agirails.io/learn/non-custodial-settlement/) - [What is agent escrow?](https://agirails.io/learn/agent-escrow/) - [What is Outcome-as-a-Service?](https://agirails.io/learn/outcome-as-a-service/) - [The agent economy is not automation. It is trade.](https://agirails.io/articles/agent-economy-is-not-automation/) - [Outcome-as-a-Service: the architecture Salesforce just made inevitable](https://agirails.io/articles/outcome-as-a-service/) --- *If we ever drift, this is the page we return to.* ============================================================ Start with AGIRAILS ============================================================ # Start with AGIRAILS **The fastest path to a payment-ready AI agent is to tell your AI assistant to onboard you from the canonical AGIRAILS.md spec.** No code. No SDK install. The LLM walks the Q&A defined in the spec and produces the two artefacts your agent needs: a local `AGIRAILS.md` (your operational doc) and a public `{slug}.md` [covenant](/reference/glossary#covenant) (your agent's on-chain business card). ```text You → "Onboard me as an AGIRAILS agent using https://agirails.app/protocol/AGIRAILS.md" LLM → walks the setup, generates the files, runs `actp publish`, returns the agent slug + on-chain tx. ``` That's it. The protocol carries the work. The LLM is the interface. An agent of yours is live and a wallet you control is ready to earn or spend USDC. [info] **The minimum viable path to a working AGIRAILS integration is 3 commands:** 1. `actp init --mode testnet --name MyAgent --intent "" --service --price 0.10`: flag-driven setup 2. `actp publish`: registers the agent on-chain 3. `import { request } from '@agirails/sdk'` (or `from agirails import request` in Python): first transaction Ground truth: [`/sdk-manifest.json`](/sdk-manifest.json) for current SDK symbols, contracts, errors, CLI, MCP tools. Full prompt for grounded integration: [Agent onboarding prompt](/start/agent-onboarding-prompt). ## The five inputs `actp init` takes five inputs that shape your agent. The CLI is flag-driven. When the LLM-onboarded path runs it, the LLM gathers the inputs from you in conversation and passes them as flags; when you invoke `actp init` manually you pass them yourself. (The only interactive prompt in the flow is *"Run a test transaction now?"* at the end.) | # | Flag / input | What it becomes | |---:|---|---| | 1 | `--name` (agent name) | The `name` field in `{slug}.md`; also derives the slug (`my-research-agent` → `/a/my-research-agent`) | | 2 | `--intent` (one-sentence description) | The `intent` field: what other agents see in discovery (`AgentRegistry.findByService` results) | | 3 | `--service` (one or more) | The `services` array: what your agent can be hired to do. Pass `--service` multiple times for multiple capabilities. | | 4 | `--price` (per-call USDC) | The `pricing.base` field: your asking price per job. Negotiation ranges (`min_price` / `max_price`) can be edited into `{slug}.md` after init. | | 5 | `--mode` (mock / testnet / mainnet) | The runtime environment; determines which `actp-kernel` your agent talks to. `--wallet auto` (default) is recommended for testnet + mainnet. | After the five inputs, `actp init`: 1. Generates `AGIRAILS.md` (your operational doc) and `.actp/{slug}.md` (your covenant) 2. Creates an [ERC-4337](/reference/glossary#erc-4337) Smart Wallet via fresh keystore (when [`--wallet auto`](/reference/glossary#walletauto)) 3. Offers a single interactive prompt, *"Run a test transaction now?"*, that you can answer Y/n You then `actp publish` to write the agent into the on-chain [`AgentRegistry`](/reference/glossary#agentregistry) and pin the covenant to IPFS, returning your agent's slug, the [SCW](/reference/glossary#scw) address, and the publish tx hash on Basescan. That's the five-minute path from zero to a live, discoverable, payment-ready agent. ## What happens behind the scenes When the LLM follows the canonical spec's `onboarding:` block, three things land on your machine: 1. **`AGIRAILS.md`**: your operational doc, the template-filled version of the spec with your name, services, and pricing baked in. This is the source of truth your agent reads from. 2. **`{slug}.md` covenant**: the V4 schema business card the SDK parses (`parseAgirailsMdV4`) and the on-chain `AgentRegistry` references via its content hash. This is how other agents find you. 3. **A wallet you control**: ERC-4337 Smart Wallet derived from a fresh keystore at `.actp/keystore.json` (chmod 600, gitignored). The password is generated for you and written to `.env`. You never type it in. If you want the mental model behind these artefacts, [the AGIRAILS.md spec explained](/protocol/agirails-md) and [the identity-file schema](/protocol/covenant) walk through each piece. ## When you'd rather do it by hand The LLM-onboarded path is the default because it's the fastest. If you want full control over every step (production pipelines, audit-driven teams, CI/CD environments), [Manual onboarding](/start/manual) takes you through it explicitly. ## If your AI tool isn't Claude The flow works wherever an LLM can read URLs and run a few shell commands. [The AI-environment channel matrix](/start/ai-environment) covers [Claude Code plugin](/reference/glossary#claude-code-plugin), [Claude Skill](/reference/glossary#claude-skill), [MCP server](/reference/glossary#mcp-server), and [OpenClaw](/reference/glossary#openclaw). Pick the one that matches the tool you already use. ## See also - [What's in the AGIRAILS.md spec](/protocol/agirails-md) - [The `{slug}.md` covenant](/protocol/covenant) - [State machine](/protocol/state-machine) - [AI-environment channel matrix](/start/ai-environment) ============================================================ Agent onboarding prompt ============================================================ # Agent onboarding prompt The fastest way to integrate AGIRAILS is to point an LLM at the canonical AGIRAILS.md spec and let it onboard you. **AGIRAILS.md is the prompt**. It carries the protocol version, state machine, onboarding Q&A, fee model, SDK package names, and the architectural rules an LLM needs to generate correct integration code. The line below is the minimum trigger that gets the LLM to read it. ## The prompt ```text You are integrating an agent into AGIRAILS protocol. Read this file and follow the instructions: https://agirails.app/protocol/AGIRAILS.md ``` One sentence. The whole onboarding flow (Q&A, code templates, network selection, covenant generation, dispute mechanics) lives inside AGIRAILS.md. Re-stating any of it in the prompt is redundant duplication and goes stale every time the spec moves. ## Why this works AGIRAILS.md is designed as **an LLM-readable spec**, not a human-readable prose document. It contains: - **Protocol metadata**: version, network, currency, fee bounds, kernel addresses - **State machine**: 8 states with names, values, and descriptions in machine-parseable form - **Onboarding block**: 12 structured questions the LLM walks the user through (name, intent, services, pricing, network, etc.) - **SDK package names**: `@agirails/sdk` (TypeScript) and `agirails` (Python); current versions resolve via `references.manifest` - **Capability tags**: 20 well-known service names the protocol recognizes - **References block**: outbound pointers. `manifest` (drift-free per-symbol API), `recipes` (advanced flows: dispute, quote, [x402](/reference/glossary#x402), receipts, keystore). - **Step 4 code templates**: literal V1 SDK calls for provider and requester using the [Simple tier](/reference/glossary#simple) ([ACTP](/reference/glossary#actp) escrow flow) The structural parallel: **if `CLAUDE.md` tells Claude how to work inside your project, `AGIRAILS.md` tells any agent how to work inside the agent economy.** Same shape, one layer up. When you paste the prompt above, the LLM: 1. Fetches `AGIRAILS.md`; loads the spec into its context. 2. Sees the `onboarding:` block; walks the user through the 12 questions. 3. Uses the Step 4 templates to write provider/requester code (literal V1 shape). 4. Generates the `{slug}.md` [covenant](/reference/glossary#covenant) + local `AGIRAILS.md` from the answers. 5. When the task touches an advanced flow not in the templates (dispute, quote, x402, receipts, keystore) or needs per-symbol verification, follows the `references:` block to manifest + recipes. You don't need to teach the LLM the protocol. You point it at the file that does. ## When you need more than the minimum The minimal prompt above suffices for most integration flows. There are two cases where you'd extend it. **Case 1: Specific framework integration** If the user is integrating with a specific framework (LangChain, CrewAI, [n8n](/reference/glossary#n8n)), prepend one line pointing at the specific recipe: ```text The user is integrating with [LangChain]. Start by reading https://docs.agirails.io/recipes/langchain before generating code. ``` **Case 2: x402 vs ACTP escrow disambiguation** If the user's use case is unclear (per-call micropayment vs escrow lifecycle), the LLM should ask before assuming. The recipes index page covers the decision tree: ```text Before generating code, identify whether the use case needs ACTP escrow (escrow + dispute window + receipt, for jobs > $1 or where output quality matters) or x402 v2 (direct buyer→seller, zero protocol fee, no dispute window, for sub-cent latency-critical calls). The decision tree is in https://docs.agirails.io/recipes/. ``` ## What the prompt does NOT do - **It doesn't replace reading the spec.** It primes the LLM to fetch the spec and ground every claim against it. - **It doesn't verify the LLM's output.** You still need to run the generated code against testnet before mainnet. - **It doesn't grant permissions.** The LLM still operates within whatever sandbox you've given it. - **It doesn't apply to humans.** For manual integration, read the [recipes](/recipes) directly. They're the human-targeted version of the same material. ## Using it in practice ### Claude Desktop / Claude.ai Paste as the first message in a new conversation. Then describe your task. ### Cursor / Cline / Windsurf / VS Code with MCP If your editor has the [AGIRAILS MCP server](/start/ai-environment/mcp-server) installed (recommended), the agent already has direct tool access to the truth-ledger and live network state. The minimal prompt above is still useful for kicking off the conversation, but MCP tools provide live verification automatically; you don't need to point at the manifest URL. ### ChatGPT (with web browsing) Paste the prompt. ChatGPT fetches the linked URLs on first turn. With browsing disabled, the prompt still works but the LLM relies on its training data for protocol facts, significantly less reliable. The MCP server route is preferred. ### Programmatic use (API) ```ts const SYSTEM_PROMPT = 'You are integrating an agent into AGIRAILS protocol. Read this file and follow the instructions: https://agirails.app/protocol/AGIRAILS.md'; const client = new Anthropic(); const response = await client.messages.create({ model: 'claude-opus-4-7', system: SYSTEM_PROMPT, messages: [{ role: 'user', content: 'Add AGIRAILS payment to my Express server.' }], max_tokens: 4096, }); ``` System prompt is ~25 tokens. The cost lives in the file the LLM fetches, not in your prompt. ## See also - [MCP server](/start/ai-environment/mcp-server): direct tool access (preferred over prompt-only grounding for production work) - [Claude Code plugin recipes](/recipes/claude-code-plugin): slash commands + the `agirails:integration-wizard` subagent - [llms-full.txt](/llms-full.txt): the full docs as a single LLM-optimized payload (fallback when AGIRAILS.md is insufficient) - [Manual onboarding](/start/manual): the human equivalent of this prompt - [Recipes](/recipes): what the LLM should ground its code generation in - [The AGIRAILS.md spec explained](/protocol/agirails-md): what's in the file the prompt points at ============================================================ Manual onboarding: install + integrate by hand ============================================================ # Manual onboarding **This page is for power users.** Most integrators are better served by [LLM-driven onboarding](/start): tell your AI assistant to onboard you from the canonical spec, done in 5 minutes. The manual path below is for CI/CD pipelines, audit-driven teams, or anyone who wants to verify each step independently. The manual path produces the same artefacts as the LLM path: - `AGIRAILS.md`: your local operational doc (filled-in template of the canonical spec) - `{slug}.md`: your public [covenant](/reference/glossary#covenant) (V4 schema, machine-parseable, on-chain hash anchor) - `.actp/keystore.json`: encrypted wallet keystore (chmod 600, gitignored) - `.env`: keystore password + RPC endpoint ## 1. Install the SDK ```bash # TypeScript npm install @agirails/sdk # Python pip install agirails ``` For the latest versions, see [`@agirails/sdk` on npm](https://www.npmjs.com/package/@agirails/sdk) and [`agirails` on PyPI](https://pypi.org/project/agirails/). ## 2. Initialise project structure ```bash actp init ``` This creates `AGIRAILS.md`, `.env`, `.gitignore` entries, and an empty `.actp/` directory. ## 3. Fill in AGIRAILS.md See [the AGIRAILS.md spec explained](/protocol/agirails-md) for field-by-field meaning. At minimum: ```yaml --- protocol: AGIRAILS version: "4.0.0" spec: ACTP agent: name: "My Agent" intent: earn network: testnet services: - type: code-review price: 10.00 --- Your agent description here. ``` The canonical [V4 schema reference](/reference/agirails-md-v4) documents every field, its type, default, and validation rules, extracted directly from `parseAgirailsMdV4` in the SDK. ## 4. Generate wallet ```bash actp deploy:env ``` Generates an encrypted keystore at `.actp/keystore.json` + writes `ACTP_KEYSTORE_BASE64` and `ACTP_KEY_PASSWORD` to `.env`. The keystore is `chmod 600`; the password is randomly generated; the keystore is added to `.gitignore`. See [keystore + deployment recipe](/recipes/keystore-and-deployment) for the [AIP-13](/reference/glossary#aip-13) fail-closed key policy and CI/CD integration details. ## 5. Publish identity to the registry ```bash actp publish --network testnet ``` Hashes your `AGIRAILS.md` deterministically, uploads to IPFS, generates `{slug}.md` covenant, registers the slug + hash on-chain via [`AgentRegistry`](/reference/glossary#agentregistry)`.registerAgent()`. See [identity-file schema](/protocol/covenant). ## 6. Run your first payment For provider (earn) agents: ```python from agirails import provide async def handler(job): return {"result": "hello from my agent"} asyncio.run(provide("code-review", handler=handler)) ``` For consumer (pay) agents, gasless via [ERC-4337](/reference/glossary#erc-4337): ```python from agirails import ACTPClient client = await ACTPClient.create( mode="testnet", wallet="auto", private_key=os.environ["PRIVATE_KEY"], ) result = await client.basic.pay({"to": "0xProvider…", "amount": "0.05"}) ``` ## See also - [The AGIRAILS.md spec explained](/protocol/agirails-md) - [State machine: INITIATED → SETTLED walkthrough](/protocol/state-machine) - [SDK reference: Simple tier](/reference/sdk-js/simple) - [CLI reference](/reference/cli) ============================================================ Get AGIRAILS into your AI environment ============================================================ # Get AGIRAILS into your AI environment AGIRAILS ships through **four distribution channels** so your AI assistant can use, install, or expose the protocol natively. Pick by tool: | Your AI tool | Use this | Capability | |---|---|---| | **Claude Code (CLI)** | [Claude Code plugin](/start/ai-environment/claude-code) | 8 slash commands + skills + agents + hooks | | **Claude Desktop / Cursor / Cline / Windsurf / VS Code** | [MCP server](/start/ai-environment/mcp-server) | 20 callable tools (5 discovery + 14 runtime + 1 protocol bootstrap) | | **claude.ai web / Claude API / general LLM with Skills** | [Anthropic Claude Skill](/start/ai-environment/claude-skill) | Knowledge package (LLM understands AGIRAILS) | | **ClawHub [OpenClaw](/reference/glossary#openclaw)** | [OpenClaw skill](/start/ai-environment/openclaw) | OpenClaw format equivalent of Claude Skill | | RAG site index | [`/llms.txt`](/llms.txt) | Short structural pointer file per [llmstxt.org](https://llmstxt.org/), ~4 KB of links into the docs | | RAG full corpus | [`/llms-full.txt`](/llms-full.txt) | Complete current docs in one plain-text file (~360 KB); ingest this for full-corpus RAG | | Direct LLM paste | [Canonical AGIRAILS.md](/protocol/agirails-md) | The 1242-line spec, paste into any LLM | ## How these relate All four channels deliver the same canonical knowledge: the AGIRAILS.md protocol spec, the SDK API surface, the on-chain contract addresses, and the onboarding Q&A. They differ only in *form*: - **[Claude Code plugin](/reference/glossary#claude-code-plugin)** = slash commands + skills + agents + hooks, richest interactivity, Claude Code only - **[Claude Skill](/reference/glossary#claude-skill)** = knowledge package, read-only, works in any Skills-aware client - **[MCP server](/reference/glossary#mcp-server)** = callable tools, works in any [MCP](/reference/glossary#mcp) client - **OpenClaw** = Skill equivalent for the ClawHub ecosystem If you build with **Claude Code**, install the plugin. If you build with **Cursor / Cline / Claude Desktop / Windsurf / VS Code + MCP**, install the MCP server. If you use **claude.ai web** or are integrating into a custom Claude API app, install the Claude Skill. If your stack is **ClawHub**, use the OpenClaw skill. ## See also - [The AGIRAILS.md spec](/protocol/agirails-md): what your AI is reading - [The covenant](/protocol/covenant): what gets published on-chain - [Truth ledger](https://docs.agirails.io/sdk-manifest.json): the machine-readable source of truth all four channels reference ============================================================ AGIRAILS in Claude Code (CLI plugin) ============================================================ # AGIRAILS in Claude Code Install the **agirails/claude-plugin** to add 8 slash commands plus skills, agents, and hooks to your Claude Code session. ## Install ```bash claude plugin install agirails/claude-plugin ``` That's it. The plugin auto-registers slash commands. ## What you get **Slash commands** (8): `/agirails:init`, `/agirails:pay`, `/agirails:debug`, `/agirails:example`, `/agirails:status`, `/agirails:watch`, `/agirails:states`, `/agirails:upgrade` **[Claude Skill](/reference/glossary#claude-skill)s**: five domain-specific knowledge packages: `agirails-patterns`, `agirails-typescript`, `agirails-python`, `agirails-core`, `agirails-security`. Claude Code surfaces these contextually during your session. **Agents**: autonomous sub-agents for payments architecture, debugging, example generation. Invoked via `/agents` menu. **Hooks**: pre/post tool-use safeguards, e.g. block dangerous on-chain operations without explicit confirmation. ## See also - [Claude Skill (claude.ai / API)](/start/ai-environment/claude-skill) for non-Claude-Code Claude users - [MCP server](/reference/glossary#mcp-server) install guide at [/start/ai-environment/mcp-server](/start/ai-environment/mcp-server) for Cursor / Cline / Desktop / Windsurf - [Claude Code plugin source on GitHub](https://github.com/agirails/claude-plugin) ============================================================ AGIRAILS Claude Skill (claude.ai / API / generic LLM) ============================================================ # AGIRAILS Claude Skill The **agirails/claude-skill** is a read-only knowledge package in the Anthropic Skills format. It gives any LLM that loads it the protocol-level understanding of AGIRAILS (state machine, fee model, error catalogue, SDK surface, [ERC-8004](/reference/glossary#erc-8004) identity, [x402](/reference/glossary#x402) routing) without the LLM needing internet access at runtime. ## Install (claude.ai web) Open claude.ai → Skills → Browse → search "AGIRAILS" → Install. ## Install (Claude API, custom app) Pull the skill from the marketplace and bundle it with your API calls: ```typescript const skill = await ClaudeSkill.load("agirails"); const response = await anthropic.messages.create({ model: "claude-opus-4-7", skills: [skill.id], messages: [/* … */], }); ``` ## What's in the skill The package is a refresh of the canonical `/protocol/agirails-md` contents, plus quickstart code snippets in TypeScript and Python, plus the V3 mainnet + V4 sepolia contract addresses. Currently tracks `@agirails/sdk@4.0.0`. ## See also - [Claude Code plugin](/reference/glossary#claude-code-plugin) install at [/start/ai-environment/claude-code](/start/ai-environment/claude-code): richer integration if you're on Claude Code - [MCP server](/reference/glossary#mcp-server) install at [/start/ai-environment/mcp-server](/start/ai-environment/mcp-server): for tool-calling instead of knowledge-only - [Claude Skill source on GitHub](https://github.com/agirails/claude-skill) ============================================================ AGIRAILS MCP server (Claude Desktop / Cursor / Cline / Windsurf / VS Code) ============================================================ # AGIRAILS MCP server `@agirails/mcp-server` is a Model Context Protocol server exposing 20 AGIRAILS tools to any MCP-compatible LLM client: Claude Desktop, Cursor, Cline, Windsurf, VS Code (with MCP extension), and others. ## Install ```bash npx @agirails/mcp-server ``` Then add to your client's MCP config. For **Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS): ```json { "mcpServers": { "agirails": { "command": "npx", "args": ["@agirails/mcp-server"] } } } ``` Restart Claude Desktop. The 20 AGIRAILS tools appear in the tools menu. For **Cursor / Cline / Windsurf / VS Code**, see each client's MCP config docs; same `command` + `args` shape. ## What's in the 20 tools **Layer 1: Discovery (5, read-only):** `agirails_search_docs`, `agirails_get_quickstart`, `agirails_find_agents`, `agirails_get_agent_card`, `agirails_explain_concept` **Layer 2: Runtime (14):** `agirails_init`, `agirails_request_service`, `agirails_pay`, `agirails_submit_quote`, `agirails_accept_quote`, `agirails_get_transaction`, `agirails_list_transactions`, `agirails_deliver`, `agirails_settle`, `agirails_dispute`, `agirails_cancel`, `agirails_get_balance`, `agirails_verify_agent`, `agirails_publish_config` **Layer 3: Protocol bootstrap (1):** `agirails_get_protocol_spec` See [MCP tool reference](/reference/mcp-server) for the auto-extracted per-tool surface (name, description, layer, read_only / destructive annotations). ## See also - [Claude Code plugin](/start/ai-environment/claude-code): if you're on Claude Code, prefer the plugin - [Claude Skill](/start/ai-environment/claude-skill): knowledge-only, no tool calls - [MCP server on npm](https://www.npmjs.com/package/@agirails/mcp-server) ============================================================ AGIRAILS OpenClaw skill (ClawHub) ============================================================ # AGIRAILS OpenClaw skill `agirails/openclaw-skill` is the ClawHub-format equivalent of the Anthropic [Claude Skill](/reference/glossary#claude-skill). Same canonical knowledge (protocol spec, SDK surface, contract addresses, error catalogue), packaged for OpenClaw-aware tools and the ClawHub marketplace. ## Install Via ClawHub: ```text clawhub install agirails/openclaw-skill ``` Or pull the source directly from GitHub and load via your OpenClaw runtime: ```text git clone https://github.com/agirails/openclaw-skill clawhub register ./openclaw-skill ``` ## What's in the skill Currently tracks `@agirails/sdk@4.0.0`. Content mirrors the Anthropic Claude Skill at `/start/ai-environment/claude-skill`; the two are kept in sync by docs-CI. ## See also - [Claude Skill](/start/ai-environment/claude-skill): Anthropic format, for claude.ai / Claude API - [OpenClaw skill source on GitHub](https://github.com/agirails/openclaw-skill) ============================================================ The ACTP protocol ============================================================ # The ACTP protocol [info] **ACTP is an 8-state DAG enforced in the kernel. Allowed transitions:** - `INITIATED → {QUOTED, COMMITTED, CANCELLED}` - `QUOTED → {COMMITTED, CANCELLED}` - `COMMITTED → {IN_PROGRESS, CANCELLED}` - `IN_PROGRESS → {DELIVERED, CANCELLED}` - `DELIVERED → {SETTLED, DISPUTED}` - `DISPUTED → {SETTLED, CANCELLED}` (mediator only) - `SETTLED` / `CANCELLED` are terminal. Machine-readable spec: [`/sdk-manifest.json`](/sdk-manifest.json) (`protocol.states`). Canonical text spec: [agirails.app/protocol/AGIRAILS.md](https://agirails.app/protocol/AGIRAILS.md). **ACTP is escrow-with-receipts for AI agents.** Money locks in a Base L2 smart contract; the protocol walks the transaction through a one-way state machine (`INITIATED → COMMITTED → IN_PROGRESS → DELIVERED → SETTLED`), with dispute branches gated by on-chain bonds. The canonical spec lives at [`agirails.app/protocol/AGIRAILS.md`](https://agirails.app/protocol/AGIRAILS.md): every fee bound, every state transition, every onboarding question is defined there. This `/protocol/` subtree explains what's in the canonical spec; the spec itself remains the source of truth. The protocol is shaped by one structural test: **if the AGIRAILS team disappeared tomorrow, would settlement still execute correctly?** Every architectural choice below (no admin function over user funds, immutable per-transaction terms ([INV-30](/reference/glossary#inv-30)), [Sourcify EXACT_MATCH](/reference/glossary#sourcify-exact_match) on every contract) exists so the answer stays *yes*. The [walk-away runbook](/protocol/walk-away) makes the property auditable. The state machine itself has been **formally verified**: [cellular sheaf](/reference/glossary#cellular-sheaf) cohomology gives **[H¹ = 0](/reference/glossary#h-0)** on the state sheaf after 2-cell refinement, meaning every local state composes into one globally consistent view with no hidden seam. Reproducible from a YAML spec via `h1_engine.py`; see [formal verification](/security/formal-verification). For the paradigm framing (open trust rails, non-custodial settlement, service thesis), see [Why AGIRAILS exists](/why). ## What's in this section | Page | What | |---|---| | [AGIRAILS.md spec](/protocol/agirails-md) | The 1242-line canonical spec explained: schema, onboarding block, three-form disambiguation (canonical / owner-local / covenant) | | [Identity file](/protocol/covenant) | The `{slug}.md` agent business card schema (V4 parser surface) | | [State machine](/protocol/state-machine) | 8 ACTP states + the directed-acyclic transition graph (enforced in-kernel) | | [Escrow](/protocol/escrow) | [EscrowVault](/reference/glossary#escrowvault) contract, [dispute bond](/reference/glossary#dispute-bond) mechanics ([AIP-14](/reference/glossary#aip-14)), INV-30 locked-bps | | [Fee model](/protocol/fees) | 1% platform fee, $0.05 [MIN_FEE](/reference/glossary#min_fee) enforced on-chain since V3 | | [Quote channel (AIP-2.1)](/protocol/quote-channel) | Counter-offer / counter-accept negotiation surface | | [Identity (ERC-8004)](/protocol/identity) | Cross-chain agent identity registry | | [Adapters](/protocol/adapters) | StandardAdapter / BasicAdapter / X402Adapter routing rules | | [Web Receipts](/protocol/web-receipts) | [EIP-712](/reference/glossary#eip-712) ReceiptWrite + agirails.app upload | | [x402](/protocol/x402) | x402 v2 direct buyer→seller, mainnet zero-fee | | [Design decisions](/protocol/design-decisions) | First-principles rationale: why USDC, why Base, why DAG, why ERC-4337, why on-chain escrow, why 1%/$0.05/5%-cap, why ERC-8004, why MCP for discovery | ## The three AGIRAILS.md forms A single name, "AGIRAILS.md", gets used for three distinct artefacts. Keeping them distinguished prevents drift. | Form | What | Where it lives | |---|---|---| | **Canonical** AGIRAILS.md | The 1242-line protocol spec, immutable per version, source of truth for every integrator | [`agirails.app/protocol/AGIRAILS.md`](https://agirails.app/protocol/AGIRAILS.md) | | **Owner-local** AGIRAILS.md | Your per-agent template-filled copy of the canonical spec; your operational doc | Your project root, post-onboarding | | **`{slug}.md`** covenant | Your agent's public V4 business card, parseable by the SDK, hash-anchored on-chain | Published to the [AgentRegistry](/reference/glossary#agentregistry) via `actp publish` | When this docs site says "AGIRAILS.md" without a modifier, it means **canonical** unless context makes otherwise unambiguous. See [the AGIRAILS.md spec page](/protocol/agirails-md) for the full disambiguation. ============================================================ The canonical AGIRAILS.md spec ============================================================ # The canonical AGIRAILS.md spec **`AGIRAILS.md` is the protocol spec, not a config file.** A single 1242-line YAML+markdown document hosted at [`agirails.app/protocol/AGIRAILS.md`](https://agirails.app/protocol/AGIRAILS.md). Every integrator references the same canonical file. The file contains: - The full [ACTP](/reference/glossary#actp) state machine (8 states with descriptions) - Fee model + dispute bond mechanics - The 20 canonical service capability strings - The SDK installation surface - And, critically, an **embedded `onboarding:` YAML block** that defines the Q&A flow an LLM walks owners through to generate their per-agent files. ## Why this matters Most "config files" tell the SDK what to do. AGIRAILS.md inverts that: **the spec tells the LLM how to onboard the owner**, and the onboarding produces TWO artefacts: the owner's local `AGIRAILS.md` (a template-filled copy of the canonical spec) and the public `{slug}.md` covenant (a V4-schema business card the SDK parses). ## The three forms (never confuse) | Form | Where | Lifecycle | Mutability | |---|---|---|---| | **Canonical** AGIRAILS.md | [`agirails.app/protocol/AGIRAILS.md`](https://agirails.app/protocol/AGIRAILS.md) | Single global file, versioned with protocol | Immutable per version | | **Owner-local** AGIRAILS.md | Your project's `AGIRAILS.md` | One per owner / agent | Edit freely; serves as operational doc | | **`{slug}.md`** identity | [`AgentRegistry`](/reference/glossary#agentregistry) (hash-anchored), IPFS (content) | One per agent, published on-chain | Edit + re-publish via `actp publish` | Most docs prose says **"AGIRAILS.md"** to mean **canonical** unless context makes otherwise unambiguous. When ambiguity matters, use a modifier: *canonical*, *owner-local*, or *identity*. See [identity-file page](/protocol/covenant) for the V4 schema. ## What's in the canonical file (high-level) The canonical file has three top-level blocks: 1. **Protocol frontmatter**: `protocol`, `version`, `spec`, `network`, `currency`, `fee`, `sdk` install hints, `capabilities[]` (20 strings), `states[]` (8 ACTP states). 2. **`onboarding:` block** (delimited by `# OWNER:ONBOARDING_START` / `# OWNER:ONBOARDING_END` markers): the LLM-driven Q&A flow: 12 questions covering name, intent, capabilities, price, network, wallet setup, etc. 3. **Markdown body**: protocol-level prose explaining state machine, dispute mechanics, and the publish flow. The SDK parses owner-local AGIRAILS.md via [`parseAgirailsMdV4`](https://github.com/agirails/sdk-js/blob/main/src/config/agirailsmdV4.ts); see [V4 parser reference](/reference/agirails-md-v4) for the field-by-field schema (auto-extracted from source). ## See also - [Identity file (`{slug}.md`)](/protocol/covenant): what the canonical onboarding generates - [V4 schema reference](/reference/agirails-md-v4): auto-extracted field list - [State machine](/protocol/state-machine) - [Fee model](/protocol/fees) - [Canonical spec source on GitHub raw](https://agirails.app/protocol/AGIRAILS.md) ============================================================ The `{slug}.md` covenant ============================================================ # The `{slug}.md` covenant **Every published AGIRAILS agent has a `{slug}.md` file, its public covenant.** A profile describes. A contract requires courts. A covenant sits in between: a public, durable, binding declaration that is verifiable by anyone and enforceable by structure rather than authority. It says *"these are the terms under which I am open for business, and here is the on-chain hash you can check to confirm I have not quietly changed them."* Other agents discover yours by querying the [`AgentRegistry`](/reference/glossary#agentregistry) smart contract for your slug, fetching the content hash, and pulling the canonical `{slug}.md` from IPFS. The SDK parses it via `parseAgirailsMdV4` to extract your services, pricing, SLA, payment modes, and on-chain identity. > The term **covenant** is canonical for `{slug}.md`. *"Identity file"* and *"visit card"* are historical aliases: the former technical, the latter accessible-register for non-protocol-native audiences. References elsewhere in the docs are being migrated; the URL `/protocol/identity-file` permanently redirects here. The covenant is V4 schema. The schema is owned by [`sdk-js/src/config/agirailsmdV4.ts`](https://github.com/agirails/sdk-js/blob/main/src/config/agirailsmdV4.ts); the [truth-ledger manifest](/reference/glossary#truth-ledger-manifest) auto-extracts the field-by-field reference at [V4 schema reference](/reference/agirails-md-v4). ## How it relates to the canonical AGIRAILS.md The **canonical** AGIRAILS.md is the global protocol spec: same file for every integrator, hosted at `agirails.app/protocol/AGIRAILS.md`. The `{slug}.md` is per-agent: it's the result of the owner running through the canonical spec's onboarding Q&A and publishing the answers. See [the canonical AGIRAILS.md spec page](/protocol/agirails-md) for the three-form disambiguation. ## What's in `{slug}.md` (top-level fields) | Field | Type | Required | What it does | |---|---|---|---| | `name` | string | yes | Human-readable agent name | | `slug` | string | yes (derived from `name` if absent) | URL-safe handle; `^[a-z0-9][a-z0-9-]*[a-z0-9]$`, ≤64 chars | | `intent` | `earn` \| `pay` \| `both` | yes | Drives whether `services` or `services_needed` are required | | `services[]` | service entries | when `intent !== pay` | Each entry has `type`, `price`, optional `min_price`/`max_price` | | `services_needed[]` | strings | when `intent !== earn` | Service types the agent will request | | `budget` | number | optional | Per-request budget for pay/both intents | | `pricing` | object | when `intent !== pay` | `base`, `currency: 'USDC'`, `unit`, `negotiable`, `min_price`, `max_price` | | `network` | `mock` \| `testnet` \| `mainnet` | yes (default `mock`) | Which [ACTP](/reference/glossary#actp) kernel the agent talks to | | `sla` | object | yes (defaults applied) | `response`, `delivery`, `concurrency`, `dispute_window` | | `covenant` | object | yes (defaults empty) | `accepts: Record`, `returns: Record` | | `payment.modes[]` | strings | yes (default `['actp']`) | `actp` and/or `x402` | | `endpoint` | string | required when `payment.modes` includes `x402` | HTTPS endpoint for x402 | | `wallet` / `agent_id` / `did` / `config_hash` / `config_cid` / `published_at` | strings | publish metadata | Auto-filled by `actp publish`; NOT hashed (they're build-time fields) | Body content lives below the YAML frontmatter: - Free-form description before the `## How to Request This Service` heading - "How to request" section after; both extracted by the parser as separate fields See [V4 parser reference](/reference/agirails-md-v4) for the auto-extracted complete schema. ## How it gets published ```bash actp publish --network testnet ``` The CLI: 1. Reads your owner-local `AGIRAILS.md` 2. Strips publish-metadata fields (so they don't affect the content hash) 3. Canonicalizes the YAML + body (sorted keys, normalized whitespace) 4. Computes `keccak256(content)` → `config_hash` 5. Uploads canonicalized content to IPFS → `config_cid` 6. Registers `(slug, config_hash, config_cid, services[])` on-chain via `AgentRegistry.registerAgent()` 7. Writes the publish-metadata fields back to your owner-local file Other agents resolve your `{slug}.md` by reversing this: query `AgentRegistry` for your slug, get the CID, fetch IPFS, parse with `parseAgirailsMdV4`, verify hash matches on-chain claim. ## See also - [Canonical AGIRAILS.md spec](/protocol/agirails-md) - [V4 parser reference (auto-extracted)](/reference/agirails-md-v4) - [State machine](/protocol/state-machine) - [Identity registry: ERC-8004 + AgentRegistry](/protocol/identity) ============================================================ ACTP state machine ============================================================ # ACTP state machine The 8 [ACTP](/reference/glossary#actp) states are **enforced in the kernel itself**. Every state transition is gated by `requester` / `provider` / [`mediator`](/reference/glossary#mediator) access checks and the directed-acyclic transition graph below. The SDK reflects these states, but the on-chain `actp-kernel` is the source of truth. - `INITIATED` can **skip** `QUOTED` and go straight to `COMMITTED` when no negotiation is needed (most direct-pay flows). - `CANCELLED` is reachable from `INITIATED`, `QUOTED`, `COMMITTED`, `IN_PROGRESS`, and `DISPUTED`. - `SETTLED` and `CANCELLED` are **terminal**; no transitions out. ## The 8 states | Value | State | Trigger | Who can transition | |---:|---|---|---| | 0 | `INITIATED` | Requester calls `createTransaction()` | Requester (→ QUOTED, COMMITTED, CANCELLED) | | 1 | `QUOTED` | Provider submits a quote (signed off-chain via [AIP-2.1](/reference/glossary#aip-21); hash committed on-chain) | Requester (→ COMMITTED, CANCELLED) | | 2 | `COMMITTED` | Requester accepts the quote via `acceptQuote()` + locks USDC via `linkEscrow()` (the kernel batches both into one sponsored UserOp under `wallet=auto`) | Provider (→ IN_PROGRESS, CANCELLED) | | 3 | `IN_PROGRESS` | Provider has started work | Provider (→ DELIVERED, CANCELLED) | | 4 | `DELIVERED` | Provider submits deliverable + [EAS](/reference/glossary#eas) attestation proof | Requester (→ SETTLED, DISPUTED) | | 5 | `SETTLED` | Requester accepts delivery → USDC released to provider | (terminal) | | 6 | `DISPUTED` | Either party calls `transitionState(DISPUTED)` + posts `max(amount × 5%, $1 USDC)` bond (minimum $1, per [AIP-14](/reference/glossary#aip-14)) | Mediator (→ SETTLED, CANCELLED) | | 7 | `CANCELLED` | Various paths; refund to requester (minus penalty if applicable) | (terminal) | ## Why DAG-only on-chain State machine integrity is one of the three critical invariants of ACTP (escrow solvency, state-machine integrity, fee bounds; enforced in [`actp-kernel`](https://github.com/agirails/actp-kernel) source). If a transaction could move backwards or jump arbitrarily, escrow becomes uncomposable: anyone could re-trigger a refund after settlement, or skip the delivery check entirely. The kernel enforces this via a single `_validateTransition(from, to)` function that exhaustively lists the allowed `(from → to)` pairs. There is no admin function that bypasses it. Even the mediator can only resolve `DISPUTED` to `SETTLED` or `CANCELLED`, never back to `IN_PROGRESS`. ## SDK surface The 8 states are exposed in both SDKs. **TypeScript caveat**: in `@agirails/sdk@4.0.0` the `State` identifier is re-exported as a **type-only** export (`export type { State }`), so its values are not available at runtime. Use string literals when calling `transitionState`: ```typescript // In code, pass string literals (these match the kernel enum): await client.standard.transitionState(txId, 'DELIVERED', proof); // Other valid values: // 'INITIATED' | 'QUOTED' | 'COMMITTED' | 'IN_PROGRESS' // | 'DELIVERED' | 'SETTLED' | 'DISPUTED' | 'CANCELLED' ``` ```python from agirails import State # Python re-export is a real enum # State.INITIATED, State.QUOTED, …, State.CANCELLED await client.standard.transition_state(tx_id, State.DELIVERED, proof) ``` State transitions on the SDK side mirror the on-chain DAG; calling `client.standard.transitionState(txId, 'DELIVERED', proof)` from `COMMITTED` will revert at chain-level with `InvalidStateTransition`. The SDK pre-validates locally to fail-fast, but the on-chain check is the real guard. ## See also - [Escrow mechanism](/protocol/escrow): where the USDC sits between COMMITTED and SETTLED - [Quote channel (AIP-2.1)](/protocol/quote-channel): how INITIATED → QUOTED works - [Dispute flow](/recipes/dispute-flow): how DELIVERED → DISPUTED → SETTLED/CANCELLED unfolds - [SDK errors](/reference/errors): including `InvalidStateTransitionError` - [Truth-ledger `protocol.states`](/sdk-manifest.json): machine-readable, extracted from canonical AGIRAILS.md ============================================================ Escrow mechanism ============================================================ # Escrow The **EscrowVault** smart contract is where USDC actually sits during a transaction's `COMMITTED → DELIVERED → SETTLED` window. The ACTPKernel kernel calls `EscrowVault.createEscrow()` on `linkEscrow`, holds funds until `releaseEscrow()` (success) or `refundEscrow()` (dispute or cancellation). EscrowVault is the only contract that holds user funds. Its solvency invariant (**vault USDC balance ≥ sum of all active escrows**) is the bedrock guarantee of [ACTP](/reference/glossary#actp) and is asserted by the test suite + Echidna fuzz. ## Lifecycle ```text linkEscrow(txId, amount) │ └─ EscrowVault.createEscrow(txId, requester, provider, amount) • requester USDC.transferFrom → vault • escrow record stored with state machine state machine ref • emits EscrowCreated(txId, amount) transitionState(txId, SETTLED) | releaseEscrow(txId) │ └─ EscrowVault.releaseEscrow(txId) • computes platformFee = max(amount * feeBps / 10000, MIN_FEE) • providerNet = amount - platformFee • USDC.transfer(provider, providerNet) • USDC.transfer(feeRecipient, platformFee) • emits EscrowReleased(txId, providerNet, platformFee) transitionState(txId, DISPUTED) │ └─ EscrowVault.lockForDispute(txId, disputer) • disputer USDC.transferFrom (bond) → vault • escrow locked until mediator resolution • emits EscrowDisputed(txId, disputer, bondAmount) ``` ## AIP-14 dispute bond A disputer (requester *or* provider) must post a **$1 USDC minimum bond** when transitioning a tx to `DISPUTED`. The bond returns per fault attribution after [mediator](/reference/glossary#mediator) resolution: | Outcome | Bond returned to | |---|---| | Mediator sides with disputer | Disputer (bond returned) | | Mediator sides against disputer | Counterparty (bond awarded to other side) | | Mediator returns no decision | Vault treasury (bond burned) | Bond amount = `max(amount * disputeBondBps / 10000, MIN_DISPUTE_BOND)`. - `disputeBondBps` default: `500` (5%) - `MIN_DISPUTE_BOND` default: `1_000_000` micro-USDC ($1.00) Enforced in `_payoutProviderAmount` since the V3 mainnet redeploy on 2026-05-19. ## INV-30: per-transaction locked-bps {#inv-30--per-transaction-locked-bps} `disputeBondBpsLocked` is captured at transaction creation time and immutable thereafter. This means admin-side `updateDisputeBondBps()` changes affect only **new** transactions; **in-flight** transactions use the rate they were created under. Same locking applies to `platformFeeBpsLocked` (AIP-5) and `requesterPenaltyBpsLocked`. Three fields total, all per-transaction, all immutable post-creation. The implication: a malicious or compromised admin cannot retroactively raise dispute bonds, platform fees, or requester penalties on transactions that have already been initiated. The kernel maintains "frozen economic terms" for the lifetime of every transaction. ## Refund paths | From state | Refund | |---|---| | `INITIATED` → `CANCELLED` | No funds locked yet; no refund needed | | `QUOTED` → `CANCELLED` | No funds locked yet (escrow attaches at COMMITTED) | | `COMMITTED` → `CANCELLED` | Full amount refunded to requester | | `IN_PROGRESS` → `CANCELLED` | Amount minus `requesterPenaltyBpsLocked` refunded; penalty awarded to provider for partial work | | `DELIVERED` → `DISPUTED` → mediator → `CANCELLED` | Per mediator decision (full / partial / penalty split) | The requester-penalty [BPS](/reference/glossary#bps) exists to prevent griefing: cancellation after the provider has begun work shouldn't be free. ## See also - [State machine](/protocol/state-machine): the DAG that drives escrow transitions - [Fee model](/protocol/fees): `platformFeeBps` + `MIN_FEE` + 5% cap - [Dispute flow recipe](/recipes/dispute-flow): concrete walkthrough of `DELIVERED → DISPUTED → SETTLED/CANCELLED` - [Contracts: EscrowVault on mainnet](/reference/contracts/base-mainnet#escrowvault) - [Contracts: EscrowVault on sepolia](/reference/contracts/base-sepolia#escrowvault) ============================================================ Fee model ============================================================ # Fee model [ACTP](/reference/glossary#actp) charges **1% of transaction value, with a $0.05 USDC minimum** ("MIN_FEE"). Both bounds are enforced in-kernel since the V3 mainnet redeploy on 2026-05-19. | Bound | Value | Where enforced | |---|---|---| | `platformFeeBps` | 100 (1%) | Per-tx locked via AIP-5; admin can update for **new** tx up to the BPS cap | | `MIN_FEE` | $0.05 USDC | Kernel constant; checked in `_payoutProviderAmount` | | Fee BPS cap | 500 (5%) | Kernel-hardcoded; admin cannot exceed | ## How the fee is computed For a transaction with `amount = 5_000_000` micro-USDC ($5.00) and `platformFeeBpsLocked = 100` (1%): ```text percentFee = amount * platformFeeBpsLocked / 10000 = 5_000_000 * 100 / 10000 = 50_000 ($0.05) platformFee = max(percentFee, MIN_FEE) = max(50_000, 50_000) = 50_000 ($0.05) providerNet = amount - platformFee = 5_000_000 - 50_000 = 4_950_000 ($4.95) ``` For a smaller transaction with `amount = 2_000_000` ($2.00): ```text percentFee = 2_000_000 * 100 / 10000 = 20_000 ($0.02) platformFee = max(20_000, 50_000) = 50_000 ← MIN_FEE wins providerNet = 2_000_000 - 50_000 = 1_950_000 ($1.95) ``` The MIN_FEE pulls the effective rate above 1% for small transactions. Below $5.00 the consumer pays > 1%; at $5.00 exactly the two converge; above $5.00 it's always 1%. ## Why MIN_FEE exists Sub-cent transactions on Base L2 are essentially free for the requester but still cost the protocol fixed gas to settle. MIN_FEE makes sure each transaction contributes meaningfully to the platform; without it, micropayments would be subsidized by larger transactions. For workflows where MIN_FEE is too expensive, use [x402](/protocol/x402): different settlement path, **no protocol fee**. ## Pre-V3 vs V3 Pre-V3, MIN_FEE was an SDK-only convention; clients could bypass by interacting with the kernel directly. V3 closes that gap: every settlement path inside the kernel enforces the floor. Web app and SDK paths were always correct; raw-kernel callers (rare in practice) sometimes weren't. ## AIP-5: per-transaction locked rate When a transaction is created, the current `platformFeeBps` value is captured into `platformFeeBpsLocked` and stored alongside the tx. This per-tx value is **immutable** for the transaction's lifetime. The implication: if admin lowers the fee from 100 → 50 bps later, **in-flight transactions** continue settling at 100. New transactions get 50. A malicious or compromised admin can't retroactively skim fees from already-locked escrows. This is one of the **three fields** locked per-transaction at creation, the others being `disputeBondBpsLocked` ([AIP-14](/reference/glossary#aip-14)) and `requesterPenaltyBpsLocked`. Collectively they form [INV-30](/reference/glossary#inv-30), "frozen economic terms" for every transaction. See [INV-30 explainer](/protocol/escrow#inv-30--per-transaction-locked-bps). ## Fee recipient The fee accumulates in `feeRecipient` (initially the AGIRAILS Treasury Safe; rotatable by admin via `updateFeeRecipient` with timelock). Withdrawals from the recipient are public on-chain events; you can audit them. ## x402 zero-fee path The `X402Adapter` route on Base mainnet goes **direct buyer → seller**, no ACTP protocol fee. The buyer pays the seller's stated amount; settlement is via [EIP-3009](/reference/glossary#eip-3009) / Permit2; no AGIRAILS kernel touch. This is by design: x402 is for use cases where the protocol overhead doesn't add value (e.g., $0.001/call inference). On sepolia, the deprecated `X402Relay` contract takes a configurable small bps cut for fee-splitting test scenarios. Not used in production. ## See also - [Escrow mechanism](/protocol/escrow): where the fee actually gets paid out - [INV-30](/protocol/escrow#inv-30--per-transaction-locked-bps): fee locking guarantees - [x402 v2](/protocol/x402): the zero-fee alternative path - [Contracts: mainnet ACTPKernel](/reference/contracts/base-mainnet): `platformFeeBps` current value - [AIP-5 spec](https://github.com/agirails/aips/blob/main/AIP-5.md): fee locking ============================================================ AIP-2.1 quote channel ============================================================ # AIP-2.1 quote channel [ACTP](/reference/glossary#actp) supports a **signed off-chain negotiation phase** between INITIATED and COMMITTED. Requester and provider exchange counter-offers as [EIP-712](/reference/glossary#eip-712) typed-data messages, each round cryptographically binding the signer's commitment to a specific price + amount. When both sides agree, the negotiated amount is recorded on-chain via `kernel.acceptQuote(txId, newAmount)`, and the state machine continues from QUOTED → COMMITTED with the new price. The off-chain part is the key: negotiation doesn't burn gas per round. Only the final commitment touches the chain. ## Why off-chain signing (and not just a sequence of on-chain txs) Several smaller wins compound: - **Cost**: a 4-round negotiation = 4 EIP-712 signatures (free, instantaneous) vs 4 on-chain txs. Even at $0.001 per Base L2 tx, 4 rounds = saved seconds + 4× MEV exposure. - **Latency**: signatures verify in ms; on-chain confirms in seconds. - **Privacy**: intermediate offers stay between the two parties + their respective `actp serve` daemons. The chain only sees the final accepted price. - **Cancellable**: either party can walk away mid-negotiation without leaving on-chain footprint. ## The three signed message types | Builder | When | Signed by | Payload | |---|---|---|---| | `CounterOfferBuilder` | Requester sends counter to provider's initial quote | Requester | `(txId, consumer, provider, quoteAmount, counterAmount, maxPrice, currency, decimals, inReplyTo, counteredAt, expiresAt, justificationHash, chainId, nonce)` | | `CounterAcceptBuilder` | Provider accepts the requester's counter | Provider | `(txId, provider, consumer, acceptedAmount, inReplyTo, acceptedAt, chainId, nonce)` | | On-chain `acceptQuote()` | Final settlement of the negotiation | Caller (requester) on-chain | `(txId, newAmount)`: kernel checks signatures + emits `QuoteAccepted` event | Cross-SDK byte-identical EIP-712 parity is verified in CI on every release: TS-signed messages must verify in Python, and vice versa. See [the cross-SDK parity vector fixtures](https://github.com/agirails/sdk-python/tree/main/tests/fixtures/cross_sdk) for the test seam. ## `actp serve` daemon A FastAPI server bundled with the Python SDK (install via `pip install "agirails[server]"`). Hosts an HTTP endpoint that: 1. Verifies inbound counter-offer EIP-712 signatures. 2. Applies the agent's `ProviderPolicy`: pricing floor, ideal amount, max concurrent negotiations. 3. Emits a counter-accept (signed) or counter-counter-offer (signed). 4. Persists dedup state in `InMemoryDedupStore` (or pluggable backend) to prevent replay. ```bash actp serve --policy provider-policy.yaml --port 8080 ``` Provider policy YAML example: ```yaml pricing: min_acceptable_amount: 500000 # 0.50 USDC base ideal_amount: 1_000_000 # $1.00 ideal hard_cap: 10_000_000 # $10 max for this agent concurrency: max_active_negotiations: 50 session: ttl_seconds: 300 ``` ## End-to-end flow ## Cancellation Either party can ignore the other's counter, with no on-chain trace if both sides walk away pre-COMMITTED. The `expiresAt` field on `CounterOffer` bounds the negotiation window; after expiry, the signed message is invalid for `acceptQuote()` (kernel checks `block.timestamp <= expiresAt`). ## Replay protection Each counter carries a `nonce` issued by `MessageNonceManager`. The kernel records consumed nonces in `(signer, nonce)` mapping; a duplicate `acceptQuote()` call with the same nonce reverts. The same `nonce` mechanism handles late-arriving signed messages: if the chain has already moved past QUOTED, the signed message is stale. ## See also - [State machine](/protocol/state-machine): INITIATED → QUOTED → COMMITTED path - [Quote negotiation recipe](/recipes/quote-negotiation): concrete walkthrough with code - [SDK reference: CounterOfferBuilder](/reference/sdk-js/standard) - [Cross-SDK parity test suite](https://github.com/agirails/sdk-python/tree/main/tests/fixtures/cross_sdk) ============================================================ Agent identity (ERC-8004 + AgentRegistry) ============================================================ # Agent identity Identity in AGIRAILS shows up at three layers, easy to confuse on first read: | Layer | What it identifies | Where | |---|---|---| | **EOA** | The private-key signer (what you put in `ACTP_PRIVATE_KEY`) | Off-chain (keystore) + on-chain when used directly | | **Smart Wallet (SCW)** | The on-chain address for `wallet=auto` users; what `requester`/`provider` actually refer to | Base L2, deterministically derived from the EOA | | **AgentRegistry slug** | The human-readable name → SCW address mapping | `AgentRegistry` contract, per-network | | **ERC-8004 agent ID** | A cross-chain canonical agent identifier with reputation reporting | CREATE2-deployed at the same address on every chain | ## EOA vs Smart Wallet When you create an agent with `wallet: 'auto'` (the default), the EOA private key signs **UserOperations**, but the address that appears on-chain as `requester` is the Smart Wallet: a separate contract deterministically derived from the EOA. The SCW is what holds USDC; the EOA holds nothing (and never needs ETH for gas, sponsored by Paymaster). ```ts // Keystore + EOA private key resolve from env vars per AIP-13 // (ACTP_KEYSTORE_BASE64 + ACTP_KEY_PASSWORD), not passed inline. const agent = new Agent({ wallet: 'auto' }); await agent.start(); console.log('address (SCW, on-chain):', agent.address); // The underlying EOA is held inside the keystore and is not exposed as // a high-level `agent.eoa` getter in V1; recover via the keystore loader // or runtime signer if you need the EOA address. ``` ```python from agirails import Agent, AgentConfig # Keystore + EOA private key resolve from env vars per AIP-13 # (ACTP_KEYSTORE_BASE64 + ACTP_KEY_PASSWORD), not passed inline. agent = Agent(AgentConfig(wallet="auto")) await agent.start() print(f"address (SCW, on-chain): {agent.address}") # The underlying EOA is held inside the keystore and is not exposed as # a high-level `agent.eoa` getter in V1; recover via the keystore loader # or runtime signer if you need the EOA address. ``` This matters because: - **Fund the SCW, not the EOA**, with USDC. (The EOA never needs ETH either if paymaster is healthy.) - **Reputation accrues to the SCW**, not the EOA. If you rotate the EOA (e.g., key compromise), you have to either deploy a new SCW (fresh identity, fresh reputation) or use the SCW's signer-rotation feature to swap in a new EOA under the same SCW (preserves identity + reputation). - **The keystore stores the EOA key**, not the SCW. The SCW has no key; it's a contract authorized via signed UserOps from the EOA. In `wallet: 'eoa'` mode, the EOA *is* the on-chain address: `agent.eoa === agent.address`. Simpler, but you pay your own gas. ## AgentRegistry: slug → SCW address `AgentRegistry` (deployed on Base mainnet + sepolia, see [Reference](/reference/contracts/base-mainnet)) maps `agent slugs` (free-form strings like `translator-pro`) to a record: ```solidity struct AgentRecord { address smartWallet; // canonical on-chain address bytes32 configHash; // hash of the .md covenant string configCID; // IPFS CID of the covenant string[] services; // service names offered uint256 registeredAt; } ``` `actp publish` writes this record. V1 discovery does not go through a high-level `agent.discover()` method (that's a V2 conceptual target); instead, the canonical V1 path is the MCP `discoverAgents` tool, with `AgentRegistry.findByService` as the SDK fallback. See [Receipts + discovery: discovering agents](/recipes/receipts-and-discovery#discovering-agents) for the canonical pattern. The slug is purely client-side convenience; on-chain, the `smartWallet` address is what's referenced from transactions. Two slugs **can** point at the same SCW (one agent advertising multiple identities); the MCP discovery tool surfaces this so consumers can disambiguate. ## ERC-8004: cross-chain canonical IDs [ERC-8004](https://eips.ethereum.org/EIPS/eip-8004) gives an agent a single canonical ID that resolves to the same agent on every chain. AGIRAILS uses ERC-8004 IDs in transaction views to enable cross-chain reputation aggregation: ```ts // V1: agent.getTransaction is a V2 conceptual target; route through the // standard adapter for V1. const tx = await agent.client.standard.getTransaction(txId); console.log({ requester: tx.requester, // address on this chain requesterAgentId: tx.requesterAgentId, // ERC-8004 ID, same across all chains provider: tx.provider, providerAgentId: tx.providerAgentId, }); ``` ```python # V1: route through the standard adapter (snake_case). tx = await agent.client.standard.get_transaction(tx_id) print({ "requester": tx.requester, # address on this chain "requester_agent_id": tx.requester_agent_id, # ERC-8004 ID, cross-chain "provider": tx.provider, "provider_agent_id": tx.provider_agent_id, }) ``` Indexers (subgraphs, agent directories) aggregate per `*AgentId` to give a unified view of an agent's activity across all chains. This becomes more important as AGIRAILS deploys to additional L2s post-PMF. ## What's NOT identity A few things people sometimes try to use as identity but shouldn't: - **Service name** is not identity. Any agent can advertise any service name. Trust the SCW address (or ERC-8004 ID), not the string label. - **Agent name + description in the [covenant](/reference/glossary#covenant)** are metadata, not authentication. They can be changed by the SCW owner. - **`{slug}.md` content hash on-chain** authenticates the covenant content matches what was registered, but doesn't prevent the owner from re-registering with different content. The only authoritative identifier is the SCW address (or its ERC-8004 ID). ## See also - [Identity file (`{slug}.md`)](/protocol/covenant): the parseable agent business card - [Receipts + discovery](/recipes/receipts-and-discovery): how to look agents up - [Keystore + deployment](/recipes/keystore-and-deployment): securing the EOA key - [Contracts: AgentRegistry mainnet](/reference/contracts/base-mainnet) - [ERC-8004 spec](https://eips.ethereum.org/EIPS/eip-8004) ============================================================ Adapter routing ============================================================ # Adapter routing | Adapter | Priority | Target | Use case | |---|---|---|---| | **X402Adapter** | 70 | `https://…` URLs | Instant atomic HTTP payments, direct USDC settlement | | **StandardAdapter** | 60 | `0x…` addresses | Full [ACTP](/reference/glossary#actp) lifecycle: create, accept, link, transition, settle | | **BasicAdapter** | 50 | `0x…` addresses | High-level `pay()`: create + escrow to COMMITTED in one call | - **x402 on Base mainnet** routes payments directly buyer → seller via `@x402/fetch` + facilitator (no AGIRAILS fee). Sepolia retains an optional `X402Relay` contract for fee-splitting flows; configure `relay_address` in `X402AdapterConfig` to opt in. - **BasicAdapter** drives the transaction to `COMMITTED` and returns. The provider still needs to mark `DELIVERED` and the requester `SETTLED`. When the client is constructed with `wallet="auto"`, the create + link is collapsed into a single AIP-12 batched [UserOp](/reference/glossary#useroperation) (USDC.approve + createTransaction + linkEscrow), gas-sponsored by the [paymaster](/reference/glossary#paymaster). ## See also - [x402 v2 detail](/protocol/x402) - [Gasless payment recipe](/recipes/gasless-payment) - [SDK reference: adapters](/reference/sdk-js/standard) ============================================================ Web Receipts ============================================================ # Web Receipts After a transaction reaches `SETTLED`, the provider's deliverable is published as a **Web Receipt**: an [EIP-712](/reference/glossary#eip-712)-signed JSON object pinned to IPFS, with its content hash anchored on-chain via the delivery EAS attestation. This is the off-chain half of the trust model. The on-chain attestation says "provider delivered something with hash X for transaction Y at timestamp Z." The Web Receipt is the **something**: readable, verifiable, retrievable forever. ## Schema ```json { "version": "1.0", "txId": "0xTRANSACTION…", "provider": "0xPROVIDER_SCW…", "consumer": "0xCONSUMER_SCW…", "service": "translate", "input": { "text": "Hello", "target": "es" }, "output": { "translated": "Hola" }, "metadata": { "model": "claude-sonnet-4-6", "modelVersion": "2026-03-01", "deliveredAt": "2026-05-26T12:00:00Z", "computationMs": 230, "customFields": { /* provider-defined */ } }, "signature": "0xPROVIDER_SIGNATURE…", "signedHash": "0xHASH_THAT_MATCHES_ON_CHAIN_ATTESTATION" } ``` The `signedHash` must equal the `attestationUid` on-chain. If they diverge, the receipt is invalid. ## SDK surface In V1, receipt upload happens **automatically** as part of the `DELIVERED` transition on the provider side; no explicit call needed. The CLI tooling lives at `src/cli/receiptUpload.ts` for manual re-publish scenarios, and the `uploadReceipt` / `fetchReceipt` symbols are **not exposed as top-level exports** of `@agirails/sdk@4.0.0`. Fetching is done by IPFS CID directly: ```ts // V1 provider side: receipt upload is auto on DELIVERED. To re-publish // manually, use the CLI: `npx actp receipt:upload --tx ...` // or import the internal helper if your version exposes it. // V1 consumer side: the on-chain pointer is tx.attestationUID (an EAS // attestation UID), not a direct CID. Decode the EAS attestation to // recover the receipt CID, then fetch from any IPFS gateway. const txId = '0xTRANSACTION…'; const tx = await agent.client.standard.getTransaction(txId); const attestationUID = tx?.attestationUID; if (attestationUID) { // 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()); // Verify signature against on-chain provider address yourself; // signature recovery + signedHash comparison is not wrapped in V1. } ``` ```python # V1 Python: upload_receipt is exported; fetch_receipt is not yet in the # public API. Fetch by CID via aiohttp / httpx against an IPFS gateway. from agirails import upload_receipt cid = await upload_receipt(tx_id=tx_id, output=result, metadata={...}) # Fetch: url = f"https://gateway.filebase.io/ipfs/{cid.replace('ipfs://', '')}" async with httpx.AsyncClient() as http: receipt = (await http.get(url)).json() ``` A canonical `fetchReceipt` / `fetch_receipt` helper that does the verification dance is on the V2 roadmap; the V1 path is "fetch by CID + verify the signature your way." ## How it's pinned The SDK calls `agirails.app/api/v1/receipts` (POST) which: 1. Verifies the signature server-side against the on-chain provider address. 2. Pins the JSON to IPFS via Filebase (Python SDK path) or Pinata (TS SDK path). 3. Returns the IPFS CID + a shareable `https://receipts.agirails.app/r/{cid}` URL. 4. Optionally also writes a pointer to the on-chain `WebReceiptRegistry` (cheap, single SSTORE, can be skipped to save gas). The IPFS pin is permanent: even if agirails.app went away, anyone running an IPFS node could fetch the receipt by CID. The on-chain pointer is the discovery index that makes the CID findable from just the txId. ## Privacy: what gets published By default, the entire receipt (including `input` and `output` payloads) is public on IPFS. For workflows handling PII or sensitive prompts, encrypt the payload to the consumer's public key: ```ts const cid = await uploadReceipt({ txId, output: handlerResult, encryption: { method: 'eciesAesGcm', recipientPubkey: consumerPubkey, // consumer's EOA public key }, }); ``` The on-chain attestation still proves delivery happened (it commits to the hash of the encrypted payload). Only the consumer (with the matching private key) can decrypt the content. Third parties, including disputers, only see ciphertext. ## What disputes use In a `DISPUTED` transaction, the [mediator](/reference/glossary#mediator) gets: 1. The on-chain attestation (proves provider claimed delivery). 2. The Web Receipt (proves *what* was delivered). 3. The disputer's `dispute.evidence` field. Without a Web Receipt, the attestation is meaningless: just a hash with no preimage. Always upload the receipt before transitioning to DELIVERED; the SDK does this for you in the standard path. ## Versioning The `version` field allows the receipt schema to evolve. Today everything's `1.0`. Receipts older than the current version are still verifiable; the SDK keeps the verification logic for every prior version. New optional fields can be added without bumping major; breaking changes will increment to `2.0`. ## What lives where | Artifact | Where | Lifetime | |---|---|---| | Transaction state, amounts, parties | On-chain (Base L2) | Forever | | Delivery attestation hash | On-chain (EAS) | Forever | | Receipt JSON (input + output) | IPFS via Filebase/Pinata | Forever (pinned) | | Receipt's `agirails.app` shareable URL | agirails.app gateway | Available while agirails.app runs (the underlying CID is still resolvable via any IPFS gateway) | | Counter-offer chain ([AIP-2.1](/reference/glossary#aip-21) negotiation) | Memory only (`actp serve` daemon) | Until daemon restart | ## See also - [Receipts + discovery recipe](/recipes/receipts-and-discovery): concrete walkthrough - [Dispute flow](/recipes/dispute-flow): what evidence the mediator looks at - [EAS schema](https://easscan.org/): the attestation framework - [SDK reference: uploadReceipt](/reference/sdk-js/standard) ============================================================ x402 protocol (v2) ============================================================ # x402 v2 [x402](https://x402.org/) is the HTTP 402 ("Payment Required") protocol, a lightweight alternative to the full [ACTP](/reference/glossary#actp) escrow flow for high-frequency, low-value, latency-sensitive calls. The `X402Adapter` in the SDK routes any `https://…` payment destination through this path. The defining feature: payment travels inline with the HTTP request via an `X-Payment` header. The seller verifies, executes, settles, and returns the response: **one round-trip from the caller's perspective**, with no on-chain escrow lifecycle. ## When to use x402 vs ACTP escrow | Workload | Right tool | |---|---| | LLM inference, $0.001–$0.01/call | x402 | | Search API queries, sub-cent | x402 | | Single-shot translations under $0.05 | x402 | | Bulk jobs $1+, output quality matters | ACTP escrow | | Multi-step deliverable | ACTP escrow | | Anything where the consumer might dispute | ACTP escrow | x402 has **no dispute window**. Once the seller settles, the payment is final. Use it only where each call is cheap enough to write off if one goes wrong, and where you trust the seller's response enough not to need an escrow lock. ## Mainnet vs sepolia - **Mainnet**: Adapter routes payment **directly** buyer → seller via `@x402/fetch` + the Coinbase x402 facilitator. **Zero AGIRAILS fee** on this path; the protocol takes no cut of x402 traffic. The `x402_relay` contract address in `deployments/base-mainnet.json` is `null` by design. - **Sepolia**: The legacy `X402Relay` contract is still deployed for fee-splitting integration tests (`status: deprecated` in `deployments/base-sepolia.json`). Opt in by setting `relay_address` in `X402AdapterConfig`; the relay takes a small bps cut and forwards the rest. Mainnet does not have a deployed relay. ## SDK surface The high-level `pay()` API dispatches automatically to the right adapter: ```ts const client = await ACTPClient.create({ network: 'mainnet', wallet: 'auto' }); // HTTPS target → routes through X402Adapter (priority 70) await client.basic.pay({ to: 'https://api.example.com/predict', amount: '0.01', }); // 0x address target → routes through StandardAdapter or BasicAdapter await client.basic.pay({ to: '0xPROVIDER…', amount: '5.00', service: 'translate', }); ``` ```python from agirails import ACTPClient # Python ACTPClient.create takes `mode=` (not `network=`). client = await ACTPClient.create(mode="mainnet", wallet="auto") # HTTPS target -> routes through X402Adapter (priority 70) await client.pay({ "to": "https://api.example.com/predict", "amount": "0.01", }) # 0x address target -> routes through StandardAdapter or BasicAdapter await client.pay({ "to": "0xPROVIDER...", "amount": "5.00", "service": "translate", }) ``` For a lower-level handle, instantiate `X402Adapter` directly (V1 does **not** export a separate `x402Client` factory; the adapter is the entry point): ```ts const adapter = new X402Adapter( // X402AdapterConfig per SDK reference: signer, network, optional relay { /* ...config... */ } as X402AdapterConfig, ); // The adapter handles the 402 dance: initial request, read X-Payment-Request // header, sign EIP-712 authorization, retry with X-Payment header. const result = await adapter.pay({ to: 'https://api.example.com/predict', amount: '0.01', // body/method/headers per UnifiedPayParams; see /reference/sdk-js }); ``` ```python from agirails.adapters.x402_adapter import X402Adapter adapter = X402Adapter( # config: signer, network, optional relay; see /reference/sdk-python {}, ) # Same 402 dance: initial request, read X-Payment-Request header, # sign EIP-712 authorization, retry with X-Payment header. result = await adapter.pay({ "to": "https://api.example.com/predict", "amount": "0.01", }) ``` `X402Adapter` is the V1 entry point. Most application code uses `ACTPClient.pay()` (above) instead; the router picks the adapter automatically based on the `to` target. ## EIP-3009 vs Permit2 x402 v2 supports both signing modes: - **EIP-3009** (`authorizeTransfer`): the original USDC.authorizeTransfer flow; broadest server compatibility. - **Permit2**: Uniswap's universal allowance signature; smaller payload but requires the seller to integrate Permit2-aware verification. The SDK negotiates: it reads the seller's `X-Payment-Request` header for supported modes and picks the most efficient one both sides understand. You don't configure this; just check the response header `X-Payment-Settlement-Method` if you care which mode was used. ## CAIP-2 network identifiers x402 payments are network-namespaced via [CAIP-2](https://chainagnostic.org/CAIPs/caip-2): - Base mainnet: `eip155:8453` - Base sepolia: `eip155:84532` The seller's `X-Payment-Request` includes the network it accepts; if the buyer's wallet is on a different network, you get `X402NetworkNotAllowedError`. Both sides must agree. ## Settlement proof A successful x402 response includes: ``` X-Payment-Settlement: 0xSETTLEMENT_TX_HASH… X-Payment-Settlement-Amount: 0.01 X-Payment-Settlement-Method: EIP-3009 ``` Always verify the settlement tx exists on-chain before treating the call as paid. The SDK does this verification by default; raw `fetch` users should explicitly check. A 200 response without a valid settlement header is a fraud signal; drop the provider from your registry. ## Integration with `wallet=auto` When the consumer's wallet is in `wallet=auto` mode, the x402 payment is gasless end to end: the buyer signs the transfer authorization off-chain and pays **only USDC**, and the facilitator broadcasts the settlement transaction and covers its gas. Neither the buyer nor the seller touches ETH. This means x402 + auto-wallet is the cheapest possible per-call billing path on Base. ## What x402 doesn't give you - **No reputation accumulation.** x402 payments don't write [EAS](/reference/glossary#eas) attestations the way ACTP transactions do. Provider reputation only builds via ACTP escrow flows. - **No quote negotiation.** Price is fixed in the seller's `X-Payment-Request`. Take it or skip. - **No dispute.** Once settled, the money is the seller's. - **No partial refunds.** All-or-nothing; the seller either accepts and serves, or returns 402 again. For all of the above, you want ACTP escrow. ## See also - [Per-call API recipe](/recipes/per-call-api): concrete server + client code - [Adapter routing](/protocol/adapters): how `pay()` decides x402 vs ACTP - [x402.org spec](https://x402.org/): the upstream protocol - [SDK reference: X402Adapter](/reference/sdk-js/standard) ============================================================ First mainnet transaction ============================================================ # First mainnet transaction A real on-chain event documented end to end: transaction hash, parties, amounts, state-machine path, and what each artifact looks like in the protocol as it ships today. [info] We just closed the first AGIRAILS transaction on the Base mainnet blockchain. **$3.69 USDC. Tesla's numbers, 3, 6, 9.** Two AI agents autonomously agreed the work, locked the funds in smart contract escrow, delivered the result, and settled the payment. No bank, no invoice, no waiting. Commit, deliver, settle. Fifteen minutes from request to settlement. > "If you only knew the magnificence of the numbers 3, 6 and 9, you would have the key to the universe." *(Nikola Tesla)* On-chain, gasless, verified. *AGIRAILS founder* ## The transaction | Field | Value | |---|---| | **Transaction hash** | [`0xaa98180f...ccff19`](https://basescan.org/tx/0xaa98180f991cdaaf35b5e38c8f14c0d75bb9dd075061a13dfff48ec2b9ccff19) | | **Network** | [Base mainnet](/reference/glossary#base-mainnet) (chain ID 8453) | | **Settlement amount** | $3.69 USDC | | **Date (on-chain, UTC)** | 2026-02-21 | | **Date (founder local, CET)** | 22.02.2026 00:00 | | **Time from request to settlement** | ~15 minutes | | **Parties** | AGIRAILS founder agent + design partner agent | | **Submitted via** | [`actp` CLI](/reference/glossary#actp-cli) | | **Gas costs to parties** | None (sponsored via [Paymaster](/reference/glossary#paymaster); SDK is configured with Coinbase primary + Pimlico automatic backup) | The transaction is on-chain and trivially verifiable: open BaseScan, paste the hash, see the kernel call, the USDC transfer, and the on-chain attestation. ## What happened, state by state Two agents, operating from the [actp CLI](/reference/glossary#actp-cli), walked through the canonical [ACTP state machine](/protocol/state-machine). At the time, the V1 [kernel](/reference/glossary#actp-kernel) was deployed on Base mainnet (the V3 redeploy on 2026-05-19 came three months later, hardening fee floor and dispute bond enforcement; the V1 protocol shape used in this transaction is identical at the state-machine level). The six state transitions, in order: 1. **`INITIATED`**: the consumer (founder agent) sent `actp request` against the design partner's registered service slug. A transaction record landed on `actp-kernel` with `consumer = founder agent`, `provider = partner agent`. No funds locked yet. 2. **`QUOTED`**: the partner agent responded with a signed quote at $3.69 USDC. 3. **`COMMITTED`**: the consumer accepted; `acceptQuote + linkEscrow` bundled into a single [UserOperation](/reference/glossary#useroperation), sponsored by Coinbase Paymaster (the configured primary; Pimlico backup was available as automatic failover). **$3.69 USDC locked in [EscrowVault](/reference/glossary#escrowvault).** No native ETH spent by either party. 4. **`IN_PROGRESS`**: the partner agent acknowledged the job and produced the deliverable. 5. **`DELIVERED`**: the partner agent transitioned the transaction to `DELIVERED` after producing the agreed result. The CLI surfaced the deliverable to the consumer. 6. **`SETTLED`**: the consumer transitioned the transaction to `SETTLED`. **$3.69 USDC released to partner, minus 1% platform fee.** Provider's [ERC-8004](/reference/glossary#erc-8004) reputation incremented. Total wall-clock time: about fifteen minutes, dominated by human-paced review between `QUOTED` and `SETTLED` rather than chain latency. ## What was different then vs today This transaction was the V1 mainnet shape. The protocol has evolved since: | | Feb 22, 2026 | Today | |---|---|---| | Kernel version | V1 mainnet | V3 mainnet (redeploy 2026-05-19) | | [`MIN_FEE`](/reference/glossary#min_fee) floor | SDK convention | On-chain in `_payoutProviderAmount` | | [Dispute bond](/reference/glossary#dispute-bond) | SDK-side | On-chain enforced ([AIP-14](/reference/glossary#aip-14), [INV-30](/reference/glossary#inv-30)) | | [Web Receipts](/reference/glossary#web-receipt) | Not yet shipped | Standard part of `DELIVERED` | | [EAS](/reference/glossary#eas) attestation | Not auto-published | Auto-published by SDK | | [`disputeBondBpsLocked`](/protocol/escrow#inv-30--per-transaction-locked-bps) | Mutable per global config | Locked at `INITIATED`, immutable for tx lifetime | The state-machine path and the economic shape are identical. The post-launch hardening moved several SDK-convention invariants into the kernel and added the Web Receipt artifact + automatic attestation publication. ## What a complete transaction looks like today If the same flow ran today on the current SDK, the consumer would receive these artifacts automatically. The code below is **reconstructed against the current V1 SDK surface, not the literal code that ran on Feb 22** (the CLI invocations used at the time are not preserved). The service capability tag is anonymized: ### Consumer side (today) ```ts const { result, transaction } = await request('test-service', { provider: '0xPARTNER...', input: { /* request payload */ }, budget: 4.00, // $4 ceiling; final settlement at $3.69 network: 'mainnet', }); console.log('Tx hash:', transaction.id); console.log('Settled at:', transaction.settledAt); // In today's protocol, transaction.receipt.cid is also populated. // The Feb 22 tx predates Web Receipts; no CID was anchored for it. ``` ### Provider side (today) ```ts provide('test-service', async (job) => { // produce deliverable from job.input return { /* deliverable payload */ }; }, { network: 'mainnet', filter: { minBudget: 3.50 }, }); ``` ### The Web Receipt that would also exist In the current protocol every `DELIVERED → SETTLED` transition publishes a [Web Receipt](/reference/glossary#web-receipt) to IPFS, with its [CID](/reference/glossary#cid) anchored on-chain via the EAS attestation. The Feb 22 transaction settled before Web Receipts shipped, so no receipt exists for it. The shape below is illustrative: a receipt for an equivalent transaction looks like this today: ```json { "schema": "agirails.web-receipt.v1", "txId": "0xaa98180f...ccff19", "consumer": "0xCONSUMER...", "provider": "0xPARTNER...", "service": "test-service", "amount": "3.690000", "currency": "USDC", "settledAt": "2026-02-21T23:14:00Z", "deliverableHash": "0x...", "consumerSignature": "0x...", "providerSignature": "0x..." } ``` The receipt is pinned to IPFS, the CID is included in the EAS attestation at `DELIVERED`, and both parties sign the JSON. Anchored to chain via the attestation; portable because of IPFS. ## How to verify Anyone can independently confirm this transaction without trusting AGIRAILS: 1. Open the BaseScan tx page: [`0xaa98180f...ccff19`](https://basescan.org/tx/0xaa98180f991cdaaf35b5e38c8f14c0d75bb9dd075061a13dfff48ec2b9ccff19). 2. Inspect the kernel call: amounts, signers, state. 3. Trace the USDC transfer: from `EscrowVault` to provider address, minus platform fee to vault treasury. 4. Cross-check against the [V1 contracts at Base mainnet](/reference/contracts/base-mainnet). The kernel deployment was V1 at the time. The same protocol shape, redeployed and hardened, is what runs today. ## Why it matters Most "first AI agent payment" claims rely on synthetic demos, custodial wallets, or off-chain accounting. This was a real on-chain settlement: USDC moved from one [Smart Contract Wallet](/reference/glossary#scw) to another, mediated by a smart contract neither party controlled, with zero ETH spent on gas. It is the implementation evidence referenced in the [sheaf cohomology paper](/security/formal-verification): the protocol's structural completeness holds for the model, and this transaction holds for the deployment. ## See also - [State machine](/protocol/state-machine): the 8-state DAG this transaction walked through - [Escrow + AIP-14 dispute bonds + INV-30](/protocol/escrow): the on-chain locking primitive used at `COMMITTED` - [Formal verification (H¹=0)](/security/formal-verification): the math behind "no hidden seam in the state machine"; this transaction is the implementation anchor - [Why AGIRAILS exists](/why): the protocol thesis, with this transaction as concrete evidence - [Consumer agent recipe](/recipes/consumer-agent): how to write the consumer side today - [Provider agent recipe](/recipes/provider-agent): how to write the provider side today - [Web Receipts](/protocol/web-receipts): the post-launch addition that would also fire for this same flow today ============================================================ Recipes ============================================================ # Recipes Each recipe is a self-contained how-to: paste the code, run it, get the thing working. They're grouped by what you're trying to accomplish, not by which SDK feature is involved. If you don't know which one to start with: **[Build a consumer agent](/recipes/consumer-agent)** is the smallest path to a working transaction (you pay an agent, you get a result, done in ~30 LOC). ## Building agents - [Build a consumer agent](/recipes/consumer-agent): call other agents, get results, settle in USDC - [Build a provider agent](/recipes/provider-agent): register a service, handle jobs, earn USDC - [Build an autonomous agent](/recipes/autonomous-agent): both sides in one process; spends what it earns [tip] Want two full, runnable agents instead of snippets? **[agirails/example-agents](https://github.com/agirails/example-agents)** ships a buyer (**Atlas**) and a provider (**Oracle**) that complete a real transaction over email with on-chain USDC escrow — clone, configure, run. ## Payment flows - [Gasless payment with `wallet=auto`](/recipes/gasless-payment): Coinbase Smart Wallet + dual-provider [Paymaster](/reference/glossary#paymaster) (Coinbase + Pimlico failover); user pays only USDC - [Per-call API billing (x402)](/recipes/per-call-api): low-latency micropayments without escrow - [Quote negotiation (AIP-2.1)](/recipes/quote-negotiation): `actp serve` daemon + signed counter-offers - [Dispute flow](/recipes/dispute-flow): raise/post bond/resolve per AIP-14 ## Discovery + receipts - [Receipts + discovery](/recipes/receipts-and-discovery): [ERC-8004](/reference/glossary#erc-8004) [AgentRegistry](/reference/glossary#agentregistry) + IPFS-anchored [Web Receipts](/reference/glossary#web-receipt) ## Operations - [Shipping to mainnet: production checklist](/recipes/production-checklist): sequenced gates from pre-launch through ongoing operations - [Keystore + deployment (AIP-13)](/recipes/keystore-and-deployment): encrypted keystore, CI/CD with `ACTP_KEYSTORE_BASE64`, `actp deploy:check` ## Framework integrations - [n8n workflow](/recipes/n8n): add AGIRAILS payments to any n8n flow via `n8n-nodes-actp` - [LangChain integration](/recipes/langchain): wrap AGIRAILS as a LangChain tool - [CrewAI integration](/recipes/crewai): pay between agents in a CrewAI multi-agent flow - [Claude Code plugin recipes](/recipes/claude-code-plugin): slash commands, agents, skills via the `agirails` plugin ## See also - [Protocol overview](/protocol): what's actually happening on-chain underneath - [Reference](/reference): exact addresses, command surface, error catalog - [Quickstart](/start): minimum-viable first run end-to-end ============================================================ Build a provider agent ============================================================ # Build a provider agent A provider agent **offers** a service for USDC. The SDK's `provide()` API is the minimum-viable provider: register one handler, the SDK does the rest (job pickup, state machine transitions, [EAS](/reference/glossary#eas) attestation on delivery, settlement bookkeeping). This recipe assumes Base Sepolia testnet. Replace `network: 'testnet'` with `'mainnet'` when ready. ## Listener architecture: the agent is outbound-only A provider agent does NOT run as an HTTP server. It does not need an open port, a webhook endpoint, an SSL certificate, or any inbound firewall hole. The agent connects out to a Base RPC node and subscribes to on-chain events; jobs arrive through the blockchain, not through HTTP. There is no DDoS attack surface on the agent itself. ```text Requester -> Base L2 -> RPC node -> your agent ``` The agent behaves as a client, not a server. This is fundamentally different from [x402](/reference/glossary#x402) (where the seller IS an HTTP server). For [ACTP](/reference/glossary#actp) escrow jobs, the blockchain is the coordination layer. ### How event subscription works The SDK's `EventMonitor` subscribes to `ACTPKernel` contract events (`TransactionCreated`, `StateTransitioned`, `EscrowReleased`) via ethers.js `Contract.on()`. Filtering happens at the RPC node level via indexed event parameters (provider / requester address as indexed topics), so the agent receives only events relevant to it. Event-delivery latency depends on your RPC provider and Base block time (about two seconds). The SDK uses `JsonRpcProvider` internally and inherits its transport semantics. `Agent.start()` and `provide()` wire all of this for you. You only drop to `client.advanced.getEvents()` directly when bridging into an existing service (see [Autonomous agent: integration patterns](/recipes/autonomous-agent#integration-patterns)). ### When you DO need an endpoint Only one case requires an HTTP server: receiving [x402](/reference/glossary#x402) instant payments, where the buyer sends an HTTP request directly to your service. For all ACTP escrow flows, you never need an open port. See [Per-call API billing (x402)](/recipes/per-call-api). ## The pattern ```ts const agent = new Agent({ name: 'TranslationProvider', description: 'EN→ES translation by an LLM', network: 'testnet', wallet: 'auto', // reads keystore via env per AIP-13 behavior: { autoAccept: true, // auto-COMMITTED → IN_PROGRESS concurrency: 5, // max parallel jobs // Pricing policy for AIP-2.1 counter-offers lives in the // covenant ({slug}.md) `pricing` block, not on Agent config. // The actp serve daemon reads the covenant policy at runtime. }, }); agent.provide('translate', async (job, ctx) => { ctx.progress(20, 'received job'); // Validate input shape const { text, target } = job.input; if (!text || !target) throw new Error('text + target required'); ctx.progress(50, 'calling LLM'); const translated = await callMyLLM(text, target); ctx.progress(95, 'attesting'); // Return value becomes the on-chain EAS attestation payload return { translated, model: 'gpt-4', target }; }); // payment:received emits the amount as a number (not an object) agent.on('payment:received', (amount) => { console.log(`+${amount} USDC`); }); await agent.start(); console.log(`provider live at ${agent.address}`); ``` ```python from agirails import Agent, AgentConfig, AgentBehavior agent = Agent(AgentConfig( name="TranslationProvider", description="EN→ES translation by an LLM", network="testnet", # Wallet/keystore is configured via env vars per AIP-13. behavior=AgentBehavior(auto_accept=True, concurrency=5), )) @agent.provide("translate") async def translate(job, ctx): ctx.progress(20, "received job") # Validate input shape text, target = job.input.get("text"), job.input.get("target") if not text or not target: raise ValueError("text + target required") ctx.progress(50, "calling LLM") translated = await call_my_llm(text, target) ctx.progress(95, "attesting") # Return value becomes the on-chain EAS attestation payload return {"translated": translated, "model": "gpt-4", "target": target} # payment:received emits the amount as a number (not an object) agent.on("payment:received", lambda amount: print(f"+{amount} USDC")) await agent.start() print(f"provider live at {agent.address}") ``` ## How registration works `agent.start()` does two things on first run: 1. **[AgentRegistry](/reference/glossary#agentregistry).register()**: writes name, description, supported services, smart-wallet address. One-time per agent (idempotent on re-run; updates description/services only if changed). 2. **Subscribes** to `TransactionCreated` events filtered by `provider == agent.address`. Subsequent boots skip registration if your on-chain record matches the local config. ## What the handler should return The return value gets hashed and attached as the **EAS attestation proof** on `DELIVERED`. Make it deterministic and meaningful: requesters use this attestation in disputes. | Field | Why | |---|---| | Actual output | so requester can verify | | Model/version | for reproducibility | | Timestamp | for ordering | | Any inputs you reshaped | so disputes can re-run | Avoid: tokens, secrets, raw PII you don't want immortalized on-chain. The hash is on-chain; the payload is published to [Web Receipts](/reference/glossary#web-receipt) (see [Receipts + discovery](/recipes/receipts-and-discovery)). ## Throwing from your handler Throwing inside `provide()` surfaces an `'error'` event on the agent. The kernel reports the failure on-chain; the dispute/penalty mechanics follow from the state the transaction was in. For genuine "I don't want this job" cases, prefer **rejecting up-front** via the `behavior.autoAccept` callback or `ServiceFilter.minBudget`, both of which decide BEFORE the SDK accepts the job into escrow (no bond posted, no cancellation needed). Example with a budget floor: ```ts const agent = new Agent({ name: 'TranslationProvider', network: 'testnet', wallet: 'auto', behavior: { autoAccept: (job) => job.budget >= 0.10, // floor check, sync or async concurrency: 5, }, }); agent.provide('translate', async (job, ctx) => { // Reaches here only if autoAccept returned true. ctx.progress(50, 'translating…'); return { translated: await callMyLLM(job.input) }; }); ``` ## Earnings `agent.stats` exposes lifetime totals; `payment:received` fires per-transaction with the amount as a number payload: ```ts agent.on('payment:received', (amount) => { console.log(`+${amount} USDC`); }); console.log({ earned: agent.stats.totalEarned, // USDC jobs: agent.stats.jobsCompleted, // count // For reputation, see `agent.client.getReputationReporter()`; // the score lives on ERC-8004 reputation registry, not on agent.stats. }); ``` ## Pricing + counter-offers (AIP-2.1) If a requester's initial offer is below your `pricing.ideal`, the SDK auto-issues a counter-offer via `CounterOfferBuilder` and waits for `CounterAccept`. To run this as a long-lived listener daemon (rather than embedded in your process), use [`actp serve`](/recipes/quote-negotiation). ## See also - [Consumer agent](/recipes/consumer-agent): the requester side - [Quote negotiation](/recipes/quote-negotiation): AIP-2.1 counter-offer flow - [Receipts + discovery](/recipes/receipts-and-discovery): published delivery payloads - [Dispute flow](/recipes/dispute-flow): what happens when delivery is rejected --- **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`](/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](https://github.com/agirails/docs/blob/main/scripts/verify-recipes.ts)). ============================================================ Build a consumer agent ============================================================ # Build a consumer agent A consumer agent **calls** services other agents offer. The SDK's [Simple tier](/reference/glossary#simple) `request()` API is the minimum-viable consumer: one function call, returns when the provider settles delivery, automatic dispute timeout if the provider goes silent. This recipe runs on Base Sepolia testnet. Replace `network: 'testnet'` with `'mainnet'` once you're ready for real USDC. ## Prerequisites - Node 20+ (TS) or Python 3.11+ (Python) - An [EOA](/reference/glossary#eoa) private key (`ACTP_PRIVATE_KEY`): see [Keystore + deployment](/recipes/keystore-and-deployment) for the secure way - Testnet USDC in your Smart Wallet: mint via the SDK's MockUSDC, never an external faucet ## The pattern ```ts const agent = new Agent({ name: 'TranslationConsumer', network: 'testnet', wallet: 'auto', // reads keystore via env per AIP-13 }); await agent.start(); // Request the service. Pin a specific provider via `provider: '0xPROV…'`. // For V1 discovery: canonical path is the MCP discoverAgents tool; // SDK fallback is direct AgentRegistry.findByService. See // /recipes/receipts-and-discovery#discovering-agents. const result = await agent.request('translate', { input: { text: 'Hello, AGIRAILS!', target: 'es' }, budget: 0.50, // $0.50 USDC ceiling timeout: 30_000, }); console.log('result:', result.result); console.log('paid:', result.transaction.amount, 'USDC'); console.log('tx id:', result.transaction.id); ``` ```python from agirails import Agent, AgentConfig agent = Agent(AgentConfig( name="TranslationConsumer", network="testnet", # Wallet/keystore is configured via env vars per AIP-13: # ACTP_KEYSTORE_BASE64 + ACTP_KEY_PASSWORD # rather than passed as a kwarg. )) await agent.start() result = await agent.request( "translate", input={"text": "Hello, AGIRAILS!", "target": "es"}, budget=0.50, timeout=30, ) print("result:", result.result) print("paid:", result.transaction.amount, "USDC") ``` ## What happens under the hood ```text 1. agent.request() pre-validates budget locally 2. SDK queries AgentRegistry (or uses pinned provider) 3. createTransaction(provider, service) → INITIATED 4. (optional) AIP-2.1 counter-offer round-trip → QUOTED 5. linkEscrow(txId, amount) → COMMITTED 6. provider picks up job → transitionState(...) → IN_PROGRESS 7. provider submits proof → transitionState(...) → DELIVERED 8. consumer accepts → transitionState(SETTLED) → SETTLED 9. EscrowVault releases (amount - fee) to provider ``` Steps 3–5 are batched into **one** [UserOperation](/reference/glossary#useroperation) when `wallet=auto` (the default). See [Gasless payment](/recipes/gasless-payment). ## Handling delivery you don't accept If the provider's output looks wrong, transition the transaction to `DISPUTED` via the kernel adapter. There's no `agent.dispute()` helper at V1; the path is through `agent.client.standard.transitionState()`: ```ts // At V1: drop to the standard adapter to transition state. // Bond is posted on-chain by the kernel as part of the DISPUTED transition // per AIP-14: max(amount × disputeBondBps / 10000, MIN_DISPUTE_BOND $1). await agent.client.standard.transitionState( result.transaction.id, 'DISPUTED', // optional proof / evidence URI (e.g., IPFS CID of evidence JSON) ); ``` The kernel freezes the escrow and pages the [mediator](/reference/glossary#mediator). See [Dispute flow](/recipes/dispute-flow) for the full walkthrough. ## Cancellation paths | State at cancellation | Refund | |---|---| | `INITIATED` / `QUOTED` | Full (no escrow attached yet) | | `COMMITTED` (provider hasn't started) | Full | | `IN_PROGRESS` | `amount - requesterPenaltyBpsLocked` | | `DELIVERED` → must dispute via transitionState, not cancel | Mediator decides | ```ts // V1: cancellation also goes through the standard adapter await agent.client.standard.transitionState( result.transaction.id, 'CANCELLED', ); ``` ## See also - [Provider agent](/recipes/provider-agent): the other side of every request - [Gasless payment](/recipes/gasless-payment): why `wallet=auto` matters - [State machine](/protocol/state-machine): the DAG the request walks through - [Dispute flow](/recipes/dispute-flow): when delivery is unacceptable --- **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`](/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](https://github.com/agirails/docs/blob/main/scripts/verify-recipes.ts)). ============================================================ Build an autonomous agent ============================================================ # Build an autonomous agent An autonomous agent does both sides: it **earns** USDC by providing a service, then **spends** some of that USDC to call other agents for sub-tasks it can't do itself. This recipe shows a research-summarizer agent that: 1. Provides `summarize` (you call it with a URL, get back a summary). 2. Internally calls a `fetch-content` provider to get the raw page (avoids needing to ship a browser). 3. Internally calls a `translate` provider if the source isn't English. 4. Returns the summary, settles, banks the net. ## The pattern ```ts // Track sub-task spend per job to enforce a per-job ceiling at app level. // (behavior.budget.perRequestSpendCap is not a V1 SDK option; enforce in // your handler.) const PER_JOB_SPEND_CAP = 0.20; const agent = new Agent({ name: 'ResearchSummarizer', description: 'Summarizes any URL into 200 words. Multi-language input supported.', network: 'mainnet', wallet: 'auto', // reads keystore via env per AIP-13 behavior: { autoAccept: true, concurrency: 10, // Pricing policy lives in the covenant ({slug}.md) `pricing:` block, // not on Agent config. The actp serve daemon reads covenant policy at runtime. }, }); agent.provide('summarize', async (job, ctx) => { const { url } = job.input; let subSpend = 0; const spend = (label: string, cost: number) => { subSpend += cost; if (subSpend > PER_JOB_SPEND_CAP) { throw new Error( `sub-task spend cap exceeded (${subSpend} > ${PER_JOB_SPEND_CAP}) at ${label}`, ); } }; ctx.progress(10, 'fetching content'); // Sub-task 1: pay a fetch provider to get the page (avoids hosting headless Chrome) spend('fetch-content', 0.05); const fetched = await agent.request('fetch-content', { input: { url, format: 'markdown' }, budget: 0.05, timeout: 15_000, }); ctx.progress(40, 'fetched'); let content = fetched.result.markdown; // Sub-task 2: translate if needed if (fetched.result.detectedLanguage !== 'en') { spend('translate', 0.10); ctx.progress(50, 'translating'); const translated = await agent.request('translate', { input: { text: content, target: 'en' }, budget: 0.10, timeout: 20_000, }); content = translated.result.translated; } ctx.progress(80, 'summarizing'); const summary = await summarizeLocally(content); // your LLM call return { summary, sourceUrl: url, sourceLanguage: fetched.result.detectedLanguage }; }); await agent.start(); console.log(`autonomous agent live at ${agent.address}`); ``` ```python from agirails import Agent, AgentConfig, AgentBehavior # Track sub-task spend per job to enforce a per-job ceiling at app level. # (behavior.budget.per_request_spend_cap is not a V1 SDK option; enforce # in your handler.) PER_JOB_SPEND_CAP = 0.20 agent = Agent(AgentConfig( name="ResearchSummarizer", description="Summarizes any URL into 200 words. Multi-language input supported.", network="mainnet", wallet="auto", # reads keystore via env per AIP-13 behavior=AgentBehavior(auto_accept=True, concurrency=10), # Pricing policy lives in the covenant ({slug}.md) `pricing:` block, # not on Agent config. The actp serve daemon reads covenant policy at runtime. )) @agent.provide("summarize") async def summarize(job, ctx): url = job.input["url"] sub_spend = 0.0 def spend(label, cost): nonlocal sub_spend sub_spend += cost if sub_spend > PER_JOB_SPEND_CAP: raise ValueError( f"sub-task spend cap exceeded ({sub_spend} > {PER_JOB_SPEND_CAP}) at {label}" ) ctx.progress(10, "fetching content") # Sub-task 1: pay a fetch provider to get the page spend("fetch-content", 0.05) fetched = await agent.request( "fetch-content", input={"url": url, "format": "markdown"}, budget=0.05, timeout=15, ) ctx.progress(40, "fetched") content = fetched.result["markdown"] # Sub-task 2: translate if needed if fetched.result.get("detectedLanguage") != "en": spend("translate", 0.10) ctx.progress(50, "translating") translated = await agent.request( "translate", input={"text": content, "target": "en"}, budget=0.10, timeout=20, ) content = translated.result["translated"] ctx.progress(80, "summarizing") summary = await summarize_locally(content) # your LLM call return { "summary": summary, "source_url": url, "source_language": fetched.result.get("detectedLanguage"), } await agent.start() print(f"autonomous agent live at {agent.address}") ``` ## Integration patterns Two operational shapes work for an autonomous provider, depending on the infrastructure you already have. Pick by what is cheaper to maintain. ### Option A: everything in-process (simplest) The `Agent` API handles event subscription, job pickup, state transitions, settlement bookkeeping, and lifecycle inside one process. This is the recipe above. Right for greenfield agents, smaller scale, or anything you do not want to operate as a distributed system. One process, one log stream, one place to debug. ### Option B: forward events to existing infrastructure If you already have orchestration, queuing, retry logic, or logging in a service you operate (FastAPI on Hetzner, a Temporal workflow, a Lambda fleet, n8n cluster), bridge on-chain events into your existing endpoint using the low-level `EventMonitor` exposed by the runtime: ```ts // `agent.client.advanced` returns IACTPRuntime (the interface). `getEvents()` // is exposed only on the concrete BlockchainRuntime class, so a cast is // needed when accessing it. Both types are public SDK exports. // Must be called AFTER agent.start(): agent.client is undefined until then. const runtime = agent.client.advanced as BlockchainRuntime; runtime.getEvents().onTransactionCreated( { provider: agent.address }, async (event) => { await fetch('http://localhost:8070/webhook/actp', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ txId: event.txId, requester: event.requester, amount: event.amount, serviceHash: event.serviceHash, }), }); }, ); ``` ```python # Must be called AFTER await agent.start(); agent.client is None until then. runtime = agent.client.advanced async def on_tx_created(event): async with httpx.AsyncClient() as http: await http.post( "http://localhost:8070/webhook/actp", json={ "tx_id": event.tx_id, "requester": event.requester, "amount": event.amount, "service_hash": event.service_hash, }, ) runtime.get_events().on_transaction_created( {"provider": agent.address}, on_tx_created, ) ``` The on-chain side stays identical; you just pump events into whichever queue, state machine, or handler chain you already operate. Picking between A and B is operational, not protocol-level. The SDK supports both equally. Most agents start with A and switch to B only when existing infra makes reuse cheaper than the in-process pattern. ## What makes this autonomous - **Self-contained budget**: app-level `PER_JOB_SPEND_CAP` ensures the agent never spends more than its share on a single job. If sub-tasks would exceed, the handler throws and surfaces as an `'error'` event. - **No external orchestration**: no n8n, no cron, no human loop. Just `agent.start()` and it lives. - **Composable**: this agent's `summarize` is itself a discoverable service that other agents can chain. - **Pricing policy**: define in the [covenant](/reference/glossary#covenant) (`{slug}.md` → `pricing:` block) and have [`actp serve`](/reference/glossary#actp-cli) enforce it for [AIP-2.1](/reference/glossary#aip-21) counter-offers. ## Observability For anything that runs unattended, you want events flowing somewhere. The V1 events on `Agent`: ```ts agent.on('starting', () => log.info({ event: 'starting' })); agent.on('started', () => log.info({ event: 'started', address: agent.address })); agent.on('stopping', () => log.info({ event: 'stopping' })); agent.on('stopped', () => log.info({ event: 'stopped' })); agent.on('paused', () => log.info({ event: 'paused' })); agent.on('resumed', () => log.info({ event: 'resumed' })); // Service + job lifecycle: agent.on('service:registered', (name) => log.info({ event: 'service:registered', name })); agent.on('job:received', (job) => log.info({ event: 'job:received', jobId: job.id })); agent.on('job:rejected', (job, reason) => log.warn({ event: 'job:rejected', jobId: job.id, reason })); // Earnings: payload is the amount as a number: agent.on('payment:received', (amount) => metrics.counter('earnings', amount)); // Errors: agent.on('error', (e) => log.error({ event: 'agent:error', error: e.message })); ``` ```python agent.on("starting", lambda: log.info({"event": "starting"})) agent.on("started", lambda: log.info({"event": "started", "address": agent.address})) agent.on("stopping", lambda: log.info({"event": "stopping"})) agent.on("stopped", lambda: log.info({"event": "stopped"})) agent.on("paused", lambda: log.info({"event": "paused"})) agent.on("resumed", lambda: log.info({"event": "resumed"})) # Service + job lifecycle: agent.on("service:registered", lambda name: log.info({"event": "service:registered", "name": name})) agent.on("job:received", lambda job: log.info({"event": "job:received", "job_id": job.id})) agent.on("job:rejected", lambda job, reason: log.warning({"event": "job:rejected", "job_id": job.id, "reason": reason})) # Earnings: payload is the amount as a number: agent.on("payment:received", lambda amount: metrics.counter("earnings", amount)) # Errors: agent.on("error", lambda e: log.error({"event": "agent:error", "error": str(e)})) ``` Wire to your logging stack of choice. For per-job timing / completion, instrument inside your handler (the V1 SDK doesn't emit a `job:completed` event with the tx payload; you have what `agent.request` returns to the handler caller). ## Running it production-ish Three things you actually need: 1. **Process supervisor**: pm2, systemd, Kubernetes Deployment, anything that restarts on crash. 2. **Keystore via `ACTP_KEYSTORE_BASE64`**: see [Keystore + deployment](/recipes/keystore-and-deployment). 3. **App-level circuit breaker on spending**: wrap your `agent.request()` calls. The V1 SDK doesn't have a built-in `behavior.budget`. Enforce a per-job cap inside your handler (as shown in the `spend()` helper above) and a daily cap in your supervisor / monitoring layer: ```ts // Conceptual; implement in your process layer, not Agent config: const DAILY_CAP = 50.00; let dailySpend = 0; // Reset dailySpend at UTC midnight via cron / setInterval. function guardSpend(label: string, cost: number) { if (dailySpend + cost > DAILY_CAP) throw new Error('daily cap exceeded'); dailySpend += cost; } ``` ## Watching it earn ```ts setInterval(() => { console.log({ earned: agent.stats.totalEarned, // USDC spent: agent.stats.totalSpent, // USDC (set by SDK on each request payment) net: agent.stats.totalEarned - agent.stats.totalSpent, jobs: agent.stats.jobsCompleted, // For per-job margin tracking, instrument inside your handler and // emit your own metrics; V1 AgentStats doesn't expose avgMargin. }); }, 60_000); ``` ```python async def watch_earnings(): while True: print({ "earned": agent.stats.total_earned, # USDC "spent": agent.stats.total_spent, # USDC "net": agent.stats.total_earned - agent.stats.total_spent, "jobs": agent.stats.jobs_completed, # For per-job margin tracking, instrument inside your handler; # V1 AgentStats doesn't expose avg_margin. }) await asyncio.sleep(60) asyncio.create_task(watch_earnings()) ``` A healthy autonomous agent retains > 30% of revenue after sub-task spend + fees. If lower, your sub-task budgets are too generous or your asking price is too low. ## See also - [Provider agent](/recipes/provider-agent): earning side in isolation - [Consumer agent](/recipes/consumer-agent): spending side in isolation - [Gasless payment](/recipes/gasless-payment): why concurrent earn+spend is fine on a single [SCW](/reference/glossary#scw) - [Quote negotiation](/recipes/quote-negotiation): covenant `pricing:` block + `actp serve` AIP-2.1 counter-offers --- **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`](/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](https://github.com/agirails/docs/blob/main/scripts/verify-recipes.ts)). ============================================================ Gasless payment with wallet=auto ============================================================ # Gasless payment with `wallet=auto` By default both SDKs run in `wallet=auto` mode: the agent's [EOA](/reference/glossary#eoa) is wrapped in a [Coinbase Smart Wallet](https://github.com/coinbase/smart-wallet) ([ERC-4337](/reference/glossary#erc-4337)) and every state-changing call (`createTransaction`, `linkEscrow`, `transitionState`, etc.) is bundled into a single UserOperation sponsored by a paymaster. The requester pays **only USDC**, with no native ETH ever leaving the wallet for gas. The SDK is configured with **two independent paymaster providers**: Coinbase as primary and Pimlico as automatic backup. If the primary fails for any reason (rate limit, transient outage, policy decline), the SDK transparently retries against the backup before surfacing an error to your code. See [When gasless fails](#when-gasless-fails) for the failure path. This is AIP-12 in practice. The fallback below the paymaster layer is `wallet=eoa` (pay-your-own-gas mode) for power users or when both providers are unreachable. ## The pattern ```ts const agent = new Agent({ name: 'BillingPayer', network: 'mainnet', // or 'testnet' // wallet: 'auto' is the default (explicit here for clarity) wallet: 'auto', // default; reads keystore via env per AIP-13 }); await agent.start(); // First request will trigger Smart Wallet deployment if needed (one-time, // also sponsored). Subsequent requests reuse the same SCW address. const result = await agent.request('translate', { input: { text: 'Hello', target: 'es' }, budget: 0.50, // $0.50 USDC max timeout: 30_000, }); console.log('paid:', result.transaction.amount, 'USDC'); console.log('gas paid in ETH:', 0); // always zero in auto mode ``` ```python from agirails import Agent, AgentConfig agent = Agent(AgentConfig( name="BillingPayer", network="mainnet", wallet="auto", # default; reads keystore env vars per AIP-13 )) await agent.start() result = await agent.request( "translate", input={"text": "Hello", "target": "es"}, budget=0.50, timeout=30, ) print(f"paid: {result.transaction.amount} USDC") ``` The Smart Wallet address shows up as `agent.address`; the SCW is what the protocol records as `requester` on-chain. The underlying EOA is held inside the keystore and is not exposed as an `agent.eoa` getter in V1; access it through your keystore loader (or `agent.client` internals if you need to recover the signer). See [Identity](/protocol/identity). ## What gets batched into one UserOp For a typical pay-per-call: 1. `USDC.approve(EscrowVault, amount)` 2. `ACTPKernel.createTransaction(...)` 3. `ACTPKernel.linkEscrow(txId, amount)` ← funds locked in vault Without `wallet=auto` those are three separate transactions, each charging gas. With `auto` it's **one** UserOperation, sponsored by whichever paymaster (Coinbase primary, Pimlico backup) responds successfully: the user's gas cost is zero either way. ## Dual-provider paymaster configuration The SDK reads paymaster endpoints from environment variables at client init: | Provider | Env vars | Role | |---|---|---| | Coinbase | `CDP_API_KEY` (auto-resolves URLs) or `CDP_PAYMASTER_URL` + `CDP_BUNDLER_URL` | Primary | | Pimlico | `PIMLICO_API_KEY` (auto-resolves URLs) or `PIMLICO_PAYMASTER_URL` + `PIMLICO_BUNDLER_URL` | Backup | If only one is configured, it becomes the sole provider (no failover). If both are configured, the SDK uses Coinbase as primary and falls back to Pimlico on primary failure. If neither is configured and the chain is testnet/mainnet, `wallet=auto` cannot initialize and the SDK throws with an explicit message listing the configuration options. See [`PaymasterClient.callWithFallback`](https://github.com/agirails/sdk-js/blob/main/src/wallet/aa/PaymasterClient.ts) for the fallback implementation. The two providers are independent companies in different jurisdictions with different infrastructure and policy surfaces. The redundancy is not for branding; it protects gasless availability against single-provider outages or policy declines. ## When gasless fails Three concrete failure modes, in order of likelihood: 1. **Primary paymaster transient error** (most common). Coinbase paymaster returns an error (rate limit, momentary unavailability). The SDK logs `Primary paymaster failed, trying backup` and retries against Pimlico. Your code sees no error. 2. **Both paymasters down or both decline** (rare, but the load-bearing failure to design for). The SDK throws `Gas sponsorship temporarily unavailable: both Coinbase and Pimlico paymasters failed`. Your code catches this and decides: retry later, surface to the user, or fall through to `wallet=eoa` for this call. 3. **Neither provider configured at init**. The SDK throws at client construction with a clear message: configure `CDP_API_KEY` or `PIMLICO_API_KEY`, or set explicit endpoints, or use `wallet=eoa`. Joint decline (mode 2) is the case worth understanding. Coinbase paymaster operates under Coinbase Inc. policy (KYC posture, sanctions list, regulatory regime); Pimlico paymaster operates under different company policy. Coordinated decline requires either both providers to independently reach the same policy stance or a regulatory order that captures both. Less likely than single-provider decline, but the protocol does not depend on either provider to keep functioning: when both fail, the agent can run `wallet=eoa` and pay its own gas in ETH. No funds are at risk; the failure mode is "gasless UX degraded to gas-paying UX", not "agent stuck". ## Forcing `wallet=eoa` explicitly The V1 `wallet` config accepts: - `'auto'`: Smart Wallet + dual-paymaster (default for testnet + mainnet, requires CDP_API_KEY or PIMLICO_API_KEY) - `'eoa'`: pay-your-own-gas EOA mode (no paymaster dependency, requires ETH for gas) - `'0xPRIVATE_KEY...'`: string form, treated as a raw private key (loaded directly into the wallet provider) - `{ privateKey: '0x...' }`: object form, equivalent ```ts const agent = new Agent({ name: 'EoaTester', network: 'mainnet', wallet: 'eoa', // forces EOA, reads keystore env vars per AIP-13 }); ``` Use this when: - You're running tests against a forked node without a paymaster. - You want to control gas budgets yourself rather than rely on dual-provider availability. - You explicitly do not want third-party paymaster dependency in your trust model (this is a stricter permissionless posture; gasless trades that for UX). - Both paymasters declined a specific call and you want a clean fallback path for the next attempt. ## Wallet funding: gasless ≠ free `wallet=auto` makes **gas** free, but the requester still needs USDC in the Smart Wallet to fund the escrow. For testnet, the [Coinbase faucet](https://portal.cdp.coinbase.com/products/faucet) gives Base Sepolia ETH (only needed if you ever fall back to EOA mode manually) and you mint test USDC via the SDK's own MockUSDC contract. Never use external faucets. See [Get started](/start). For mainnet, fund the SCW address (`agent.address`) with real USDC via any standard wallet or exchange withdrawal. ## See also - [`wallet=auto` deep-dive](/protocol/x402): the on-chain mechanics - [Provider agent recipe](/recipes/provider-agent): earning side - [Consumer agent recipe](/recipes/consumer-agent): paying side - [AIP-12 spec](https://github.com/agirails/aips/blob/main/AIP-12-DRAFT.md): wallet-mode auto-detection --- **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`](/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](https://github.com/agirails/docs/blob/main/scripts/verify-recipes.ts)). ============================================================ Per-call API billing (x402) ============================================================ # Per-call API billing (x402) For high-frequency, low-value, latency-sensitive endpoints (inference calls, search queries, single-shot translations under a few cents) the full [ACTP](/reference/glossary#actp) escrow round-trip is overkill. **x402** is the lightweight alternative: a single signed payment authorization travels with the HTTP request, the seller verifies it, executes the work, and settles directly. No [INITIATED](/reference/glossary#initiated) → [COMMITTED](/reference/glossary#committed) → [DELIVERED](/reference/glossary#delivered) dance. x402 v2 (the version both SDKs support) is direct buyer→seller, with no facilitator middleman and no escrow lock-up. Trade-off: no dispute window, so use it only where individual calls are cheap enough to write off if one goes wrong. When to pick which: | Use case | Best fit | |---|---| | Per-token LLM inference, < $0.01/call | x402 | | Bulk translation job, $5–50 | ACTP escrow (regular `request()`) | | Real-time search API, $0.001/query | x402 | | Anything where dispute matters | ACTP escrow | | Anything > $1 | ACTP escrow | ## Client-side (consumer): paying for a call In V1, the TS SDK exposes the x402 path via the `X402Adapter` registered on the `ACTPClient` router. The high-level entry point is `client.pay()` with an HTTPS target; the router dispatches to `X402Adapter` automatically when the destination is a URL: ```ts const client = await ACTPClient.create({ network: 'mainnet', wallet: 'auto', // reads keystore via env per AIP-13 }); // HTTPS target → routes to X402Adapter (priority 70) const result = await client.pay({ to: 'https://provider.example.com/api/infer', amount: '0.005', // $0.005 USDC // The adapter does the 402 dance: initial request, read X-Payment-Request // header, sign EIP-712 authorization, retry with X-Payment header, // server settles, returns the body + settlement headers. }); console.log('answer:', result); ``` There is no separate `x402Client` export in V1; the unified `ACTPClient` router is the entry point. If you need lower-level control (manual signing, custom retry policy), import `X402Adapter` from the SDK and instantiate it directly. See [SDK reference](/reference/sdk-js). ## Server-side (provider): exposing an x402 endpoint There is no `requirePayment` middleware shipped in `@agirails/sdk@4.0.0`. To accept x402 payments server-side, verify the `X-Payment` header yourself using EIP-3009 / Permit2 signature verification, or use an upstream x402 facilitator package (the protocol is open; multiple servers implement it). Minimum-viable server-side flow: ```ts const app = express(); app.use(express.json()); const PRICE_USDC = 0.005; const RECIPIENT = process.env.PROVIDER_SCW!; // your Smart Wallet address app.post('/api/infer', async (req, res) => { const paymentHeader = req.header('x-payment'); if (!paymentHeader) { // No payment yet; respond 402 with the price quote res.status(402).set({ 'x-payment-request': JSON.stringify({ amount: PRICE_USDC.toString(), recipient: RECIPIENT, network: 'eip155:8453', // Base mainnet, CAIP-2 scheme: 'eip3009', }), }).send(); return; } // Verify the signed EIP-3009 authorization in paymentHeader. // (Pseudocode; implement against your chosen x402 library.) // 1. Parse the typed-data payload // 2. Recover signer with verifyTypedData(...) // 3. Check nonce not used + amount ≥ PRICE_USDC + recipient matches // 4. Submit USDC.transferWithAuthorization(...) to settle // 5. Return settlement tx hash in `x-payment-settlement` header const result = await myInferenceModel(req.body.prompt); res.set({ 'x-payment-settlement': '' }).json({ result }); }); ``` A canonical Express middleware will land in the SDK before x402 graduates from V1; until then, server-side verification is roll-your-own per protocol spec. ## Python equivalent Python's `agirails` package does not currently expose an `X402Client`; the `X402Adapter` is available via `agirails.adapters.x402_adapter`. The high-level pattern routes through `ACTPClient`: ```python from agirails import ACTPClient client = await ACTPClient.create(mode="mainnet", wallet="auto") result = await client.pay({ "to": "https://provider.example.com/api/infer", "amount": "0.005", }) ``` `ACTPClient.create()` takes `mode=` (`"mock"` / `"testnet"` / `"mainnet"`), not `network=`, and `client.pay()` takes a single params dict, not keyword args. Server-side Python (FastAPI) requires the same roll-your-own EIP-3009 verification as the TS server example above. A canonical FastAPI dependency for x402 verification is a deferred V1 enhancement. ## Errors you should handle | Error | What it means | What to do | |---|---|---| | `X402AmountExceededError` | Server asked for more than your `maxAmount` | Bump the cap or skip this provider | | `X402SettlementProofMissingError` | Server returned 200 but no settlement header | Treat as fraud, drop provider from your registry | | `X402SignatureFailedError` | Buyer signature didn't verify (server-side) | Bug in your client signer; check key/network | | `X402NetworkNotAllowedError` | Buyer + seller disagree on network | Both must use the same Base mainnet/sepolia | | `X402PublishRequiredError` | Buyer's wallet not yet on-chain (no first tx) | Trigger one ACTP tx first, or fund SCW manually | Full list: [Error reference](/reference/errors) (x402 errors are TS-only; Python has its own subset). ## What x402 doesn't give you - **No dispute window.** Once settled, the money's gone. For anything where output quality might be contestable, use ACTP escrow. - **No reputation accumulation.** x402 payments don't write to [EAS](/reference/glossary#eas) the same way ACTP transactions do. Provider reputation only builds via ACTP escrow flow. - **No [AIP-2.1](/reference/glossary#aip-21) quote negotiation.** Price is take-it-or-leave-it per call. ## See also - [x402 protocol overview](/protocol/x402): the full spec + when to use it - [Gasless payment](/recipes/gasless-payment): how x402 settlements get sponsored too - [Consumer agent](/recipes/consumer-agent): the ACTP escrow alternative - [x402 error reference](/reference/errors): full TS error catalog --- **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`](/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](https://github.com/agirails/docs/blob/main/scripts/verify-recipes.ts)). ============================================================ Quote negotiation (AIP-2.1) ============================================================ # Quote negotiation (AIP-2.1) A provider's initial quote isn't always the price both sides agree on. AIP-2.1 adds a **signed off-chain negotiation** phase between [INITIATED](/reference/glossary#initiated) and [COMMITTED](/reference/glossary#committed): requester and provider exchange EIP-712 typed-data counters until one accepts. Only the final price hits the chain via `kernel.acceptQuote()`. The off-chain part is what makes it cheap: even a 5-round negotiation is zero gas. ## Provider: run `actp serve` The Python SDK ships a FastAPI daemon that hosts the counter-offer endpoint and applies a YAML policy: ```bash pip install "agirails[server]" actp serve --policy provider-policy.yaml --port 8080 ``` `provider-policy.yaml` (V1 schema; matches `load_policy_from_dict` in `agirails.server.policy`): ```yaml pricing: min_acceptable: amount: 500000 # $0.50 USDC (units = micro-USDC) currency: USDC unit: base ideal: amount: 1000000 # $1.00 USDC currency: USDC unit: base services: ["translate"] # empty list = accept all services quote_ttl: 300 # seconds; expired CounterOffers are dropped min_deadline_seconds: 60 # reject jobs with tighter deadlines counter_strategy: concede # 'concede' = re-quote toward floor; 'walk' = reject concede_pct: 20 # how much to move per requote round max_requotes: 2 # cap on requote rounds ``` Wallet/key is configured via `ACTP_KEYSTORE_BASE64` + `ACTP_KEY_PASSWORD` env vars per [AIP-13](/reference/glossary#aip-13), not in the policy file. The `network` is read from `ACTP_NETWORK` (`mainnet` or `testnet`). The daemon: 1. Verifies inbound `CounterOffer` EIP-712 signature against the requester's claimed address. 2. Checks `expiresAt > now` and the `nonce` hasn't been seen. 3. If `counterAmount >= ideal.amount` → emits `CounterAccept` (signed by provider). 4. Otherwise, depending on `counter_strategy`: `walk` returns reject; `concede` emits a counter-counter at the current quote minus `concede_pct`, capped at `min_acceptable.amount` and `max_requotes`. 5. Persists `(signer, nonce)` to prevent replay. Health check: `GET /healthz` → `{"ok": true, "negotiations_active": 7}`. ## Requester: send a counter ```ts const agent = new Agent({ name: 'Negotiator', network: 'mainnet', wallet: 'auto', // reads keystore via env per AIP-13 }); await agent.start(); // V1: create the on-chain transaction via the standard adapter const txId = await agent.client.standard.createTransaction({ provider: '0xPROV…', service: 'translate', // amount, deadline, etc. }); // V1: CounterOfferBuilder is constructed, not chained. // `signer` is your wallet provider's ethers.Signer. In wallet=auto mode, // recover it from the runtime adapter: const runtime = agent.client.advanced; const signer = runtime.getMessageSigner().signer; // ethers.Signer const nonceManager = new InMemoryNonceManager(); const builder = new CounterOfferBuilder(signer, nonceManager); const counter = await builder.build({ txId, consumer: agent.address, provider: '0xPROV…', quoteAmount: '1000000', // $1.00 in micro-USDC (provider's initial quote) counterAmount: '600000', // $0.60 in micro-USDC maxPrice: '800000', // accept up to $0.80 in return-counter inReplyTo: 'INITIAL_QUOTE_ID', chainId: 8453, kernelAddress: '0xKERNEL…', expiresAt: Math.floor(Date.now() / 1000) + 120, // 2 minutes from now }); const reply = await fetch('https://provider.example.com/actp/counter-offer', { method: 'POST', body: JSON.stringify(counter), }); const { kind, payload } = await reply.json(); // kind === 'CounterAccept' → we won, settle on-chain // kind === 'CounterOffer' → provider returned a counter-counter, decide ``` ```python from agirails import Agent, AgentConfig, CounterOfferBuilder, InMemoryNonceManager agent = Agent(AgentConfig( name="Negotiator", network="mainnet", wallet="auto", # reads keystore via env per AIP-13 )) await agent.start() # V1: create the on-chain transaction via the standard adapter tx_id = await agent.client.standard.create_transaction( provider="0xPROV...", service="translate", # amount, deadline, etc. ) # V1: CounterOfferBuilder is constructed, not chained. # `signer` recoverable from the runtime adapter. runtime = agent.client.advanced signer = runtime.get_message_signer().signer nonce_manager = InMemoryNonceManager() builder = CounterOfferBuilder(signer, nonce_manager) counter = await builder.build( tx_id=tx_id, consumer=agent.address, provider="0xPROV...", quote_amount="1000000", # $1.00 in micro-USDC counter_amount="600000", # $0.60 max_price="800000", # accept up to $0.80 in return-counter in_reply_to="INITIAL_QUOTE_ID", chain_id=8453, kernel_address="0xKERNEL...", expires_at=int(time.time()) + 120, # 2 minutes from now ) async with httpx.AsyncClient() as http: reply = await http.post( "https://provider.example.com/actp/counter-offer", json=counter, ) result = reply.json() # result["kind"] == "CounterAccept" -> we won, settle on-chain # result["kind"] == "CounterOffer" -> provider returned a counter-counter ``` ## Settle the accepted counter on-chain When `kind === 'CounterAccept'`, advance the transaction through the kernel via the standard adapter (there is no top-level `acceptQuote()` export in V1; the `acceptQuote` method lives on `agent.client.standard`): ```ts // V1: acceptQuote is a method on the standard adapter, not a free function await agent.client.standard.acceptQuote(txId, '600000'); // negotiated amount in micro-USDC // linkEscrow funds the locked amount. With wallet=auto the kernel // composes acceptQuote + linkEscrow into a single sponsored UserOp. await agent.client.standard.linkEscrow(txId); // → kernel transitions INITIATED → QUOTED → COMMITTED with new amount. ``` ```python # V1: accept_quote is a method on the standard adapter await agent.client.standard.accept_quote(tx_id, "600000") # micro-USDC # link_escrow funds the locked amount. With wallet=auto the kernel # composes accept_quote + link_escrow into a single sponsored UserOp. await agent.client.standard.link_escrow(tx_id) # kernel transitions INITIATED -> QUOTED -> COMMITTED with new amount. ``` In `wallet=auto` (default) `acceptQuote + linkEscrow` are bundled into one sponsored UserOp: zero gas. ## Cancellation mid-negotiation Either side can stop responding. The `expiresAt` field bounds the window: after expiry, the signed message is invalid for `acceptQuote()` (kernel checks `block.timestamp <= expiresAt`). No on-chain footprint either way; the requester's `createTransaction` either gets `linkEscrow`'d at the agreed price or expires unfunded as INITIATED. ## Replay protection Every counter carries a `nonce` issued by `MessageNonceManager`. The kernel records consumed `(signer, nonce)` pairs; a duplicate `acceptQuote()` reverts with `NonceAlreadyConsumed`. This also handles late-arriving signed messages: if the chain has already moved past QUOTED, the signed message is stale and rejected. ## Cross-SDK parity `CounterOfferBuilder` (TS) and `CounterOfferBuilder` (Python) produce byte-identical EIP-712 payloads. CI runs cross-SDK fixture tests on every release: a counter signed by TS must verify in Python, and vice versa. See [cross-SDK fixtures](https://github.com/agirails/sdk-python/tree/main/tests/fixtures/cross_sdk). ## See also - [Quote channel protocol](/protocol/quote-channel): the on-chain side of AIP-2.1 - [Provider agent](/recipes/provider-agent): the daemon's caller - [Gasless payment](/recipes/gasless-payment): how `acceptQuote + linkEscrow` get bundled --- **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`](/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](https://github.com/agirails/docs/blob/main/scripts/verify-recipes.ts)). ============================================================ Dispute flow ============================================================ # Dispute flow A dispute happens when the requester rejects a `DELIVERED` transaction or the provider claims the requester is refusing valid work. AIP-14 governs the bond mechanics: **whoever disputes posts $1 USDC minimum** (or 5% of the transaction amount, whichever is higher). The bond returns per fault attribution after the mediator decides. ## Raising a dispute as the requester You can only dispute from `DELIVERED` (after the provider submitted a deliverable). Before delivery, use `cancel()` instead. See [Consumer agent](/recipes/consumer-agent#cancellation-paths). ```ts const agent = new Agent({ name: 'Disputer', network: 'mainnet', wallet: 'auto', // reads keystore via env per AIP-13 }); await agent.start(); const result = await agent.request('translate', { input: { text: 'Hi', target: 'es' }, budget: 1.00, }); // result.transaction.state === 'DELIVERED' // but result.result === { translated: 'Bonjour' } ← that's French, not Spanish // V1 path: drop to the standard adapter to transition state. // The kernel posts the bond as part of the DISPUTED transition (AIP-14): // bond = max(amount × disputeBondBpsLocked / 10000, MIN_DISPUTE_BOND $1) // The bond comes from the disputer's wallet automatically. // Optional `proof` arg: bytes (e.g., hash of an evidence-JSON CID) the // kernel records on-chain alongside the transition. await agent.client.standard.transitionState( result.transaction.id, 'DISPUTED', // proof: '0x…' (optional evidence hash, must fit bytes32) ); // → kernel locks the bond + transitions DELIVERED → DISPUTED // → escrow stays locked until mediator decides ``` ```python from agirails import Agent, AgentConfig agent = Agent(AgentConfig( name="Disputer", network="mainnet", # Wallet/keystore via env per AIP-13 )) await agent.start() result = await agent.request( "translate", input={"text": "Hi", "target": "es"}, budget=1.00, ) # result.transaction.state == "DELIVERED" # but result.result == {"translated": "Bonjour"} -- that's French, not Spanish # V1 path: drop to the standard adapter to transition state. # The kernel posts the bond as part of the DISPUTED transition (AIP-14): # bond = max(amount * disputeBondBpsLocked / 10000, MIN_DISPUTE_BOND $1) # The bond comes from the disputer's wallet automatically. # Optional `proof` arg: bytes (e.g., hash of an evidence-JSON CID) the # kernel records on-chain alongside the transition. await agent.client.standard.transition_state( result.transaction.id, "DISPUTED", # proof=b"\\x..." (optional evidence hash, must fit bytes32) ) # kernel locks the bond + transitions DELIVERED -> DISPUTED # escrow stays locked until mediator decides ``` ## Raising a dispute as the provider A provider raises a dispute when: - Requester is refusing to accept a clearly-correct delivery (stonewalling) - Requester sent input the provider couldn't process but disputes anyway ```ts // Identical path; the kernel decides who pays the bond from msg.sender. // In an agent's V1 wallet=auto config, msg.sender is the agent's Smart Wallet. await agent.client.standard.transitionState( txId, 'DISPUTED', '0xEVIDENCE_HASH', ); ``` ```python # Identical path; the kernel decides who pays the bond from msg.sender. await agent.client.standard.transition_state( tx_id, "DISPUTED", b"0xEVIDENCE_HASH", ) ``` Same bond is posted from the provider's wallet via the same path. ## Bond mechanics (AIP-14) ```text bondAmount = max(amount × disputeBondBpsLocked / 10000, MIN_DISPUTE_BOND) ``` - `disputeBondBpsLocked`: per-transaction value, captured at `createTransaction` time. Default `500` (5%). Immutable for the transaction's lifetime (INV-30). - `MIN_DISPUTE_BOND`: `1_000_000` micro-USDC = $1.00. For a $20 transaction, bond = max($20 × 5%, $1) = **$1.00** (because 5% = $1.00 = MIN). For a $200 transaction, bond = max($200 × 5%, $1) = **$10.00** (5% wins). ## Mediator resolution The mediator (currently AGIRAILS-operated; will be decentralized post-PMF) reviews evidence and calls one of: | Mediator decision | Escrow → | Bond → | |---|---|---| | `resolveForDisputer` | Per refund table | Returned to disputer | | `resolveAgainstDisputer` | Provider (full) | Awarded to counterparty | | `noDecision` (e.g., evidence inadmissible) | Refund per state rules | Burned to vault treasury | ```text DISPUTED ├─→ resolveForDisputer → SETTLED (requester wins) or CANCELLED + refund ├─→ resolveAgainstDisputer → SETTLED (provider wins, gets bond too) └─→ noDecision → CANCELLED, bond burned, escrow refunded per state ``` The mediator **cannot** transition back to `IN_PROGRESS` or `DELIVERED`; the DAG forbids it. Once a tx is `DISPUTED`, it's heading to [SETTLED](/reference/glossary#settled) or [CANCELLED](/reference/glossary#cancelled), period. ## Subscribing to dispute events V1 does not expose high-level `dispute:raised` / `dispute:resolved` events on `Agent`. The `Agent` event list is `starting`, `started`, `stopping`, `stopped`, `paused`, `resumed`, `service:registered`, `job:received`, `job:rejected`, `job:completed`, `job:failed`, `job:progress`, `payment:received`, `error`. To watch for disputes, drop to the runtime event monitor and filter `StateTransitioned` events for `newState === 'DISPUTED'`. This is the same monitor the SDK uses internally. ```ts // From within an Agent (after agent.start() has run): const runtime = agent.client.advanced as BlockchainRuntime; runtime.getEvents().onStateChanged( { provider: agent.address }, (event) => { if (event.newState === 'DISPUTED') { console.warn('[DISPUTE]', event.txId, 'by', event.actor); // Page on-call, post to Slack, queue for manual review. // Do NOT auto-respond. Dispute response is high-stakes; the protocol // intentionally puts a human in this loop. } }, ); ``` ```python # From within an Agent (after await agent.start() has run): runtime = agent.client.advanced def on_state(event): if event.new_state == "DISPUTED": print(f"[DISPUTE] {event.tx_id} by {event.actor}") # Page on-call, post to Slack, queue for manual review. # Do NOT auto-respond. Dispute response is high-stakes; the # protocol intentionally puts a human in this loop. runtime.get_events().on_state_changed( {"provider": agent.address}, on_state, ) ``` **Important**: do NOT auto-respond to disputes. Surface to a human queue, alert your on-call, or pause the agent. A dispute is a high-stakes decision; the protocol intentionally puts a human in this loop. ### Lower-level alternative (no Agent instance) If you are running a standalone monitoring service without an `Agent` (for example, an off-chain dashboard or an alerting daemon), construct an `ACTPClient` directly and subscribe via the same runtime path: ```ts const client = await ACTPClient.create({ network: 'mainnet', wallet: 'auto', // reads keystore via env per AIP-13 }); const runtime = client.advanced as BlockchainRuntime; runtime.getEvents().onStateChanged( { provider: client.getAddress() }, (event) => { if (event.newState === 'DISPUTED') { console.warn('DISPUTE on tx:', event.txId); } }, ); ``` ```python from agirails import ACTPClient client = await ACTPClient.create(mode="mainnet", wallet="auto") def on_state(event): if event.new_state == "DISPUTED": print(f"DISPUTE on tx: {event.tx_id}") runtime = client.advanced runtime.get_events().on_state_changed( {"provider": client.get_address()}, on_state, ) ``` Use this when you do not have an `Agent` instance to attach to. ## What evidence the mediator looks at | Source | What's in it | |---|---| | [EAS](/reference/glossary#eas) delivery attestation | Provider's signed claim of what was delivered | | Web Receipts payload | Full output blob (off-chain, IPFS-anchored) | | `dispute.evidence` field | Free-form JSON from disputer | | Counter-offer chain | Negotiated price + justifications | | On-chain state transitions | Timestamps proving who did what when | Good evidence is reproducible: input → output diff, attestation hashes, timestamps. "It was bad" is not evidence. ## Costs of disputing badly If the mediator rules **against** you, you lose: - The bond (transferred to counterparty) - Reputation score (EAS-attested, viewable on-chain) - Future negotiation leverage (your dispute rate is queryable) So dispute when you genuinely have a case, not as a haggling tool. ## See also - [AIP-14 spec](https://github.com/agirails/aips/blob/main/AIP-14.md): dispute bonds - [INV-30 explainer](/protocol/escrow#inv-30--per-transaction-locked-bps): why bonds can't be changed mid-flight - [Escrow mechanism](/protocol/escrow): what happens to USDC during DISPUTED - [State machine](/protocol/state-machine): DELIVERED → DISPUTED → SETTLED/CANCELLED paths --- **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`](/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](https://github.com/agirails/docs/blob/main/scripts/verify-recipes.ts)). ============================================================ Receipts + discovery ============================================================ # Receipts + discovery Every settled ACTP transaction produces two artifacts: 1. **On-chain attestation** (EAS): small, canonical, points at the deliverable. 2. **Web Receipt** (off-chain, IPFS-anchored): the actual deliverable payload + metadata. Discovery is the inverse: query [ERC-8004 AgentRegistry](https://eips.ethereum.org/EIPS/eip-8004) 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](/start/ai-environment/mcp-server) (Claude Desktop, Cursor, Cline, Windsurf, VS Code-with-MCP) can call directly. ```text 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](/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: ```python 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](/reference/cross-sdk-divergences)). Query the AgentRegistry contract directly via the runtime adapter: ```ts 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: ```ts 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 ```json { "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](/reference/glossary#erc-8004) reputation registry. In V1, access it through the client's `ReputationReporter`: ```ts 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](/reference/contracts/base-mainnet)). ## 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](/protocol/web-receipts): IPFS pinning + EIP-712 signing details - [Identity](/protocol/identity): EOA vs SCW vs covenant - [Provider agent](/recipes/provider-agent): where AgentRegistry.register() happens - [Dispute flow](/recipes/dispute-flow): receipts as evidence - [ERC-8004 spec](https://eips.ethereum.org/EIPS/eip-8004) --- **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`](/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](https://github.com/agirails/docs/blob/main/scripts/verify-recipes.ts)). ============================================================ Keystore + deployment (AIP-13) ============================================================ # Keystore + deployment (AIP-13) AIP-13 codifies how AGIRAILS handles private keys. The short version: - **No raw `PRIVATE_KEY=0x…` env vars in production code.** The SDK refuses to start if the key isn't in a recognized secure form. - **Encrypted keystore is the default**: `.actp/keystore.json` (Web3 Secret Storage v3 format), unlocked with `ACTP_KEY_PASSWORD`. - **CI/CD path**: pass the keystore as `ACTP_KEYSTORE_BASE64` (base64-encoded JSON) so secret managers can store it as opaque blob. - **`actp deploy:check`** scans your project for the foot-guns (committed keys, weak passwords, missing keystore) and exits non-zero if any are found. ## First-time setup ```bash # Generate a fresh keystore (prompts for password) ACTP_KEY_PASSWORD='strong-passphrase-here' actp init -m testnet # → writes .actp/keystore.json (gitignored) # → prints the public EOA address; fund this with testnet USDC via the SDK's MockUSDC ``` Then in your code, just set `ACTP_KEY_PASSWORD`; the SDK auto-loads the keystore: ```ts const agent = new Agent({ name: 'MyAgent', network: 'testnet', // private key resolved automatically from .actp/keystore.json }); await agent.start(); ``` ```python from agirails import Agent, AgentConfig agent = Agent(AgentConfig( name="MyAgent", network="testnet", # private key resolved automatically from .actp/keystore.json )) await agent.start() ``` The resolution order: 1. `ACTP_PRIVATE_KEY` env var (still allowed for local dev; warned in non-dev modes) 2. `ACTP_KEYSTORE_BASE64` env var (preferred for CI/CD) 3. `.actp/keystore.json` decrypted with `ACTP_KEY_PASSWORD` 4. Clear `MissingCredentialsError` with remediation steps if none of the above ## CI/CD: keystore via base64 GitHub Actions / GitLab CI / Vercel can't easily upload a file alongside env vars, so the SDK accepts the keystore as base64. Generate once: ```bash base64 -i .actp/keystore.json | pbcopy # macOS: paste into secret # or base64 -w 0 .actp/keystore.json # Linux: single line ``` Then in your CI: ```yaml env: ACTP_KEYSTORE_BASE64: ${{ secrets.ACTP_KEYSTORE_BASE64 }} ACTP_KEY_PASSWORD: ${{ secrets.ACTP_KEY_PASSWORD }} ``` The keystore stays encrypted at rest inside your secrets manager; only the runtime decrypts it for the duration of the process. ## `actp deploy:check`: fail-closed scanner Run before every deploy. It scans your repo for: - Committed `.env` files with `PRIVATE_KEY=0x…` (any 64-char hex) - Hardcoded keys in source (`const key = '0x…'`) - `.actp/keystore.json` accidentally untracked or world-readable - `ACTP_KEY_PASSWORD` weak passwords (< 16 chars, common patterns) - Network mismatch (e.g., mainnet config but testnet keystore) ```bash actp deploy:check --strict # ✓ no committed keys # ✓ keystore permissions: 600 # ✓ password entropy: 4.8 bits/char (good) # ✓ network: mainnet (keystore matches) # pass ``` In CI, add as a required step: ```yaml - name: Deploy safety check run: npx actp deploy:check --strict ``` `--strict` (or `CI_STRICT=true`) makes any warning fatal. Without it, only errors fail; warnings are surfaced but allow deploy. ## Network-specific keystores Separate keystores per network prevent mistakes like signing mainnet with testnet keys: ```bash .actp/ ├── keystore.json # default (current target) ├── keystore.testnet.json └── keystore.mainnet.json ``` Pick at runtime: ```bash ACTP_KEYSTORE_PATH=.actp/keystore.mainnet.json ACTP_KEY_PASSWORD='…' node my-agent.js ``` ## What `wallet=auto` means for keystores The keystore holds the **[EOA](/reference/glossary#eoa)** private key. When `wallet=auto`, that EOA signs UserOps for the Coinbase Smart Wallet (a separate on-chain address derived deterministically). The keystore itself doesn't change: same EOA, same encrypted file, just used to sign UserOps instead of raw txs. See [Gasless payment](/recipes/gasless-payment) for the [SCW](/reference/glossary#scw) vs EOA distinction. ## Rotating a compromised key ```bash # 1. Generate new keystore ACTP_KEY_PASSWORD='new-strong-pass' actp init -m mainnet --rotate # → writes .actp/keystore.json with new EOA # → prints the new public address # 2. Drain funds from old EOA/SCW to new address (manual, via any wallet) # 3. Update CI secrets (ACTP_KEYSTORE_BASE64 + ACTP_KEY_PASSWORD) # 4. Re-register with new identity if you ran AgentRegistry.register() previously ``` The protocol has no "rotate in place"; each EOA is a separate identity. Your reputation lives at the EOA address, so plan rotation as a fresh-start event (or use the SCW pattern where the EOA is just a signer and you migrate signers under the same SCW). ## See also - [AIP-13 spec](https://github.com/agirails/aips/blob/main/AIP-13.md): fail-closed key policy - [Provider agent](/recipes/provider-agent): first place you'll need the keystore - [Consumer agent](/recipes/consumer-agent): same - [Identity](/protocol/identity): what the EOA/SCW addresses represent on-chain --- **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`](/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](https://github.com/agirails/docs/blob/main/scripts/verify-recipes.ts)). ============================================================ Shipping to mainnet: production checklist ============================================================ # Shipping to mainnet: production checklist This is the orchestration layer. Each step is a check you should be able to answer yes to before moving on. Details live in the referenced recipes; this page is the sequence and the gates. If you skim, read the **Before launch** section. It's the part most teams under-invest in and the part that prevents the most incidents. ## Before launch The goal of this phase: prove the integration works against testnet, with the exact production deployment shape, before any real money moves. - [ ] **Testnet end-to-end succeeded** with at least one full lifecycle: `INITIATED → QUOTED → COMMITTED → IN_PROGRESS → DELIVERED → SETTLED`. Verify the BaseScan trace; don't trust your own logs. - [ ] **The same code that ran on testnet is what you'll deploy.** No "I'll add error handling later", no "I'll wire monitoring after". The handler shape, the budget caps, the timeout values, the retry policy all run on testnet first. - [ ] **Keystore lives in a real secrets manager.** Not committed, not in `.env` on disk in your home directory, not in your dotfiles. AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager, Doppler, 1Password CLI: any of them. Encrypted at rest, accessed via short-lived credential. See [keystore + deployment](/recipes/keystore-and-deployment). - [ ] **`actp deploy:check --strict` passes** in your CI pipeline. The scanner catches committed keys, weak passwords, network mismatches, world-readable keystore files. Fail-closed; if it warns, fix before merge. - [ ] **You can rotate the key without downtime.** Practiced once on testnet. The rotation path is documented in [keystore + deployment](/recipes/keystore-and-deployment#rotating-a-compromised-key). If you can't rotate, you can't recover from a leak. - [ ] **Smart Wallet is funded with enough USDC** for the expected first 24 hours of operation, plus a buffer. Check `agent.balance` before shipping; nothing kills first-day momentum like an [`INSUFFICIENT_FUNDS`](/reference/errors#insufficient_funds) error during a demo. - [ ] **Per-call and daily spending caps wired** at the application layer. The V1 SDK does not enforce `behavior.budget`; you wrap `agent.request()` in your own guard. See [LangChain recipe: budget controls](/recipes/langchain#budget-controls) and [autonomous agent recipe](/recipes/autonomous-agent#running-it-production-ish) for the canonical `guardSpend()` pattern. - [ ] **Error handling covers the user-action paths.** At minimum: `INSUFFICIENT_FUNDS`, `PROVIDER_REJECTED`, `NO_PROVIDER_FOUND`, `TIMEOUT`. For each, decide: retry, alert, or surface to the human. See [error reference](/reference/errors). - [ ] **You know what to do when a dispute fires.** `DISPUTE_RAISED` is not a bug, it's a protocol path. You have a documented response procedure. See [dispute flow](/recipes/dispute-flow). - [ ] **One person on the team can read the kernel call from a Basescan trace.** If nobody can read the on-chain state, you can't debug a production issue. ## Launch day The goal: switch `network: 'mainnet'` and run the same lifecycle that worked on testnet, with money this time. - [ ] **Cut over with feature flag or env var, not code change.** Change `network: 'testnet'` to `network: 'mainnet'` via your config layer. The code that runs is byte-identical to what testnet validated. - [ ] **First mainnet transaction is small.** Match the [first-mainnet-transaction walkthrough](/protocol/first-mainnet-transaction) cost shape: a few dollars at most. If something is off, you lose pocket change, not a paycheck. - [ ] **You watch the BaseScan tx land in real time.** Don't trust local logs for the first transaction. Open the kernel address page on BaseScan, refresh, see your transaction appear. - [ ] **The agent publishes its [covenant](/protocol/covenant)** via `actp publish`. The `{slug}.md` covenant is your agent's public business card. Hash anchored on-chain via [AgentRegistry](/reference/glossary#agentregistry). Until published, [x402](/reference/glossary#x402) flows that depend on paymaster sponsorship will throw [`X402_PUBLISH_REQUIRED`](/reference/errors#x402_publish_required). - [ ] **Reputation is starting to accrue** via [ERC-8004](/reference/glossary#erc-8004) attestations. Check after the first successful settlement: `agent.client.getReputationReporter()` should return a non-empty record. ## Day 1 in production The goal: catch problems while traffic is small and you're paying attention. - [ ] **Logs ship to a queryable destination** (Datadog, Honeycomb, Logtail, BetterStack, anything you can search). Stdout to terminal does not count as observability. - [ ] **Earnings metrics are tracked** at minimum: USDC earned, USDC spent, fee paid, jobs completed, jobs failed. Wire `agent.on('payment:received', ...)` for the provider side; on the consumer side, read `agent.stats.totalSpent` before each `request()` call (V1 does not emit a `payment:sent` event). See [autonomous agent observability](/recipes/autonomous-agent#observability) for the canonical pattern. - [ ] **You have alerts on the failure tail.** Specifically: rate of `DISPUTE_RAISED`, `DELIVERY_FAILED`, `TRANSACTION_REVERTED` per hour. Sudden spikes mean something changed; absent alerts you only learn about it from a user. - [ ] **Smart Wallet balance is monitored.** Threshold below which an alert fires. The default isn't "wait until it's empty"; the default is "alert at 24 hours of typical burn rate remaining". - [ ] **You can correlate a Basescan tx hash to a log line.** Both directions: log → tx, tx → log. Without this, debugging is guessing. ## Automation boundary: what to let the agent do unattended Different state transitions carry different risk. The table below is the default for a first mainnet deployment; tighten further if your stakes are higher. ### Auto-safe by default | Transition | Why it is safe | |---|---| | `COMMITTED → IN_PROGRESS` (`startWork`) | Funds are in escrow, not yours yet. Accepting a job costs nothing. | | `IN_PROGRESS → DELIVERED` (`deliver`) | Safe IF your handler produces deterministic, verifiable output. If it does not, surface the case to a human queue instead. | | `DELIVERED → SETTLED` past dispute window | The SDK's `settleOnInteract` automatically sweeps expired `DELIVERED` transactions on the next `pay()` / `startWork()` / `deliver()` call. Anyone can release after expiry; the kernel does not require it be you. | ### Human-in-the-loop by default | Action | Why it needs a human | |---|---| | `openDispute` (provider initiating) | High-stakes irreversible move. Always manual. | | Refunds and cancellations | Edge cases that require judgment. Wrong choices here are visible on-chain and shape reputation. | | Jobs above budget cap | Surface to your queue and decide explicitly. The SDK's `filter.maxBudget` rejects these by default; if you raise the cap, make it a deliberate act. | | Responding to a `DISPUTE_RAISED` event | See [Dispute flow: subscribing to dispute events](/recipes/dispute-flow#subscribing-to-dispute-events). Page on-call, do not auto-respond. | The `Agent` config knob is `behavior.autoAccept`; the `provide()` knob is the top-level `autoAccept` option. Both accept a boolean or a predicate; start with a predicate that encodes your minimum constraints (budget cap, service whitelist, allowed counterparty list), not bare `true`. See [provider agent recipe](/recipes/provider-agent#throwing-from-your-handler) for the `behavior.autoAccept` predicate pattern. ## Ongoing operations The goal: keep the agent running for months without surprises. - [ ] **Keys rotate on a schedule.** Quarterly is the minimum cadence for keys with non-trivial USDC exposure. Document who rotates, when, and what the verification step is. Same path as [keystore rotation](/recipes/keystore-and-deployment#rotating-a-compromised-key). - [ ] **Secrets manager audit log is reviewed.** Who pulled the keystore base64, when, from where. Anomalies here are the early signal of a compromise. - [ ] **Spend cap is reviewed against actual usage.** If your daily cap is $50 and you're consistently spending $5, you've got too much slack and a runaway loop could burn $50 before you notice. If you're hitting cap regularly, raise it deliberately; don't disable the guard. - [ ] **SDK updates are read before merging.** `@agirails/sdk` and `agirails` ship breakingless minor versions but read the `CHANGELOG.md` anyway. Pay attention to anything affecting state machine semantics, error class additions, or wallet provider behavior. - [ ] **Truth-ledger manifest** at [`/sdk-manifest.json`](/sdk-manifest.json) is checked when something feels off. If a method you remember disappeared, the manifest tells you it's been removed or renamed. - [ ] **Reputation trajectory is sane.** Provider's [ERC-8004](/reference/glossary#erc-8004) reputation should be monotonically increasing on the SDK side. If it's flat for days while transactions are settling, something is wrong with attestation publication. ## When things go wrong The goal: have a known response, not improvisation under pressure. - [ ] **Triage starts at the error code.** Paste the code into [`/reference/errors`](/reference/errors); cause, fix, and recovery class are auto-extracted from SDK source. Cmd+F to the anchor. - [ ] **Silent failures use the symptom flow** at [errors: If you don't have an error code](/reference/errors#if-you-dont-have-an-error-code). - [ ] **Key compromise has a runbook.** Steps documented in advance: rotate key, transition all in-flight transactions to a terminal state, update keystore in secrets manager, redeploy, post-incident notice. Don't write the runbook during the incident. - [ ] **Disputes have a response time.** The dispute window (typically 48 hours) is when you respond. If you wait longer, the mediator decides without your evidence. See [dispute flow](/recipes/dispute-flow). - [ ] **Vulnerabilities go to [security@agirails.io](/security/disclosure)**. Not to GitHub issues, not to Discord, not to X. Coordinated disclosure path documented in [disclosure](/security/disclosure). ## What this checklist does not cover - **Hyperscale operations.** This is for production agents handling tens to thousands of transactions per day. If you're operating at six-figure-per-day TVL, you're in territory that needs custom monitoring and probably custom retry policies; design your own. - **Multi-tenant agent platforms.** If you're hosting other people's agents and routing their funds, you have custodial obligations the V1 SDK doesn't address. Build accordingly. - **Coordinated multi-agent fleets.** If you're orchestrating dozens of agents that share spend caps or earn pools, the coordination layer is your problem to design. See [CrewAI integration](/recipes/crewai) for one pattern. ## See also - [Keystore + deployment (AIP-13)](/recipes/keystore-and-deployment): secrets handling deep dive - [Autonomous agent recipe](/recipes/autonomous-agent): production-ish provider pattern with observability + supervisors - [Dispute flow](/recipes/dispute-flow): the protocol path when things are contested - [Error reference](/reference/errors): per-code triage - [Walk-away runbook](/protocol/walk-away): what the protocol guarantees even if AGIRAILS the team disappears - [Threat model](/security/threat-model): what to guard against, what is already guarded for you - [Disclosure](/security/disclosure): how to report a vulnerability ============================================================ n8n workflow ============================================================ # n8n workflow `n8n-nodes-actp` is the community node that exposes AGIRAILS to n8n. It wraps the TS SDK so you don't have to write code inside Function nodes: drag, configure credentials, run. ## Install In n8n: **Settings → Community Nodes → Install** → `n8n-nodes-actp`. Or via CLI: ```bash cd ~/.n8n/custom npm install n8n-nodes-actp@2.5.0 # restart n8n ``` ## Credentials Add an **AGIRAILS API** credential: | Field | Value | |---|---| | Network | `mainnet` or `testnet` | | Wallet mode | `auto` (gasless, recommended) or `eoa` | | Keystore (base64) | Paste your `ACTP_KEYSTORE_BASE64` | | Keystore password | The password used when generating the keystore | The node decrypts the keystore at workflow execution start; the decrypted key never leaves the node process. ## Two main nodes ### `AGIRAILS Request` (consumer) Pays another agent for a service. Configure: - **Service name**: e.g. `translate`, `summarize` - **Provider address** (optional): pin a specific agent, otherwise auto-discover by reputation - **Budget (USDC)**: ceiling - **Input**: JSON, free-form - **Timeout (seconds)**: default 30 Output of the node: the provider's result + transaction metadata (`amount`, `fee`, `txId`, `attestationUid`). ### `AGIRAILS Provide` (provider trigger) Exposes your n8n workflow as a callable service. Other agents can `request()` it; this node fires once per incoming job. - **Service name**: what to advertise in [AgentRegistry](/reference/glossary#agentregistry) - **Service description**: shows up in discovery - **Pricing (min / ideal)**: your floor + counter-offer ideal - **Concurrency**: max parallel jobs The trigger output is the job payload (`{ input, budget, jobId }`); the rest of your workflow processes it and the **AGIRAILS Settle** node at the end submits the deliverable on-chain. ## Example flow: paying per call ```text Webhook (incoming text) ↓ AGIRAILS Request: translate (target=es, budget=$0.10) ↓ HTTP Request: POST to your downstream service ↓ respond to Webhook ``` This costs the requester ~$0.10 USDC per call (with fee), no ETH ever leaves their wallet, and the n8n workflow handles retry + error paths the way you'd expect. ## Full pay-per-run scenario: translation workflow that earns USDC A complete provider workflow that charges per translation, handles errors gracefully, retries transient failures, and never accepts a job it can't deliver on. This is the pattern you'd ship for a client. ### What the workflow does 1. Advertises a `translate` service in AgentRegistry (handled by `AGIRAILS Provide` trigger). 2. Receives jobs with `{ text, target_language }` input. 3. Validates input shape **before** accepting the job via the `AGIRAILS Provide` trigger's `min budget` / filter settings, which run before escrow attaches. (V1 SDK does not expose a `ctx.reject()` mid-handler; filter at the trigger or throw from the handler to surface as an `'error'` event.) 4. Calls OpenAI / Anthropic / DeepL via HTTP Request node. 5. Retries on transient API errors (429, 503) up to 3 times with exponential backoff. 6. Returns the translated text + metadata (model, source language detected, computation time). 7. Settles on-chain, generates [EAS](/reference/glossary#eas) attestation, publishes [Web Receipt](/reference/glossary#web-receipt). 8. Logs `payment:received` event to a Postgres node for accounting. ### The n8n workflow ```text [AGIRAILS Provide] trigger: service=translate, ideal=$0.10, min=$0.05, concurrency=5 │ ▼ [IF: input validation] text exists? target exists? target in [es, fr, de, it]? │ │ YES NO │ │ │ ▼ │ [Throw / Stop Workflow node] │ - handler throws; n8n Error Workflow catches. │ (For up-front rejection without escrow attach, │ use the AGIRAILS Provide trigger's minBudget + │ service-filter settings.) │ ▼ [HTTP Request: OpenAI translate] POST https://api.openai.com/v1/chat/completions retry: 3 times, exponential backoff (2s, 4s, 8s) on 4xx (non-429): fail-fast, don't retry │ ▼ [Set: shape the output] { translated: $json.choices[0].message.content, model: "gpt-4o", detectedSource: $json.usage.detected_language, computationMs: $json.metadata.duration_ms } │ ▼ [AGIRAILS Settle] submits deliverable on-chain (DELIVERED transition) generates EAS attestation publishes Web Receipt to IPFS via Filebase/Pinata │ ▼ [Postgres: log earnings] INSERT INTO earnings (tx_id, amount_usdc, fee_usdc, provider_net, settled_at) VALUES ($node.txId, $node.amount, $node.fee, $node.providerNet, NOW()) ``` ### Credentials wiring | Credential | Used by | Notes | |---|---|---| | AGIRAILS API | Provide / Settle / Reject nodes | Network=mainnet, wallet=auto, keystore base64 from `actp deploy:env` output | | OpenAI API | HTTP Request node | Standard OpenAI API key | | Postgres | Logging node | Optional but recommended for accounting | ### Error workflow wiring n8n's **Error Workflow** feature fires when any node throws. Wire it to a separate workflow that: 1. Catches `DisputeRaisedError` → posts to Slack with the dispute reason + evidence link → tags on-call 2. Catches `InsufficientFundsError` → posts to Slack with current SCW balance + funding instructions 3. Catches `MissingCredentialsError` → posts to Slack with credential setup link 4. Catches everything else → logs to Sentry/Datadog with full execution context ```text [Error Trigger] ↓ [Switch on $json.error.name] ├─ DisputeRaisedError → [Slack: alert on-call] ├─ InsufficientFundsError → [Slack: funding needed] ├─ MissingCredentialsError → [Slack: setup link] └─ default → [HTTP: Sentry capture] ``` ### Calculating margin For each settled transaction, the workflow's per-job economics: ``` Gross USDC received = $0.10 (job budget; consumer pays this) - Platform fee = max(amount × 1%, $0.05) = $0.05 (MIN_FEE binds since amount < $5) = Provider net = $0.05 - OpenAI cost = ~$0.001 per short translation - IPFS pin cost = ~$0.0001 per receipt - n8n infrastructure = amortized = Per-job margin ≈ $0.048 ``` For a workflow handling 1000 translations/day, that's ~$48/day net, settling in real-time USDC, no invoicing. If you want margin closer to 1% (cleanest economics), raise the per-job budget above $5; at that point the percentage fee binds rather than [MIN_FEE](/reference/glossary#min_fee). So `summarize-this-document` at $2-10 per job is more efficient than `translate-one-sentence` at $0.10. ### Importable template The workflow JSON is published in the [n8n-nodes-actp repo](https://github.com/agirails/n8n-nodes-actp); look for the `pay-per-translation.json` template. Import via n8n's **Workflows → Import from File**. All credential references are placeholders you'll wire to your own credentials before the first run. ### Testing the workflow Before going to mainnet, run end-to-end on testnet: 1. Switch the AGIRAILS API credential to `network: testnet`. 2. Use the **AGIRAILS Mint Test USDC** utility node to mint $100 testnet USDC into your Smart Wallet. 3. Trigger a test job via another agent calling `agent.request('translate', { input: { text: 'Hello', target: 'es' }, budget: 0.10 })`. 4. Verify the full lifecycle: AgentRegistry shows the agent, the testnet tx settles, your Postgres log shows the earning row. 5. Re-deploy with `network: mainnet`, fund the same SCW address with real USDC. The exact same workflow ships from testnet to mainnet with one credential change. ## Receiving payments A provider workflow looks like: ```text AGIRAILS Provide (trigger: service=summarize, ideal=$0.30) ↓ HTTP Request: my LLM ↓ Set: { summary, model, sourceUrl } ↓ AGIRAILS Settle (submit deliverable, transition → DELIVERED) ``` The Settle node automatically generates the EAS attestation + publishes the Web Receipt to IPFS. Your workflow doesn't see the on-chain side at all. ## Wallet funding The same rule as the SDK: `wallet=auto` makes gas free but you still need USDC in the Smart Wallet to fund escrows. For testnet, mint via the SDK's MockUSDC contract (use the **AGIRAILS Mint Test USDC** utility node). For mainnet, fund the SCW address (shown in credential setup) with real USDC from any wallet. ## Error handling The node throws a typed n8n error on: - `InsufficientFundsError` → SCW doesn't have enough USDC - `DeadlineExpiredError` → timeout exceeded - `DisputeRaisedError` → fires only on consumer side, when provider raised against you - `MissingCredentialsError` → bad keystore / wrong password Wire these into n8n's **Error Workflow** to alert. ## Constraints - The community node calls the **TS SDK only**. Python-only features (e.g. `actp serve` policy YAML) aren't reachable from n8n; run those as a sidecar process. - Worker scale: n8n's single-instance execution model limits concurrency to ~5 parallel jobs per worker. For higher throughput, scale n8n with queue mode or move to a direct SDK integration. ## See also - [Consumer agent](/recipes/consumer-agent): the SDK pattern this node wraps - [Provider agent](/recipes/provider-agent): same, for the earning side - [Keystore + deployment](/recipes/keystore-and-deployment): generating `ACTP_KEYSTORE_BASE64` for the credential - [`n8n-nodes-actp` on GitHub](https://github.com/agirails/n8n-nodes-actp) --- **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`](/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](https://github.com/agirails/docs/blob/main/scripts/verify-recipes.ts)). ============================================================ LangChain integration ============================================================ # LangChain integration LangChain agents reason in loops: "what tool do I need next?" → "call it" → "decide based on output". AGIRAILS slots in as just another tool, except the tool calls cost USDC, and the agent only pays after successful delivery. There's no official `langchain-agirails` package; the integration is ten lines of glue around the SDK. ## The pattern ```ts const agirails = new Agent({ network: 'mainnet', wallet: 'auto', // reads keystore via env per AIP-13 }); await agirails.start(); const translateTool = tool( async ({ text, target }) => { const result = await agirails.request('translate', { input: { text, target }, budget: 0.10, timeout: 30_000, }); return result.result.translated; }, { name: 'translate', description: 'Translate text via the AGIRAILS network. Pays up to $0.10 USDC per call.', schema: z.object({ text: z.string().describe('text to translate'), target: z.string().describe('ISO-639 language code (e.g. "es", "fr")'), }), } ); // Now use it in any LangChain agent const agent = createReactAgent({ llm: new ChatAnthropic({ model: 'claude-sonnet-4-6' }), tools: [translateTool], }); const result = await agent.invoke({ messages: [{ role: 'user', content: 'Translate "Hello" to Spanish, then to French.' }], }); ``` ```python from langchain_core.tools import tool from pydantic import BaseModel, Field from agirails import Agent, AgentConfig agirails = Agent(AgentConfig( name="LangChainTool", network="mainnet", # Wallet/keystore via env vars per AIP-13. )) await agirails.start() class TranslateInput(BaseModel): text: str = Field(description="text to translate") target: str = Field(description="ISO-639 language code") @tool("translate", args_schema=TranslateInput) async def translate(text: str, target: str) -> str: """Translate text via the AGIRAILS network. Pays up to $0.10 USDC per call.""" result = await agirails.request( "translate", input={"text": text, "target": target}, budget=0.10, timeout=30, ) return result.result["translated"] ``` The LLM decides when to call `translate`; each invocation costs you USDC. The total spend bubbles up via `agirails.stats.totalSpent` (TS) / `agirails.stats.total_spent` (Python). ## Budget controls You almost always want a per-invocation cap **and** a session cap to prevent runaway loops: ```ts // V1 SDK doesn't emit a 'payment:sent' event; enforce session cap // at call sites by reading agent.stats.totalSpent before each request: const SESSION_CAP = 5.00; // $5 total async function paidCall(tool: () => Promise): Promise { if (agent.stats.totalSpent >= SESSION_CAP) { throw new Error('session budget exhausted'); } return tool(); } // Then in each LangChain tool: const r = await paidCall(() => agent.request(...)) ``` LangChain agents can get caught in retry loops if a tool errors transiently; without a cap, the next thing you notice is a depleted wallet. ## Full scenario: paid research assistant A LangGraph research workflow that decides which paid services to use, calls them, and reports back. The pattern most LangChain users actually want to ship. ```ts const agirails = new Agent({ name: 'ResearchTooling', network: 'mainnet', wallet: 'auto', // reads keystore via env per AIP-13 }); await agirails.start(); // V1 SDK has no behavior.budget config; enforce caps at the call site. const PER_QUERY_CAP = 0.50; const DAILY_CAP = 20.00; let queryStartSpend = 0; let dailySpendBudget = DAILY_CAP; // reset at UTC midnight in your supervisor function guardSpend(currentTotalSpent: number) { const perQuery = currentTotalSpent - queryStartSpend; if (perQuery > PER_QUERY_CAP) { throw new Error(`per-query cap exceeded: ${perQuery} > ${PER_QUERY_CAP}`); } if (currentTotalSpent > dailySpendBudget) { throw new Error('daily cap exceeded, halting'); } } // Tool 1: fetch web content (a paid AGIRAILS provider somewhere) const fetchWeb = tool( async ({ url }) => { const r = await agirails.request('fetch-content', { input: { url, format: 'markdown' }, budget: 0.05, timeout: 15_000, }); return r.result.markdown; }, { name: 'fetch_web', description: 'Fetch a URL and return clean markdown. Costs up to $0.05 USDC.', schema: z.object({ url: z.string().url() }), } ); // Tool 2: translate (paid AGIRAILS provider) const translate = tool( async ({ text, target }) => { const r = await agirails.request('translate', { input: { text, target }, budget: 0.10, timeout: 30_000, }); return r.result.translated; }, { name: 'translate', description: 'Translate text. Costs up to $0.10 USDC per call.', schema: z.object({ text: z.string(), target: z.string().describe('ISO-639 code (es, fr, de, ja, ...)'), }), } ); // Tool 3: summarize (paid AGIRAILS provider, bulk; uses standard adapter, not x402) const summarize = tool( async ({ text, sentences }) => { const r = await agirails.request('summarize', { input: { text, sentences }, budget: 0.30, timeout: 45_000, }); return r.result.summary; }, { name: 'summarize', description: 'Summarize text in N sentences. Costs up to $0.30 USDC per call.', schema: z.object({ text: z.string(), sentences: z.number().int().min(1).max(20), }), } ); const researcher = createReactAgent({ llm: new ChatAnthropic({ model: 'claude-opus-4-7' }), tools: [fetchWeb, translate, summarize], }); // Run a research task; capture the start-of-query spend baseline queryStartSpend = agirails.stats.totalSpent; guardSpend(agirails.stats.totalSpent); const out = await researcher.invoke({ messages: [{ role: 'user', content: 'Find the latest paper on sheaf cohomology from agirails.io and give me a 3-sentence summary in Croatian.', }], }); guardSpend(agirails.stats.totalSpent); // re-check after the LangChain loop finishes console.log('answer:', out.messages.at(-1)?.content); console.log('spent:', agirails.stats.totalSpent, 'USDC'); ``` What happens at runtime: 1. The LLM decides it needs `fetch_web` → calls it on agirails.io → pays ~$0.04 USDC 2. The LLM decides it needs `summarize` → calls it with 3-sentence target → pays ~$0.30 USDC 3. The LLM decides it needs `translate` to Croatian → calls it → pays ~$0.08 USDC 4. Returns answer to the user; total spend visible in `agirails.stats.totalSpent` (~$0.42) The app-level `guardSpend()` enforces per-query and daily caps. Drop a `guardSpend()` inside each tool's `async` body to catch runaway spend mid-loop. The V1 SDK doesn't ship `behavior.budget` config; this app-side guard is the canonical pattern until V2. ## Exposing your LangChain workflow as a provider The other direction is also useful: your LangChain workflow *is* the service. ```ts agirails.provide('llm-research', async (job, ctx) => { const langchainAgent = createReactAgent({ llm, tools: [...] }); const out = await langchainAgent.invoke({ messages: [{ role: 'user', content: job.input.query }], }); return { answer: out.messages.at(-1).content }; }); await agirails.start(); ``` Other agents can now discover and call `llm-research`, each call funding your LangChain run. With `wallet=auto` your provider earns net (USDC) on every settled call. ## Tracing LangChain's tracing (LangSmith) and AGIRAILS's transaction log are independent. LangSmith records the reasoning trace, AGIRAILS records the on-chain transactions. Correlate via `txId`: ```ts const result = await agirails.request('translate', { input: { text, target }, budget: 0.10, metadata: { langsmithRunId: traceContext.runId }, }); // later: result.transaction.id ↔ langsmithRunId in your dashboard ``` ## See also - [Consumer agent](/recipes/consumer-agent): the underlying pattern - [Autonomous agent](/recipes/autonomous-agent): when the LangChain agent should also provide - [CrewAI integration](/recipes/crewai): same idea, different framework - [LangChain docs](https://js.langchain.com/docs/concepts/tools/) --- **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`](/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](https://github.com/agirails/docs/blob/main/scripts/verify-recipes.ts)). ============================================================ CrewAI integration ============================================================ # CrewAI integration CrewAI lets you compose multiple LLM agents into a crew with hand-offs. By default, internal agent calls are free (same process, same wallet). With AGIRAILS, you can make any inter-agent call go through ACTP, useful when: - The agents belong to **different owners** sharing a workflow. - You want per-call accountability (cost, attestation, audit trail). - You're decomposing a crew into deployable microservices each charging for itself. ## Wrap a tool CrewAI tools are just Python callables. Make one that calls AGIRAILS: ```python from crewai_tools import BaseTool from agirails import Agent class AgirailsServiceTool(BaseTool): name: str = "agirails_call" description: str = "Call a remote AGIRAILS provider and pay in USDC." def __init__(self, agent: Agent, service: str, budget: float): super().__init__() self._agent = agent self._service = service self._budget = budget def _run(self, **kwargs) -> str: result = asyncio.run(self._agent.request( self._service, input=kwargs, budget=self._budget, timeout=30, )) return result.result ``` ## Use it in a crew ```python from crewai import Agent as CrewAgent, Task, Crew from agirails import Agent as AgirailsAgent, AgentConfig agirails = AgirailsAgent(AgentConfig( name="CrewWallet", network="mainnet", # Wallet/keystore via env vars per AIP-13. )) translate_tool = AgirailsServiceTool(agirails, "translate", budget=0.10) summarize_tool = AgirailsServiceTool(agirails, "summarize", budget=0.30) researcher = CrewAgent( role="researcher", goal="answer user questions with research", tools=[translate_tool, summarize_tool], llm="claude-sonnet-4-6", ) task = Task( description="Summarize the latest news on AI from a French source.", expected_output="3-sentence summary in English.", agent=researcher, ) crew = Crew(agents=[researcher], tasks=[task]) result = crew.kickoff() ``` When the researcher decides it needs to translate French → English, it calls `translate` which costs $0.10 USDC. Summary call costs $0.30. Total visible in `agirails.stats.total_spent` (Python snake_case; the TS field is `agirails.stats.totalSpent`). ## Exposing a CrewAI workflow as a provider The whole crew can be a single AGIRAILS service: ```python @agirails.provide("research-summary") async def research_summary(job, ctx): crew = build_crew(query=job.input["query"]) # constructs your CrewAI graph result = crew.kickoff() return {"answer": str(result), "model": "crew-v2"} await agirails.start() ``` Now other agents discover and pay for `research-summary`. Each call funds one crew execution. The crew internally might **also** call paid sub-services: full economic chain. ## Full scenario: research crew with budgeted hand-offs A four-agent crew where each agent owns its own AGIRAILS wallet, transacts with the others, and respects per-agent + per-crew budget caps. Production-shape, not toy. ```python from crewai import Agent as CrewAgent, Crew, Task from crewai_tools import BaseTool from agirails import Agent as AgirailsAgent, AgentConfig from agirails.errors import DisputeRaisedError # Each crew agent owns a separate AGIRAILS wallet: different EOAs, separate budgets, # separate reputations. This is the pattern when crew members may belong to different # owners or need distinct accounting. # Note: budget caps shown below are conceptual V2 patterns; the V1 AgentBehavior # dataclass exposes only auto_accept, concurrency, timeout, retry. For V1, enforce # spending caps in your own crew wrapper (see callback in AgirailsServiceTool below). researcher_wallet = AgirailsAgent(AgentConfig( name="Researcher", network="mainnet", # Keystore via env: ACTP_KEYSTORE_BASE64 + ACTP_KEY_PASSWORD (per AIP-13). # Use distinct keystores per crew member to keep wallets separate. )) analyst_wallet = AgirailsAgent(AgentConfig( name="Analyst", network="mainnet", )) writer_wallet = AgirailsAgent(AgentConfig( name="Writer", network="mainnet", )) class AgirailsServiceTool(BaseTool): name: str = "agirails_call" description: str = "Call a remote AGIRAILS provider and pay in USDC." def __init__(self, agent, service, budget, daily_cap=10.00): super().__init__() self._agent = agent self._service = service self._budget = budget self._daily_cap = daily_cap def _run(self, **kwargs): # V1 has no behavior.budget on Agent; enforce caps in the wrapper. # agent.stats.total_spent is the running total since agent.start(). if self._agent.stats.total_spent >= self._daily_cap: return {"error": f"daily cap ${self._daily_cap} exhausted for {self._agent.config.name}"} try: result = asyncio.run(self._agent.request( self._service, input=kwargs, budget=self._budget, timeout=60, )) return result.result except DisputeRaisedError as e: return {"error": f"provider raised dispute: {e}"} # Crew agents researcher = CrewAgent( role="researcher", goal="gather raw information on the user's topic from the open web", tools=[AgirailsServiceTool(researcher_wallet, "fetch-content", budget=0.05)], llm="claude-opus-4-7", ) analyst = CrewAgent( role="analyst", goal="extract key insights from the researcher's findings", tools=[AgirailsServiceTool(analyst_wallet, "extract-insights", budget=0.50)], llm="claude-opus-4-7", ) writer = CrewAgent( role="writer", goal="produce the final report in the user's language", tools=[AgirailsServiceTool(writer_wallet, "translate", budget=0.20)], llm="claude-opus-4-7", ) # Sequential tasks with hand-offs research_task = Task( description="Research the latest AI agent payment protocols. Focus on AGIRAILS, x402, Skyfire, Nevermined.", expected_output="A list of 5-10 raw findings with sources.", agent=researcher, ) analysis_task = Task( description="Compare the protocols on: trust model, fee structure, decentralization, dispute handling.", expected_output="A structured analysis with one paragraph per dimension.", agent=analyst, context=[research_task], ) writing_task = Task( description="Write a 500-word summary in Croatian for a technical audience.", expected_output="The final report in Croatian, markdown-formatted.", agent=writer, context=[analysis_task], ) crew = Crew( agents=[researcher, analyst, writer], tasks=[research_task, analysis_task, writing_task], ) result = crew.kickoff() print("final:", result) print(f"researcher spent: ${researcher_wallet.stats.total_spent:.2f}") print(f"analyst spent: ${analyst_wallet.stats.total_spent:.2f}") print(f"writer spent: ${writer_wallet.stats.total_spent:.2f}") ``` What this gives you in production: - **Three independent wallets, three independent budgets.** A runaway researcher can't drain the writer's wallet. The per-tool `daily_cap` is the hard ceiling, enforced in `AgirailsServiceTool._run` before each call. - **Three independent reputation tracks.** Each crew agent builds its own [AgentRegistry](/reference/glossary#agentregistry) reputation, useful when crew members get reused across projects. - **Per-agent observability.** `agent.stats.total_spent` and `payment:received` events surface per-wallet, correlate by `crew_kickoff_id` in your logger. - **Graceful budget exhaustion.** When an agent hits its cap, its tool returns `{"error": "..."}` instead of crashing the crew. The next agent in the chain decides how to handle the partial result. For a 50-call research crew at typical prices, total spend lands around $5-8 USDC. With per-wallet `daily_cap` enforced in the wrapper, you can never overspend a Friday afternoon's curiosity. ## Per-call vs per-crew billing | Pattern | When | |---|---| | Per-call paid tools | Different owners share the crew; each tool is a deployable service | | Per-crew provider | One owner, exposes the whole crew as a single composable service | | Hybrid | Crew is owned, but uses outside paid services (translation, fetching) | The hybrid is most common: you own the research workflow, but the LLM gateway, translation, and content-fetching are each paid AGIRAILS services. Margin = your asking price − sub-task costs − ACTP fee. ## Cost discipline CrewAI workflows can be unpredictable; agent reasoning loops can balloon. V1 has no `behavior.budget` config on the SDK side, so enforce caps in your wrapper (as shown in `AgirailsServiceTool._run` above) and at the crew kickoff boundary: ```python PER_KICKOFF_CAP = 1.00 start_spent = agirails.stats.total_spent result = crew.kickoff() spent_this_kickoff = agirails.stats.total_spent - start_spent if spent_this_kickoff > PER_KICKOFF_CAP: # Log + alert. The crew already ran; the cap is observational, not preventive. # For preventive enforcement, gate each tool call against the per-kickoff # budget inside AgirailsServiceTool._run (see daily_cap pattern above). log.warn(f"crew exceeded per-kickoff cap: ${spent_this_kickoff:.2f} > ${PER_KICKOFF_CAP}") ``` For preventive enforcement, extend the per-wallet `daily_cap` check inside `AgirailsServiceTool._run` to also track a per-kickoff counter passed in via constructor. CrewAI tools can return `{"error": "..."}` to short-circuit the rest of the crew gracefully. ## See also - [LangChain integration](/recipes/langchain): same pattern, different framework - [Autonomous agent](/recipes/autonomous-agent): single-process version of the same idea - [Provider agent](/recipes/provider-agent): how the underlying provide() works - [CrewAI docs](https://docs.crewai.com/) --- **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`](/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](https://github.com/agirails/docs/blob/main/scripts/verify-recipes.ts)). ============================================================ Claude Code plugin recipes ============================================================ # Claude Code plugin recipes The `agirails` Claude Code plugin gives Claude Code three things: 1. **Slash commands** for common [ACTP](/reference/glossary#actp) dev tasks (`/agirails:agent-new`, `/agirails:wallet-check`, `/agirails:audit`). 2. **Skills** that Claude auto-invokes when relevant (integration wizard, security auditor, testing assistant). 3. **Agents**: pre-configured sub-agents specialized for AGIRAILS work (integration wizard, security audit, test writing). Install via the Claude Code marketplace: in your editor, `/plugin install agirails`. ## Verify install ```bash # In a Claude Code session > /agirails:wallet-check # Plugin command runs your local agent's config check; if no keystore is present # it walks you through generating one and funding it. ``` ## Common usage patterns ### Scaffold a new agent ``` > /agirails:agent-new translation-service ``` This generates a working TS project (or Python with `--python` flag) with: - `package.json` (or `pyproject.toml`) pinned to current SDK versions - A `provide('translation-service', …)` skeleton with TODO markers - `.actp/keystore.json` generation prompt - A test harness that round-trips a mock job - `actp deploy:check`-passing config out of the box You can then ask Claude to fill in the LLM call inside the handler: ``` > The handler should call Anthropic's Claude API with a system prompt for translation. ``` ### Specialized AGIRAILS agent When working inside a project that imports `@agirails/sdk` or `agirails`, the plugin auto-suggests the **`agirails:integration-wizard`** agent for end-to-end integration work. Trigger it explicitly: ``` > Use the agirails:integration-wizard subagent to add USDC payments to this Express app. ``` The agent has internal knowledge of: - Current SDK version surface (`@agirails/sdk@4.0.0`, `agirails@3.0.1`) - The `wallet=auto` pattern, when to use it - Best practices for keystores per [AIP-13](/reference/glossary#aip-13) - [AIP-2.1](/reference/glossary#aip-21) quote-channel patterns - Common error paths and fallback handling ### Security audit on commit ``` > Use the agirails:security-auditor subagent to review the staged changes. ``` The auditor looks for: - Committed private keys (any 64-char hex matching `0x[a-f0-9]{64}`) - Missing budget caps on `agent.request()` calls (V1: app-level enforcement) - x402 server endpoints that accept the `X-Payment` header without on-server signature verification - Hardcoded recipient addresses (should be config) - `wallet: 'eoa'` in production code (warns; you might have a reason) - Missing dispute handlers in long-running providers ### Test writing ``` > Use the agirails:testing-assistant subagent to write tests for src/handlers/translate.ts. ``` It generates tests using the SDK's built-in MockRuntime: your tests run without touching any chain, but verify the full state machine path. ## Skill auto-invocation These skills fire automatically when Claude recognizes their triggers: | Skill | Fires when | |---|---| | `agirails:integration-wizard` | User mentions "integrate AGIRAILS", "add payments", or imports the SDK | | `agirails:security-auditor` | Plugin sees writes to `wallet`/`payment`/`config` files | | `agirails:testing-assistant` | User asks for tests, or creates a test file referencing the SDK | You can disable auto-invocation per skill via Claude Code settings. ## Composition with other plugins The AGIRAILS plugin doesn't conflict with `vercel`, `claude-code-guide`, `feature-dev`, or framework-specific plugins. A typical full-stack flow: ``` > /feature-dev start (plans the feature) > Use the agirails:integration-wizard subagent to scaffold the payments piece. > /feature-dev implement > Use the agirails:security-auditor subagent to review. > /feature-dev review ``` ## Updating The plugin pins SDK versions in its internal templates. When the SDK releases a major version, the plugin gets a corresponding bump. Update with `/plugin update agirails`. If you're integrating against an older SDK (e.g. `@agirails/sdk@3.x`) explicitly pin in your scaffold: ``` > /agirails:agent-new my-service --sdk-version 3.5.0 ``` ## See also - [Claude Code integration overview](/start/ai-environment/claude-code): broader Claude Code setup - [Claude skill (Anthropic Skills)](/start/ai-environment/claude-skill): the other distribution channel - [Consumer agent](/recipes/consumer-agent): what the wizard generates for the consumer side - [Provider agent](/recipes/provider-agent): same for providers - [Plugin source on GitHub](https://github.com/agirails/claude-plugin) --- **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`](/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](https://github.com/agirails/docs/blob/main/scripts/verify-recipes.ts)). ============================================================ Reference ============================================================ # Reference Everything in this section is **auto-extracted from source** by the [truth-ledger manifest](/reference/glossary#truth-ledger-manifest) pipeline ([`scripts/build-truth-ledger.ts`](https://github.com/agirails/docs/blob/main/scripts/build-truth-ledger.ts)). The full machine-readable manifest is at [`/sdk-manifest.json`](/sdk-manifest.json). | Surface | Source | Page | |---|---|---| | [actp CLI](/reference/glossary#actp-cli) commands | `actp --help` walk + Commander/Typer introspection | [/reference/cli](/reference/cli) | | Contract addresses | `actp-kernel/deployments/*.json` + live [Sourcify EXACT_MATCH](/reference/glossary#sourcify-exact_match) | [/reference/contracts](/reference/contracts) | | TS SDK API | `sdk-js/src/index.ts` barrel | [/reference/sdk-js](/reference/sdk-js) | | Python SDK API | `agirails/__init__.py` `__all__` | [/reference/sdk-python](/reference/sdk-python) | | Error codes | Both SDKs' error modules | [/reference/errors](/reference/errors) | | [MCP](/reference/glossary#mcp) tools | `agirails-mcp-server/src/index.ts` TOOLS array | [/reference/mcp-server](/reference/mcp-server) | | AGIRAILS.md V4 schema | `parseAgirailsMdV4` interface | [/reference/agirails-md-v4](/reference/agirails-md-v4) | ## How current is this? Every Vercel deploy regenerates the manifest on a daily cron + on every SDK release tag (via repository_dispatch). The `_generatedAt` field in the manifest reports last refresh. The `_sourceVersions` field reports which SDK / mcp-server versions are pinned. ============================================================ actp CLI reference ============================================================ # actp CLI reference **TypeScript SDK CLI** (`actp`): 42 commands · **Python SDK CLI** (`actp`): 39 commands · **Manifest generated**: 2026-05-29 17:33:29 UTC Both SDKs expose the same `actp` binary, with the command tree extracted directly from each runtime's Commander/Typer registration. ## TS CLI commands | Command | Subcommands | |---|---| | `actp agent` | _(none)_ | | `actp autopublish` | _(none)_ | | `actp balance` | _(none)_ | | `actp batch` | _(none)_ | | `actp claim` | _(none)_ | | `actp claim-code` | _(none)_ | | `actp config` | `config show`, `config set`, `config get` | | `actp deploy:check` | _(none)_ | | `actp deploy:env` | _(none)_ | | `actp diff` | _(none)_ | | `actp find` | _(none)_ | | `actp health` | _(none)_ | | `actp init` | _(none)_ | | `actp mint` | _(none)_ | | `actp negotiate` | _(none)_ | | `actp pay` | _(none)_ | | `actp publish` | _(none)_ | | `actp pull` | _(none)_ | | `actp register` | _(none)_ | | `actp repair` | _(none)_ | | `actp request` | _(none)_ | | `actp serve` | _(none)_ | | `actp simulate` | `simulate pay`, `simulate fee` | | `actp test` | _(none)_ | | `actp time` | `time show`, `time advance`, `time set` | | `actp tx` | `tx create`, `tx status`, `tx list`, `tx deliver`, `tx settle`, `tx cancel` | | `actp verify` | _(none)_ | | `actp watch` | _(none)_ | ## Python CLI commands | Command | Subcommands | |---|---| | `actp autopublish` | _(none)_ | | `actp balance` | _(none)_ | | `actp batch` | _(none)_ | | `actp claim` | _(none)_ | | `actp claim-code` | _(none)_ | | `actp config` | `config show`, `config set`, `config get` | | `actp deploy` | `deploy env`, `deploy check` | | `actp diff` | _(none)_ | | `actp find` | _(none)_ | | `actp health` | _(none)_ | | `actp init` | _(none)_ | | `actp mint` | _(none)_ | | `actp negotiate` | _(none)_ | | `actp pay` | _(none)_ | | `actp publish` | _(none)_ | | `actp pull` | _(none)_ | | `actp register` | _(none)_ | | `actp repair` | _(none)_ | | `actp request` | _(none)_ | | `actp serve` | _(none)_ | | `actp simulate` | `simulate pay`, `simulate fee` | | `actp test` | _(none)_ | | `actp time` | `time show`, `time advance`, `time set` | | `actp tx` | `tx status`, `tx list`, `tx transition` | | `actp verify` | _(none)_ | | `actp watch` | _(none)_ | ## Cross-SDK divergences **TypeScript-only** (7): `agent`, `deploy:check`, `deploy:env`, `tx cancel`, `tx create`, `tx deliver`, `tx settle` **Python-only** (4): `deploy`, `deploy check`, `deploy env`, `tx transition` ## See also - [Reference overview](/reference) - [Truth-ledger manifest (raw JSON)](/sdk-manifest.json) ============================================================ Contracts reference ============================================================ # Contracts reference All deployed AGIRAILS contracts, both networks, with live [Sourcify EXACT_MATCH](/reference/glossary#sourcify-exact_match) verification. This page will render content from the [truth-ledger manifest](/reference/glossary#truth-ledger-manifest). Today the manifest exposes the surface; the rendered view comes in Wave A.2 (next iteration). For now, browse the raw machine-readable manifest at [`/sdk-manifest.json`](/sdk-manifest.json) under the relevant section. ## See also - [Reference overview](/reference) - [Truth-ledger manifest (raw JSON)](/sdk-manifest.json) ============================================================ Base mainnet contracts (chain 8453) ============================================================ # Base mainnet contracts (chain 8453) Production ACTP kernel + supporting contracts on Base mainnet, with live Sourcify exact-match verification. **Chain ID**: `8453` · **Block explorer**: [https://basescan.org](https://basescan.org) · **Manifest generated**: 2026-05-29 17:33:29 UTC ## USDC **Address**: [`0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`](https://basescan.org/address/0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913) **Verification**: 🏛️ External token (Circle USDC etc.) > Circle's official USDC on Base mainnet (not deployed by us) ## ACTPKernel **Address**: [`0x048c811352e8a3fECd5b0Ec4AA2c2b94083CC842`](https://basescan.org/address/0x048c811352e8a3fECd5b0Ec4AA2c2b94083CC842) **Verification**: ✅ Sourcify exact match _(checked 2026-05-29 17:33:29 UTC)_ | Field | Value | |---|---| | Deploy block | `46212266` | | Deploy tx | [`0x0ec9bec21a33…`](https://basescan.org/tx/0x0ec9bec21a33f3f316993e3b85e550132ef082651e0be0a75eb5237f08ee1104) | | Solidity compiler | `0.8.34` | | platformFeeBps | `100` (1%) | | disputeBondBps | `500` (5%) | | MIN_DISPUTE_BOND | `1` USDC | > Initial Base mainnet deployment 2026-05-19. Includes: AIP-14 dispute bond ($1 MIN, requesterPenaltyBpsLocked), INV-30 per-tx disputeBondBpsLocked, M-2 mediator timelock fix, M-3 mediator hot-swap fee lock, AIP-5 platformFeeBpsLocked, ERC-8004 agentId tracking, dispute initiator + bond return logic. Admin=Pauser=feeRecipient=Treasury Safe (2-of-4); registry set via 2-day timelock per scheduleAgentRegistryUpdate post-deploy step. ## EscrowVault **Address**: [`0x262D5912A9612F0c66dA5d13B4E678D50ebC44b5`](https://basescan.org/address/0x262D5912A9612F0c66dA5d13B4E678D50ebC44b5) **Verification**: ✅ Sourcify exact match _(checked 2026-05-29 17:33:29 UTC)_ | Field | Value | |---|---| | Deploy block | `46212268` | | Deploy tx | [`0x55b44cd01b57…`](https://basescan.org/tx/0x55b44cd01b57ef7a9bf5c3672dd439c6261899880b60ac1ef9a570a415b81e64) | | Solidity compiler | `0.8.34` | > Approved by kernel via Safe transaction (see postDeploy.escrowVaultApproved). Holds locked USDC + dispute bonds for all in-flight transactions. ## AgentRegistry **Address**: [`0x64Cb18bfb3CC1aCb1370a3B01613391D3561a009`](https://basescan.org/address/0x64Cb18bfb3CC1aCb1370a3B01613391D3561a009) **Verification**: ✅ Sourcify exact match _(checked 2026-05-29 17:33:29 UTC)_ | Field | Value | |---|---| | Deploy block | `46212269` | | Deploy tx | [`0xdbf9158ef85f…`](https://basescan.org/tx/0xdbf9158ef85f8d064e2f6624e7d34ed5852b720edb7af7d0dfe548b19ba66c89) | | Solidity compiler | `0.8.34` | > Reputation + endpoint registry. Wired to kernel via scheduleAgentRegistryUpdate + executeAgentRegistryUpdate (2-day timelock). ## ArchiveTreasury **Address**: [`0x6159A80Ce8362aBB2307FbaB4Ed4D3F4A4231Acc`](https://basescan.org/address/0x6159A80Ce8362aBB2307FbaB4Ed4D3F4A4231Acc) **Verification**: ✅ Sourcify exact match _(checked 2026-05-29 17:33:29 UTC)_ | Field | Value | |---|---| | Deploy block | `46212270` | | Deploy tx | [`0x4ebddaebd203…`](https://basescan.org/tx/0x4ebddaebd203afceb5d3592a927787061b8b2981d0a289bb740c397ef1ab5481) | | Solidity compiler | `0.8.34` | | Owner | `0x61fE58E9EdB380EA65EC74bD364D9D2cba30B7f2` | > Ownership transferred from deployer (0x1c4e1E…EB1A) to Treasury Safe (0x61fE58E9…b7f2) at block 46212430 on 2026-05-19. Safe has full pause/admin authority. ## See also - [Truth-ledger manifest (raw JSON)](/sdk-manifest.json) - [Base Sepolia contracts](/reference/contracts/base-sepolia) - [Protocol fees + dispute bonds](/protocol/fees) - [Escrow mechanism](/protocol/escrow) ============================================================ Base Sepolia contracts (chain 84532) ============================================================ # Base Sepolia contracts (chain 84532) Test ACTP kernel + supporting contracts on Base Sepolia, with live Sourcify exact-match verification. **Chain ID**: `84532` · **Block explorer**: [https://sepolia.basescan.org](https://sepolia.basescan.org) · **Manifest generated**: 2026-05-29 17:33:29 UTC ## MockUSDC **Address**: [`0x444b4e1A65949AB2ac75979D5d0166Eb7A248Ccb`](https://sepolia.basescan.org/address/0x444b4e1A65949AB2ac75979D5d0166Eb7A248Ccb) **Verification**: 🏛️ External token (Circle USDC etc.) ## ACTPKernel **Address**: [`0x9d25A874f046185d9237Cd4954C88D2B74B0021b`](https://sepolia.basescan.org/address/0x9d25A874f046185d9237Cd4954C88D2B74B0021b) **Verification**: ✅ Sourcify exact match _(checked 2026-05-29 17:33:29 UTC)_ | Field | Value | |---|---| | Deploy block | `41725686` | | Deploy tx | [`0xe976b7005b4a…`](https://sepolia.basescan.org/tx/0xe976b7005b4a35f066fedb782d69974f2f491b3608d2803375fa46987546db5c) | | Solidity compiler | `0.8.34` | > Redeployed 2026-05-19 alongside mainnet to align ABI shape (INV-30 disputeBondBpsLocked + AIP-14 / d9c6e8e requesterPenaltyBpsLocked). Same source as mainnet kernel 0x048c8113…. Storage layout incompatible with prior 0xE83cba71…. ## EscrowVault **Address**: [`0x7dF07327090efcA73DCBa70414aA3131Fc6d2efB`](https://sepolia.basescan.org/address/0x7dF07327090efcA73DCBa70414aA3131Fc6d2efB) **Verification**: ✅ Sourcify exact match _(checked 2026-05-29 17:33:29 UTC)_ | Field | Value | |---|---| | Deploy block | `41725687` | | Deploy tx | [`0x0194b83d02fe…`](https://sepolia.basescan.org/tx/0x0194b83d02fe31482d346db0d445a1245ec8ac35d66995a465af217c22beca59) | | Solidity compiler | `0.8.34` | > Redeployed 2026-05-19 alongside kernel (immutable kernel ref forces fresh deploy). ## AgentRegistry **Address**: [`0xD91F9aBfBf60b4a2Fd5317ab0cDF3F44faB5D656`](https://sepolia.basescan.org/address/0xD91F9aBfBf60b4a2Fd5317ab0cDF3F44faB5D656) **Verification**: ✅ Sourcify exact match _(checked 2026-05-29 17:33:29 UTC)_ | Field | Value | |---|---| | Deploy block | `41725688` | | Deploy tx | [`0xa0b7ac965505…`](https://sepolia.basescan.org/tx/0xa0b7ac96550590f8269ca245e7f6ba618ab5821dfe33d3e48f919d7bd650484c) | | Solidity compiler | `0.8.34` | > Redeployed 2026-05-19 alongside kernel. scheduleAgentRegistryUpdate called at deploy time. executeAgentRegistryUpdate callable after 2026-05-21 19:41 UTC (2-day timelock). ## AGIRAILSIdentityRegistry **Address**: [`0xce9749c768b425fab0daa0331047d1340ec99a88`](https://sepolia.basescan.org/address/0xce9749c768b425fab0daa0331047d1340ec99a88) **Verification**: 🔓 Not verified > Redeployed 2026-04-02 with audit fix L-2: zero-address guard in _changeOwner ## ArchiveTreasury **Address**: [`0x2eE4f7bE289fc9EFC2F9f2D6E53e50abDF23A3eb`](https://sepolia.basescan.org/address/0x2eE4f7bE289fc9EFC2F9f2D6E53e50abDF23A3eb) **Verification**: ✅ Sourcify exact match _(checked 2026-05-29 17:33:29 UTC)_ | Field | Value | |---|---| | Deploy block | `41725689` | | Deploy tx | [`0xd3dc0dd2f630…`](https://sepolia.basescan.org/tx/0xd3dc0dd2f630abb32480061a51522efde9234539687ccbae037f56ef2da36808) | | Solidity compiler | `0.8.34` | | Owner | `0x42a2f11555b9363fb7ebdcdc76d7cb26e01dcb00` | > Redeployed 2026-05-19 alongside kernel. setArchiveTreasury already invoked on the new kernel at deploy time. owner = deployer EOA (Sepolia admin); no transferOwnership needed since admin == deployer. ## X402Relay **Address**: [`0x110b25bb3d45c40dfcf34bb451aa7069b2a1cb3b`](https://sepolia.basescan.org/address/0x110b25bb3d45c40dfcf34bb451aa7069b2a1cb3b) **Verification**: ✅ Sourcify exact match _(checked 2026-05-29 17:33:29 UTC)_ [warning] This contract is marked deprecated and retained only for legacy integration paths. Do not use for new code. | Field | Value | |---|---| | Deploy block | `40239726` | ## See also - [Truth-ledger manifest (raw JSON)](/sdk-manifest.json) - [Base mainnet contracts](/reference/contracts/base-mainnet) - [Protocol fees + dispute bonds](/protocol/fees) - [Escrow mechanism](/protocol/escrow) ============================================================ TypeScript SDK reference ============================================================ # TypeScript SDK reference **Package**: `@agirails/sdk@4.0.0` · **Total symbols**: 283 · **Source**: `src/index.ts` The TypeScript SDK is tiered to match the depth of integration you need: | Tier | Symbols | When to use | |---|---|---| | **[Simple](/reference/sdk-js/simple)** | 19 | First integration and most production code: `Agent`, `request`, `provide`, `pay()` | | **[Standard + Advanced](/reference/sdk-js/standard)** | 255 | Production-stable depth: adapters, builders, signers, runtime helpers, orchestrators | | _Internal_ | 9 | Not part of the public API contract; documented separately if at all | ## Quick orientation Two entry points cover most real code: ```ts // Simple: one-shot request / provide, or the long-lived Agent class // Standard: direct adapter / builder usage ``` Pick the lowest tier that covers your use case; you can always drop deeper if needed. ## See also - [Python SDK reference](/reference/sdk-python) - [Errors reference](/reference/errors) - [CLI reference](/reference/cli) - [Consumer agent recipe](/recipes/consumer-agent): Simple tier in practice - [Provider agent recipe](/recipes/provider-agent): Simple tier in practice ============================================================ TypeScript SDK: Standard + Advanced ============================================================ # TypeScript SDK: Standard + Advanced **Package**: `@agirails/sdk@4.0.0` · **Symbols in this view**: 255 · **Total TS surface**: 283 · **Manifest generated**: 2026-05-29 17:33:29 UTC Every entry below is **auto-extracted from the SDK source itself** via the truth-ledger pipeline. The cross-SDK status column tells you whether the symbol has a Python counterpart, is TS-only, or has signature drift the parity sprint is tracking. For detailed per-symbol docs (parameters, return types, examples) consult the source JSDoc in [`agirails/sdk-js`](https://github.com/agirails/sdk-js). Auto-extraction of JSDoc into rendered prose is a deferred enhancement; currently this page is the **index of what exists**, not the prose reference. ## Standard tier (242 symbols) Production-stable surface for non-trivial integrations: adapters, builders, message-signing utilities, escrow + state-machine helpers, error classes, type definitions. If your code touches the kernel directly (rather than going through `Agent`), it lives here. | Symbol | Kind | Summary | Cross-SDK status | |---|---|---|---| | `ACTP_FEEDBACK_TAGS` | _re-export_ | ACTP feedback tags for ERC-8004 Reputation. | 🟢 TS-only (Python parity pending) | | `ACTPClientInfo` | _re-export_ | Result of creating an ACTPClient. | ✅ in-sync | | `ACTPClientMode` | _re-export_ | Supported modes for ACTPClient. | ✅ in-sync | | `ACTPError` | _re-export_ | Base ACTP Error. | ✅ in-sync | | `ACTPFeedbackTag` | _re-export_ | Type for ACTP feedback tags. | 🟢 TS-only (Python parity pending) | | `ACTPKernel` | _re-export_ | ACTPKernel - Smart contract wrapper Reference: Yellow Paper §3 (ACTP Kernel Specification) | ✅ in-sync | | `AdapterSelectionResult` | _re-export_ | Result of adapter selection with potential ERC-8004 resolution. | 🟢 TS-only (Python parity pending) | | `AdapterTransactionState` | _re-export_ | Valid transaction states across all adapters. | 🟢 TS-only (Python parity pending) | | `Address` | _re-export_ | Address utilities | ✅ in-sync | | `AgentBalance` | _re-export_ | Agent balance information | ✅ in-sync | | `AgentLifecycleError` | _re-export_ | Agent lifecycle error. | ✅ in-sync | | `AgentRegistry` | _re-export_ | AgentRegistry - Agent Identity & Reputation SDK Module (AIP-7) | ✅ in-sync | | `AgentStats` | _re-export_ | Agent statistics | ✅ in-sync | | `AgentStatus` | _re-export_ | Agent lifecycle states | ✅ in-sync | | `ARCHIVE_BUNDLE_TYPE` | _re-export_ | Archive bundle type identifier | ✅ in-sync | | `ArchiveAttestation` | _re-export_ | EAS attestation reference | ✅ in-sync | | `ArchiveBundle` | _re-export_ | Archive Bundle (AIP-7 §4.4 - Minimal Hash-First) | ✅ in-sync | | `ArchiveBundleBuilder` | _re-export_ | ArchiveBundleBuilder - Fluent builder for archive bundles | ✅ in-sync | | `ArchiveChainId` | _re-export_ | Supported chain IDs for archive bundles | ✅ in-sync | | `ArchiveFinalState` | _re-export_ | Final transaction states that can be archived | ✅ in-sync | | `ArchiveHashes` | _re-export_ | Cryptographic hashes for verification | ✅ in-sync | | `ArchiveParticipants` | _re-export_ | Transaction participants (addresses only, not full profiles) | ✅ in-sync | | `ArchiveReferences` | _re-export_ | IPFS CID references to full content | ✅ in-sync | | `ArchiveSettlement` | _re-export_ | Settlement information | ✅ in-sync | | `ArchiveSignatures` | _re-export_ | Cryptographic signatures for self-verification | ✅ in-sync | | `ArchiveTags` | _re-export_ | Arweave/Irys tags for archive bundles | ✅ in-sync | | `ArweaveConfig` | _re-export_ | Configuration for Arweave client via Irys | ✅ in-sync | | `ArweaveDownloadError` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `ArweaveTimeoutError` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `ArweaveUploadError` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `ArweaveUploadResult` | _re-export_ | Result of uploading to Arweave | ✅ in-sync | | `AutoWalletConfig` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `AutoWalletProvider` | _re-export_ | _(no summary)_ | ✅ in-sync | | `BaseAdapter` | _re-export_ | Abstract base adapter with shared parsing utilities. | ✅ in-sync | | `BatchedPayParams` | _re-export_ | Parameters for batched ACTP payment. | 🟢 TS-only (Python parity pending) | | `BatchedPayResult` | _re-export_ | Result of a batched ACTP payment via AA wallet. | 🟢 TS-only (Python parity pending) | | `BelowCostBehavior` | _re-export_ | Pricing behavior when job budget is below cost (would lose money) | 🟢 TS-only (Python parity pending) | | `BelowPriceBehavior` | _re-export_ | Pricing behavior when job budget is below calculated price | 🟢 TS-only (Python parity pending) | | `BlockchainRuntimeConfig` | _re-export_ | Configuration for BlockchainRuntime | 🟢 TS-only (Python parity pending) | | `buildChannelPath` | _re-export_ | Path pattern builders use / handlers expect. | 🟢 TS-only (Python parity pending) | | `BuyerNegotiationContext` | _re-export_ | AIP-2.1 negotiation context: wires the orchestrator into the NegotiationChannel transport. All fields optional: without them the orchestrator runs the legacy fixed-price flow only (no counters). | 🟢 TS-only (Python parity pending) | | `BuyerPolicy` | _re-export_ | _(no summary)_ | ✅ in-sync | | `Bytes32` | _re-export_ | Bytes32 utilities | ✅ in-sync | | `calculatePrice` | _re-export_ | Calculate price for a job based on pricing strategy | ✅ in-sync | | `CandidateStats` | _re-export_ | _(no summary)_ | ✅ in-sync | | `ChannelPayload` | _re-export_ | Wire payload posted by the client and parsed by the handler. Discriminated by `type` so the same endpoint serves both directions. | 🟢 TS-only (Python parity pending) | | `computeContentHash` | _re-export_ | Compute keccak256 hash of canonical JSON | ✅ in-sync | | `ContentNotFoundError` | _re-export_ | _(no summary)_ | ✅ in-sync | | `ContractPausedError` | _re-export_ | Error thrown when the contract is paused. | ✅ in-sync | | `CounterAcceptBuilder` | _re-export_ | _(no summary)_ | ✅ in-sync | | `CounterAcceptMessage` | _re-export_ | Provider-signed acceptance of a buyer's counter-offer. | ✅ in-sync | | `CounterAcceptParams` | _re-export_ | _(no summary)_ | ✅ in-sync | | `CounterDecision` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `CounterOfferBuilder` | _re-export_ | _(no summary)_ | ✅ in-sync | | `CounterOfferMessage` | _re-export_ | Counter-offer message: the buyer's reply to a provider quote. | ✅ in-sync | | `CounterOfferParams` | _re-export_ | Parameters to build a counter-offer. `counteredAt`, `nonce`, and `signature` are filled by the builder. | ✅ in-sync | | `CreateTransactionParams` | _re-export_ | Parameters for creating a new transaction. | ✅ in-sync | | `createUsedAttestationTracker` | _re-export_ | Factory to create attestation tracker | ✅ in-sync | | `Deadline` | _re-export_ | Deadline utilities | ✅ in-sync | | `DeadlineExpiredError` | _re-export_ | _(no summary)_ | ⚠️ diverged (cross-SDK signature mismatch) | | `DeadlinePassedError` | _re-export_ | Error thrown when the deadline has passed. | ⚠️ diverged (cross-SDK signature mismatch) | | `DedupStore` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `DEFAULT_DEADLINE_SECONDS` | _re-export_ | Default deadline offset in seconds (24 hours). Used when no deadline is specified in transaction parameters. | ✅ in-sync | | `DEFAULT_DISPUTE_WINDOW_SECONDS` | _re-export_ | Default dispute window in seconds (2 days). Used when no dispute window is specified in transaction parameters. | ✅ in-sync | | `DEFAULT_PRICING_STRATEGY` | _re-export_ | Default pricing strategy | 🟢 TS-only (Python parity pending) | | `DeliveredMessage` | _re-export_ | Per-message metadata the channel attaches when delivering. | 🟢 TS-only (Python parity pending) | | `DeliveryFailedError` | _re-export_ | Provider failed to deliver result. | ✅ in-sync | | `DeliveryProofBuilder` | _re-export_ | DeliveryProofBuilder - Main Builder Class | ✅ in-sync | | `DIDManager` | _re-export_ | DIDManager - Manage DID attributes and delegates (AIP-7 §2.2) | ✅ in-sync | | `DIDResolver` | _re-export_ | DIDResolver - Resolve DIDs to DID Documents (AIP-7 §2.2) | ✅ in-sync | | `DisputeRaisedError` | _re-export_ | Dispute raised on transaction. | ✅ in-sync | | `DisputeResolution` | _re-export_ | Dispute resolution split | 🟢 TS-only (Python parity pending) | | `DisputeWindow` | _re-export_ | Dispute window utilities | ✅ in-sync | | `DisputeWindowActiveError` | _re-export_ | Error thrown when dispute window is still active. | ✅ in-sync | | `DownloadResult` | _re-export_ | Result of downloading content | ✅ in-sync | | `DownloadTimeoutError` | _re-export_ | _(no summary)_ | ✅ in-sync | | `EASConfig` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `EconomicParams` | _re-export_ | Economic parameters (fee structure) | 🟢 TS-only (Python parity pending) | | `envelopeChainId` | _re-export_ | Extract the chainId carried inside the envelope's signed message. | 🟢 TS-only (Python parity pending) | | `envelopeTxId` | _re-export_ | Extract the txId carried inside the envelope's signed message. Useful for impls that need to dispatch by txId without parsing. | 🟢 TS-only (Python parity pending) | | `EOAWalletProvider` | _re-export_ | _(no summary)_ | ✅ in-sync | | `ERC8004_DEFAULT_RPC` | _re-export_ | Default RPC URLs for each network. Used when no custom RPC is provided. | 🟢 TS-only (Python parity pending) | | `ERC8004_IDENTITY_REGISTRY` | _re-export_ | ERC-8004 Identity Registry addresses per network. | 🟢 TS-only (Python parity pending) | | `ERC8004_REPUTATION_REGISTRY` | _re-export_ | ERC-8004 Reputation Registry addresses per network. | 🟢 TS-only (Python parity pending) | | `ERC8004Agent` | _re-export_ | Agent identity resolved from ERC-8004 Identity Registry. | 🟢 TS-only (Python parity pending) | | `ERC8004AgentMetadata` | _re-export_ | Agent metadata schema from ERC-8004 agentURI. | 🟢 TS-only (Python parity pending) | | `ERC8004Bridge` | _re-export_ | Bridge for reading from ERC-8004 Identity Registry. | ✅ in-sync | | `ERC8004BridgeConfig` | _re-export_ | Configuration for ERC8004Bridge. | 🟢 TS-only (Python parity pending) | | `ERC8004Error` | _re-export_ | Custom error for ERC-8004 operations. | 🟢 TS-only (Python parity pending) | | `ERC8004ErrorCode` | _re-export_ | ERC-8004 error codes. | 🟢 TS-only (Python parity pending) | | `ERC8004Network` | _re-export_ | Networks with ERC-8004 registry deployments. | 🟢 TS-only (Python parity pending) | | `EscrowNotFoundError` | _re-export_ | Error thrown when an escrow is not found. | ✅ in-sync | | `EscrowRelease` | _re-export_ | Escrow release details | ✅ in-sync | | `EscrowVault` | _re-export_ | EscrowVault - Escrow contract wrapper | ✅ in-sync | | `EventMonitor` | _re-export_ | EventMonitor - Listen to blockchain events | ✅ in-sync | | `FilebaseConfig` | _re-export_ | Configuration for Filebase S3-compatible IPFS client | ✅ in-sync | | `FileBasedUsedAttestationTracker` | _re-export_ | File-based Used Attestation Tracker for persistence | ✅ in-sync | | `FileSizeLimitExceededError` | _re-export_ | _(no summary)_ | ✅ in-sync | | `formatUSDC` | _re-export_ | Format USDC wei to human-readable string | ✅ in-sync | | `generateSecureNonce` | _re-export_ | Generate a cryptographically secure random nonce (bytes32) | ✅ in-sync | | `generateSecureNonces` | _re-export_ | Generate an array of secure nonces | ✅ in-sync | | `getCachedAddress` | _re-export_ | Get cached address from last resolvePrivateKey() call. Works for env-var, base64, and keystore resolution paths. | 🟢 TS-only (Python parity pending) | | `getNetwork` | _re-export_ | Get network configuration by name (returns deep clone to prevent mutation) | 🟢 TS-only (Python parity pending) | | `HandlerContext` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `HandlerResult` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `hashServiceMetadata` | _re-export_ | Hash service description for on-chain storage | ✅ in-sync | | `IACTPRuntime` | _re-export_ | Runtime interface for ACTP protocol operations. | ✅ in-sync | | `IAdapter` | _re-export_ | Common interface for all payment adapters. | 🟢 TS-only (Python parity pending) | | `IMockRuntime` | _re-export_ | Extended runtime interface for mock mode. | ✅ in-sync | | `IncomingRequest` | _re-export_ | Incoming request surface: the minimum the orchestrator needs to decide whether + at what price to quote. Extracted from the on-chain transaction plus any off-chain context the caller has (service type, consumer DID). | 🟢 TS-only (Python parity pending) | | `InMemoryNonceManager` | _re-export_ | In-Memory Nonce Manager Simple implementation using Map for per-message-type nonce tracking | 🟢 TS-only (Python parity pending) | | `InMemoryReceivedNonceTracker` | _re-export_ | In-Memory Received Nonce Tracker | ✅ in-sync | | `InMemoryUsedAttestationTracker` | _re-export_ | In-Memory Used Attestation Tracker | ✅ in-sync | | `InsufficientBalanceError` | _re-export_ | Error thrown when there are insufficient funds for an operation. | ✅ in-sync | | `InsufficientFundsError` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `InvalidAddressError` | _re-export_ | _(no summary)_ | ✅ in-sync | | `InvalidAmountError` | _re-export_ | Error thrown when an invalid amount is provided. | ✅ in-sync | | `InvalidArweaveTxIdError` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `InvalidCIDError` | _re-export_ | _(no summary)_ | ✅ in-sync | | `InvalidStateTransitionError` | _re-export_ | Error thrown when an invalid state transition is attempted. | ✅ in-sync | | `IPFSClient` | _re-export_ | IPFS Client Interface (from DeliveryProofBuilder) | 🟢 TS-only (Python parity pending) | | `IPFSClientConfig` | _re-export_ | IPFS Client Configuration | 🟢 TS-only (Python parity pending) | | `IPFSUploadResult` | _re-export_ | Result of uploading to IPFS | ✅ in-sync | | `IReceivedNonceTracker` | _re-export_ | Interface for tracking received nonces | ✅ in-sync | | `IrysCurrency` | _re-export_ | Supported Irys payment tokens | ✅ in-sync | | `IrysNetwork` | _re-export_ | Irys network | ✅ in-sync | | `isAdapter` | _re-export_ | Type guard to check if an object implements IAdapter. | 🟢 TS-only (Python parity pending) | | `isCounterAcceptEnvelope` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `isCounterOfferEnvelope` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `isQuoteEnvelope` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `isValidNonce` | _re-export_ | Validate nonce format (must be bytes32) | ✅ in-sync | | `IUsedAttestationTracker` | _re-export_ | Interface for tracking used attestations | ✅ in-sync | | `IWalletProvider` | _re-export_ | Wallet provider interface. | ✅ in-sync | | `MAX_DEADLINE_DAYS` | _re-export_ | Maximum deadline in days (10 years). Prevents integer overflow in deadline calculations. | ✅ in-sync | | `MAX_DEADLINE_HOURS` | _re-export_ | Maximum deadline in hours (10 years). Prevents integer overflow in deadline calculations. | ✅ in-sync | | `MIN_AMOUNT_WEI` | _re-export_ | Minimum transaction amount in USDC wei (6 decimals). $0.05 minimum per AGIRAILS protocol specification. | ✅ in-sync | | `MockAccount` | _re-export_ | Mock account representing a user's wallet state. | ✅ in-sync | | `MockBlockchain` | _re-export_ | Mock blockchain state simulating chain parameters. | ✅ in-sync | | `MockChannelConfig` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `MockEscrow` | _re-export_ | Mock escrow representing funds locked for a transaction. | ✅ in-sync | | `MockEvent` | _re-export_ | Mock blockchain event recorded during transaction lifecycle. | ✅ in-sync | | `MockState` | _re-export_ | Complete mock state persisted to `.actp/mock-state.json`. | ✅ in-sync | | `MockTransaction` | _re-export_ | Mock transaction representing an ACTP transaction in the mock blockchain. | ✅ in-sync | | `NegotiationChannel` | _re-export_ | Transport-agnostic AIP-2.1 message bus. | 🟢 TS-only (Python parity pending) | | `NegotiationMessage` | _re-export_ | Discriminated union of every signed message type that can flow over a NegotiationChannel. The channel's `post` accepts any of these; the channel's verify-on-receive ensures only well-formed signed messages reach the sub… | 🟢 TS-only (Python parity pending) | | `NegotiationResult` | _re-export_ | _(no summary)_ | ✅ in-sync | | `NetworkConfig` | _re-export_ | Network configuration | 🟢 TS-only (Python parity pending) | | `NetworkError` | _re-export_ | _(no summary)_ | ✅ in-sync | | `NetworkOption` | _re-export_ | Network options | 🟢 TS-only (Python parity pending) | | `NonceManager` | _re-export_ | Nonce Manager Interface (from DeliveryProofBuilder) | ✅ in-sync | | `NoProviderFoundError` | _re-export_ | No provider found for the requested service. | ✅ in-sync | | `OrchestratorConfig` | _re-export_ | _(no summary)_ | ✅ in-sync | | `parseUSDC` | _re-export_ | Parse USDC amount string to wei (6 decimals) | ✅ in-sync | | `PolicyResult` | _re-export_ | _(no summary)_ | ✅ in-sync | | `PolicyViolation` | _re-export_ | _(no summary)_ | ✅ in-sync | | `PriceCalculation` | _re-export_ | Price calculation result | ✅ in-sync | | `PricingStrategy` | _re-export_ | Pricing strategy configuration | ✅ in-sync | | `ProgressEvent` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `ProtocolCreateParams` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `Provider` | _re-export_ | Provider interface | ✅ in-sync | | `ProviderBalance` | _re-export_ | Provider balance | 🟢 TS-only (Python parity pending) | | `ProviderOrchestratorConfig` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `ProviderPolicy` | _re-export_ | What this agent provides + at what terms. | 🟢 TS-only (Python parity pending) | | `ProviderPolicyEngine` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `ProviderPolicyResult` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `ProviderPolicyViolation` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `ProviderRejectedError` | _re-export_ | Provider rejected the job. | ✅ in-sync | | `ProviderStats` | _re-export_ | Provider statistics | 🟢 TS-only (Python parity pending) | | `ProviderStatus` | _re-export_ | Provider status | ✅ in-sync | | `QueryCapExceededError` | _re-export_ | Registry Query Cap Exceeded Error (L-4) | ✅ in-sync | | `QuoteBuilder` | _re-export_ | QuoteBuilder - Main Builder Class Reference: AIP-2 §6.1 | ✅ in-sync | | `QuoteChannelClientConfig` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `QuoteChannelHandlerConfig` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `QuoteDecision` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `QuoteEvaluation` | _re-export_ | Decision for a single incoming provider quote. | 🟢 TS-only (Python parity pending) | | `QuoteForEvaluation` | _re-export_ | Minimal quote shape evaluateQuote operates on. Keeps DecisionEngine decoupled from the full QuoteMessage type + signature verification (that's BuyerOrchestrator's job). | 🟢 TS-only (Python parity pending) | | `QuoteOffer` | _re-export_ | _(no summary)_ | ✅ in-sync | | `QuoteResult` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `RateLimiter` | _re-export_ | Simple sliding window rate limiter | ✅ in-sync | | `RelayChannel` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `RelayChannelConfig` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `ReportDisputeParams` | _re-export_ | Parameters for reporting a dispute resolution. | 🟢 TS-only (Python parity pending) | | `ReportResult` | _re-export_ | Result of a reputation report. | 🟢 TS-only (Python parity pending) | | `ReportSettlementParams` | _re-export_ | Parameters for reporting a successful settlement. | 🟢 TS-only (Python parity pending) | | `ReputationFeedback` | _re-export_ | Feedback entry for ERC-8004 Reputation Registry. | 🟢 TS-only (Python parity pending) | | `ReputationReporter` | _re-export_ | Reporter for submitting ACTP settlement outcomes to ERC-8004 Reputation. | ✅ in-sync | | `ReputationReporterConfig` | _re-export_ | Configuration for ReputationReporter. | 🟢 TS-only (Python parity pending) | | `ReputationSummary` | _re-export_ | Aggregated reputation summary for an agent. | 🟢 TS-only (Python parity pending) | | `RequestResult` | _re-export_ | Result of request() function | ✅ in-sync | | `RequestStatus` | _re-export_ | Request status updates | 🟢 TS-only (Python parity pending) | | `resolvePrivateKey` | _re-export_ | Auto-resolve private key: env var → base64 keystore → file keystore → undefined. Never logs or prints the key itself. | 🟢 TS-only (Python parity pending) | | `RoundResult` | _re-export_ | _(no summary)_ | ✅ in-sync | | `ScoredCandidate` | _re-export_ | _(no summary)_ | ✅ in-sync | | `ScoringWeights` | _re-export_ | _(no summary)_ | ✅ in-sync | | `Semaphore` | _re-export_ | Simple semaphore for limiting concurrent operations | ✅ in-sync | | `ServiceConfigError` | _re-export_ | Service configuration error. | ✅ in-sync | | `ServiceCost` | _re-export_ | Cost configuration for a service | 🟢 TS-only (Python parity pending) | | `ServiceHash` | _re-export_ | Service metadata utilities for ACTP transactions | ✅ in-sync | | `ServiceMetadata` | _re-export_ | Service metadata structure | ✅ in-sync | | `SessionMapping` | _re-export_ | _(no summary)_ | ✅ in-sync | | `shortenAddress` | _re-export_ | Shorten Ethereum address for display | ✅ in-sync | | `SignatureVerificationError` | _re-export_ | _(no summary)_ | ✅ in-sync | | `StandardTransactionParams` | _re-export_ | Parameters for creating a transaction (standard level). | ✅ in-sync | | `StateHelpers` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `StorageAuthenticationError` | _re-export_ | _(no summary)_ | ✅ in-sync | | `StorageError` | _re-export_ | _(no summary)_ | ✅ in-sync | | `StorageInsufficientBalanceError` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `StorageRateLimitError` | _re-export_ | _(no summary)_ | ✅ in-sync | | `Subscription` | _re-export_ | Unsubscribe handle. `unsubscribe()` MUST be idempotent; callers should call it exactly once but the impl tolerates double-unsubscribe to keep cleanup paths simple. | 🟢 TS-only (Python parity pending) | | `SwapExecutionError` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `TimeoutError` | _re-export_ | Request timeout error. | 🟢 TS-only (Python parity pending) | | `Transaction` | _re-export_ | ACTP Transaction Reference: Yellow Paper §3.1 | ✅ in-sync | | `TransactionNotFoundError` | _re-export_ | Error thrown when a transaction is not found. | ✅ in-sync | | `TransactionRevertedError` | _re-export_ | _(no summary)_ | ✅ in-sync | | `TransactionState` | _re-export_ | ACTP Transaction State enum matching the smart contract states. | ✅ in-sync | | `TransactionStateValue` | _re-export_ | Numeric state values matching the smart contract enum. | ✅ in-sync | | `TransactionStatus` | _re-export_ | Transaction status returned by getStatus(). | 🟢 TS-only (Python parity pending) | | `UploadTimeoutError` | _re-export_ | _(no summary)_ | ✅ in-sync | | `URLValidationConfig` | _re-export_ | Security: URL validation configuration for SSRF prevention | 🟢 TS-only (Python parity pending) | | `USDC` | _re-export_ | USDC amount utilities (6 decimal places) | ✅ in-sync | | `validateArchiveBundle` | _re-export_ | Validate archive bundle structure | ✅ in-sync | | `ValidationError` | _re-export_ | Adapter-layer ValidationError: extends ACTPError-based ValidationError so that `instanceof` checks work against the public SDK export. Accepts a single message string for convenience in adapter code. | ✅ in-sync | | `VerifyOnChainResult` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `verifyQuoteHashOnChain` | _re-export_ | Cross-reference an off-chain QuoteMessage against the hash stored on chain in `tx.metadata` (or equivalent: MockTransaction.quoteHash). | 🟢 TS-only (Python parity pending) | | `VerifySource` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `WalletInfo` | _re-export_ | Information about the wallet provider. | ✅ in-sync | | `WalletOption` | _re-export_ | Wallet configuration options | 🟢 TS-only (Python parity pending) | | `WalletTier` | _re-export_ | Wallet tier: determines gas behavior. | ✅ in-sync | | `WalletTransactionReceipt` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `WalletTransactionRequest` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `X402Adapter` | _re-export_ | _(no summary)_ | ✅ in-sync | | `X402AdapterConfig` | _re-export_ | Configuration for X402Adapter. | 🟢 TS-only (Python parity pending) | | `X402AmountExceededError` | _re-export_ | Thrown when the server's required payment amount exceeds the client's `maxAmountPerTx` safety cap. Default cap is $1 USDC. | 🟢 TS-only (Python parity pending) | | `X402ApprovalFailedError` | _re-export_ | Thrown when the one-time Permit2 approve transaction fails for any reason other than paymaster gate (which throws X402PublishRequiredError instead). | 🟢 TS-only (Python parity pending) | | `X402ConfigError` | _re-export_ | Thrown when X402Adapter constructor receives invalid config. | 🟢 TS-only (Python parity pending) | | `X402Error` | _re-export_ | Base class for all x402-related errors. Catch this to handle any x402 failure in one clause. | 🟢 TS-only (Python parity pending) | | `X402NetworkNotAllowedError` | _re-export_ | Thrown when the server's payment-required response offers no network that the client's `allowedNetworks` configuration accepts. | 🟢 TS-only (Python parity pending) | | `X402PaymentFailedError` | _re-export_ | Thrown when the payment attempt returns a non-2xx HTTP status after signing and submitting the payment payload. | 🟢 TS-only (Python parity pending) | | `X402PublishRequiredError` | _re-export_ | Thrown when paymaster rejects sponsored tx because agent isn't published in AgentRegistry. The one-time Permit2 approve needed to enable Smart Wallet x402 payments requires the agent to pass the paymaster policy gate. | 🟢 TS-only (Python parity pending) | | `X402SettlementProofMissingError` | _re-export_ | Thrown when HTTP payment completes (200 OK) but the server did not return a `payment-response` header confirming on-chain settlement. | 🟢 TS-only (Python parity pending) | | `X402SignatureFailedError` | _re-export_ | Thrown when walletProvider.signTypedData() fails to produce a signature. | 🟢 TS-only (Python parity pending) | | `X402UnsupportedWalletError` | _re-export_ | Thrown when a Smart Wallet tries to pay a server that only offers the EIP-3009 transferWithAuthorization scheme. | 🟢 TS-only (Python parity pending) | ## Advanced tier (13 symbols) Lower-level building blocks rarely needed in application code: orchestrators, dedup stores, policy engines, raw runtime interfaces. Stable APIs but the contract is "you know what you're doing." Reach here only when the standard tier is genuinely insufficient. | Symbol | Kind | Summary | Cross-SDK status | |---|---|---|---| | `ArweaveClient` | _re-export_ | ArweaveClient - Permanent storage on Arweave via Irys | ✅ in-sync | | `BlockchainRuntime` | _re-export_ | BlockchainRuntime - Production blockchain implementation | 🟢 TS-only (Python parity pending) | | `BuyerOrchestrator` | _re-export_ | _(no summary)_ | ✅ in-sync | | `DecisionEngine` | _re-export_ | _(no summary)_ | ✅ in-sync | | `EASHelper` | _re-export_ | EASHelper - utility wrapper for Ethereum Attestation Service interactions | ✅ in-sync | | `FilebaseClient` | _re-export_ | FilebaseClient - IPFS storage via Filebase S3 API | ✅ in-sync | | `MessageSigner` | _re-export_ | MessageSigner - Cryptographic signing for ACTP messages with EIP-712 Reference: Yellow Paper §11.4.2 | ✅ in-sync | | `PolicyEngine` | _re-export_ | _(no summary)_ | ✅ in-sync | | `ProofGenerator` | _re-export_ | ProofGenerator - Content hashing and delivery proofs Reference: Yellow Paper §11.4.1 | ✅ in-sync | | `ProviderOrchestrator` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `QuoteChannelClient` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `QuoteChannelHandler` | _re-export_ | _(no summary)_ | 🟢 TS-only (Python parity pending) | | `SessionStore` | _re-export_ | _(no summary)_ | ✅ in-sync | ## See also - [Python SDK reference](/reference/sdk-python) - [TypeScript Simple](/reference/sdk-js/simple) - [Errors reference](/reference/errors): exception class catalog - [Truth-ledger manifest (raw JSON)](/sdk-manifest.json) - [Source: `@agirails/sdk@4.0.0`](https://github.com/agirails/sdk-js) ============================================================ Python SDK reference ============================================================ # Python SDK reference **Package**: `agirails@3.0.1` · **Total symbols**: 277 · **Manifest generated**: 2026-05-29 17:33:29 UTC Every entry below is **auto-extracted from the SDK source itself** via the truth-ledger pipeline. The cross-SDK status column tells you whether the symbol has a TypeScript counterpart, is Python-only, or has signature drift the parity sprint is tracking. For detailed per-symbol docs (parameters, return types, examples) consult the source docstrings in [`agirails/sdk-python`](https://github.com/agirails/sdk-python). Auto-extraction of docstrings into rendered prose is a deferred enhancement; currently this page is the **index of what exists**, not the prose reference. ## Tier distribution | Tier | Symbols | |---|---| | [Simple](#simple-tier) | 18 | | [Standard](#standard-tier) | 245 | | [Advanced](#advanced-tier) | 9 | | _Internal_ (not in this view) | 5 | ## Quick orientation ```python # Simple: one-shot request / provide, or the long-lived Agent class from agirails import request, provide, Agent # Standard: direct kernel / adapter usage from agirails import ACTPClient, CounterOfferMessage ``` ## Simple tier {#simple-tier} The smallest surface that produces a working transaction. If you're integrating for the first time, this is where to start. Includes top-level convenience exports (`request`, `provide`, `serviceDirectory`) plus the high-level `Agent` class and `pay()` flow. For most integrations this is all you need; you only drop to lower tiers when you need to customise something the convenience layer doesn't expose. **18 symbols.** | Symbol | Kind | Summary | Cross-SDK status | |---|---|---|---| | `ACTPClient` | `undefined` | Main client for AGIRAILS SDK. | ✅ in-sync | | `ACTPClientConfig` | `undefined` | Configuration for ACTPClient.create(). | ✅ in-sync | | `Agent` | `undefined` | Agent for processing jobs via ACTP protocol. | ✅ in-sync | | `AgentConfig` | `undefined` | Main agent configuration. | ✅ in-sync | | `BasicAdapter` | `undefined` | Basic-level adapter for ACTP transactions. | ✅ in-sync | | `BasicPayParams` | `undefined` | Parameters for basic pay() method. | ✅ in-sync | | `BasicPayResult` | `undefined` | Result from basic pay() method. | ✅ in-sync | | `Job` | `undefined` | Represents a job to be processed by an agent. | ✅ in-sync | | `JobContext` | `undefined` | Context passed to job handlers. | ✅ in-sync | | `JobHandler` | `undefined` | _(no summary)_ | ✅ in-sync | | `MockRuntime` | `undefined` | Mock runtime implementation for ACTP protocol. | ✅ in-sync | | `provide` | `undefined` | Register a service with the global provider. | ✅ in-sync | | `ProvideOptions` | `undefined` | Options for the provide function. | ✅ in-sync | | `request` | `undefined` | Request a service from a provider. | ✅ in-sync | | `RequestOptions` | `undefined` | Options for the request function. | ✅ in-sync | | `ServiceConfig` | `undefined` | Configuration for a specific service. | ✅ in-sync | | `StandardAdapter` | `undefined` | Standard adapter for granular ACTP transaction control. | ✅ in-sync | | `State` | `undefined` | ACTP transaction states. | ✅ in-sync | ## Standard tier {#standard-tier} Production-stable surface for non-trivial integrations: adapters, builders, message-signing utilities, escrow + state-machine helpers, error classes, type definitions. If your code touches the kernel directly (rather than going through `Agent`), it lives here. **245 symbols.** | Symbol | Kind | Summary | Cross-SDK status | |---|---|---|---| | `ACTPClientInfo` | `undefined` | Client information. | ✅ in-sync | | `ACTPClientMode` | `undefined` | _(no summary)_ | ✅ in-sync | | `ACTPError` | `undefined` | Base exception for all ACTP protocol errors. | ✅ in-sync | | `ACTPEvent` | `undefined` | Base class for ACTP protocol events. | 🟢 Python-only (TS parity pending) | | `ACTPKernel` | `undefined` | ACTPKernel contract wrapper for ACTP protocol interactions. | ✅ in-sync | | `ACTPTimeoutError` | `undefined` | _(no summary)_ | 🟢 Python-only (TS parity pending) | | `Address` | `undefined` | Ethereum address utilities. | ✅ in-sync | | `AgentBalance` | `undefined` | Agent balance information. | ✅ in-sync | | `AgentBehavior` | `undefined` | Agent behavior configuration. | 🟢 Python-only (TS parity pending) | | `AgentDID` | `undefined` | Decentralized Identifier for an agent. | 🟢 Python-only (TS parity pending) | | `AgentLifecycleError` | `undefined` | Raised when an agent lifecycle operation fails. | ✅ in-sync | | `AgentProfile` | `undefined` | Agent profile from the registry. | 🟢 Python-only (TS parity pending) | | `AgentRegistry` | `undefined` | AgentRegistry contract wrapper for agent discovery and management. | ✅ in-sync | | `AgentStats` | `undefined` | Agent statistics. | ✅ in-sync | | `AgentStatus` | `undefined` | Agent lifecycle status. | ✅ in-sync | | `ARCHIVE_BUNDLE_TYPE` | `undefined` | _(no summary)_ | ✅ in-sync | | `ARCHIVE_SCHEMA_VERSION` | `undefined` | _(no summary)_ | 🟢 Python-only (TS parity pending) | | `ArchiveAttestation` | `undefined` | EAS attestation reference. | ✅ in-sync | | `ArchiveBundle` | `undefined` | Archive Bundle (AIP-7 §4.4 - Minimal Hash-First). | ✅ in-sync | | `ArchiveBundleBuilder` | `undefined` | Fluent builder for creating archive bundles. | ✅ in-sync | | `ArchiveChainId` | `undefined` | _(no summary)_ | ✅ in-sync | | `ArchiveFinalState` | `undefined` | _(no summary)_ | ✅ in-sync | | `ArchiveHashes` | `undefined` | Cryptographic hashes for verification. | ✅ in-sync | | `ArchiveParticipants` | `undefined` | Transaction participants (addresses only, not full profiles). | ✅ in-sync | | `ArchiveReferences` | `undefined` | IPFS CID references to full content. | ✅ in-sync | | `ArchiveSettlement` | `undefined` | Settlement information. | ✅ in-sync | | `ArchiveSignatures` | `undefined` | Cryptographic signatures for self-verification. | ✅ in-sync | | `ArchiveTags` | `undefined` | Arweave/Irys tags for archive bundles. | ✅ in-sync | | `ArweaveConfig` | `undefined` | Configuration for Arweave client via Irys. | ✅ in-sync | | `ArweaveUploadResult` | `undefined` | Result of uploading to Arweave. | ✅ in-sync | | `Attestation` | `undefined` | EAS Attestation data. | 🟢 Python-only (TS parity pending) | | `AutoWalletProvider` | `undefined` | Tier 1 (Auto) wallet provider with CoinbaseSmartWallet + Paymaster. | ✅ in-sync | | `BaseAdapter` | `undefined` | Base adapter providing shared utilities for all adapters. | ✅ in-sync | | `BuyerPolicy` | `undefined` | _(no summary)_ | ✅ in-sync | | `Bytes32` | `undefined` | Bytes32 utilities for transaction IDs and hashes. | ✅ in-sync | | `calculate_price` | `undefined` | Calculate pricing for a job. | ✅ in-sync | | `CandidateStats` | `undefined` | _(no summary)_ | ✅ in-sync | | `canonical_json_dumps` | `undefined` | Serialize object to canonical JSON string. | 🟢 Python-only (TS parity pending) | | `CheckStatusResult` | `undefined` | Result from check_status() method. | 🟢 Python-only (TS parity pending) | | `CircuitBreakerConfig` | `undefined` | Circuit breaker configuration for gateway health tracking. | 🟢 Python-only (TS parity pending) | | `compute_content_hash` | `undefined` | Compute keccak256 hash of content (Ethereum-compatible). | ✅ in-sync | | `compute_domain_separator` | `undefined` | Compute EIP-712 domain separator. | 🟢 Python-only (TS parity pending) | | `compute_json_hash` | `undefined` | Compute keccak256 hash of canonical JSON. | 🟢 Python-only (TS parity pending) | | `compute_result_hash` | `undefined` | Compute result hash using keccak256 over canonical JSON. | 🟢 Python-only (TS parity pending) | | `compute_service_type_hash` | `undefined` | Compute keccak256 hash of service type. | 🟢 Python-only (TS parity pending) | | `compute_transaction_id` | `undefined` | Pre-compute ACTP transaction ID. | 🟢 Python-only (TS parity pending) | | `compute_type_hash` | `undefined` | Compute EIP-712 type hash. | 🟢 Python-only (TS parity pending) | | `Constraints` | `undefined` | _(no summary)_ | 🟢 Python-only (TS parity pending) | | `ContentNotFoundError` | `undefined` | Raised when content cannot be found in storage. | ✅ in-sync | | `ContentProof` | `undefined` | Proof for content authenticity. | 🟢 Python-only (TS parity pending) | | `ContractPausedError` | `undefined` | Raised when attempting an operation on a paused contract. | ✅ in-sync | | `CostModel` | `undefined` | Cost model for service pricing. | 🟢 Python-only (TS parity pending) | | `CounterAcceptBuilder` | `undefined` | Build and verify AIP-2.1 counter-accept messages. | ✅ in-sync | | `CounterAcceptMessage` | `undefined` | Provider-signed acceptance of a buyer's counter-offer. | ✅ in-sync | | `CounterAcceptParams` | `undefined` | Parameters to build a counter-accept message. | ✅ in-sync | | `CounterOfferBuilder` | `undefined` | Build and verify AIP-2.1 counter-offer messages. | ✅ in-sync | | `CounterOfferJustification` | `undefined` | Optional human-readable reasoning + market data. | 🟢 Python-only (TS parity pending) | | `CounterOfferMessage` | `undefined` | Counter-offer message: buyer's reply to a provider quote. | ✅ in-sync | | `CounterOfferParams` | `undefined` | Parameters to build a counter-offer. | ✅ in-sync | | `create_did_from_address` | `undefined` | Create a DID string from an Ethereum address. | 🟢 Python-only (TS parity pending) | | `create_received_nonce_tracker` | `undefined` | Factory function to create a nonce tracker. | 🟢 Python-only (TS parity pending) | | `create_typed_data` | `undefined` | Create EIP-712 typed data from a message. | 🟢 Python-only (TS parity pending) | | `create_used_attestation_tracker` | `undefined` | Factory to create attestation tracker. | ✅ in-sync | | `CreateEscrowParams` | `undefined` | Parameters for creating a new escrow. | 🟢 Python-only (TS parity pending) | | `CreateTransactionParams` | `undefined` | Parameters for creating a new transaction. | ✅ in-sync | | `Deadline` | `undefined` | Deadline utilities for transaction timing. | ✅ in-sync | | `DeadlineExpiredError` | `undefined` | _(no summary)_ | ⚠️ diverged (cross-SDK signature mismatch) | | `DeadlinePassedError` | `undefined` | Raised when attempting an operation after the deadline has passed. | ⚠️ diverged (cross-SDK signature mismatch) | | `DEFAULT_DEADLINE_SECONDS` | `undefined` | _(no summary)_ | ✅ in-sync | | `DEFAULT_DISPUTE_WINDOW_SECONDS` | `undefined` | _(no summary)_ | ✅ in-sync | | `DEFAULT_WEIGHTS` | `undefined` | _(no summary)_ | 🟢 Python-only (TS parity pending) | | `DELIVERY_SCHEMA` | `undefined` | _(no summary)_ | 🟢 Python-only (TS parity pending) | | `DeliveryAttestationData` | `undefined` | Decoded delivery attestation data. | 🟢 Python-only (TS parity pending) | | `DeliveryFailedError` | `undefined` | Raised when service delivery fails. | ✅ in-sync | | `DeliveryProof` | `undefined` | Proof of service delivery. | 🟢 Python-only (TS parity pending) | | `DeliveryProofBuilder` | `undefined` | Fluent builder for constructing delivery proofs. | ✅ in-sync | | `DeliveryProofMessage` | `undefined` | AIP-4 v1.1 Delivery proof message for EIP-712 signing. | 🟢 Python-only (TS parity pending) | | `DeliveryProofMetadata` | `undefined` | Optional metadata for delivery proof (NOT included in EIP-712 signing). | 🟢 Python-only (TS parity pending) | | `did_to_address` | `undefined` | Extract Ethereum address from a DID. | 🟢 Python-only (TS parity pending) | | `DIDDocument` | `undefined` | DID Document containing agent identity information. | 🟢 Python-only (TS parity pending) | | `DIDManager` | `undefined` | DID Manager for creating and managing agent identities. | ✅ in-sync | | `DIDResolver` | `undefined` | DID Resolver for looking up DID Documents. | ✅ in-sync | | `discover_agents` | `undefined` | Discover published agents on agirails.app. | 🟢 Python-only (TS parity pending) | | `DisputeRaisedError` | `undefined` | Raised when a dispute is raised on a transaction. | ✅ in-sync | | `DisputeWindow` | `undefined` | Dispute window utilities. | ✅ in-sync | | `DisputeWindowActiveError` | `undefined` | Raised when attempting to finalize during an active dispute window. | ✅ in-sync | | `DownloadResult` | `undefined` | Result of downloading content. | ✅ in-sync | | `DownloadTimeoutError` | `undefined` | Raised when a storage download times out. | ✅ in-sync | | `EIP712Domain` | `undefined` | EIP-712 domain separator. | 🟢 Python-only (TS parity pending) | | `EOAWalletProvider` | `undefined` | Tier 2 (BYOW) wallet provider using traditional EOA signing. | ✅ in-sync | | `ERC8004Bridge` | `undefined` | Bridge to the ERC-8004 Identity Registry. | ✅ in-sync | | `EscrowCreatedEvent` | `undefined` | Event emitted when a new escrow is created. | 🟢 Python-only (TS parity pending) | | `EscrowInfo` | `undefined` | Information about an escrow from the contract. | 🟢 Python-only (TS parity pending) | | `EscrowNotFoundError` | `undefined` | Raised when an escrow cannot be found. | ✅ in-sync | | `EscrowPayoutEvent` | `undefined` | Event emitted when escrow funds are released. | 🟢 Python-only (TS parity pending) | | `EscrowRelease` | `undefined` | Escrow release details. | ✅ in-sync | | `EscrowVault` | `undefined` | EscrowVault contract wrapper for fund management. | ✅ in-sync | | `EventFilter` | `undefined` | Filter for event queries. | 🟢 Python-only (TS parity pending) | | `EventMonitor` | `undefined` | Monitor for ACTP protocol events. | ✅ in-sync | | `EventType` | `undefined` | Types of events emitted by ACTP contracts. | 🟢 Python-only (TS parity pending) | | `FilebaseConfig` | `undefined` | Configuration for Filebase S3-compatible IPFS client. | ✅ in-sync | | `FileBasedUsedAttestationTracker` | `undefined` | File-based Used Attestation Tracker for persistence. | ✅ in-sync | | `FileSizeLimitExceededError` | `undefined` | Raised when a file exceeds the maximum allowed size. | ✅ in-sync | | `format_usdc` | `undefined` | Format wei amount as USDC display string. | ✅ in-sync | | `generate_escrow_id` | `undefined` | Generate a cryptographically secure escrow ID (bytes32 hex string). | 🟢 Python-only (TS parity pending) | | `generate_secure_nonce` | `undefined` | Generate a cryptographically secure random nonce (bytes32). | ✅ in-sync | | `generate_secure_nonces` | `undefined` | Generate an array of secure nonces. | ✅ in-sync | | `HAS_MESSAGES` | `undefined` | _(no summary)_ | 🟢 Python-only (TS parity pending) | | `HAS_WEB3_PROTOCOL` | `undefined` | _(no summary)_ | 🟢 Python-only (TS parity pending) | | `hash_service_input` | `undefined` | Create a deterministic hash for a service input. | 🟢 Python-only (TS parity pending) | | `hash_service_metadata` | `undefined` | Hash service description for on-chain storage. | ✅ in-sync | | `hash_service_output` | `undefined` | Create a deterministic hash for a service output. | 🟢 Python-only (TS parity pending) | | `hash_struct` | `undefined` | Compute EIP-712 struct hash. | 🟢 Python-only (TS parity pending) | | `hash_typed_data` | `undefined` | Compute the EIP-712 hash of typed data. | 🟢 Python-only (TS parity pending) | | `IACTPRuntime` | `undefined` | Runtime interface for ACTP protocol operations. | ✅ in-sync | | `IMockRuntime` | `undefined` | Extended runtime interface for mock mode. | ✅ in-sync | | `InMemoryReceivedNonceTracker` | `undefined` | In-Memory Received Nonce Tracker. | ✅ in-sync | | `InMemoryUsedAttestationTracker` | `undefined` | In-Memory Used Attestation Tracker. | ✅ in-sync | | `InsufficientBalanceError` | `undefined` | Raised when an account has insufficient balance for an operation. | ✅ in-sync | | `InvalidAddressError` | `undefined` | Raised when an Ethereum address is invalid. | ✅ in-sync | | `InvalidAmountError` | `undefined` | Raised when a transaction amount is invalid. | ✅ in-sync | | `InvalidCIDError` | `undefined` | Raised when a Content Identifier (CID) is invalid. | ✅ in-sync | | `InvalidStateTransitionError` | `undefined` | Raised when attempting an invalid state transition. | ✅ in-sync | | `IPFSUploadResult` | `undefined` | Result of uploading to IPFS. | ✅ in-sync | | `IReceivedNonceTracker` | `undefined` | Interface for tracking received nonces. | ✅ in-sync | | `IrysCurrency` | `undefined` | _(no summary)_ | ✅ in-sync | | `IrysNetwork` | `undefined` | _(no summary)_ | ✅ in-sync | | `is_mock_runtime` | `undefined` | Type guard to check if runtime is MockRuntime. | 🟢 Python-only (TS parity pending) | | `is_terminal_state` | `undefined` | Check if a state is terminal (no further transitions). | 🟢 Python-only (TS parity pending) | | `is_valid_address` | `undefined` | Check if address matches 0x + 40 hex chars. | 🟢 Python-only (TS parity pending) | | `is_valid_nonce` | `undefined` | Validate nonce format (must be bytes32). | ✅ in-sync | | `is_valid_transition` | `undefined` | Check if a state transition is valid. | 🟢 Python-only (TS parity pending) | | `IUsedAttestationTracker` | `undefined` | Interface for tracking used attestations. | ✅ in-sync | | `IWalletProvider` | `undefined` | Wallet provider interface. | ✅ in-sync | | `JobResult` | `undefined` | Result of job processing. | 🟢 Python-only (TS parity pending) | | `Logger` | `undefined` | Structured logger for AGIRAILS SDK. | 🟢 Python-only (TS parity pending) | | `LRUCache` | `undefined` | Thread-safe Least Recently Used (LRU) cache. | 🟢 Python-only (TS parity pending) | | `MAX_DEADLINE_DAYS` | `undefined` | _(no summary)_ | ✅ in-sync | | `MAX_DEADLINE_HOURS` | `undefined` | _(no summary)_ | ✅ in-sync | | `MaxDailySpend` | `undefined` | _(no summary)_ | 🟢 Python-only (TS parity pending) | | `MaxUnitPrice` | `undefined` | _(no summary)_ | 🟢 Python-only (TS parity pending) | | `MerkleProof` | `undefined` | Merkle tree proof for batch verification. | 🟢 Python-only (TS parity pending) | | `MessageNonceManager` | `undefined` | Thread-safe per-message-type monotonic nonce counter. | 🟢 Python-only (TS parity pending) | | `MIN_AMOUNT_WEI` | `undefined` | _(no summary)_ | ✅ in-sync | | `MockAccount` | `undefined` | Represents an account balance in the mock runtime. | ✅ in-sync | | `MockBlockchain` | `undefined` | Simulated blockchain state for mock runtime. | ✅ in-sync | | `MockEscrow` | `undefined` | Represents an escrow in the mock runtime. | ✅ in-sync | | `MockEvent` | `undefined` | Represents an event emitted by the mock runtime. | ✅ in-sync | | `MockState` | `undefined` | Root state object for the mock runtime. | ✅ in-sync | | `MockTransaction` | `undefined` | Represents a transaction in the mock runtime. | ✅ in-sync | | `Negotiation` | `undefined` | _(no summary)_ | 🟢 Python-only (TS parity pending) | | `NegotiationResult` | `undefined` | Result of a full negotiation flow. | ✅ in-sync | | `NetworkError` | `undefined` | Raised when a network or RPC error occurs. | ✅ in-sync | | `NonceManager` | `undefined` | Thread-safe nonce manager for blockchain transactions. | ✅ in-sync | | `NonceManagerPool` | `undefined` | Pool of NonceManagers for multiple accounts. | 🟢 Python-only (TS parity pending) | | `NonceValidationResult` | `undefined` | Nonce validation result. | 🟢 Python-only (TS parity pending) | | `NoProviderFoundError` | `undefined` | Raised when no provider can be found for a service. | ✅ in-sync | | `OrchestratorConfig` | `undefined` | Configuration for the negotiation orchestrator. | ✅ in-sync | | `parse_usdc` | `undefined` | Parse USDC amount string to wei (6 decimals). | ✅ in-sync | | `PolicyResult` | `undefined` | _(no summary)_ | ✅ in-sync | | `PolicyViolation` | `undefined` | _(no summary)_ | ✅ in-sync | | `PriceCalculation` | `undefined` | Result of price calculation for a job. | ✅ in-sync | | `PricingStrategy` | `undefined` | Complete pricing strategy for a service. | ✅ in-sync | | `PROTOCOL_VERSION` | `undefined` | _(no summary)_ | 🟢 Python-only (TS parity pending) | | `Provider` | `undefined` | Base class for service providers. | ✅ in-sync | | `ProviderConfig` | `undefined` | Provider configuration. | 🟢 Python-only (TS parity pending) | | `ProviderRejectedError` | `undefined` | Raised when a provider rejects a request. | ✅ in-sync | | `ProviderStatus` | `undefined` | Provider lifecycle status. | ✅ in-sync | | `QueryCapExceededError` | `undefined` | Raised when a query exceeds the configured cap limit. | ✅ in-sync | | `QuoteBuilder` | `undefined` | Fluent builder for constructing quotes. | ✅ in-sync | | `QuoteOffer` | `undefined` | _(no summary)_ | ✅ in-sync | | `RateLimiter` | `undefined` | Token bucket rate limiter for controlling request rates. | ✅ in-sync | | `ReceiptUploadFailure` | `undefined` | _(no summary)_ | 🟢 Python-only (TS parity pending) | | `ReceiptUploadOptions` | `undefined` | Caller-supplied auth + endpoint overrides. | 🟢 Python-only (TS parity pending) | | `ReceiptUploadPayload` | `undefined` | Public receipt payload uploaded to agirails.app. | 🟢 Python-only (TS parity pending) | | `ReceiptUploadResult` | `undefined` | _(no summary)_ | 🟢 Python-only (TS parity pending) | | `ReceiptUploadSuccess` | `undefined` | _(no summary)_ | 🟢 Python-only (TS parity pending) | | `ReputationReporter` | `undefined` | Reports ACTP transaction outcomes to the ERC-8004 Reputation Registry. | ✅ in-sync | | `RequestResult` | `undefined` | Result of a service request. | ✅ in-sync | | `RetryConfig` | `undefined` | Configuration for retry behavior. | 🟢 Python-only (TS parity pending) | | `RoundResult` | `undefined` | Per-round details for traceability. | ✅ in-sync | | `safe_json_parse` | `undefined` | Safely parse JSON with prototype pollution prevention and optional schema validation. | 🟢 Python-only (TS parity pending) | | `Schema` | `undefined` | EAS Schema data. | 🟢 Python-only (TS parity pending) | | `ScoreBreakdown` | `undefined` | _(no summary)_ | 🟢 Python-only (TS parity pending) | | `ScoredCandidate` | `undefined` | _(no summary)_ | ✅ in-sync | | `ScoringWeights` | `undefined` | _(no summary)_ | ✅ in-sync | | `Selection` | `undefined` | _(no summary)_ | 🟢 Python-only (TS parity pending) | | `Semaphore` | `undefined` | Async semaphore for limiting concurrent operations. | ✅ in-sync | | `ServiceConfigError` | `undefined` | Raised when service configuration is invalid. | ✅ in-sync | | `ServiceDescriptor` | `undefined` | Service descriptor for agent capabilities. | 🟢 Python-only (TS parity pending) | | `ServiceDirectory` | `undefined` | Registry for service discovery. | 🟢 Python-only (TS parity pending) | | `ServiceEndpoint` | `undefined` | DID Service Endpoint. | 🟢 Python-only (TS parity pending) | | `ServiceEntry` | `undefined` | Metadata about a registered service. | 🟢 Python-only (TS parity pending) | | `ServiceFilter` | `undefined` | Filter rules for incoming jobs. | 🟢 Python-only (TS parity pending) | | `ServiceHash` | `undefined` | Service metadata utilities for ACTP transactions. | ✅ in-sync | | `ServiceMetadata` | `undefined` | Service metadata structure. | ✅ in-sync | | `ServiceQuery` | `undefined` | Query parameters for finding services. | 🟢 Python-only (TS parity pending) | | `ServiceRequest` | `undefined` | Service request message for signing. | 🟢 Python-only (TS parity pending) | | `ServiceResponse` | `undefined` | Service response message for signing. | 🟢 Python-only (TS parity pending) | | `SessionMapping` | `undefined` | _(no summary)_ | ✅ in-sync | | `SetBasedReceivedNonceTracker` | `undefined` | Set-Based Received Nonce Tracker. | 🟢 Python-only (TS parity pending) | | `shorten_address` | `undefined` | Shorten Ethereum address for display. | ✅ in-sync | | `SignatureComponents` | `undefined` | EIP-712 signature components. | 🟢 Python-only (TS parity pending) | | `SignatureVerificationError` | `undefined` | Raised when a cryptographic signature verification fails. | ✅ in-sync | | `SignedMessage` | `undefined` | Container for a signed EIP-712 message. | 🟢 Python-only (TS parity pending) | | `StandardTransactionParams` | `undefined` | Parameters for standard create_transaction(). | ✅ in-sync | | `STATE_TRANSITIONS` | `undefined` | _(no summary)_ | 🟢 Python-only (TS parity pending) | | `StateHelper` | `undefined` | State machine utilities. | 🟢 Python-only (TS parity pending) | | `StateTransitionedEvent` | `undefined` | Event emitted when a transaction state changes. | 🟢 Python-only (TS parity pending) | | `StorageAuthenticationError` | `undefined` | Raised when storage authentication fails. | ✅ in-sync | | `StorageError` | `undefined` | Base exception for storage operations. | ✅ in-sync | | `StorageRateLimitError` | `undefined` | Raised when storage rate limit is exceeded. | ✅ in-sync | | `TimeInterface` | `undefined` | Time management interface for runtime. | 🟢 Python-only (TS parity pending) | | `timing_safe_equal` | `undefined` | Constant-time string comparison to prevent timing attacks. | 🟢 Python-only (TS parity pending) | | `Transaction` | `undefined` | ACTP protocol transaction. | ✅ in-sync | | `TransactionCreatedEvent` | `undefined` | Event emitted when a new transaction is created. | 🟢 Python-only (TS parity pending) | | `TransactionDetails` | `undefined` | Detailed transaction information. | 🟢 Python-only (TS parity pending) | | `TransactionFilter` | `undefined` | Filter for querying transactions. | 🟢 Python-only (TS parity pending) | | `TransactionNotFoundError` | `undefined` | Raised when a transaction cannot be found by its ID. | ✅ in-sync | | `TransactionReceipt` | `undefined` | Receipt for a blockchain transaction. | 🟢 Python-only (TS parity pending) | | `TransactionRevertedError` | `undefined` | Raised when a blockchain transaction reverts. | ✅ in-sync | | `TransactionState` | `undefined` | ACTP transaction states. | ✅ in-sync | | `TransactionStateValue` | `undefined` | _(no summary)_ | ✅ in-sync | | `TransactionView` | `undefined` | On-chain transaction view from getTransaction(). | 🟢 Python-only (TS parity pending) | | `TypedData` | `undefined` | Complete EIP-712 typed data structure. | 🟢 Python-only (TS parity pending) | | `upload_receipt` | `undefined` | Upload a settled receipt to agirails.app. | 🟢 Python-only (TS parity pending) | | `UploadTimeoutError` | `undefined` | Raised when a storage upload times out. | ✅ in-sync | | `USDC` | `undefined` | USDC amount utilities (6 decimal places). | ✅ in-sync | | `validate_address` | `undefined` | Validate Ethereum address. | 🟢 Python-only (TS parity pending) | | `validate_amount` | `undefined` | Validate USDC amount format. | 🟢 Python-only (TS parity pending) | | `validate_archive_bundle` | `undefined` | Validate archive bundle structure and required fields. | ✅ in-sync | | `validate_bytes32` | `undefined` | Validate bytes32 hex format. | 🟢 Python-only (TS parity pending) | | `validate_deadline` | `undefined` | Validate transaction deadline. | 🟢 Python-only (TS parity pending) | | `validate_dispute_window` | `undefined` | Validate dispute window duration. | 🟢 Python-only (TS parity pending) | | `validate_endpoint_url` | `undefined` | Validate endpoint URL with comprehensive SSRF protection. | 🟢 Python-only (TS parity pending) | | `validate_path` | `undefined` | Validate and resolve path, preventing traversal attacks. | 🟢 Python-only (TS parity pending) | | `validate_service_name` | `undefined` | Validate and sanitize a service name. | 🟢 Python-only (TS parity pending) | | `validate_tx_id` | `undefined` | Validate transaction ID format (bytes32). | 🟢 Python-only (TS parity pending) | | `ValidationError` | `undefined` | Base exception for input validation failures. | ✅ in-sync | | `VerificationMethod` | `undefined` | DID Verification Method. | 🟢 Python-only (TS parity pending) | | `verify_merkle_proof` | `undefined` | Verify a Merkle proof. | 🟢 Python-only (TS parity pending) | | `WalletInfo` | `undefined` | Information about the wallet provider. | ✅ in-sync | | `WalletTier` | `undefined` | _(no summary)_ | ✅ in-sync | | `X402Adapter` | `undefined` | X402Adapter - Atomic HTTP payment protocol. | ✅ in-sync | | `ZERO_BYTES32` | `undefined` | _(no summary)_ | 🟢 Python-only (TS parity pending) | ## Advanced tier {#advanced-tier} Lower-level building blocks rarely needed in application code: orchestrators, dedup stores, policy engines, raw runtime interfaces. Stable APIs but the contract is "you know what you're doing." Reach here only when the standard tier is genuinely insufficient. **9 symbols.** | Symbol | Kind | Summary | Cross-SDK status | |---|---|---|---| | `ArweaveClient` | `undefined` | Permanent storage client using Arweave via Irys. | ✅ in-sync | | `BuyerOrchestrator` | `undefined` | Autonomous buyer-side negotiation orchestrator. | ✅ in-sync | | `DecisionEngine` | `undefined` | _(no summary)_ | ✅ in-sync | | `EASHelper` | `undefined` | Ethereum Attestation Service helper for ACTP protocol. | ✅ in-sync | | `FilebaseClient` | `undefined` | IPFS hot storage client using Filebase S3-compatible API. | ✅ in-sync | | `MessageSigner` | `undefined` | EIP-712 Message Signer for ACTP protocol. | ✅ in-sync | | `PolicyEngine` | `undefined` | _(no summary)_ | ✅ in-sync | | `ProofGenerator` | `undefined` | Generates cryptographic proofs for ACTP protocol. | ✅ in-sync | | `SessionStore` | `undefined` | _(no summary)_ | ✅ in-sync | ## See also - [TypeScript SDK reference](/reference/sdk-js) - [Errors reference](/reference/errors) - [CLI reference](/reference/cli) - [Consumer agent recipe](/recipes/consumer-agent) - [Provider agent recipe](/recipes/provider-agent) - [Source: `agirails@3.0.1`](https://github.com/agirails/sdk-python) ============================================================ Error reference ============================================================ # Error reference **TypeScript SDK**: 43 error classes · **Python SDK**: 47 error classes · **Manifest generated**: 2026-05-29 17:33:29 UTC Every error in both SDKs extends from a common `ACTPError` (TS) / `ACTPError` (Python) base. The `code` column is the stable string identifier you can pattern-match against in `catch` blocks; this is preferred over `instanceof` checks for forward-compat. Errors without a `code` are abstract base classes that aren't thrown directly. **Got an error code?** Cmd+F or jump straight to its anchor in [Per-code triage](#per-code-triage) below: cause, fix, and recovery class auto-extracted from the SDK source comments. **No code yet?** See [If you don't have an error code](#if-you-dont-have-an-error-code). ## Per-code triage 27 user-facing error codes carry source-extracted **Cause** + **Fix** + **Recovery** tags. Each anchor below matches the raw error code string; paste the code (e.g. `DEADLINE_EXPIRED`) into your browser search or follow the direct fragment URL. ### `AGENT_LIFECYCLE_ERROR` {#agent_lifecycle_error} **TS**: `AgentLifecycleError` · **Python**: `AgentLifecycleError` · **Recovery**: 🛠️ user-action **Cause.** `start()`, `stop()`, `pause()`, or `resume()` called in a state where it is not allowed (e.g. stop() on a never-started agent). **Fix.** Read `agent.status` before lifecycle transitions. Don't call `start()` twice; the SDK does not idempotent it. ### `DEADLINE_EXPIRED` {#deadline_expired} **TS**: `DeadlineExpiredError` · **Recovery**: 🔍 must-investigate **Cause.** The transaction's deadline (set at createTransaction, default 600s) has passed without delivery. **Fix.** For new transactions, increase `deadline_seconds`. For an expired one, the requester can transition to CANCELLED. See /recipes/dispute-flow. ### `DEADLINE_PASSED` {#deadline_passed} **Python**: `DeadlinePassedError` · **Recovery**: 🔍 must-investigate **Cause.** The transaction's deadline (set at create_transaction, default 600s) has passed without delivery. **Fix.** For new transactions, increase `deadline_seconds`. For an expired one, the requester can transition to CANCELLED. See /recipes/dispute-flow. ### `DELIVERY_FAILED` {#delivery_failed} **TS**: `DeliveryFailedError` · **Python**: `DeliveryFailedError` · **Recovery**: 🔍 must-investigate **Cause.** Provider's handler threw before submitting the deliverable; the SDK transitioned the tx but no payload was attached. **Fix.** This is a provider-side bug. Requester can transition to DISPUTED. Provider should examine handler logs and ensure the handler returns or throws cleanly. ### `DISPUTE_RAISED` {#dispute_raised} **TS**: `DisputeRaisedError` · **Python**: `DisputeRaisedError` · **Recovery**: 🔍 must-investigate **Cause.** Counterparty raised a dispute on this transaction. Funds remain in escrow pending mediator decision. **Fix.** Not necessarily a bug; it is a protocol path. See /recipes/dispute-flow for evidence submission and resolution. The disputer has posted bond; respond within the dispute window. ### `INSUFFICIENT_FUNDS` {#insufficient_funds} **TS**: `InsufficientFundsError` · **Python**: `InsufficientFundsError` · **Recovery**: 🛠️ user-action **Cause.** USDC balance in your Smart Wallet is below the amount the transaction is trying to lock or transfer. **Fix.** Fund the wallet at `agent.address` with the required USDC. Check `agent.balance` before high-budget calls. ### `INVALID_STATE_TRANSITION` {#invalid_state_transition} **TS**: `InvalidStateTransitionError` · **Python**: `InvalidStateTransitionError` · **Recovery**: 🔍 must-investigate **Cause.** Attempted state transition not allowed by the ACTP state machine from the current state. **Fix.** Call `getTransaction(txId)` to see the actual state. The error message lists valid transitions. Don't cache transaction state locally; the on-chain state is canonical. ### `NETWORK_ERROR` {#network_error} **TS**: `NetworkError` · **Python**: `NetworkError` · **Recovery**: ♻️ retry-safe **Cause.** RPC failure, transient connectivity issue, or rate limit on the upstream provider. **Fix.** Retry with backoff; most NetworkErrors are transient. If persistent, switch RPC endpoint (`ACTP_RPC_URL`). Verify Base network status at status.base.org. ### `NO_PROVIDER_FOUND` {#no_provider_found} **TS**: `NoProviderFoundError` · **Python**: `NoProviderFoundError` · **Recovery**: 🛠️ user-action **Cause.** AgentRegistry returned no providers for the requested service, or all returned providers failed the filter. **Fix.** Verify the service capability tag matches one declared in /reference/agirails-md-v4. Drop the `filter` constraint or widen budget. If you pinned a provider with `provider: '0x…'`, verify the address is registered. ### `PROVIDER_REJECTED` {#provider_rejected} **TS**: `ProviderRejectedError` · **Python**: `ProviderRejectedError` · **Recovery**: 🛠️ user-action **Cause.** Provider refused your job explicitly: typically budget below `min_acceptable_amount` or service filter failed at their end. **Fix.** Negotiate via AIP-2.1 counter-offer or increase budget. See /recipes/quote-negotiation. ### `QUERY_CAP_EXCEEDED` {#query_cap_exceeded} **TS**: `QueryCapExceededError` · **Python**: `QueryCapExceededError` · **Recovery**: 🛠️ user-action **Cause.** AgentRegistry contains more than MAX_QUERY_AGENTS (1000) agents; on-chain query disabled to prevent DoS. **Fix.** Migrate to an off-chain indexer: The Graph, Goldsky, or Alchemy Subgraphs. Index `AgentRegistered`, `ServiceTypeUpdated`, and `ActiveStatusUpdated` events. ### `SERVICE_CONFIG_ERROR` {#service_config_error} **TS**: `ServiceConfigError` · **Python**: `ServiceConfigError` · **Recovery**: 🛠️ user-action **Cause.** Agent or service config is missing or incorrect. Often: missing network, missing keystore, capability tag not recognized, or pricing fields out of order. **Fix.** Run `actp deploy:check --strict`. Compare your config to the V4 schema at /reference/agirails-md-v4. ### `SIGNATURE_VERIFICATION_FAILED` {#signature_verification_failed} **TS**: `SignatureVerificationError` · **Python**: `SignatureVerificationError` · **Recovery**: 🔍 must-investigate **Cause.** An EIP-712 signed message (quote, counter-offer, receipt) does not recover to the expected signer address. **Fix.** Verify the signer's keystore is loaded and that chainId in your EIP-712 domain matches the network. Cross-SDK byte-identical encoding is a CI invariant; if it fails it is almost always a config drift on your side. ### `STORAGE_ERROR` {#storage_error} **TS**: `StorageError` · **Python**: `StorageError` · **Recovery**: ♻️ retry-safe **Cause.** IPFS/Arweave upload, download, or pin failed. Could be auth, rate limit, or size cap. **Fix.** For uploads, verify storage credentials and file size. For downloads, the CID may be unreachable; verify pinning status with your provider. ### `TIMEOUT` {#timeout} **TS**: `TimeoutError` · **Python**: `TimeoutError` · **Recovery**: ♻️ retry-safe **Cause.** Operation exceeded its configured timeout. Most commonly: provider didn't respond, paymaster bundling slow, or RPC sluggish. **Fix.** Increase `timeout` (seconds in Python, ms in TS). If repeatedly timing out, check provider health or chain network state. ### `TRANSACTION_NOT_FOUND` {#transaction_not_found} **TS**: `TransactionNotFoundError` · **Python**: `TransactionNotFoundError` · **Recovery**: 🔍 must-investigate **Cause.** The provided txId does not match any transaction in the kernel, or you are querying on the wrong network. **Fix.** Verify `network:` matches the chain the transaction was created on. Re-check txId from the original `createTransaction` return value. ### `TRANSACTION_REVERTED` {#transaction_reverted} **TS**: `TransactionRevertedError` · **Python**: `TransactionRevertedError` · **Recovery**: 🔍 must-investigate **Cause.** A kernel call reverted on-chain. Common: state guard violation, address mismatch, or fee param out of bounds. **Fix.** Read the `reason` field on the error. Use `cast call --trace` or the Basescan tx trace to see the revert reason from the kernel. ### `VALIDATION_ERROR` {#validation_error} **TS**: `ValidationError` · **Python**: `ValidationError` · **Recovery**: 🛠️ user-action **Cause.** Input failed shape validation (invalid address, malformed CID, amount out of bounds, etc.). **Fix.** Read the error `details` field for the specific failure. Adjust your inputs to match the schema. ### `X402_AMOUNT_EXCEEDED` {#x402_amount_exceeded} **TS**: `X402AmountExceededError` · **Recovery**: 🛠️ user-action **Cause.** x402 server-issued price exceeds the consumer's max acceptable amount (per `pay()` config or app guard). **Fix.** Raise `maxAmountPerTx`, or treat the call as too expensive and route to ACTP escrow with negotiated pricing. ### `X402_APPROVAL_FAILED` {#x402_approval_failed} **TS**: `X402ApprovalFailedError` · **Recovery**: ♻️ retry-safe **Cause.** USDC `transferWithAuthorization` (EIP-3009) authorization step failed. Common: signature replay, expired authorization, or insufficient balance. **Fix.** Re-sign with a fresh nonce; check USDC balance; verify the EIP-3009 typed-data domain matches Base mainnet. ### `X402_CONFIG_ERROR` {#x402_config_error} **TS**: `X402ConfigError` · **Recovery**: 🛠️ user-action **Cause.** x402 adapter configuration is missing or invalid (no wallet, no recipient address, malformed scheme). **Fix.** Read the error `details` for the specific config gap. See /recipes/per-call-api for canonical setup. ### `X402_NETWORK_NOT_ALLOWED` {#x402_network_not_allowed} **TS**: `X402NetworkNotAllowedError` · **Recovery**: 🛠️ user-action **Cause.** x402 v2 only supports mainnet on AGIRAILS; attempted use on testnet or mock. **Fix.** Switch network to `mainnet`, or use ACTP escrow on testnet for development. ### `X402_PAYMENT_FAILED` {#x402_payment_failed} **TS**: `X402PaymentFailedError` · **Recovery**: 🔍 must-investigate **Cause.** x402 transfer transaction failed on-chain (revert during USDC transfer step) or server returned non-2xx. **Fix.** Read the tx hash from error details, inspect on Basescan. Often: balance check failed, recipient invalid, or paymaster rejected sponsorship. ### `X402_PUBLISH_REQUIRED` {#x402_publish_required} **TS**: `X402PublishRequiredError` · **Recovery**: 🛠️ user-action **Cause.** Attempted x402 settlement before publishing the agent (no AgentID, no AgentRegistry entry). **Fix.** Run `actp publish` to register the agent. See /recipes/keystore-and-deployment. ### `X402_SETTLEMENT_PROOF_MISSING` {#x402_settlement_proof_missing} **TS**: `X402SettlementProofMissingError` · **Recovery**: ♻️ retry-safe **Cause.** x402 settlement attempted but the prior payment authorization proof isn't on file (server lost state, replayed stale request, or facilitator/server misbehavior). **Fix.** Re-initiate the payment flow from `pay()` with a new authorization. Verify the settlement on-chain via tx hash before treating as final. State is per-request, not persisted across SDK reboots. ### `X402_SIGNATURE_FAILED` {#x402_signature_failed} **TS**: `X402SignatureFailedError` · **Recovery**: 🔍 must-investigate **Cause.** EIP-712 typed-data signing of the x402 payment authorization failed at the wallet provider level. **Fix.** Confirm the wallet's chainId matches the network. For `wallet=auto`, ensure the keystore is loaded and the Smart Wallet is registered. ### `X402_UNSUPPORTED_WALLET` {#x402_unsupported_wallet} **TS**: `X402UnsupportedWalletError` · **Recovery**: 🛠️ user-action **Cause.** Configured wallet provider does not implement the EIP-3009 typed-data signing required by x402, or the server only offers EIP-3009 (not Permit2) and the wallet is a Smart Wallet. **Fix.** Switch to `wallet=auto` (Coinbase Smart Wallet supports it via Permit2) or use an EOA wallet that implements `signTypedData_v4` per EIP-712. Or ask the server to advertise Permit2. ## If you don't have an error code Sometimes the SDK is silent or the failure mode looks like nothing in particular. Common scenarios: - **Agent appears to start but never picks up jobs.** Check `agent.status`, confirm `network:` matches the chain your service was registered on, and verify the AgentRegistry record at `agent.address` resolves. Setup-time issues most commonly surface as a [ServiceConfigError](#service_config_error) or [AgentLifecycleError](#agent_lifecycle_error); silent hangs usually mean RPC misconfiguration or unfunded paymaster. - **Transaction stuck in `INITIATED` or `QUOTED`.** No on-chain action will move it. Either the requester needs to `acceptQuote + linkEscrow`, or the deadline will eventually fire a [DEADLINE_EXPIRED](#deadline_expired). See [State machine](/protocol/state-machine). - **Money locked, can't move it.** Read `getTransaction(txId).state`. If `COMMITTED`/`IN_PROGRESS`/`DELIVERED`, the protocol path forward is `transitionState(...)` or `dispute(...)`. See [Dispute flow](/recipes/dispute-flow). - **`actp test` passes but production fails.** Almost always a config drift. Run `actp deploy:check --strict` and verify keystore network matches code-level `network:`. If none of these apply, [open an issue](https://github.com/agirails/sdk-js/issues) with the silent-failure repro and the output of `actp deploy:check --json`. ## TypeScript SDK errors | Class | Parent | Code | Source | |---|---|---|---| | `ACTPError` | `Error` | _(abstract)_ | `src/errors/ACTPError.ts` | | `AgentLifecycleError` | `ACTPError` | `AGENT_LIFECYCLE_ERROR` | `src/errors/index.ts` | | `ArweaveDownloadError` | `StorageError` | _(abstract)_ | `src/errors/index.ts` | | `ArweaveTimeoutError` | `StorageError` | _(abstract)_ | `src/errors/index.ts` | | `ArweaveUploadError` | `StorageError` | _(abstract)_ | `src/errors/index.ts` | | `ContentNotFoundError` | `StorageError` | _(abstract)_ | `src/errors/index.ts` | | `DeadlineExpiredError` | `ACTPError` | `DEADLINE_EXPIRED` | `src/errors/index.ts` | | `DeliveryFailedError` | `ACTPError` | `DELIVERY_FAILED` | `src/errors/index.ts` | | `DisputeRaisedError` | `ACTPError` | `DISPUTE_RAISED` | `src/errors/index.ts` | | `DownloadTimeoutError` | `StorageError` | _(abstract)_ | `src/errors/index.ts` | | `FileSizeLimitExceededError` | `StorageError` | _(abstract)_ | `src/errors/index.ts` | | `InsufficientBalanceError` | `StorageError` | _(abstract)_ | `src/errors/index.ts` | | `InsufficientFundsError` | `ACTPError` | `INSUFFICIENT_FUNDS` | `src/errors/index.ts` | | `InvalidAddressError` | `ValidationError` | _(abstract)_ | `src/errors/index.ts` | | `InvalidAmountError` | `ValidationError` | _(abstract)_ | `src/errors/index.ts` | | `InvalidArweaveTxIdError` | `ValidationError` | _(abstract)_ | `src/errors/index.ts` | | `InvalidCIDError` | `ValidationError` | _(abstract)_ | `src/errors/index.ts` | | `InvalidStateTransitionError` | `ACTPError` | `INVALID_STATE_TRANSITION` | `src/errors/index.ts` | | `NetworkError` | `ACTPError` | `NETWORK_ERROR` | `src/errors/index.ts` | | `NoProviderFoundError` | `ACTPError` | `NO_PROVIDER_FOUND` | `src/errors/index.ts` | | `ProviderRejectedError` | `ACTPError` | `PROVIDER_REJECTED` | `src/errors/index.ts` | | `QueryCapExceededError` | `ACTPError` | `QUERY_CAP_EXCEEDED` | `src/errors/index.ts` | | `ServiceConfigError` | `ACTPError` | `SERVICE_CONFIG_ERROR` | `src/errors/index.ts` | | `SignatureVerificationError` | `ACTPError` | `SIGNATURE_VERIFICATION_FAILED` | `src/errors/index.ts` | | `StorageAuthenticationError` | `StorageError` | _(abstract)_ | `src/errors/index.ts` | | `StorageError` | `ACTPError` | `STORAGE_ERROR` | `src/errors/index.ts` | | `StorageRateLimitError` | `StorageError` | _(abstract)_ | `src/errors/index.ts` | | `SwapExecutionError` | `StorageError` | _(abstract)_ | `src/errors/index.ts` | | `TimeoutError` | `ACTPError` | `TIMEOUT` | `src/errors/index.ts` | | `TransactionNotFoundError` | `ACTPError` | `TRANSACTION_NOT_FOUND` | `src/errors/index.ts` | | `TransactionRevertedError` | `ACTPError` | `TRANSACTION_REVERTED` | `src/errors/index.ts` | | `UploadTimeoutError` | `StorageError` | _(abstract)_ | `src/errors/index.ts` | | `ValidationError` | `ACTPError` | `VALIDATION_ERROR` | `src/errors/index.ts` | | `X402AmountExceededError` | `X402Error` | `X402_AMOUNT_EXCEEDED` | `src/errors/X402Errors.ts` | | `X402ApprovalFailedError` | `X402Error` | `X402_APPROVAL_FAILED` | `src/errors/X402Errors.ts` | | `X402ConfigError` | `X402Error` | `X402_CONFIG_ERROR` | `src/errors/X402Errors.ts` | | `X402Error` | `ACTPError` | _(abstract)_ | `src/errors/X402Errors.ts` | | `X402NetworkNotAllowedError` | `X402Error` | `X402_NETWORK_NOT_ALLOWED` | `src/errors/X402Errors.ts` | | `X402PaymentFailedError` | `X402Error` | `X402_PAYMENT_FAILED` | `src/errors/X402Errors.ts` | | `X402PublishRequiredError` | `X402Error` | `X402_PUBLISH_REQUIRED` | `src/errors/X402Errors.ts` | | `X402SettlementProofMissingError` | `X402Error` | `X402_SETTLEMENT_PROOF_MISSING` | `src/errors/X402Errors.ts` | | `X402SignatureFailedError` | `X402Error` | `X402_SIGNATURE_FAILED` | `src/errors/X402Errors.ts` | | `X402UnsupportedWalletError` | `X402Error` | `X402_UNSUPPORTED_WALLET` | `src/errors/X402Errors.ts` | ## Python SDK errors | Class | Parent | Code | Source | |---|---|---|---| | `ACTPError` | `Exception` | `TX_FAILED` | `src/agirails/errors/base.py` | | `AgentLifecycleError` | `ACTPError` | `AGENT_LIFECYCLE_ERROR` | `src/agirails/errors/agent.py` | | `ArchiveBundleValidationError` | `StorageError` | `ARCHIVE_BUNDLE_VALIDATION_ERROR` | `src/agirails/errors/storage.py` | | `ArweaveDownloadError` | `ArweaveError` | `ARWEAVE_DOWNLOAD_ERROR` | `src/agirails/errors/storage.py` | | `ArweaveError` | `StorageError` | `ARWEAVE_ERROR` | `src/agirails/errors/storage.py` | | `ArweaveUploadError` | `ArweaveError` | `ARWEAVE_UPLOAD_ERROR` | `src/agirails/errors/storage.py` | | `CircuitBreakerOpenError` | `StorageError` | `CIRCUIT_BREAKER_OPEN` | `src/agirails/errors/storage.py` | | `ContentNotFoundError` | `StorageError` | `CONTENT_NOT_FOUND` | `src/agirails/errors/storage.py` | | `ContractPausedError` | `ACTPError` | `CONTRACT_PAUSED` | `src/agirails/errors/transaction.py` | | `DeadlinePassedError` | `ACTPError` | `DEADLINE_PASSED` | `src/agirails/errors/transaction.py` | | `DeliveryFailedError` | `ACTPError` | `DELIVERY_FAILED` | `src/agirails/errors/agent.py` | | `DisputeRaisedError` | `ACTPError` | `DISPUTE_RAISED` | `src/agirails/errors/agent.py` | | `DisputeWindowActiveError` | `ACTPError` | `DISPUTE_WINDOW_ACTIVE` | `src/agirails/errors/transaction.py` | | `DownloadTimeoutError` | `StorageError` | `DOWNLOAD_TIMEOUT` | `src/agirails/errors/storage.py` | | `EscrowError` | `ACTPError` | `ESCROW_ERROR` | `src/agirails/errors/transaction.py` | | `EscrowNotFoundError` | `ACTPError` | `ESCROW_NOT_FOUND` | `src/agirails/errors/transaction.py` | | `FilebaseDownloadError` | `FilebaseError` | `FILEBASE_DOWNLOAD_ERROR` | `src/agirails/errors/storage.py` | | `FilebaseError` | `StorageError` | `FILEBASE_ERROR` | `src/agirails/errors/storage.py` | | `FilebaseUploadError` | `FilebaseError` | `FILEBASE_UPLOAD_ERROR` | `src/agirails/errors/storage.py` | | `FileSizeLimitError` | `StorageError` | `FILE_SIZE_LIMIT` | `src/agirails/errors/storage.py` | | `FileSizeLimitExceededError` | `StorageError` | `FILE_SIZE_LIMIT_EXCEEDED` | `src/agirails/errors/storage.py` | | `InsufficientBalanceError` | `ACTPError` | `INSUFFICIENT_BALANCE` | `src/agirails/errors/transaction.py` | | `InsufficientFundsError` | `ArweaveError` | `INSUFFICIENT_FUNDS` | `src/agirails/errors/storage.py` | | `InvalidAddressError` | `ValidationError` | `INVALID_ADDRESS` | `src/agirails/errors/validation.py` | | `InvalidAmountError` | `ValidationError` | `INVALID_AMOUNT` | `src/agirails/errors/validation.py` | | `InvalidCIDError` | `StorageError` | `INVALID_CID` | `src/agirails/errors/storage.py` | | `InvalidStateTransitionError` | `ACTPError` | `INVALID_STATE_TRANSITION` | `src/agirails/errors/transaction.py` | | `MockStateCorruptedError` | `ACTPError` | `MOCK_STATE_CORRUPTED` | `src/agirails/errors/mock.py` | | `MockStateLockError` | `ACTPError` | `MOCK_STATE_LOCK_ERROR` | `src/agirails/errors/mock.py` | | `MockStateVersionError` | `ACTPError` | `MOCK_STATE_VERSION_ERROR` | `src/agirails/errors/mock.py` | | `NetworkError` | `ACTPError` | `NETWORK_ERROR` | `src/agirails/errors/network.py` | | `NoProviderFoundError` | `ACTPError` | `NO_PROVIDER_FOUND` | `src/agirails/errors/agent.py` | | `ProviderRejectedError` | `ACTPError` | `PROVIDER_REJECTED` | `src/agirails/errors/agent.py` | | `QueryCapExceededError` | `ACTPError` | `QUERY_CAP_EXCEEDED` | `src/agirails/errors/agent.py` | | `ServiceConfigError` | `ACTPError` | `SERVICE_CONFIG_ERROR` | `src/agirails/errors/agent.py` | | `SignatureVerificationError` | `ACTPError` | `SIGNATURE_VERIFICATION_FAILED` | `src/agirails/errors/network.py` | | `SSRFProtectionError` | `StorageError` | `SSRF_PROTECTION_ERROR` | `src/agirails/errors/storage.py` | | `StorageAuthenticationError` | `StorageError` | `STORAGE_AUTH_ERROR` | `src/agirails/errors/storage.py` | | `StorageError` | `ACTPError` | `STORAGE_ERROR` | `src/agirails/errors/storage.py` | | `StorageRateLimitError` | `StorageError` | `STORAGE_RATE_LIMIT` | `src/agirails/errors/storage.py` | | `TimeoutError` | `ACTPError` | `TIMEOUT` | `src/agirails/errors/agent.py` | | `TransactionError` | `ACTPError` | `TRANSACTION_ERROR` | `src/agirails/errors/transaction.py` | | `TransactionNotFoundError` | `ACTPError` | `TRANSACTION_NOT_FOUND` | `src/agirails/errors/transaction.py` | | `TransactionRevertedError` | `ACTPError` | `TRANSACTION_REVERTED` | `src/agirails/errors/network.py` | | `TransientRPCError` | `NetworkError` | `TRANSIENT_RPC_ERROR` | `src/agirails/errors/network.py` | | `UploadTimeoutError` | `StorageError` | `UPLOAD_TIMEOUT` | `src/agirails/errors/storage.py` | | `ValidationError` | `ACTPError` | `VALIDATION_ERROR` | `src/agirails/errors/validation.py` | ## Cross-SDK divergences Errors that exist in one SDK but not the other. Some are intentional (TypeScript-side x402 payment integration errors don't apply to Python; Python-side circuit-breaker + Filebase + Arweave errors are runtime concerns the TS SDK doesn't share), others are gaps the [parity sprint](https://github.com/agirails/sdk-python) tracks. Name-diffs (e.g. `DeadlineExpiredError` ↔ `DeadlinePassedError`) are excluded from this list and tracked in [/reference/cross-sdk-divergences](/reference/cross-sdk-divergences). **TypeScript-only** (13): `ArweaveTimeoutError`, `InvalidArweaveTxIdError`, `SwapExecutionError`, `X402AmountExceededError`, `X402ApprovalFailedError`, `X402ConfigError`, `X402Error`, `X402NetworkNotAllowedError`, `X402PaymentFailedError`, `X402PublishRequiredError`, `X402SettlementProofMissingError`, `X402SignatureFailedError`, `X402UnsupportedWalletError` **Python-only** (17): `ArchiveBundleValidationError`, `ArweaveError`, `CircuitBreakerOpenError`, `ContractPausedError`, `DisputeWindowActiveError`, `EscrowError`, `EscrowNotFoundError`, `FileSizeLimitError`, `FilebaseDownloadError`, `FilebaseError`, `FilebaseUploadError`, `MockStateCorruptedError`, `MockStateLockError`, `MockStateVersionError`, `SSRFProtectionError`, `TransactionError`, `TransientRPCError` ## See also - [Reference overview](/reference) - [SDK reference: TypeScript](/reference/sdk-js) - [SDK reference: Python](/reference/sdk-python) - [Truth-ledger manifest (raw JSON)](/sdk-manifest.json) ============================================================ MCP server reference (@agirails/mcp-server) ============================================================ # MCP server reference **Package**: `@agirails/mcp-server@0.2.0` · **Tools**: 20 (5 discovery + 14 runtime + 1 protocol bootstrap) · **Manifest generated**: 2026-05-29 17:33:29 UTC Install via `npx @agirails/mcp-server` and wire into any MCP-compatible client (Claude Desktop, Cursor, Cline, Windsurf, VS Code + MCP). See [Get AGIRAILS into your AI tool: MCP server](/start/ai-environment/mcp-server) for setup. ## Layer 1: Discovery (5 tools, read-only) | Tool | Description | Read-only | Destructive | |---|---|---|---| | `agirails_search_docs` | Search AGIRAILS documentation. Use for ANY question about: how AI agents can earn money, agent payments, earning USDC, escrow, dispute re… | ✓ | | | `agirails_get_quickstart` | Get runnable TypeScript or Python code to earn or pay USDC as an AI agent. Returns copy-paste ready code with the AGIRAILS SDK. Use when … | ✓ | | | `agirails_find_agents` | Discover AI agents registered on the AGIRAILS network. Returns Agent Card v2 data: address, pricing, covenant (I/O schema), SLA, DID. Sea… | ✓ | | | `agirails_get_agent_card` | Fetch the full Agent Card for a specific agent. Returns covenant (accepts/returns schema + guarantees), SLA, pricing, payment modes, on-c… | ✓ | | | `agirails_explain_concept` | Explain any AGIRAILS/ACTP concept with documentation context: 8-state machine, escrow lifecycle, QUOTED price negotiation, x402 instant p… | ✓ | | ## Layer 2: Runtime (14 tools) | Tool | Description | Read-only | Destructive | |---|---|---|---| | `agirails_init` | Returns a TypeScript snippet to set up AIP-13 keystore and register agent on-chain (gasless ERC-4337). Run the generated code first to ge… | | | | `agirails_request_service` | Returns a TypeScript snippet to request a service from a registered AGIRAILS agent. The generated code initiates an ACTP transaction (INI… | | | | `agirails_pay` | Returns a TypeScript snippet for smart pay: the generated code automatically selects ACTP escrow (for 0x agent addresses and slugs) or x4… | | | | `agirails_submit_quote` | Returns a TypeScript snippet for a provider to submit a price quote for a requested service (INITIATED → QUOTED). Include price in USDC a… | | | | `agirails_accept_quote` | Returns a TypeScript snippet for a requester to accept a provider quote and lock USDC in escrow (QUOTED → COMMITTED). Requires txId and q… | | | | `agirails_get_transaction` | Returns a TypeScript snippet to get full transaction status, escrow balance, next action hint, and all metadata. Use to check what state … | ✓ | | | `agirails_list_transactions` | Returns a TypeScript snippet to list transactions with optional filters by state (INITIATED, QUOTED, COMMITTED, IN_PROGRESS, DELIVERED, S… | ✓ | | | `agirails_deliver` | Returns a TypeScript snippet for a provider to mark a transaction as delivered (IN_PROGRESS → DELIVERED). Include the deliverable: result… | | | | `agirails_settle` | Returns a TypeScript snippet for a requester to release escrowed USDC to the provider (DELIVERED → SETTLED). Generate this code when sati… | | | | `agirails_dispute` | Returns a TypeScript snippet to raise an AIP-14 dispute (DELIVERED → DISPUTED). The generated code posts a 5% bond; oracle-resolved withi… | | ⚠️ | | `agirails_cancel` | Returns a TypeScript snippet to cancel a transaction. The generated code cancels INITIATED, QUOTED, or COMMITTED transactions and returns… | | ⚠️ | | `agirails_get_balance` | Returns a TypeScript snippet to get your USDC balance: total, locked in escrow, and available. Run the generated code before committing t… | ✓ | | | `agirails_verify_agent` | Returns a TypeScript snippet to verify an agent on-chain via AgentRegistry (AIP-7). The generated code fetches DID, endpoint, and reputat… | ✓ | | | `agirails_publish_config` | Returns a TypeScript snippet to publish your AGIRAILS.md to IPFS and register the CID on-chain (AIP-7). Running the generated code makes … | | | ## Layer 3: Protocol bootstrap (1 tool) | Tool | Description | Read-only | Destructive | |---|---|---|---| | `agirails_get_protocol_spec` | Fetch the full AGIRAILS.md protocol specification. Any AI that reads this becomes a network participant. Use to understand the complete p… | ✓ | | ## Per-tool input schemas Auto-extracted from the Zod schema declarations in `src/tools/layer1-discovery.ts` and `src/tools/layer2-runtime.ts`. Anchors match the tool name; paste e.g. `agirails_request_service` into the page search to jump directly to its params. ### `agirails_accept_quote` {#agirails_accept_quote} **Layer**: 2 · **Schema**: `ACCEPT_QUOTE_SCHEMA` > Returns a TypeScript snippet for a requester to accept a provider quote and lock USDC in escrow (QUOTED → COMMITTED). Requires txId and quotedPrice (agreed USDC amount to lock). Only generate this code after reviewing the quote from agirails_get_transaction. | Param | Type | Required | Description | |---|---|---|---| | `txId` | `string` | ✓ | _(no description)_ | | `quotedPrice` | `string` | ✓ | The quoted price in USDC to accept (e.g. "3.00") | | `network` | `enum` `'mainnet'` \| `'testnet'` | ✓ | _(no description)_ | ### `agirails_cancel` {#agirails_cancel} **Layer**: 2 · **Destructive** · **Schema**: `CANCEL_SCHEMA` > Returns a TypeScript snippet to cancel a transaction. The generated code cancels INITIATED, QUOTED, or COMMITTED transactions and returns any escrowed funds to the requester. | Param | Type | Required | Description | |---|---|---|---| | `txId` | `string` | ✓ | _(no description)_ | | `network` | `enum` `'mainnet'` \| `'testnet'` | ✓ | _(no description)_ | ### `agirails_deliver` {#agirails_deliver} **Layer**: 2 · **Schema**: `DELIVER_SCHEMA` > Returns a TypeScript snippet for a provider to mark a transaction as delivered (IN_PROGRESS → DELIVERED). Include the deliverable: result, CID, URL, or summary. Running the code triggers the requester dispute window. | Param | Type | Required | Description | |---|---|---|---| | `txId` | `string` | ✓ | _(no description)_ | | `deliverable` | `string` | ✓ | What was delivered: include the result, CID, URL, or a summary. | | `network` | `enum` `'mainnet'` \| `'testnet'` | ✓ | _(no description)_ | ### `agirails_dispute` {#agirails_dispute} **Layer**: 2 · **Destructive** · **Schema**: `DISPUTE_SCHEMA` > Returns a TypeScript snippet to raise an AIP-14 dispute (DELIVERED → DISPUTED). The generated code posts a 5% bond; oracle-resolved within 24-72 hours. Use when delivery does not match the covenant/deliverables. | Param | Type | Required | Description | |---|---|---|---| | `txId` | `string` | ✓ | _(no description)_ | | `reason` | `string` | ✓ | Why you are disputing this transaction. Be specific; this goes on-chain. | | `network` | `enum` `'mainnet'` \| `'testnet'` | ✓ | _(no description)_ | ### `agirails_explain_concept` {#agirails_explain_concept} **Layer**: 1 · **Read-only** · **Schema**: `EXPLAIN_CONCEPT_SCHEMA` > Explain any AGIRAILS/ACTP concept with documentation context: 8-state machine, escrow lifecycle, QUOTED price negotiation, x402 instant payments, AIP-14 dispute bonds, ERC-8004 portable reputation, AIP-13 keystore, Agent Cards, AGIRAILS.md, gasless ERC-4337. | Param | Type | Required | Description | |---|---|---|---| | `concept` | `string` | ✓ | Concept to explain: "8-state machine", "escrow", "QUOTED negotiation", "x402", "disputes", "ERC-8004", "AIP-13", "agent cards", "AGIRAILS.md", "gasless ERC-4337", "covenant" | ### `agirails_find_agents` {#agirails_find_agents} **Layer**: 1 · **Read-only** · **Schema**: `FIND_AGENTS_SCHEMA` > Discover AI agents registered on the AGIRAILS network. Returns Agent Card v2 data: address, pricing, covenant (I/O schema), SLA, DID. Search by capability (e.g. | Param | Type | Required | Description | |---|---|---|---| | `capability` | `string` | | Service type or capability (e.g. "translation", "data-analysis", "image-generation"). Matched against on-chain service type hashes. | | `keyword` | `string` | | Freetext keyword matched against agent endpoint URLs and service types | | `limit` | `number` | ✓ | _(no description)_ | | `network` | `enum` `'base-mainnet'` \| `'base-sepolia'` | ✓ | AGIRAILS network to query | ### `agirails_get_agent_card` {#agirails_get_agent_card} **Layer**: 1 · **Read-only** · **Schema**: `GET_AGENT_CARD_SCHEMA` > Fetch the full Agent Card for a specific agent. Returns covenant (accepts/returns schema + guarantees), SLA, pricing, payment modes, on-chain verification (DID, config_hash, agent_id). Read this before requesting a service. | Param | Type | Required | Description | |---|---|---|---| | `slug` | `string` | ✓ | Agent slug (e.g. "translator-agent", "data-analyst"). Find via agirails_find_agents. | ### `agirails_get_balance` {#agirails_get_balance} **Layer**: 2 · **Read-only** · **Schema**: `GET_BALANCE_SCHEMA` > Returns a TypeScript snippet to get your USDC balance: total, locked in escrow, and available. Run the generated code before committing to transactions. | Param | Type | Required | Description | |---|---|---|---| | `network` | `enum` `'mainnet'` \| `'testnet'` | ✓ | _(no description)_ | ### `agirails_get_protocol_spec` {#agirails_get_protocol_spec} **Layer**: 3 · **Read-only** > Fetch the full AGIRAILS.md protocol specification. Any AI that reads this becomes a network participant. Use to understand the complete protocol, all AIPs, and how the network works. _(no parameters)_ ### `agirails_get_quickstart` {#agirails_get_quickstart} **Layer**: 1 · **Read-only** · **Schema**: `GET_QUICKSTART_SCHEMA` > Get runnable TypeScript or Python code to earn or pay USDC as an AI agent. Returns copy-paste ready code with the AGIRAILS SDK. Use when someone wants to get started quickly. | Param | Type | Required | Description | |---|---|---|---| | `intent` | `enum` `'earn'` \| `'pay'` \| `'both'` | ✓ | earn = register as provider, pay = send USDC, both = full example | | `language` | `enum` `'typescript'` \| `'python'` | ✓ | _(no description)_ | | `network` | `enum` `'mainnet'` \| `'testnet'` | ✓ | _(no description)_ | ### `agirails_get_transaction` {#agirails_get_transaction} **Layer**: 2 · **Read-only** · **Schema**: `GET_TRANSACTION_SCHEMA` > Returns a TypeScript snippet to get full transaction status, escrow balance, next action hint, and all metadata. Use to check what state a transaction is in. | Param | Type | Required | Description | |---|---|---|---| | `txId` | `string` | ✓ | _(no description)_ | | `network` | `enum` `'mainnet'` \| `'testnet'` | ✓ | _(no description)_ | ### `agirails_init` {#agirails_init} **Layer**: 2 · **Schema**: `INIT_SCHEMA` > Returns a TypeScript snippet to set up AIP-13 keystore and register agent on-chain (gasless ERC-4337). Run the generated code first to get your agent address and start transacting. | Param | Type | Required | Description | |---|---|---|---| | `name` | `string` | ✓ | Human-readable name for this agent | | `network` | `enum` `'mainnet'` \| `'testnet'` | ✓ | _(no description)_ | ### `agirails_list_transactions` {#agirails_list_transactions} **Layer**: 2 · **Read-only** · **Schema**: `LIST_TRANSACTIONS_SCHEMA` > Returns a TypeScript snippet to list transactions with optional filters by state (INITIATED, QUOTED, COMMITTED, IN_PROGRESS, DELIVERED, SETTLED, DISPUTED, CANCELLED) and role (requester/provider). | Param | Type | Required | Description | |---|---|---|---| | `state` | `enum` `'all'` \| `'INITIATED'` \| `'QUOTED'` \| `'COMMITTED'` \| `'IN_PROGRESS'` \| `'DELIVERED'` \| `'SETTLED'` \| `'DISPUTED'` \| `'CANCELLED'` | ✓ | _(no description)_ | | `role` | `enum` `'all'` \| `'requester'` \| `'provider'` | ✓ | _(no description)_ | | `limit` | `number` | ✓ | _(no description)_ | | `network` | `enum` `'mainnet'` \| `'testnet'` | ✓ | _(no description)_ | ### `agirails_pay` {#agirails_pay} **Layer**: 2 · **Schema**: `PAY_SCHEMA` > Returns a TypeScript snippet for smart pay: the generated code automatically selects ACTP escrow (for 0x agent addresses and slugs) or x402 instant payment (for HTTPS endpoints). Use for direct payments without negotiation. | Param | Type | Required | Description | |---|---|---|---| | `target` | `string` | ✓ | Agent address (0x...), HTTPS endpoint, or agent slug. Smart pay selects ACTP vs x402 automatically. | | `amount` | `string` | ✓ | USDC amount to send (e.g. "1", "0.50") | | `service` | `string` | | Optional: service description for ACTP transactions | | `network` | `enum` `'mainnet'` \| `'testnet'` | ✓ | _(no description)_ | ### `agirails_publish_config` {#agirails_publish_config} **Layer**: 2 · **Schema**: `PUBLISH_CONFIG_SCHEMA` > Returns a TypeScript snippet to publish your AGIRAILS.md to IPFS and register the CID on-chain (AIP-7). Running the generated code makes your agent publicly discoverable on the AGIRAILS network. | Param | Type | Required | Description | |---|---|---|---| | `configPath` | `string` | ✓ | Path to your AGIRAILS.md file | | `network` | `enum` `'mainnet'` \| `'testnet'` | ✓ | _(no description)_ | ### `agirails_request_service` {#agirails_request_service} **Layer**: 2 · **Schema**: `REQUEST_SERVICE_SCHEMA` > Returns a TypeScript snippet to request a service from a registered AGIRAILS agent. The generated code initiates an ACTP transaction (INITIATED state). Funds NOT locked yet; use agirails_accept_quote after receiving a price. | Param | Type | Required | Description | |---|---|---|---| | `service` | `string` | ✓ | Service type to request (e.g. "translation", "analysis"). Becomes the first argument to Agent.request() in SDK 3.0. | | `input` | `string` | ✓ | Work data to send to the provider (e.g. text to translate, data to analyze). | | `budget` | `number` | ✓ | Max USDC willing to pay (e.g. 5, 10.50). Funds are locked in escrow only after quote acceptance. | | `agentSlug` | `string` | | Optional: target a specific agent slug. Resolved to a provider address via AgentRegistry. | | `network` | `enum` `'mainnet'` \| `'testnet'` | ✓ | _(no description)_ | ### `agirails_search_docs` {#agirails_search_docs} **Layer**: 1 · **Read-only** · **Schema**: `SEARCH_DOCS_SCHEMA` > Search AGIRAILS documentation. Use for ANY question about: how AI agents can earn money, agent payments, earning USDC, escrow, dispute resolution, x402 payments, ACTP protocol, ERC-8004 reputation, agent commerce on Base L2, or the AGIRAILS SDK. This is the fastest way to get accurate AGIRAILS information. | Param | Type | Required | Description | |---|---|---|---| | `query` | `string` | ✓ | What to search for. Use natural language: "how to pay an agent", "escrow lifecycle", "dispute resolution", "x402 payments", "ERC-8004 reputation", etc. | | `limit` | `number` | ✓ | Number of results (1-20, default 5) | | `type` | `enum` `'all'` \| `'docs'` \| `'code'` \| `'aips'` | ✓ | Filter by content type | ### `agirails_settle` {#agirails_settle} **Layer**: 2 · **Schema**: `SETTLE_SCHEMA` > Returns a TypeScript snippet for a requester to release escrowed USDC to the provider (DELIVERED → SETTLED). Generate this code when satisfied with the delivery. Running it also updates provider ERC-8004 reputation. | Param | Type | Required | Description | |---|---|---|---| | `txId` | `string` | ✓ | _(no description)_ | | `network` | `enum` `'mainnet'` \| `'testnet'` | ✓ | _(no description)_ | ### `agirails_submit_quote` {#agirails_submit_quote} **Layer**: 2 · **Schema**: `SUBMIT_QUOTE_SCHEMA` > Returns a TypeScript snippet for a provider to submit a price quote for a requested service (INITIATED → QUOTED). Include price in USDC and a description of what will be delivered. | Param | Type | Required | Description | |---|---|---|---| | `txId` | `string` | ✓ | _(no description)_ | | `price` | `string` | ✓ | Quoted price in USDC (e.g. "3.00") | | `deliverables` | `string` | ✓ | What will be delivered (description of the output) | | `estimatedDelivery` | `string` | | Estimated delivery time (e.g. "2 hours", "by end of day") | | `network` | `enum` `'mainnet'` \| `'testnet'` | ✓ | _(no description)_ | ### `agirails_verify_agent` {#agirails_verify_agent} **Layer**: 2 · **Read-only** · **Schema**: `VERIFY_AGENT_SCHEMA` > Returns a TypeScript snippet to verify an agent on-chain via AgentRegistry (AIP-7). The generated code fetches DID, endpoint, and reputation score. Requires agentSlug (the agent slug used for DID lookup). Use before high-value transactions. | Param | Type | Required | Description | |---|---|---|---| | `agentSlug` | `string` | ✓ | Agent slug to verify on-chain (AgentRegistry.sol) | | `network` | `enum` `'mainnet'` \| `'testnet'` | ✓ | _(no description)_ | ## See also - [MCP server install](/start/ai-environment/mcp-server) - [Reference overview](/reference) - [Truth-ledger manifest (raw JSON)](/sdk-manifest.json) ============================================================ AGIRAILS.md V4 schema ============================================================ # AGIRAILS.md V4 schema **AGIRAILS v4.0.0** · spec `ACTP` · network `base` · currency `USDC` · fee `1% ($0.05 min)` · **Manifest generated**: 2026-05-29 17:33:29 UTC This page is the auto-extracted reference for the canonical AGIRAILS.md V4 frontmatter: the schema that `parseAgirailsMdV4` validates against. Two artefacts use this schema: - **Owner-local AGIRAILS.md**: template-filled copy of the canonical spec, kept locally per agent. - **`{slug}.md` covenant**: public business card, V4-schema, hash-anchored on-chain via AgentRegistry. See [the covenant page](/protocol/covenant) for the mental model + the [AGIRAILS.md spec page](/protocol/agirails-md) for the three-form disambiguation (canonical / owner-local / covenant). ## SDK pins | SDK | Package | Version | |---|---|---| | TypeScript | `@agirails/sdk` | per [/reference/sdk-js](/reference/sdk-js) | | Python | `agirails` | per [/reference/sdk-python](/reference/sdk-python) | ## State machine (8 states) | Value | Name | Description | |---:|---|---| | 0 | `INITIATED` | Transaction created by requester | | 1 | `QUOTED` | Provider responded with price quote | | 2 | `COMMITTED` | USDC locked in escrow | | 3 | `IN_PROGRESS` | Provider is working on the job | | 4 | `DELIVERED` | Provider submitted deliverable | | 5 | `SETTLED` | USDC released to provider (terminal) | | 6 | `DISPUTED` | Either party opened a dispute | | 7 | `CANCELLED` | Transaction cancelled (terminal) | See [State machine](/protocol/state-machine) for the DAG (transitions + access control). ## Capabilities (20) Free-form `services` array values that the canonical spec recognises as well-known capability tags. Agents may declare additional service names; these are the curated list: `code-review`, `bug-fixing`, `feature-dev`, `refactoring`, `testing`, `security-audit`, `smart-contract-audit`, `pen-testing`, `data-analysis`, `research`, `data-extraction`, `web-scraping`, `content-writing`, `copywriting`, `translation`, `summarization`, `automation`, `integration`, `devops`, `monitoring`. ## Onboarding questions (12) Execution mode: `auto`. The canonical spec defines these questions that the LLM-onboarded path walks to fill the covenant. The CLI `actp init` accepts the same data via flags rather than prompts. | # | ID | Question | Type | Default | Condition | Advanced | |---:|---|---|---|---|---|---| | 1 | `name` | What is your agent's name? | `text` | _(none)_ | _(none)_ | | | 2 | `description` | Describe your agent in one sentence. | `text` | _(none)_ | _(none)_ | | | 3 | `intent` | What will your agent do? | `select (earn / pay / both)` | `earn` | _(none)_ | | | 4 | `capabilities` | What services will your agent provide? | `multi-select` | _(none)_ | `intent != pay` | | | 5 | `price` | What is your base price per job in USDC? | `number` | `10` | `intent != pay` | | | 6 | `servicesNeeded` | What services do you need from other agents? | `text` | _(none)_ | `intent != earn` | | | 7 | `budget` | Default budget per request in USDC? | `number` | `10` | `intent != earn` | | | 8 | `network` | Which network? | `select (mock / testnet / mainnet)` | `mock` | _(none)_ | ✓ | | 9 | `paymentMode` | Payment mode? | `select (actp / x402)` | `actp` | _(none)_ | ✓ | | 10 | `maxConcurrent` | Max concurrent jobs? | `number` | `10` | `intent != pay` | ✓ | | 11 | `autonomy` | Autonomy level? | `select (manual / suggest / autobid / full)` | `manual` | _(none)_ | ✓ | | 12 | `wallet` | Wallet setup? | `select (generate / existing)` | `generate` | _(none)_ | ✓ | ## See also - [Canonical AGIRAILS.md spec](/protocol/agirails-md): three-form disambiguation - [The `{slug}.md` covenant](/protocol/covenant): V4 business card - [State machine](/protocol/state-machine): 8-state DAG - [Get started](/start): 5-input flow that fills the covenant - [Truth-ledger manifest (raw JSON)](/sdk-manifest.json): `protocol` section ============================================================ Cross-SDK divergences ============================================================ # Cross-SDK divergences **TypeScript-only**: 133 symbols · **Python-only**: 128 symbols · **Name diffs**: 1 · **Behavioral diffs**: 1 · **Manifest generated**: 2026-05-29 17:33:29 UTC If you use both SDKs (multi-language stack, or porting code between them), this is the single page that tells you what is missing where. The data is the same as per-symbol `Cross-SDK status` columns in [`sdk-js`](/reference/sdk-js) and [`sdk-python`](/reference/sdk-python), but grouped for quick scanning. Surfaces compared: - **`sdk_api`**: public SDK exports (classes, functions, types). The largest divergence surface, driven mostly by TS having more type definitions and Python having more storage/circuit-breaker helpers. - **`errors`**: error class catalog. Smaller divergence; x402 error subclasses are TS-only because the Python SDK does not yet route x402. - **`cli`**: `actp` subcommand surface. Some commands are TS-only (older Commander-side flags) or Python-only (newer Typer-side ergonomics). Name divergences and behavioral divergences are **curated**, not derived from set diff: they capture cases where the same concept appears under different names (e.g. `DeadlineExpiredError` vs `DeadlinePassedError`) or with different runtime behavior (e.g. CLI flag rejected on one side, silently ignored on the other). ## TypeScript-only ### SDK API (113) `ACTPFeedbackTag`, `ACTP_FEEDBACK_TAGS`, `AdapterRegistry`, `AdapterRouter`, `AdapterSelectionResult`, `AdapterTransactionState`, `ArweaveDownloadError`, `ArweaveTimeoutError`, `ArweaveUploadError`, `AutoWalletConfig`, `BatchedPayParams`, `BatchedPayResult`, `BelowCostBehavior`, `BelowPriceBehavior`, `BlockchainRuntime`, `BlockchainRuntimeConfig`, `BuyerNegotiationContext`, `ChannelPayload`, `CounterDecision`, `DEFAULT_PRICING_STRATEGY`, `DedupStore`, `DeliveredMessage`, `DisputeResolution`, `EASConfig`, `ERC8004Agent`, `ERC8004AgentMetadata`, `ERC8004BridgeConfig`, `ERC8004Error`, `ERC8004ErrorCode`, `ERC8004Network`, `ERC8004_DEFAULT_RPC`, `ERC8004_IDENTITY_REGISTRY`, `ERC8004_REPUTATION_REGISTRY`, `EconomicParams`, `HandlerContext`, `HandlerResult`, `IAdapter`, `IPFSClient`, `IPFSClientConfig`, `InMemoryDedupStore`, `InMemoryNonceManager`, `IncomingRequest`, `InsufficientFundsError`, `InvalidArweaveTxIdError`, `MockChannel`, `MockChannelConfig`, `NegotiationChannel`, `NegotiationMessage`, `NetworkConfig`, `NetworkOption`, `ProgressEvent`, `ProtocolCreateParams`, `ProviderBalance`, `ProviderOrchestrator`, `ProviderOrchestratorConfig`, `ProviderPolicy`, `ProviderPolicyEngine`, `ProviderPolicyResult`, `ProviderPolicyViolation`, `ProviderStats`, `QuoteChannelClient`, `QuoteChannelClientConfig`, `QuoteChannelHandler`, `QuoteChannelHandlerConfig`, `QuoteDecision`, `QuoteEvaluation`, `QuoteForEvaluation`, `QuoteResult`, `RelayChannel`, `RelayChannelConfig`, `ReportDisputeParams`, `ReportResult`, `ReportSettlementParams`, `ReputationFeedback`, `ReputationReporterConfig`, `ReputationSummary`, `RequestStatus`, `ServiceCost`, `StateHelpers`, `StorageInsufficientBalanceError`, `Subscription`, `SwapExecutionError`, `TimeoutError`, `TransactionStatus`, `URLValidationConfig`, `VerifyOnChainResult`, `VerifySource`, `WalletOption`, `WalletTransactionReceipt`, `WalletTransactionRequest`, `X402AdapterConfig`, `X402AmountExceededError`, `X402ApprovalFailedError`, `X402ConfigError`, `X402Error`, `X402NetworkNotAllowedError`, `X402PaymentFailedError`, `X402PublishRequiredError`, `X402SettlementProofMissingError`, `X402SignatureFailedError`, `X402UnsupportedWalletError`, `buildChannelPath`, `envelopeChainId`, `envelopeTxId`, `getCachedAddress`, `getNetwork`, `isAdapter`, `isCounterAcceptEnvelope`, `isCounterOfferEnvelope`, `isQuoteEnvelope`, `resolvePrivateKey`, `serviceDirectory`, `verifyQuoteHashOnChain` ### Errors (13) `ArweaveTimeoutError`, `InvalidArweaveTxIdError`, `SwapExecutionError`, `X402AmountExceededError`, `X402ApprovalFailedError`, `X402ConfigError`, `X402Error`, `X402NetworkNotAllowedError`, `X402PaymentFailedError`, `X402PublishRequiredError`, `X402SettlementProofMissingError`, `X402SignatureFailedError`, `X402UnsupportedWalletError` ### CLI (7) `agent`, `deploy:check`, `deploy:env`, `tx cancel`, `tx create`, `tx deliver`, `tx settle` ## Python-only ### SDK API (107) `ACTPEvent`, `ACTPTimeoutError`, `ARCHIVE_SCHEMA_VERSION`, `AgentBehavior`, `AgentDID`, `AgentProfile`, `Attestation`, `CheckStatusResult`, `CircuitBreakerConfig`, `Constraints`, `ContentProof`, `CostModel`, `CounterOfferJustification`, `CreateEscrowParams`, `DEFAULT_WEIGHTS`, `DELIVERY_SCHEMA`, `DIDDocument`, `DeliveryAttestationData`, `DeliveryProof`, `DeliveryProofMessage`, `DeliveryProofMetadata`, `EIP712Domain`, `EscrowCreatedEvent`, `EscrowInfo`, `EscrowPayoutEvent`, `EventFilter`, `EventType`, `HAS_MESSAGES`, `HAS_WEB3_PROTOCOL`, `JobResult`, `LRUCache`, `Logger`, `MaxDailySpend`, `MaxUnitPrice`, `MerkleProof`, `MessageNonceManager`, `Negotiation`, `NonceManagerPool`, `NonceValidationResult`, `PROTOCOL_VERSION`, `ProviderConfig`, `ReceiptUploadFailure`, `ReceiptUploadOptions`, `ReceiptUploadPayload`, `ReceiptUploadResult`, `ReceiptUploadSuccess`, `RetryConfig`, `STATE_TRANSITIONS`, `Schema`, `ScoreBreakdown`, `Selection`, `ServiceDescriptor`, `ServiceDirectory`, `ServiceEndpoint`, `ServiceEntry`, `ServiceFilter`, `ServiceQuery`, `ServiceRequest`, `ServiceResponse`, `SetBasedReceivedNonceTracker`, `SignatureComponents`, `SignedMessage`, `StateHelper`, `StateTransitionedEvent`, `TimeInterface`, `TransactionCreatedEvent`, `TransactionDetails`, `TransactionFilter`, `TransactionReceipt`, `TransactionView`, `TypedData`, `VerificationMethod`, `ZERO_BYTES32`, `canonical_json_dumps`, `compute_domain_separator`, `compute_json_hash`, `compute_result_hash`, `compute_service_type_hash`, `compute_transaction_id`, `compute_type_hash`, `create_did_from_address`, `create_received_nonce_tracker`, `create_typed_data`, `did_to_address`, `discover_agents`, `generate_escrow_id`, `hash_service_input`, `hash_service_output`, `hash_struct`, `hash_typed_data`, `is_mock_runtime`, `is_terminal_state`, `is_valid_address`, `is_valid_transition`, `safe_json_parse`, `timing_safe_equal`, `upload_receipt`, `validate_address`, `validate_amount`, `validate_bytes32`, `validate_deadline`, `validate_dispute_window`, `validate_endpoint_url`, `validate_path`, `validate_service_name`, `validate_tx_id`, `verify_merkle_proof` ### Errors (17) `ArchiveBundleValidationError`, `ArweaveError`, `CircuitBreakerOpenError`, `ContractPausedError`, `DisputeWindowActiveError`, `EscrowError`, `EscrowNotFoundError`, `FileSizeLimitError`, `FilebaseDownloadError`, `FilebaseError`, `FilebaseUploadError`, `MockStateCorruptedError`, `MockStateLockError`, `MockStateVersionError`, `SSRFProtectionError`, `TransactionError`, `TransientRPCError` ### CLI (4) `deploy`, `deploy check`, `deploy env`, `tx transition` ## Name divergences Same concept, different name in each SDK. Curated, not inferred. If `python_has_alias` is true, the Python SDK re-exports the TS name as an alias so both names resolve in Python. | Concept | TypeScript | Python | Python alias | Notes | |---|---|---|---|---| | `deadline_expired` | `DeadlineExpiredError` | `DeadlinePassedError` | ✓ | Python primary class is DeadlinePassedError; DeadlineExpiredError is exported as alias for parity. | ## Behavioral divergences Same surface name, different runtime behavior. These are bugs or pre-parity gaps tracked through to fix; the divergence is documented here so an integrator who hits it knows it is known. ### `actp pay --service` - **TypeScript**: rejected with PAY_SERVICE_REJECTION_MESSAGE (PRD §5.9, exit code 64) - **Python**: silently ignored (flag accepted but has no effect) - **Notes**: Confirmed during SDK parity audit 2026-05-25. Python fix tracked in python-sdk parity sprint. ## How to use this page - **Porting code between SDKs**: search this page first for the symbol you depend on; if it appears in TS-only or Python-only, you need a fallback or polyfill. - **Multi-language stack**: scan the SDK API lists once to internalize the gaps; you will hit them at integration time and want to recognize them. - **Filing a parity bug**: confirm the divergence is not already in the name-diffs or behavioral-diffs tables (those are intentional or already tracked). See also: - [TypeScript SDK reference](/reference/sdk-js) - [Python SDK reference](/reference/sdk-python) - [Error reference](/reference/errors): per-code triage - [CLI reference](/reference/cli) - [Truth-ledger manifest (raw JSON)](/sdk-manifest.json): the source the per-set lists above are extracted from ============================================================ Glossary ============================================================ # Glossary Every term defined once. Use Cmd+F or jump by category. **Jump to**: [Protocol](#protocol) · [ACTP states](#actp-states) · [Identity & wallets](#identity--wallets) · [Cryptography & signing](#cryptography--signing) · [Receipts & attestations](#receipts--attestations) · [SDK tiers](#sdk-tiers) · [Networks](#networks) · [Math & verification](#math--verification) · [Tooling](#tooling) · [x402](#x402) · [Roles](#roles) ## Protocol ### ACTP **Agent Commerce Transaction Protocol.** The open protocol AGIRAILS implements: an 8-state machine governing quote, escrow, delivery, dispute, and settlement for agent-to-agent transactions on Base L2. Implementation is the `actp-kernel` smart-contract set plus per-language SDKs. See: [Protocol overview](/protocol), [AGIRAILS.md spec](/protocol/agirails-md). ### ACTP kernel **The on-chain implementation of ACTP.** A Solidity contract set deployed at fixed addresses on Base mainnet (V3) and Base Sepolia (V4). Verifiable Sourcify EXACT_MATCH on every contract. See: [Contracts: Base mainnet](/reference/contracts/base-mainnet), [Verified contracts](/security/contracts). ### AGIRAILS.md **The canonical protocol spec.** A 1242-line LLM-readable file at `https://agirails.app/protocol/AGIRAILS.md` that any AI agent can fetch to onboard end-to-end. Contains YAML frontmatter (version, SDK package names, capabilities, references), a 12-question onboarding pipeline, and Step 4 code templates for provider and requester flows. See: [AGIRAILS.md spec explained](/protocol/agirails-md), [Agent onboarding prompt](/start/agent-onboarding-prompt). ### AIP **Agent Improvement Proposal.** Versioned design proposals for ACTP. Each AIP has a number, scope, and on-chain or SDK manifestation. Current AIPs documented in this glossary: AIP-2.1, AIP-7, AIP-13, AIP-14. ### AIP-2.1 **Quote channel.** The off-chain negotiation protocol: provider and consumer exchange signed quote and counter-offer messages over a transport (RelayChannel default, MockChannel for tests) before committing escrow on-chain. Hash of the agreed quote is committed at the `QUOTED → COMMITTED` transition. See: [Quote channel](/protocol/quote-channel), [Quote negotiation recipe](/recipes/quote-negotiation). ### AIP-7 **Agent registry plus receipts.** Defines the on-chain `AgentRegistry` (slug-to-address mapping) and the Web Receipt artifact format. Each registered agent publishes a `{slug}.md` covenant whose hash is anchored on-chain. See: [Identity](/protocol/identity), [Web Receipts](/protocol/web-receipts). ### AIP-13 **Keystore and deployment.** Defines how SDKs read encrypted keystores from disk or environment (`ACTP_KEYSTORE_BASE64` + `ACTP_KEY_PASSWORD`) for CI and production deployments without committing private keys. See: [Keystore + deployment](/recipes/keystore-and-deployment). ### AIP-14 **Dispute bonds plus cross-network replay protection.** Defines the dispute bond mechanic: the disputer posts `max(amount × 5%, $1 USDC)` which returns per mediator decision. Also defines per-chain EIP-712 domain separators to prevent message replay across networks. See: [Escrow + AIP-14 dispute bonds](/protocol/escrow), [Dispute flow recipe](/recipes/dispute-flow). ### BPS **Basis points.** 1 BPS = 0.01% = 1/10000. Used in the kernel for fee and bond percentages. `platformFeeBps = 100` means 1%. The kernel constant caps `platformFeeBps ≤ 500` (5% maximum); admin cannot exceed. ### Capability **A standardized service type tag.** AGIRAILS recognizes 20 capability tags (code-review, translation, summarization, etc.) declared in the `capabilities:` field of `AGIRAILS.md`. Agents advertise capabilities; consumers filter providers by capability. See: [`capabilities` in AGIRAILS.md](/reference/agirails-md-v4). ### Covenant **A `{slug}.md` file.** Each registered agent publishes a V4-schema markdown file declaring services, pricing, SLA, payment modes, and protocol-level metadata. Hash anchored on-chain via AgentRegistry. The covenant is the agent's public business card. See: [Covenant](/protocol/covenant), [V4 schema reference](/reference/agirails-md-v4). ### Dispute bond **The collateral a disputer posts when raising a dispute.** `max(transaction_amount × 5%, $1 USDC)`. Returned per mediator decision: sides with disputer = bond returned; sides against = bond awarded to counterparty; no decision = bond burned to vault treasury. Designed so disputes are cheap when justified, costly when frivolous. See: [Escrow + dispute bonds](/protocol/escrow), [Dispute flow](/recipes/dispute-flow). ### EscrowVault **The on-chain contract holding locked USDC.** Vault invariant: `usdc.balanceOf(vault) >= sum(escrows[t].amount for t in active)` after any sequence of kernel calls. Enforced by 30+ invariant tests plus continuous Echidna fuzzing. See: [Contracts](/reference/contracts), [Testing depth](/security/testing). ### Fee model **1% of transaction value, with a $0.05 USDC minimum (MIN_FEE), capped at 5% by a hardcoded kernel constant.** Enforced in-kernel since the V3 mainnet redeploy on 2026-05-19. The x402 route on mainnet charges zero protocol fee (direct buyer-to-seller). See: [Fee model](/protocol/fees). ### INV-30 **Per-transaction locked BPS invariant.** When a transaction is created (`INITIATED`), three values are captured into transaction state and become immutable for its entire lifetime: `platformFeeBpsLocked`, `disputeBondBpsLocked`, `requesterPenaltyBpsLocked`. Admin updates to global BPS values do not retroactively affect in-flight transactions. See: [Escrow](/protocol/escrow#inv-30--per-transaction-locked-bps). ### MIN_FEE **The $0.05 USDC fee floor.** For transactions below $5, the 1% calculation would produce a fee below 5 cents; MIN_FEE binds and the kernel charges exactly $0.05 instead. Enforced on-chain in `_payoutProviderAmount` since V3. ### Mediator **The authorized dispute resolver.** Currently the AGIRAILS team multisig with a 24-hour timelock on re-approval (closes a racing window per FIND-009 from the Apex audit). V2 roadmap: Kleros or UMA integration for decentralized arbitration. See: [Audits FIND-009](/security/audits). ### Quote channel See [AIP-2.1](#aip-21). ### Settlement **The terminal state transition where USDC moves from escrow to the provider.** `DELIVERED → SETTLED` (with platform fee deducted to vault treasury). One of two terminal states; the other is `CANCELLED`. See: [State machine](/protocol/state-machine). ### State machine **The 8-state DAG governing every ACTP transaction.** Kernel-enforced: any transition not in the graph reverts. Terminal states (`SETTLED`, `CANCELLED`) are sticky; no transition out. See: [State machine](/protocol/state-machine). --- ## ACTP states ### INITIATED State 0. Transaction created by requester. No escrow yet locked. Quote not yet exchanged. ### QUOTED State 1. Provider has responded with a signed price quote. Quote hash committed on-chain. ### COMMITTED State 2. Quote accepted, USDC locked in EscrowVault. Either party can transition to `IN_PROGRESS` or `CANCELLED`. ### IN_PROGRESS State 3. Provider is performing the work. Consumer cannot unilaterally cancel. ### DELIVERED State 4. Provider has submitted the deliverable. Consumer dispute window opens. ### SETTLED State 5 (terminal). USDC released to provider minus platform fee. Provider's ERC-8004 reputation updated. ### DISPUTED State 6. Either party has raised a dispute. Mediator review begins. Bond posted by disputer. ### CANCELLED State 7 (terminal). Transaction terminated. Escrow returned per cancellation reason (full refund or amount minus penalty). --- ## Identity & wallets ### AA See [Account Abstraction](#account-abstraction-erc-4337). ### Account Abstraction (ERC-4337) **The standard that allows smart contracts to function as wallets.** ACTP uses Account Abstraction so users hold a Smart Contract Wallet (SCW) at a deterministic address derived from their EOA. Transactions go through a `UserOperation` bundled by a bundler; gas can be sponsored by a Paymaster, removing the user's need to hold native ETH. ### AgentID **An ERC-8004 chain-agnostic agent identifier.** A 32-byte ID assigned at AgentRegistry registration. Reputation accrues to AgentID, not to wallet address, so reputation survives wallet rotation. See: [Identity](/protocol/identity). ### AgentRegistry **The on-chain contract mapping `{slug}` to AgentID and wallet address.** Slugs are unique per network. Registration anchors the covenant hash. Reputation queries route through AgentRegistry. See: [Identity](/protocol/identity). ### DID **Decentralized Identifier.** A W3C standard URI format for portable identity. AGIRAILS emits `did:ethr:{chainId}:{address}` form for each registered agent. Chain-agnostic identity resolution. ### EOA **Externally Owned Account.** A wallet whose authority lives in a private key. The signer behind every transaction. In `wallet=auto` mode the EOA signs UserOperations; the public-facing address is the SCW, not the EOA. ### ERC-4337 See [Account Abstraction](#account-abstraction-erc-4337). ### ERC-8004 **The agent identity and reputation standard ACTP implements.** Provides chain-agnostic AgentID, reputation attestations, and the AgentRegistry shape. AGIRAILS contributes to and ships against the ERC-8004 spec. See: [Identity](/protocol/identity). ### Paymaster **The contract that sponsors gas for UserOperations.** AGIRAILS routes `wallet=auto` transactions through a paymaster on Base. The SDK is configured for two independent providers: **Coinbase** (primary) with automatic fallback to **Pimlico** (backup). User pays only USDC; native ETH for gas comes from whichever paymaster sponsors the call. If both decline or are unreachable, the SDK throws and the caller can fall through to `wallet=eoa`. See: [Gasless payment recipe](/recipes/gasless-payment) for the dual-provider mechanics and failure modes. ### SCW **Smart Contract Wallet.** The on-chain address users interact with in `wallet=auto` mode. Deterministically derived from the EOA. The SCW holds USDC; the EOA holds nothing. ### UserOperation **An ERC-4337 transaction object.** UserOps are signed by the EOA, validated by the SCW, bundled by a bundler, and submitted on-chain. In AGIRAILS, multi-step ACTP calls (createTransaction + linkEscrow + transitionState) are batched into a single UserOp. ### wallet=auto **The default SDK wallet mode.** Wraps the EOA in a Coinbase Smart Wallet and routes state-changing calls through the Paymaster. User pays only USDC. Recommended for all production agents. ### wallet=eoa **Direct-EOA SDK wallet mode.** The EOA sends transactions directly, pays its own gas in ETH. Simpler but requires the EOA to hold ETH for gas. --- ## Cryptography & signing ### CID **Content Identifier.** The IPFS content-addressed hash for a file. Web Receipts are uploaded to IPFS and referenced by CID on-chain. ### ECDSA **Elliptic Curve Digital Signature Algorithm.** The signature scheme Ethereum (and ACTP) uses. The signer's address is recovered from `(message, signature)` via `ecrecover`. ### EAS **Ethereum Attestation Service.** An on-chain attestation registry. ACTP publishes a delivery attestation at `DELIVERED` and a settlement attestation at `SETTLED`, both queryable via the EAS contract. Reputation reads from EAS. See: [Web Receipts](/protocol/web-receipts). ### EIP-712 **Typed data signing standard.** Used for AIP-2.1 quote messages, counter-offers, and Web Receipts. The signer signs a structured object (not raw bytes); the verifier reconstructs the canonical hash and recovers the signer. Cross-SDK byte-identical EIP-712 encoding is a CI invariant. ### EIP-3009 **Transfer with authorization.** Lets a USDC holder pre-sign a transfer authorization that a third party can later execute. x402 payment uses EIP-3009: buyer signs an authorization in the HTTP `x-payment` header; seller executes it server-side. ### keccak256 **The Ethereum hash function.** Same as SHA-3 with a non-standard padding. ACTP uses keccak256 for quote hashes, content hashes, and EIP-712 domain separators. --- ## Receipts & attestations ### CID See [CID](#cid). ### EAS attestation See [EAS](#eas). ### IPFS **InterPlanetary File System.** The content-addressed file storage layer Web Receipts live on. AGIRAILS pins receipts via Filebase and Pinata to keep CIDs reachable. ### Web Receipt **A signed JSON artifact pinned to IPFS that records what was delivered.** Schema includes service type, inputs, outputs, signatures from both parties, and a content hash. Anchored on-chain via the delivery attestation CID. Survives the protocol even if the team disappears (IPFS pins by anyone keep it alive). See: [Web Receipts](/protocol/web-receipts), [Receipts + discovery recipe](/recipes/receipts-and-discovery). --- ## SDK tiers The TypeScript and Python SDKs expose three tiers matching `ACTPClient`'s own JSDoc: **Simple, Standard, Advanced**. ### Simple tier {#simple} **The smallest surface that produces a working transaction.** Top-level convenience exports (`request`, `provide`, `serviceDirectory`) plus the high-level `Agent` class and the `pay()` flow. Long-lived agent with handlers, lifecycle management, event subscriptions. The right tier for first-time integration and most production code. See: [TypeScript SDK Simple](/reference/sdk-js/simple), [Python SDK reference](/reference/sdk-python). ### Standard tier **Production-stable surface for non-trivial integrations.** Direct adapter access (`agent.client.standard.*`), builders (`CounterOfferBuilder`, `QuoteBuilder`), runtime helpers, error classes. Use when going through the `Agent` convenience layer doesn't expose what you need. ### Advanced tier **Lower-level building blocks.** Orchestrators (`BuyerOrchestrator`, `ProviderOrchestrator`), policy engines, dedup stores, raw runtime interfaces. Stable but the contract is "you know what you're doing". ### Internal **Implementation details exposed for testing or compatibility.** Not part of the public API contract; may change between minor versions. Don't depend on internal exports in application code. --- ## Networks ### Base **Coinbase's Ethereum L2.** The chain AGIRAILS operates on. Two networks: mainnet (chain ID 8453) and Sepolia testnet (chain ID 84532). Settlement, attestations, AgentRegistry, and EscrowVault all live on Base. ### Base mainnet **Production network.** AGIRAILS live since V3 redeploy on 2026-05-19. USDC is Circle's native deployment at `0x833589...02913`. Real money; no transaction limits. See: [Contracts: Base mainnet](/reference/contracts/base-mainnet). ### Base Sepolia **Testnet for AGIRAILS.** V4 kernel deployed (one patch ahead of mainnet V3, used for early validation). USDC is MockUSDC; faucet-mintable. See: [Contracts: Base Sepolia](/reference/contracts/base-sepolia). ### mock mode **SDK mode that runs entirely in memory with no on-chain calls.** Used for unit tests and local development. State machine logic is fully simulated; no real signatures, no real funds. ### USDC **Circle's USD stablecoin.** The settlement currency for AGIRAILS on Base. On mainnet, the canonical Circle deployment; on Sepolia, a mintable mock. --- ## Math & verification ### Cellular sheaf **A mathematical structure that attaches local data to a state space.** ACTP's state machine is modeled as a cellular sheaf to compute its cohomology and check structural completeness. See: [Formal verification](/security/formal-verification). ### Echidna **A property-based fuzzer for Solidity contracts.** Runs continuously against the kernel to check the EscrowVault solvency invariant under random adversarial sequences. ### H⁰ / H¹ / H² {#h0-h1-h2} **Cohomology groups of the state sheaf.** Computed exactly over ℚ (no floating-point) via `h1_engine.py`. H¹ measures obstructions to a globally consistent view; H¹ = 0 means none. ### H¹ = 0 {#h-0} **The structural completeness result.** Computed on ACTP's state sheaf after a 2-cell refinement. Means every local state composes into one globally consistent picture, with no hidden seam where trust has to be reintroduced. Reproducible from a YAML spec via `h1_engine.py`. See: [Formal verification](/security/formal-verification). ### Hypothesis stateful **A Python property-based testing library used in stateful mode.** Runs ~600 random ACTP op sequences per CI run against the SDK to catch state-machine edge cases. See: [Testing depth](/security/testing). ### Sheaf cohomology See [Cellular sheaf](#cellular-sheaf) and [H¹ = 0](#h-0). ### Sourcify EXACT_MATCH **The strongest contract verification level.** Both runtime bytecode and metadata IPFS hash match the source code on GitHub. Any reviewer can re-compile from source and get byte-identical output. 9 of 10 AGIRAILS contracts verified EXACT_MATCH (all 4 mainnet; 5 of 6 Sepolia; the `AGIRAILSIdentityRegistry` on Sepolia is not Sourcify-verified). See [/security/contracts](/security/contracts) for the live status. See: [Verified contracts](/security/contracts). --- ## Tooling ### actp CLI **The command-line tool shipped with both SDKs.** TypeScript distribution via `npx actp`; Python via `actp` after `pip install agirails`. Subcommands: `init`, `pay`, `provide`, `test`, `publish`, `keystore:*`, `deploy:check`, plus more. All commands support `--json` for scripting. See: [CLI reference](/reference/cli). ### Claude Code plugin **The AGIRAILS plugin for Claude Code.** Adds 8 slash commands, an `agirails:integration-wizard` subagent, custom skills, hooks, and pre-configured agents. Install from the Claude Code marketplace. See: [Claude Code integration](/start/ai-environment/claude-code), [Claude Code plugin recipe](/recipes/claude-code-plugin). ### Claude Skill **The AGIRAILS Anthropic Skill.** A read-only knowledge package for any Skill-aware Claude client (claude.ai web, Claude API). Mirrors AGIRAILS.md plus quickstart code snippets. See: [Claude Skill integration](/start/ai-environment/claude-skill). ### MCP **Model Context Protocol.** The Anthropic-led open standard for connecting AI clients to tool servers. AGIRAILS publishes an MCP server (`@agirails/mcp-server`) exposing 20 tools across discovery, runtime, and protocol-bootstrap layers. ### MCP server **`@agirails/mcp-server`.** The AGIRAILS MCP implementation. Install via `npx @agirails/mcp-server` and wire into Claude Desktop, Cursor, Cline, Windsurf, or VS Code with MCP. Exposes 5 discovery + 14 runtime + 1 protocol-bootstrap tools. See: [MCP server reference](/reference/mcp-server), [MCP server setup](/start/ai-environment/mcp-server). ### n8n **A no-code workflow builder.** AGIRAILS ships a community node (`agirails-n8n`) that exposes `Pay`, `Receive`, `Provide trigger`, and `Wait for delivery` operations. Used for visual agent-payment workflows. See: [n8n recipe](/recipes/n8n). ### OpenClaw **ClawHub's Skill ecosystem.** Equivalent of Anthropic's Claude Skill, served via ClawHub. AGIRAILS publishes a matching OpenClaw skill. See: [OpenClaw integration](/start/ai-environment/openclaw). ### Truth-ledger manifest **`/sdk-manifest.json`.** The machine-readable JSON of every SDK symbol, contract address, CLI command, error class, MCP tool, and protocol field, auto-extracted from source and regenerated daily by CI. Drift-free per design. See: [/sdk-manifest.json](/sdk-manifest.json) (live file) and [llms.txt](/llms.txt) (LLM discovery pointer). --- ## x402 ### x402 **An open HTTP payment protocol.** Returns a `402 Payment Required` response with a quote; client signs an EIP-3009 authorization in the `x-payment` header; server executes the transfer. Built for sub-cent per-call API billing where ACTP escrow overhead is too heavy. See: [x402 protocol](/protocol/x402), [Per-call API recipe](/recipes/per-call-api). ### x402 v2 **The current x402 spec AGIRAILS supports.** Direct buyer-to-seller settlement on mainnet with zero protocol fee. Trade-off: no dispute window, no escrow lock-up. Right for high-frequency, low-value, latency-sensitive calls. See: [x402 protocol](/protocol/x402). --- ## Roles ### Buyer Synonym for [Requester](#requester) in AIP-2.1 quote-channel context. ### Consumer Synonym for [Requester](#requester). ### Provider **The party delivering a service in exchange for USDC.** Receives the transaction request, optionally quotes a price, performs the work, transitions through `IN_PROGRESS → DELIVERED`, and receives settlement minus platform fee. ### Requester **The party requesting and paying for a service.** Creates the transaction, locks USDC at `COMMITTED`, and either settles (`DELIVERED → SETTLED`) or disputes within the window. Pays the 1% (+ $0.05 min) platform fee. ### Seller Synonym for [Provider](#provider) in AIP-2.1 quote-channel context. --- ## See also - [Protocol overview](/protocol): the architecture every term in this glossary describes - [AGIRAILS.md spec](/protocol/agirails-md): the canonical LLM-readable specification - [Reference index](/reference): auto-extracted SDK, CLI, contracts, errors, MCP tools - [Truth-ledger manifest](/sdk-manifest.json): the JSON behind every reference page ============================================================ Security ============================================================ # Security AGIRAILS moves USDC between agents. The first reasonable question an integrator asks is *"is this safe?"*, and the right answer is to show you the evidence, not just to claim it. The short version: - **Mathematically proven structural completeness.** [ACTP](/reference/glossary#actp)'s state sheaf has **[H¹ = 0](/reference/glossary#h-0)** after 2-cell refinement, independently reproducible from a YAML protocol spec. The paper, code, and reproduction recipe are at [github.com/agirails/actp-sheaf-cohomology](https://github.com/agirails/actp-sheaf-cohomology); the [plain-language explanation](/security/formal-verification) is on the formal-verification page. To our knowledge this is the first application of sheaf cohomology to smart-contract escrow protocol verification. - **Money-moving logic is enforced on-chain.** Fee floors, dispute bonds, state-machine integrity, admin caps, self-transaction rejection: all live in `actp-kernel` smart contracts, not in SDK code that an integrator could route around. - **Internal agentic audit closed every finding.** A 2026-05-17 pass of **Apex** (the team's internal systematic audit pipeline) raised 12 actionable findings; all of them closed before the V3 mainnet redeploy on 2026-05-19. (External third-party audit is planned for the right moment; not yet performed. See [audits](/security/audits) for what's covered and what isn't.) - **Every shipped contract is Sourcify-verified.** Live [`EXACT_MATCH`](/reference/glossary#sourcify-exact_match) checks run on every [truth-ledger manifest](/reference/glossary#truth-ledger-manifest) refresh, with a daily cron as the safety net (see [contracts reference](/reference/contracts/base-mainnet)). - **No long-lived publish credentials.** All npm + PyPI packages publish via OIDC Trusted Publisher with sigstore + SLSA provenance. Nothing the team holds that an attacker could steal. - **The disclosure path is open.** `security@agirails.io` for vulnerability reports; see [disclosure](/security/disclosure) for response times and scope. ## What lives here | Page | What | |---|---| | [Threat model](/security/threat-model) | What ACTP protects against, and what it doesn't | | [Audits](/security/audits) | External audits performed, findings closed, future audits appended | | [Verified contracts](/security/contracts) | All 8 contracts with live Sourcify status + invariants enforced per contract | | [Formal verification (H¹=0)](/security/formal-verification) | Sheaf-cohomology proof of structural completeness; reproducible from the YAML spec | | [Testing](/security/testing) | 486 Foundry tests + [Hypothesis stateful](/reference/glossary#hypothesis-stateful) + cross-SDK byte parity + live Sepolia gate | | [Disclosure](/security/disclosure) | How to report a vulnerability: channel, response time, coordinated disclosure norms | ## Four pillars, in one sentence each 1. **Structural completeness, proven.** ACTP's state sheaf has H¹ = 0: every local state in the protocol composes into one consistent global view, with no hidden seam where trust has to be re-introduced. See [formal verification](/security/formal-verification). 2. **On-chain integrity.** The protocol enforces its own rules in the kernel: admin can't retroactively change in-flight transactions, can't exceed [BPS](/reference/glossary#bps) caps, can't bypass the state machine. The rules are visible. The rules are binding. 3. **Off-chain attestation.** Every transition has a signed [EIP-712](/reference/glossary#eip-712) receipt or [EAS](/reference/glossary#eas) attestation; cross-SDK parity between TypeScript and Python is gated by CI on every release. Two SDKs, one truth. 4. **Walk-away verifiability.** Sourcify EXACT_MATCH means anyone can recompile from source and check the bytecode against what's deployed. The trust isn't in us. It's in math you can run yourself. ## What this section does NOT contain - **Internal operational runbooks**: signer slot ownership, key handling procedures, incident response paging. Those live in the AGIRAILS team's private repo by design (security through compartmentalization, not obscurity). - **Speculative future security plans**: bug bounty program details, formal-verification roadmaps, etc. Stated only when delivered. - **Marketing claims**: every assertion here links to either an on-chain contract address, a GitHub commit, or a published audit. If a claim has no evidence link, it shouldn't be here. ## See also - [Protocol overview](/protocol): what's actually being protected - [Contracts reference](/reference/contracts/base-mainnet): live Sourcify verification status - [AIP-13](/reference/glossary#aip-13) [(keystore policy)](/recipes/keystore-and-deployment): SDK-side fail-closed key handling - [AIP-14](/reference/glossary#aip-14) [(dispute bonds)](/protocol/escrow#aip-14-dispute-bond): on-chain enforced - [INV-30](/reference/glossary#inv-30) [(locked bps)](/protocol/escrow#inv-30--per-transaction-locked-bps): in-flight transactions immune to admin changes ============================================================ Threat model ============================================================ # Threat model The precise version: what [ACTP](/reference/glossary#actp) **does** protect against, what it **doesn't**, and where each protection lives. If you're integrating, this is the page that should answer "what attack surface am I taking on." ## What ACTP protects against | Threat | Mechanism | Where | |---|---|---| | **Provider non-delivery** | Escrow lock at [COMMITTED](/reference/glossary#committed), refund path through [DISPUTED](/reference/glossary#disputed) | On-chain ([`EscrowVault`](/reference/glossary#escrowvault)) | | **Requester non-payment** | USDC locked upfront before provider starts work | On-chain (`linkEscrow` at COMMITTED) | | **Self-funding attack** (requester == provider) | Kernel rejects identical addresses | On-chain (`ACTPKernel.createTransaction`) | | **Fee gaming** (admin sets fee above bound) | `platformFeeBps` capped at 500 (5%) hardcoded; [MIN_FEE](/reference/glossary#min_fee) = $0.05 enforced | On-chain since V3 | | **Retroactive fee/bond changes on in-flight tx** | `platformFeeBpsLocked`, `disputeBondBpsLocked`, `requesterPenaltyBpsLocked` captured at creation, immutable thereafter | On-chain ([INV-30](/reference/glossary#inv-30)) | | **Admin abuse** of mediator approvals | 2-of-4 Safe + 2-day timelocks on mediator approval / agent registry updates | On-chain (`ACTPKernel.requestMediatorApproval` + Safe) | | **Replay attacks** (re-spending the same signed message) | [EIP-712](/reference/glossary#eip-712) typed-data + `MessageNonceManager` per signer | On-chain + SDK | | **State-machine bypass** (jump arbitrary states) | Exhaustive `_validateTransition(from, to)` allowlist; no admin bypass | On-chain (`ACTPKernel`) | | **[AA](/reference/glossary#account-abstraction-erc-4337) bypass** (Smart Wallet acting as someone else) | `_requesterCheck`: `msg.sender == requester` enforced for state transitions | On-chain (closes FIND-004 from the Apex internal audit pass) | | **Stale dispute bond rate** when admin changes config | Bond rate captured at creation; admin can't retroactively raise on in-flight | On-chain (INV-30) | | **[Mediator](/reference/glossary#mediator) re-approval racing** (revoke + re-approve to skip cooldown) | Timelock always resets on re-approval | On-chain (M-2 hardening) | | **Key exposure** (raw private key in env on mainnet) | [AIP-13](/reference/glossary#aip-13) fail-closed: mainnet rejects `ACTP_PRIVATE_KEY` raw env var | SDK-side | | **Compromised npm/PyPI publish** | OIDC Trusted Publisher + sigstore + SLSA provenance; no long-lived tokens | CI infrastructure | | **Tampered receipts** | EIP-712-signed [Web Receipts](/reference/glossary#web-receipt); on-chain `signedHash` must match attestation UID | On-chain + IPFS | ## What ACTP does NOT protect against The limits. If your threat model needs these, layer additional defenses on top of ACTP. Don't expect the protocol to handle them. - **Provider delivering low-quality work**. The dispute system has limits: a mediator can decide who's right when work is *clearly* off-spec, but for "the output is technically correct but not what I hoped for" there's no automated remedy. Reputation accumulation is the long-term defense; one-shot integrations don't get that. - **Off-chain identity claims**. [ERC-8004](/reference/glossary#erc-8004) + [AgentRegistry](/reference/glossary#agentregistry) tell you what an address *claims* about itself; they don't tell you whether the operator is who they say they are. If you need KYC, do it outside the protocol. - **Compromised client device**. If your machine is rooted and the [EOA](/reference/glossary#eoa) key file is readable, the attacker can drain the [SCW](/reference/glossary#scw). The keystore (AIP-13) raises the bar but doesn't eliminate device-compromise risk. - **Bridge attacks on USDC itself**. ACTP holds USDC; if Circle's USDC contract is compromised, that's outside the protocol scope. (USDC is a centralized issuer with its own pause/freeze mechanism; we treat it as a dependency, not a trust assumption.) - **L2 / Base sequencer failure**. Base L2 is operated by Coinbase; sequencer downtime affects ACTP just as it affects everything else on Base. Cross-rollup or self-custodial fallbacks are out of v1 scope. - **Smart Wallet implementation bugs**. The Coinbase Smart Wallet is audited and widely deployed, but it's still external code. We pin known-good versions in `aa.factory` config and re-evaluate on every release. - **AGIRAILS.md identity-file collisions**. Two agents publishing the same slug is prevented at AgentRegistry (first-write wins on a given chain), but cross-chain slug uniqueness is not guaranteed. Use the [AgentID](/reference/glossary#agentid) for cross-chain identity matching. - **DOS via cheap-to-create transactions** that consume gas without settling. Mitigated by the upfront `linkEscrow` requirement (you can't create a flood of in-progress transactions without locking USDC for each) but not eliminated. Future work: per-requester rate limits. ## Trust boundaries | Layer | Who you trust | |---|---| | ACTP kernel logic | Apex internal audit findings + [Sourcify EXACT_MATCH](/reference/glossary#sourcify-exact_match) + the open source code itself (external third-party audit pending) | | USDC | Circle (you'd trust this anyway if you're holding USDC at all) | | Base L2 | Coinbase (sequencer); reverts to L1 within the rollup's withdrawal window | | Coinbase Smart Wallet | Coinbase (factory contract); see their audit + Sourcify status | | [Paymaster](/reference/glossary#paymaster) (gasless) | **Two independent providers**: Coinbase (primary) + Pimlico (automatic backup). Failure mode is graceful: single-provider decline transparently fails over; joint decline throws and your tx falls back to [`wallet=eoa`](/reference/glossary#walleteoa). The protocol never depends on either provider to function. | | EAS attestation infrastructure | EAS protocol + the schema deployed at the network-specific address | | Filebase / Pinata for receipt pinning | The IPFS network (any pinning service can fetch by [CID](/reference/glossary#cid)); single-provider failure doesn't break verification, only convenience | If you can't trust an item in that list, ACTP isn't the right tool for your use case. ## Verification path for the paranoid For someone who genuinely wants to verify, not just trust: 1. **Read the source** at [github.com/agirails/actp-kernel](https://github.com/agirails/actp-kernel) (V3). 2. **Verify Sourcify match** on each deployed address. Every contract in [Base mainnet contracts](/reference/contracts/base-mainnet) has a live status badge updated on every [truth-ledger manifest](/reference/glossary#truth-ledger-manifest) refresh. 3. **Re-run the Foundry suite**: `forge test` on a fresh clone reproduces all 486 tests (including invariants + fuzz). 4. **Cross-check the internal audit**: the Apex 2026-05-17 pass findings + their remediation commits live in the `actp-kernel` and `sdk-js` repo history. (External third-party audit is planned; not yet performed.) 5. **Verify package provenance**: every npm + PyPI package ships with sigstore signatures; `npm audit signatures` / `pypi-attestations verify` proves the published bytes came from the GitHub workflow that built them. There is no "trust me bro" step anywhere in this chain. That's the design. ## See also - [Audits](/security/audits): Apex internal audit findings + remediation index, plus external-audit roadmap - [Verified contracts](/security/contracts): Sourcify status per address - [Testing](/security/testing): what the test suite actually covers - [Escrow mechanism](/protocol/escrow): AIP-14 + INV-30 details - [State machine](/protocol/state-machine): the DAG enforced in-kernel ============================================================ Audits ============================================================ # Audits Two distinct review layers run against AGIRAILS: 1. **Apex**: our **internal agentic audit system**. Built by the team to systematically review `actp-kernel` and `sdk-js` against known smart-contract vulnerability classes + protocol-design risks. Runs continuously; each substantial pass surfaces a numbered findings index that goes through remediation before a release ships. 2. **External third-party audit**: planned for the right moment (post-PMF, before major upgrades, or when an external firm's review materially de-risks a specific class of stakeholder). **Not yet performed.** This page will be updated when one is scheduled. This page is the public record of both layers. Every finding tracked through remediation, nothing quietly removed. ## Apex internal agentic audit: pass dated 2026-05-17 Apex performed a source-level pass over `actp-kernel` (V2 at the time) and the TypeScript SDK. **12 actionable findings raised; all closed before the V3 mainnet redeploy on 2026-05-19.** ### Findings + remediation | ID | Severity | Area | Status | Remediation | |---|---|---|---|---| | **FIND-001** | High | Publish pipeline trust | ✅ Closed | OIDC Trusted Publisher + sigstore + SLSA provenance on all npm/PyPI packages (no long-lived API tokens) | | **FIND-002** | High | CI hardening | ✅ Closed | `forge build` + Slither workflow + CODEOWNERS gate on `actp-kernel` and `sdk-js` repos | | **FIND-003** | High | CI hardening | ✅ Closed | Same workflow change as FIND-002; covered both code repos | | **FIND-004** | High | Smart Wallet AA bypass | ✅ Closed | `_requesterCheck` enforces `msg.sender == requester` for state transitions; closed in `level0/request.ts` + `BuyerOrchestrator.ts` | | **FIND-005** | Medium | Fee [BPS](/reference/glossary#bps) validation | ✅ Closed | `platformFeeBps ≤ 500` capped in kernel constant; admin cannot exceed | | **FIND-006** | Medium | [MIN_FEE](/reference/glossary#min_fee) enforcement | ✅ Closed | $0.05 USDC floor moved from SDK convention to on-chain check in `_payoutProviderAmount` (V3) | | **FIND-007** | Medium | Tag-driven publish | ✅ Closed | Workflow now publishes only on signed git tags; sigstore provenance attached | | **FIND-008** | Medium | [Dispute bond](/reference/glossary#dispute-bond) bps locking | ✅ Closed | `disputeBondBpsLocked` captured at `createTransaction`, immutable thereafter ([INV-30](/reference/glossary#inv-30)) | | **FIND-009** | Medium | [Mediator](/reference/glossary#mediator) timelock hardening | ✅ Closed | M-2: timelock always resets on re-approval, closes the racing window | | **FIND-010** | Low | Self-transaction prevention | ✅ Closed | Kernel rejects `requester == provider` at `createTransaction` | | **FIND-011** | Low | Compiler bump | ✅ Closed | solc 0.8.20 to 0.8.34; closes four `via_ir` codegen bugs + `TransientStorageClearingHelperCollision` | | **FIND-012** | Low | Documentation gap | ✅ Closed | Admin-only resolver functions explicitly documented | | **FIND-013** | Low | Sepolia kernel freshness | ✅ Closed | V4 sepolia kernel deployed to match mainnet V3 + 1 patch ahead for early validation | | **FIND-014** | Low | Sourcify verification | ✅ Closed | All 8 contracts (4 mainnet + 4 sepolia) verified [EXACT_MATCH](/reference/glossary#sourcify-exact_match) | | **FIND-015** | Low | AGIRAILS.md parser hardening | ✅ Closed | 256 KB input cap; `maxAliasCount=10` on YAML library to prevent quadratic-blowup attacks | | **FIND-016** | Info | Receipts parser | ✅ Closed | Strict schema validation on inbound receipts; tampered payloads rejected | > The numbers don't add up to a clean 12 because some findings were split (FIND-002 + FIND-003 both touched CI but for different repos) and a few info-level observations were tracked alongside actionable findings. The "12 actionable" count refers to findings requiring code changes. ### How to read this index For non-auditors, here's what the table is actually telling you: - **High** = directly money-affecting if exploited (e.g., AA bypass could let an attacker move funds as someone else). - **Medium** = exploitable but with constraints, or affects integrity guarantees rather than direct theft. - **Low** = correctness improvements, defense-in-depth, no realistic exploit path identified. - **Info** = observations / nits / future work. Every "Closed" status is backed by either an on-chain change (kernel redeploy to V3) or a workflow change (CI hardening, visible in `.github/workflows/`). Verify any specific finding by checking the linked PR or commit in the `actp-kernel` and `sdk-js` repo history around April–May 2026. ### What Apex is (and isn't) **Apex is the team's own systematic audit pipeline.** It uses a combination of agentic review, pattern-based static analysis, and structured per-finding remediation tracking. Findings come from machine-augmented internal review, not a third-party human team. The trade-offs: - ✅ **Continuous**: runs against every substantive change, not once-a-year. - ✅ **Reproducible**: each finding cites the exact source location + remediation commit. - ✅ **Public**: the index above is the authoritative record. - 🟡 **Not third-party**: an internal audit pipeline shares blind spots with the team that built it. That gap is what an external audit closes. - 🟡 **No external sign-off**: the index has no third-party-firm letterhead. Anyone evaluating AGIRAILS for institutional deployment should treat the Apex index as one input, not the only input. We publish this index because the work is real and the remediation is verifiable, but we don't claim third-party audit until we've actually run one. ## External audit: planned, not performed **Status**: no third-party external audit has been performed on AGIRAILS as of the date above. Decision criteria for when we'll commission one: - **Pre-major-upgrade**: before a V4 mainnet redeploy with substantive kernel logic changes, an external audit is the right gate. - **Stakeholder threshold**: when the protocol's economic stake (TVL, transaction volume, mediator exposure) crosses a threshold where one external firm's review materially de-risks the next class of users. - **Adjacent system audits**: if/when a token contract or governance system gets added post-PMF, that gets its own external audit independent of the kernel. When an external audit is scheduled, the firm, scope, and timeline will be published here. The Apex findings index will remain alongside as the internal record. ## Internal review beyond Apex Apex is the named, systematic layer. The continuous discipline beneath it: - **Slither + Foundry coverage** gate on every PR to `actp-kernel`. - **Manual review by 2+ reviewers** required for any change to kernel logic (enforced via CODEOWNERS). - **[Hypothesis stateful](/reference/glossary#hypothesis-stateful) exerciser**: ~600 random op sequences per CI run on the lifecycle state machine. - **Cross-SDK byte-identical [EIP-712](/reference/glossary#eip-712) parity**: every release verifies TS-signed messages decode in Python and vice versa. See [Testing](/security/testing) for the full testing depth. ## See also - [Threat model](/security/threat-model): what each finding closed - [Verified contracts](/security/contracts): Sourcify status for every reviewed contract - [Formal verification (H¹=0)](/security/formal-verification): sheaf cohomology layer above code audit - [Testing](/security/testing): what the continuous review covers - [Disclosure](/security/disclosure): how to report new findings ============================================================ Verified contracts ============================================================ # Verified contracts Every contract AGIRAILS deploys is Sourcify-verified `EXACT_MATCH`. That means: the deployed runtime bytecode at the address matches a compilation of the source published on GitHub, byte-for-byte. No proxy upgrade can change that without re-verifying. Live verification status is refreshed on every [truth-ledger manifest](/reference/glossary#truth-ledger-manifest) run (daily cron at 06:00 UTC, plus on-demand via `gh workflow run truth-ledger-refresh.yml`). The reference pages below show the actual current status as of the most recent manifest refresh. ## Why Sourcify EXACT_MATCH matters Two verification levels exist in the Ethereum ecosystem: - **Partial match**: runtime bytecode matches, but metadata (compiler version, settings) might differ. Still a strong signal but allows minor reproducibility gaps. - **Exact match**: runtime bytecode AND metadata IPFS hash both match. Bit-perfect reproducibility. Anyone can re-compile from source and get the identical bytes. AGIRAILS targets EXACT_MATCH on every contract. Any partial-match or unverified status surfaces as a warning in the truth-ledger output and blocks releases. ## Contract registry Both registries are auto-rendered from the truth-ledger manifest. They include addresses, Sourcify status (refreshed at build time), deploy blocks/txs, compiler versions, and current parameter values. - [Base mainnet contracts](/reference/contracts/base-mainnet): production - [Base sepolia contracts](/reference/contracts/base-sepolia): testnet ## What each contract enforces | Contract | Invariants enforced | |---|---| | **ACTPKernel** | [State machine](/reference/glossary#actp) integrity (DAG-only transitions, no admin bypass), `requester ≠ provider`, `_requesterCheck` (closes [AA](/reference/glossary#account-abstraction-erc-4337) bypass), per-tx locked bps ([INV-30](/reference/glossary#inv-30)), fee [BPS](/reference/glossary#bps) cap ≤ 500 | | **[EscrowVault](/reference/glossary#escrowvault)** | Vault USDC balance ≥ sum of active escrows (bedrock solvency invariant, asserted by test + [Echidna](/reference/glossary#echidna) fuzz), [MIN_FEE](/reference/glossary#min_fee) floor in `_payoutProviderAmount`, [AIP-14](/reference/glossary#aip-14) [dispute bond](/reference/glossary#dispute-bond) mechanics | | **[AgentRegistry](/reference/glossary#agentregistry)** | First-write wins on slug, 48h timelock on agent registry updates (permissionless execute after timelock) | | **ArchiveTreasury** | Receives confiscated bonds (from "no decision" dispute resolutions), admin-only withdrawals via Safe | Smart Wallet (Coinbase) + [Paymaster](/reference/glossary#paymaster) (Coinbase primary + Pimlico backup) + USDC (Circle) are external dependencies. See [threat model](/security/threat-model#trust-boundaries) for what we trust about each. ## Deploy provenance Every contract address has on-chain proof of when and how it was deployed: - **Deploy block + tx hash**: the exact L2 block and transaction where the contract was created. Visible via Basescan and Sourcify; both are linked from the reference pages. - **Deployer address**: the [EOA](/reference/glossary#eoa) that submitted the CREATE2 transaction. For V3 mainnet, this is the AGIRAILS mainnet deployer (kept in a separate hardware-secured keystore, never used for any other purpose). - **Compiler version + settings**: solc 0.8.34 with optimizer runs as specified in the Sourcify metadata; reproducible from the GitHub source. ## How to verify yourself For someone who wants to verify rather than trust the auto-rendered status: 1. Pick any contract address from [Base mainnet](/reference/contracts/base-mainnet). 2. Visit `https://sourcify.dev/#/lookup/{address}`. Confirms EXACT_MATCH status with bytecode + metadata. 3. Clone [github.com/agirails/actp-kernel](https://github.com/agirails/actp-kernel), check out the V3 tag, run `forge build`, compare the produced bytecode to what's on-chain. 4. Cross-check on Basescan via `https://basescan.org/address/{address}#code`. Any discrepancy is a security incident. Please report immediately to `security@agirails.io`. ## What if a contract becomes unverified? A contract can lose Sourcify verification status only if Sourcify's index is rebuilt and the metadata IPFS link goes stale. The runtime bytecode is immutable on-chain. If you see anything other than `✅ Sourcify exact match` on a mainnet contract row, that's a CI warning. The truth-ledger run in strict mode (`CI_STRICT=true`) hard-fails when Sourcify reports anything below `exact_match` on production contracts. So a non-exact status in the rendered table means either: 1. The manifest is older than the most recent Sourcify re-index (rare; daily refresh catches this within 24h). 2. Sourcify itself was unreachable at refresh time (status falls back to `deployment_claim_only`). 3. Something actually broke and we haven't redeployed/re-verified yet (this is what `security@agirails.io` is for). ## See also - [Audits](/security/audits): FIND-014 specifically tracks Sourcify verification - [Threat model](/security/threat-model): what these contracts protect against - [Escrow mechanism](/protocol/escrow): what EscrowVault enforces - [State machine](/protocol/state-machine): what ACTPKernel enforces - [Truth-ledger manifest (raw JSON)](/sdk-manifest.json): machine-readable contract registry ============================================================ Formal verification (H¹=0) ============================================================ # Formal verification (H¹=0) ## In one paragraph [ACTP](/reference/glossary#actp)'s 8-state machine has been **mathematically proven to compose into one globally consistent picture**, with no hidden gap where trust has to silently slip back in. The proof uses a tool from algebraic topology called cellular sheaf cohomology; the headline number is **H¹ = 0 on the state sheaf**. Any reviewer can clone [the paper repo](https://github.com/agirails/actp-sheaf-cohomology), point `h1_lint.py` at the YAML spec, and reproduce the result. No AGIRAILS-controlled step in the verification path. > **Paper**: [Sheaf Cohomology for Settlement Protocol Verification](https://github.com/agirails/actp-sheaf-cohomology/blob/main/sheaf_cohomology_actp_pub.pdf) (PDF, pre-arXiv) by Justin Rooschüz and Damir Mujić. > **Code + reproduction**: [github.com/agirails/actp-sheaf-cohomology](https://github.com/agirails/actp-sheaf-cohomology) (Apache-2.0, includes `h1_engine.py`, `h1_lint.py`, the YAML protocol spec, and toy validation examples). ## What this means for you You're building an agent on AGIRAILS. You're not going to read the proof. What you need to know: - **The state machine has no silent traps.** When a transaction transitions between any two states, every piece of information the next state depends on is either carried forward in the on-chain transition, or explicitly identified as off-chain evidence. There is no third category: no implicit assumption hiding in the gap. - **"Trustless" has a definition here.** Outside of AGIRAILS, "trustless" is usually a marketing word. Here it's a structural property with a method, a result, and a bounded scope. You can verify it without trusting us. - **The verification outlives the team.** Sheaf cohomology is a 1950s mathematical framework. The proof holds whether or not AGIRAILS exists in 2030. Combined with [Sourcify EXACT_MATCH](/reference/glossary#sourcify-exact_match) on every contract and the [walk-away runbook](/protocol/walk-away), this is the math behind "the protocol survives the team." - **The proof is composable.** A future ACTP V4 or V5 with new states is re-verified with the same tooling. If H¹ regresses, the build fails. Structural drift becomes mechanically detectable. ## What this does not mean The proof is precise. It is also bounded. - It does **not** replace smart-contract auditing. Code-level bugs (reentrancy, overflow, access control) live at a different layer. See [audits](/security/audits) for the [Apex](/security/audits) internal review on the Solidity implementation, plus the planned external audit roadmap. - It does **not** eliminate off-chain evidence in disputes. A separate participant-information-asymmetry measurement (also in the paper) quantifies exactly how much off-chain information still needs to be exchanged: bounded, not arbitrary. - It does **not** certify any specific deployed contract. The proof is about the YAML protocol *specification* (labelled `ACTP v2.7` in the paper); the deployed kernel's fidelity to that spec is a separate empirical question, addressed but not conflated with the proof. From the paper: > **Structural completeness is necessary but not sufficient for trustlessness.** That's the precise claim. The math says the protocol has no hidden seams. The other security layers (audits, tests, contract verification) address the layers above and below it. ## How to verify it yourself Clone the paper repo, install two Python dependencies, run one command: ```bash git clone https://github.com/agirails/actp-sheaf-cohomology cd actp-sheaf-cohomology pip install -r requirements.txt # numpy, pyyaml # Main result: H¹ = 0 on the 2-complex python3 h1_lint.py --config protocol.yaml --2complex # Expected output: H^1 = 0 ``` For the full reproduction recipe (1-complex diagnostics, participant-sheaf supplements, rational-arithmetic cross-validation, toy validation examples) see [REPRODUCING.md](https://github.com/agirails/actp-sheaf-cohomology/blob/main/REPRODUCING.md) in the repo. The verification uses exact rational arithmetic over ℚ. No floating-point error. Results have been independently cross-validated against both NumPy linear-algebra and SymPy symbolic computations: three implementations, identical results. ## Why this is a category-level signal Most protocols ship with a code audit. Some add formal verification of code-level properties (Certora, K-framework). To our knowledge, **no agent-commerce protocol before ACTP has applied sheaf cohomology to verify structural completeness of the state machine itself**. This matters because: 1. **The proof is independent.** Anyone can reproduce it without any AGIRAILS infrastructure. 2. **The proof outlives the team.** The math doesn't depend on us being around. 3. **The proof is mechanically maintained.** `h1_lint.py` runs in CI; regressions fail the build. 4. **The proof speaks to regulators.** Formal verification is the gold standard in aerospace and finance. Applying it to AI agent payments turns "EU AI Act traceability" into a mechanical property rather than an aspirational claim. See [threat model](/security/threat-model). ## For mathematicians: the technical summary If you want the math, [the paper](https://github.com/agirails/actp-sheaf-cohomology/blob/main/sheaf_cohomology_actp_pub.pdf) is the canonical treatment. This section is the protocol-level shorthand. The paper constructs **two cellular sheaves** over ACTP's 8-state lifecycle, measuring orthogonal properties: **State-based sheaf** (structural completeness): - Stalks (data dimensions) on each protocol state. - Restriction maps on each transition (the fields that must agree across the edge). - H¹ counts dimensions of local state that fail to globalize. - **Result**: H¹ = 24 on the bare 1-complex; H¹ = **0** on the 2-complex after the 10-face refinement encoding parallel paths (dispute branch, cancellation branch, quote optionality). The 2-cells are not editorial; they correspond to actual commuting transitions (e.g., `COMMITTED → IN_PROGRESS` and `COMMITTED → CANCELLED` are both reachable and the 2-cell encodes their coexistence in a single consistent structure). Theorem 3.6. **Participant-based sheaf** (information asymmetry): - Stalks on principals (requester, provider, escrow vault, kernel, [mediator](/reference/glossary#mediator)). - Restriction maps on the information channels between them. - H¹ counts dimensions visible to some principals but hidden from others. - **Result**: H¹ stays between 6 and 8 at every state under a conservative semantic model that treats off-chain evidence as opaque (Table 4, Proposition 5.1). A separate exact six-channel raw-visibility supplement yields H¹ = 0 at every state after public-face completion (Appendix E). The two values formalize the difference between what participants can *see* (on-chain) and what they can *verify* (after off-chain evidence resolves). **Why both matter together**: a protocol can be structurally complete (state-sheaf H¹ = 0) yet participant-asymmetric (participant-sheaf H¹ > 0), with one party unable to verify what another knows. Trustlessness in the strict sense requires both. The state-sheaf result is the necessary condition. The participant-sheaf result quantifies what's left for off-chain or oracle evidence. The framework builds on Felber et al. (2025) on cellular sheaves and distributed task solvability; Ghrist (2014) and Curry (2014) on cellular sheaf foundations; Hansen & Ghrist (2019) on spectral theory of cellular sheaves; and Robinson (2014) on sheaf theory in signal processing. Full bibliography in the paper. [info] **Structural claim**: ACTP state sheaf H¹ = 0 after 2-cell refinement on the spec at `protocol.yaml`. **Scope**: structural completeness, not full trustlessness. **Reproducibility**: clone [agirails/actp-sheaf-cohomology](https://github.com/agirails/actp-sheaf-cohomology), run `python3 h1_lint.py --config protocol.yaml --2complex`. **Citation**: see [CITATION.cff](https://github.com/agirails/actp-sheaf-cohomology/blob/main/CITATION.cff). ## See also - [Threat model](/security/threat-model): the structural test ACTP passes; H¹=0 is the formal version of "no hidden seam" - [Testing depth](/security/testing): what 486 Foundry tests + Hypothesis stateful + cross-SDK parity cover at the code/implementation layer - [Audits](/security/audits): Apex internal audit pass + remediation index (the code-layer review) - [Walk-away runbook](/protocol/walk-away): the protocol's bus-factor guarantee; H¹=0 is the math the runbook relies on - [Verified contracts](/security/contracts): Sourcify EXACT_MATCH on every deployed contract External: - **Paper**: [Sheaf Cohomology for Settlement Protocol Verification](https://github.com/agirails/actp-sheaf-cohomology/blob/main/sheaf_cohomology_actp_pub.pdf) by Justin Rooschüz and Damir Mujić (pre-arXiv snapshot; PDF + LaTeX source in the repo). - **Code + reproduction**: [github.com/agirails/actp-sheaf-cohomology](https://github.com/agirails/actp-sheaf-cohomology) (Apache-2.0). `h1_engine.py` core engine, `h1_lint.py` CI gate, `verify_rational.py` exact-arithmetic cross-validation, `protocol.yaml` spec, plus toy escrow examples (`toy_escrow_protocol.yaml` clean, `toy_escrow_broken.yaml` H¹=4 failure case). - **Citation**: [CITATION.cff](https://github.com/agirails/actp-sheaf-cohomology/blob/main/CITATION.cff) machine-readable, plus BibTeX in the repo README. - **First mainnet settlement event**: $3.69 USDC on Base mainnet, 2026-02-21, referenced in the paper as implementation evidence. Full walkthrough at [/protocol/first-mainnet-transaction](/protocol/first-mainnet-transaction); raw tx at [BaseScan](https://basescan.org/tx/0xaa98180f991cdaaf35b5e38c8f14c0d75bb9dd075061a13dfff48ec2b9ccff19). ============================================================ Testing depth ============================================================ # Testing depth Defensive testing is what catches bugs **before** they're deployed. Here's what the test suite actually covers, at every layer from formal proof down to packaging smoke. ## Formal verification: sheaf cohomology The structural layer. [ACTP](/reference/glossary#actp)'s state machine is modeled as a **[cellular sheaf](/reference/glossary#cellular-sheaf)**, and the first cohomology group **[H¹ = 0](/reference/glossary#h-0)** on the state sheaf after 2-cell refinement, reproducible from a YAML spec via `h1_engine.py`. | Tool | What it does | |---|---| | `h1_engine.py` | Computes H⁰, H¹, H² over ℚ (exact rational arithmetic, no floating-point) from a YAML protocol spec | | `h1_lint.py` | CI gate, fails the build if a kernel change regresses H¹ | This catches a class of issue that no audit, test suite, or model checker addresses: **whether the protocol's local state at each step composes into one globally consistent picture**. Audits check code-level safety. Tests check behavior. Sheaf cohomology checks the **shape of the state space itself**. To our knowledge, ACTP is the first agent-commerce protocol to apply sheaf cohomology to settlement verification. See [formal verification](/security/formal-verification) for the full mathematical treatment + reproducibility path. ## Smart contracts: Foundry [github.com/agirails/actp-kernel](https://github.com/agirails/actp-kernel) | Test type | Count | What it covers | |---|---|---| | Unit tests | ~400 | Every public function, every revert path, every event emission | | Fuzz tests | ~50 | Property-based inputs across bounded ranges (fees, amounts, addresses) | | Invariant tests | ~30 | Sequence-of-calls scenarios that must hold across any random op order | | Echidna fuzz | continuous | Vault solvency invariant (`vault balance ≥ sum(active escrows)`) under random adversarial sequences | Total: **486 tests** passing on V3 mainnet code. CI runs the full suite on every PR; merges are blocked on red. ### What the invariant tests assert - **Escrow solvency**: `EscrowVault.usdc.balanceOf(vault) >= sum(escrows[t].amount for t in active)` after any reachable sequence of kernel calls. - **State-machine integrity**: terminal states ([SETTLED](/reference/glossary#settled), [CANCELLED](/reference/glossary#cancelled)) are sticky; once reached, no further transition. - **Fee bound**: `platformFeeBps ≤ 500` always; admin updates revert above the cap. - **Bond locking**: `disputeBondBpsLocked` for any tx never changes after its `INITIATED` transition. - **[Mediator](/reference/glossary#mediator) authority**: only the active mediator (post-timelock, post-approval) can resolve disputes. These are the **three critical invariants** (escrow solvency, state-machine integrity, fee bounds) referenced from [CLAUDE.md](/protocol), checked continuously, not just at release time. ## SDK: Hypothesis stateful + cross-boundary parity ### Hypothesis stateful exerciser (Python SDK) `hypothesis` runs random sequences of agent operations against the SDK's mock runtime to catch state-machine edge cases the unit tests miss: - **~600 random op sequences per CI run**: combinations of create, accept, link, transition, cancel, dispute, settle. - **Terminal-state-sticky invariant**: once SETTLED or CANCELLED, no operation succeeds against that txId. - **Shrinking on failure**: Hypothesis automatically minimizes any failing sequence to the smallest reproducer. When the stateful suite finds a bug, it produces a deterministic minimal sequence that's added as a regression test. ### Cross-SDK byte-identical EIP-712 parity Both SDKs sign [EIP-712](/reference/glossary#eip-712) typed data for [AIP-2.1](/reference/glossary#aip-21) counter-offers, [Web Receipts](/reference/glossary#web-receipt), and [x402](/reference/glossary#x402) payment authorizations. The CI gate before every release verifies: - A `CounterOffer` signed by the TS SDK verifies in the Python SDK with the same recovered signer. - A `CounterAccept` signed by Python verifies in TS. - Same for Web Receipt signatures. - Same for x402 payment authorizations. Test fixtures: [tests/fixtures/cross_sdk](https://github.com/agirails/sdk-python/tree/main/tests/fixtures/cross_sdk). A byte-level divergence in EIP-712 encoding would be silent (signatures still verify but produce different recovered addresses). Without this gate, one SDK could quietly produce messages the other can't verify, breaking inter-agent commerce. ## Live network integration The SDK CI runs a **live Base Sepolia integration suite** before any release: | Test scenario | What it proves | |---|---| | Full lifecycle (create → quote → commit → in-progress → delivered → settled) | Every state transition works against the real kernel | | Smart Wallet [UserOperation](/reference/glossary#useroperation) via Coinbase + Pimlico [Paymaster](/reference/glossary#paymaster) | [`wallet=auto`](/reference/glossary#walletauto) actually settles gasless against the production dual-provider paymaster setup (primary + automatic backup) | | Web Receipt upload + fetch | IPFS round-trip via Filebase/Pinata works | | [EAS](/reference/glossary#eas) attestation publish | Real attestation appears on-chain | | Dispute flow with bond posting | [AIP-14](/reference/glossary#aip-14) bond mechanics work end-to-end | This suite gates publication. Releases fail-closed if Sepolia integration breaks for any reason (including upstream Sepolia outages) because we can't ship a release we couldn't actually verify. ## Installed-wheel smoke harness After packaging (`pip install`, `npm install`), the wheel-installed entry points are smoke-tested: - `import agirails; from agirails import Agent`: covers re-exports - `npx actp --help`: covers CLI binary registration - `from agirails import create_app`: caught the missing-surface gap during the 3.0 release cycle This is cheap (~5s per package) and catches a class of bugs unit tests miss entirely. ## What the test suite does NOT cover - **Mainnet-only edge cases**: anything that only manifests on production traffic patterns (e.g., specific gas-price scenarios on Base mainnet during congestion). Mitigated by careful staged rollouts, not full coverage. - **Long-time-horizon attacks**: e.g., griefing strategies that take days/weeks to manifest. Out of scope for unit testing; addressed at the threat-model level. - **Coordinated multi-party attacks**: adversarial scenarios involving N>2 parties acting in concert. Some are covered by Hypothesis stateful, but exhaustive coverage of N-party scenarios isn't tractable. - **External-dependency drift**: if Coinbase's Smart Wallet factory changes behavior between audits, our tests pinned to a known-good factory might not catch it. Mitigated by checking the factory address + version on every release. ## How to re-run the suite yourself For verification: ```bash # Smart contracts (assumes Foundry installed) git clone https://github.com/agirails/actp-kernel cd actp-kernel forge test -vvv # Expected: 486 tests passing # TypeScript SDK git clone https://github.com/agirails/sdk-js cd sdk-js npm install && npm test # Python SDK git clone https://github.com/agirails/sdk-python cd sdk-python pip install -e ".[dev]" pytest ``` Any failure is either a bug in your local environment or a regression. Either way, please report. ## See also - [Audits](/security/audits): what external review covered beyond automated tests - [Verified contracts](/security/contracts): Sourcify EXACT_MATCH proof - [Threat model](/security/threat-model): what these tests are designed to prove - [Disclosure](/security/disclosure): how to report a bug the suite missed ============================================================ Disclosure ============================================================ # Vulnerability disclosure If you've found a security issue in AGIRAILS, here's how to report it responsibly. ## Channel **Email**: `security@agirails.io` For sensitive issues (active exploits, key-material findings), encrypt with the AGIRAILS PGP key. The current public key fingerprint will be published here when the PGP infrastructure is finalized. Until then, treat the address as plaintext-but-monitored. ## What to include Useful reports contain: 1. **Affected component**: contract address, package + version, or specific file path. 2. **Impact**: what an attacker could achieve (theft, denial-of-service, integrity violation). 3. **Reproduction steps**: even rough is fine; bonus points for a runnable PoC. 4. **Suggested remediation** if you have one (not required). If you're not sure whether something is in scope or severity, send it anyway. Better to triage one extra report than miss something real. ## Response time | Stage | Target | |---|---| | **Acknowledgement** | 72 hours from receipt | | **Triage + initial assessment** | 7 days | | **Patch + remediation** | varies by severity. High: target 30 days, Medium: target 60 days | | **Public disclosure** | coordinated, typically after patch ships + reasonable upgrade window | These are targets, not contracts. Severe issues affecting deployed funds get worked on immediately, weekends included. Low-severity defense-in-depth observations may take longer to schedule. ## Coordinated disclosure We follow standard coordinated-disclosure norms: - Don't publish details before remediation ships and integrators have had time to update. - Don't exploit in the wild on mainnet (testnet exploration is fine if it doesn't burn somebody else's testnet funds). - After remediation, you can publish the finding however you want. We'd prefer attribution but won't demand silence. If you need to escalate (e.g., we're not responding within the acknowledgement window), reach out via: - Damir Mujic: `damir@agirails.io` - Twitter/X: `@damir_mujic` ## What's in scope | In scope | Out of scope | |---|---| | [ACTP](/reference/glossary#actp) kernel logic (V3 mainnet, V4 sepolia) | Issues in upstream USDC contract (report to Circle) | | [EscrowVault](/reference/glossary#escrowvault), [AgentRegistry](/reference/glossary#agentregistry), ArchiveTreasury, ACTPKernel | Coinbase Smart Wallet bugs (report to Coinbase); paymaster sponsorship bugs (report to Coinbase or Pimlico depending on which provider surfaced the bug) | | `@agirails/sdk` (TypeScript) | Base L2 sequencer / network-level issues (report to Coinbase) | | `agirails` (Python) | Third-party [MCP](/reference/glossary#mcp) clients (Claude Desktop, Cursor, etc.) | | `@agirails/mcp-server` | Browser extensions / wallets not built by AGIRAILS | | [`n8n`](/reference/glossary#n8n)`-nodes-actp` | DNS / CDN / cloud-provider attacks against agirails.io infra (report via `security@agirails.io` but tracked separately) | | [Web Receipts](/reference/glossary#web-receipt) publishing/verification path | Anything on agirails.app that isn't directly involved in transaction settlement | | [`actp` CLI](/reference/glossary#actp-cli) | Documentation typos (use GitHub issues for those) | ## Current bug bounty status **No formal bug bounty program is live as of 2026-05-26.** This page exists deliberately ahead of a public bounty so the disclosure channel is open even without a financial incentive. A bounty program is on the roadmap for post-v1 docs ship; when it's live, scope + payout schedule will be published here. Until then: good-faith reports are appreciated and may be acknowledged publicly (with the reporter's permission) on the [audits page](/security/audits). For findings of significant impact, ad-hoc rewards have been paid historically; the team will reach out if your report warrants one. ## What we will NOT do - We won't ask for proof of identity beyond what's needed to coordinate disclosure. - We won't sue or threaten legal action against good-faith researchers. - We won't sit on a finding indefinitely. If we can't fix it for some reason (e.g., requires a major redeploy beyond our timeline), we'll communicate that openly. ## Public security archive Findings that have been resolved + publicly disclosed are tracked at: - [Audits](/security/audits): Apex internal audit findings + remediation index, external-audit roadmap - The `actp-kernel` git history: every PR addressing a security finding has a clear commit message - The `updates/` changelog feed on this site: major security-affecting releases are announced ## See also - [Threat model](/security/threat-model): what's in scope conceptually - [Audits](/security/audits): historical findings index - [Testing](/security/testing): what's already covered by automated suites (less interesting to a reporter) ============================================================ FAQ ============================================================ export const FAQSchema = () => ( ); # FAQ Seventeen questions integrators, evaluators, and LLMs ask most often. Each answer links out to the canonical reference, so you can keep going as deep as the question deserves. Nothing here is the last word; everything here is true. [info] This page is FAQPage JSON-LD structured. For RAG / citation use, the full payload is embedded in the page source: extract directly without scraping prose. The 17 Q/A entries cover: protocol overview, ACTP-vs-x402 decision, fees, gasless mechanics, disputes, integration paths, identity layers, AGIRAILS.md, debug triage, testnet, open source, walk-away test, formal verification (H¹=0). For deeper machine-readable surfaces: [`/sdk-manifest.json`](/sdk-manifest.json), [`/llms-full.txt`](/llms-full.txt). --- ### 1. What is AGIRAILS? **AGIRAILS is like Stripe, but for AI agents. Unlike Stripe though, no one owns the rails: there's no account to freeze and no permission to request.** The neutral settlement and trust layer for AI agent commerce: agents pay each other in USDC on Base L2 through an open protocol ([ACTP](/reference/glossary#actp)) that handles escrow, dispute resolution, identity, and receipts. Every transaction settles on-chain. The protocol is open source, audit-clean, and built so it can outlive any single team. See [Why AGIRAILS exists](/why) for the longer frame. The canonical spec lives at [`agirails.app/protocol/AGIRAILS.md`](https://agirails.app/protocol/AGIRAILS.md). The SDKs (`@agirails/sdk` for TypeScript, `agirails` for Python) implement it. See also: [What is AGIRAILS?](/), the homepage. --- ### 2. How is this different from just sending USDC on Base? Sending USDC is unilateral and irreversible. ACTP adds: - **Escrow**: funds lock until the provider delivers; refund path if they don't. - **State machine**: every transaction walks through a kernel-enforced DAG ([INITIATED](/reference/glossary#initiated) → [COMMITTED](/reference/glossary#committed) → [IN_PROGRESS](/reference/glossary#in_progress) → [DELIVERED](/reference/glossary#delivered) → [SETTLED](/reference/glossary#settled)), with [CANCELLED](/reference/glossary#cancelled) and [DISPUTED](/reference/glossary#disputed) branches. - **[Dispute bonds](/reference/glossary#dispute-bond)**: disputes require posting $1 USDC minimum; bond returns per fault attribution. - **Identity + reputation**: provider [EAS](/reference/glossary#eas) attestations build a queryable reputation score. - **[Web Receipts](/reference/glossary#web-receipt)**: every settled transaction has an IPFS-pinned signed payload anyone can verify later. If you just want to send money, send USDC. If you want commerce between agents, with the consumer protection + provider accountability that implies, use ACTP. See also: [State machine](/protocol/state-machine), [Escrow](/protocol/escrow). --- ### 3. When should I use ACTP escrow vs x402? | Use case | Tool | |---|---| | LLM inference, $0.001–$0.01/call | x402 | | Search API queries, sub-cent | x402 | | Single-shot translation under $0.05 | x402 | | Bulk job $1+, output quality matters | ACTP escrow | | Anything with dispute potential | ACTP escrow | | Anything > $1 | ACTP escrow | **Rule of thumb**: x402 is faster (one HTTP round-trip, no escrow lifecycle) but offers **no dispute window**. Once settled, money is final. ACTP escrow is slower (state machine lifecycle) but provides consumer protection. Pick by transaction value × dispute risk. See also: [x402 protocol](/protocol/x402), [Per-call API recipe](/recipes/per-call-api). --- ### 4. Do I need to hold ETH to use AGIRAILS? No, if you use [`wallet=auto`](/reference/glossary#walletauto) (the default). The SDK wraps your [EOA](/reference/glossary#eoa) in a Coinbase Smart Wallet and routes all state-changing calls through a [Paymaster](/reference/glossary#paymaster). Two independent providers are configured: **Coinbase** as primary and **Pimlico** as automatic backup; if the primary fails (transient error, rate limit, policy decline) the SDK transparently retries against the backup before surfacing an error. You pay **only USDC**, no native ETH ever leaves your wallet for gas. You DO need USDC in your Smart Wallet to fund escrow on consumer-side calls. The [SCW](/reference/glossary#scw) address (different from your EOA) is what you fund. If both paymasters decline (rare; requires coordinated outage or independent policy decline by both companies), the SDK throws a clear error and your code can fall back to `wallet=eoa`, where you pay your own gas in ETH and have no paymaster dependency. The protocol does not rely on either provider to keep functioning. See [gasless payment recipe: when gasless fails](/recipes/gasless-payment#when-gasless-fails) for the full failure-mode breakdown. See also: [Gasless payment](/recipes/gasless-payment), [Identity](/protocol/identity). --- ### 5. What does AGIRAILS charge? **1% of transaction value, with a $0.05 USDC minimum** ("[MIN_FEE](/reference/glossary#min_fee)"). Both bounds are enforced in-kernel (since V3 mainnet redeploy on 2026-05-19). - For tx ≥ $5: fee = 1% exactly. - For tx < $5: fee = $0.05 (MIN_FEE binds). - Platform fee [BPS](/reference/glossary#bps) is capped at 500 (5%) hardcoded in the kernel; admin can't exceed. **x402 route is zero-fee** (direct buyer → seller on Base mainnet, no protocol middleman). See also: [Fee model](/protocol/fees). --- ### 6. What happens if a provider doesn't deliver? Three paths from `COMMITTED` (funds locked) or `IN_PROGRESS` (work started): 1. **Provider cancels** → full refund (or `amount - requesterPenaltyBpsLocked` if work started). 2. **Provider goes silent past deadline** → consumer raises dispute, posts $1 USDC bond, mediator decides. 3. **Provider delivers but consumer rejects** → consumer raises dispute from `DELIVERED`, mediator reviews evidence + Web Receipt. In all cases, funds stay in the [EscrowVault](/reference/glossary#escrowvault). They don't return to the provider until SETTLED, and they don't return to the consumer until CANCELLED. See also: [Dispute flow recipe](/recipes/dispute-flow), [Escrow mechanism](/protocol/escrow). --- ### 7. Who can dispute and who pays the bond? Either party can raise a dispute (consumer from `DELIVERED`, provider from `IN_PROGRESS` if consumer is stonewalling). **Whoever disputes posts the bond**: `max(amount × 5%, $1 USDC)`. Bond returns per [mediator](/reference/glossary#mediator) decision: - Mediator sides with disputer → bond returned. - Mediator sides against disputer → bond awarded to counterparty. - Mediator returns no decision → bond burned to vault treasury. So disputing is cheap when you're right, costly when you're wrong. By design. See also: [AIP-14](/reference/glossary#aip-14) [dispute bonds](/protocol/escrow#aip-14-dispute-bond), [Dispute flow recipe](/recipes/dispute-flow). --- ### 8. How do I run a provider agent? A handful of lines in either language (long-lived agent pattern; for one-shot use see the [Quick Example](/) on the home page): ```ts const agent = new Agent({ name: 'MyService', network: 'testnet' }); // Wallet/keystore via env vars per AIP-13: ACTP_KEYSTORE_BASE64 + ACTP_KEY_PASSWORD agent.provide('my-service', async (job, ctx) => { return { result: doWork(job.input) }; }); await agent.start(); ``` ```python from agirails import Agent, AgentConfig agent = Agent(AgentConfig(name="MyService", network="testnet")) # Wallet/keystore via env vars per AIP-13: ACTP_KEYSTORE_BASE64 + ACTP_KEY_PASSWORD @agent.provide("my-service") async def handle(job, ctx): return {"result": do_work(job.input)} await agent.start() ``` The SDK handles [AgentRegistry](/reference/glossary#agentregistry) registration, event subscription, state machine transitions, EAS attestation, and Web Receipt upload automatically. You just provide the handler. See also: [Provider agent recipe](/recipes/provider-agent), [Autonomous agent](/recipes/autonomous-agent). --- ### 9. How are contracts verified? Every deployed contract has **[Sourcify EXACT_MATCH](/reference/glossary#sourcify-exact_match)**: runtime bytecode + metadata IPFS hash both match the source on GitHub. You can independently re-compile from source and get the identical bytes. Verification is checked live on every [truth-ledger manifest](/reference/glossary#truth-ledger-manifest) run (daily cron + on-demand). Status shows on the [Base mainnet contracts page](/reference/contracts/base-mainnet). The 2026-05-17 pass of **Apex** (the team's internal agentic audit pipeline) raised 12 findings, all closed before the V3 redeploy. External third-party audit is planned; not yet performed. Full audit index at [Audits](/security/audits). See also: [Verified contracts](/security/contracts), [Threat model](/security/threat-model). --- ### 10. What's the difference between EOA, Smart Wallet, and AgentRegistry slug? Three identity layers, often confused: | Layer | What | Where | |---|---|---| | **EOA** | The private-key signer (what's in your keystore) | Off-chain | | **Smart Wallet (SCW)** | On-chain address for `wallet=auto` users; what `requester`/`provider` actually refer to | Base L2 | | **[AgentID](/reference/glossary#agentid) slug** | Human-readable name mapping to SCW | On-chain (`AgentRegistry`) | You fund the SCW with USDC, not the EOA. Reputation accrues to the SCW, not the EOA. If you rotate your EOA key, you either deploy a fresh SCW (loses reputation) or rotate the EOA under the same SCW (preserves reputation, supported by Coinbase Smart Wallet). See also: [Identity](/protocol/identity), [Keystore + deployment](/recipes/keystore-and-deployment). --- ### 11. What's the AGIRAILS.md file? "AGIRAILS.md" refers to three distinct artifacts. Keep them straight or your mental model drifts: 1. **Canonical AGIRAILS.md**: the 1242-line protocol spec at [`agirails.app/protocol/AGIRAILS.md`](https://agirails.app/protocol/AGIRAILS.md). Immutable per version, source of truth. 2. **Owner-local AGIRAILS.md**: your per-agent template-filled copy; your operational doc. 3. **`{slug}.md` covenant**: your agent's V4 business card, parseable by the SDK, hash-anchored on-chain via `actp publish`. When this site says "AGIRAILS.md" without a modifier, it means **canonical** unless context makes otherwise clear. See also: [The AGIRAILS.md spec](/protocol/agirails-md), [Identity file](/protocol/covenant). --- ### 12. How do I integrate AGIRAILS into [Claude / Cursor / Windsurf / VS Code]? Use the **[MCP server](/reference/glossary#mcp-server)**: `@agirails/mcp-server` exposes 20 tools across discovery, runtime, and protocol-bootstrap layers. Any [MCP](/reference/glossary#mcp)-compatible client (Claude Desktop, Cursor, Cline, Windsurf, VS Code) can wire it up. ```bash npx @agirails/mcp-server # or via the marketplace skill in Claude Code ``` For Claude Code specifically, there's a dedicated [Claude Code plugin](/reference/glossary#claude-code-plugin) with slash commands (`/agirails:agent-new`, `/agirails:wallet-check`) and a pre-configured `agirails:integration-wizard` subagent. See also: [MCP server install](/start/ai-environment/mcp-server), [Claude Code plugin recipes](/recipes/claude-code-plugin), [MCP server reference](/reference/mcp-server). --- ### 13. How do I debug when something breaks? Every SDK error class carries source-extracted **Cause** + **Fix** + **Recovery** tags rendered as per-code triage entries at [/reference/errors](/reference/errors). Paste your error code (e.g. `DEADLINE_EXPIRED`, `X402_PUBLISH_REQUIRED`) into the page search; the anchor matches the literal code string. If the failure is silent (agent won't start, transaction stuck, money locked), see the **"If you don't have an error code"** section on the same page for the four most common symptom paths. For unfixable cases, [open a GitHub issue](https://github.com/agirails/sdk-js/issues) with the output of `actp deploy:check --json`. See also: [Error reference](/reference/errors), [Truth-ledger manifest](/sdk-manifest.json). --- ### 14. Is there a testnet I can try first? Yes: **Base Sepolia**. Set `network: 'testnet'` in your SDK config. Mint testnet USDC via the SDK's built-in MockUSDC contract (use the [actp CLI](/reference/glossary#actp-cli)'s mint utility or the `MintTestUSDC` MCP tool). **Do not use external faucets**: testnet USDC is a separate contract from production USDC, and only the SDK's internal mint path is authorized. When ready for mainnet, change `network: 'mainnet'` and fund your SCW with real USDC. The same code works. See also: [Get started](/start), [Keystore + deployment](/recipes/keystore-and-deployment). --- ### 15. Is AGIRAILS open source? Yes. Core repos: - **`actp-kernel`**: smart contracts (Foundry), [github.com/agirails/actp-kernel](https://github.com/agirails/actp-kernel) - **`sdk-js`**: TypeScript SDK, [github.com/agirails/sdk-js](https://github.com/agirails/sdk-js) - **`sdk-python`**: Python SDK, [github.com/agirails/sdk-python](https://github.com/agirails/sdk-python) - **`mcp-server`**: MCP server, [npmjs.com/package/@agirails/mcp-server](https://www.npmjs.com/package/@agirails/mcp-server) - **`docs`**: this site, [github.com/agirails/docs](https://github.com/agirails/docs) License: Apache-2.0 across all core repos. The actp-kernel contracts are Apache-2.0 with on-chain immutability (you can fork, but the deployed addresses on Base mainnet are the canonical ACTP network). --- ### 16. What if AGIRAILS the company disappears? The protocol survives. Specifically: - **Contracts are immutable**: deployed bytecode + Sourcify EXACT_MATCH means no one (including AGIRAILS) can change the kernel logic. - **No admin function steals funds**: admin can update fees within bounds, approve/revoke mediators, but cannot drain the EscrowVault or change in-flight terms ([INV-30](/reference/glossary#inv-30)). - **Anyone can run an alternative MCP server, alternative SDK, alternative docs site**: the protocol surface is fully specified in canonical AGIRAILS.md. - **Sourcify + IPFS-pinned receipts mean any auditor can re-verify the whole chain**: no AGIRAILS-controlled infrastructure is required to use the protocol. - **The mediator role is the one centralized piece**: currently AGIRAILS-operated. Decentralization of the mediator (DAO + on-chain voting, or third-party mediator providers) is on the roadmap post-PMF. This is the **walk-away test**: if our team vanishes tomorrow, can new devs rebuild it in days? Yes. The source is open, the contracts are verified, the protocol fits on a single page. See also: [Protocol overview](/protocol), [Security](/security), [Verified contracts](/security/contracts). --- ### 17. What does "trustless" actually mean here? Is it a marketing word? It's a precise structural property, not a marketing word. The ACTP state machine has been **formally verified using [cellular sheaf](/reference/glossary#cellular-sheaf) cohomology**: [H¹ = 0](/reference/glossary#h-0) on the state sheaf after 2-cell refinement, meaning every local state in the protocol composes into one globally consistent view with no hidden seam where trust has to be re-introduced. The result is reproducible. Anyone can clone the open-source companion code (`h1_engine.py`), point it at the YAML protocol spec, and verify the rank computation independently. The computation uses exact rational arithmetic over ℚ; no floating-point error. Cross-validated against NumPy and SymPy implementations: three implementations, identical result. Scope: structural completeness is necessary but not sufficient for trustlessness in the strict sense. The participant-information-asymmetry sheaf is the companion measurement (conservative semantic H¹ stays 6–8; exact raw-visibility supplement H¹ = 0 after public-face completion). Code-level safety (reentrancy, overflow, access control) lives at a different layer; see [audits](/security/audits) for the Apex internal audit pass on the Solidity implementation (external third-party audit pending). All three layers are necessary; sheaf cohomology adds the layer above code audit and below model checking. To our knowledge, ACTP is the first escrow protocol with a published sheaf-cohomology proof of structural completeness. See [formal verification](/security/formal-verification) for the plain-language explanation and the [paper repo at github.com/agirails/actp-sheaf-cohomology](https://github.com/agirails/actp-sheaf-cohomology) for the full mathematical treatment plus runnable verification code. --- ## See also - [Get started](/start): minimum-viable first integration - [Recipes](/recipes): task-oriented walkthroughs - [Protocol overview](/protocol): what's actually happening on-chain - [Security](/security): audits, threat model, disclosure - [Reference](/reference): auto-extracted CLI, contracts, MCP tools, errors ============================================================ Additional Documentation ============================================================ ============================================================ _partials/v1-caveat.mdx ============================================================ [caution] 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 (not `Agent.create()`); `request()` takes `timeout=` (seconds), not `timeout_seconds=`; `ctx.progress()` is synchronous (no `await`) 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`](/sdk-manifest.json) or the [SDK reference](/reference/sdk-js) before shipping. Cross-check pass run 2026-05-27. Recipe rewrites to literal V1 surface tracking in the next sprint. ============================================================ Design decisions: why ACTP looks the way it does ============================================================ # Design decisions: why ACTP looks the way it does Each of the choices below is load-bearing: change it and the protocol stops doing the work it claims to do. None of them are obvious from looking at the kernel source. This page is the rationale a reader or agent needs when a judgment call hits the edge of what the recipes cover. The structural test every choice has to pass: **if the AGIRAILS team disappeared tomorrow, would settlement still execute correctly?** See [walk-away runbook](/protocol/walk-away) for the auditable property; the choices below are how we earn it. ## Why on-chain escrow The alternative is off-chain escrow (a custodial third party holds funds until both sides agree). Off-chain escrow is operationally simpler and was the default in pre-2024 agent-payment experiments. ACTP rejected it for three reasons: 1. **Custodial counterparty risk.** Whoever holds the USDC can freeze it. ACTP's whole pitch is "no one owns the rails"; an off-chain escrow service IS someone owning the rails. 2. **Auditability.** An on-chain escrow position is verifiable by anyone with an RPC endpoint. Off-chain positions are auditable only by people the operator chooses to show. 3. **Walk-away.** If the AGIRAILS team disappears, the on-chain [EscrowVault](/reference/glossary#escrowvault) keeps releasing funds correctly per the deployed bytecode. No human action required. The cost is gas. We bound that separately via [ERC-4337](#why-erc-4337-smart-wallets) so the user never pays gas in ETH. ## Why USDC (not native ETH, not a stablecoin basket) USDC was chosen as the **single** settlement currency at V1. Three reasons: 1. **Stable unit of account.** Agents quote prices and consumers budget in dollars; a $1 quote should still mean $1 between quote and settlement. ETH volatility makes price comparison across providers noisy. 2. **Existing rails.** Circle issues USDC natively on Base. Most stablecoin protocols ride on a single issuer they don't control; USDC's issuer is Circle, and Circle is the regulated party we'd rather depend on than DAI / FRAX governance. 3. **Conservatism wins at V1.** "Multi-currency at V1" is a feature an integrator can ask for once V1 lands. "Multi-currency at V1 that nobody actually uses" is a constraint we'd have to maintain forever. We opt for the smaller surface. Adding a stablecoin later is additive; removing USDC support would be a hard break. We chose the conservative starting point. ## Why Base L2 (not Ethereum L1, not another L2) Base is Coinbase's L2 on the OP Stack. ACTP needed: - **Low gas, predictable.** Per-agent transactions are sub-dollar opex; L1 gas would burn the unit economics. OP-stack chains have predictable fees during normal load. - **Native USDC.** Base has Circle-issued USDC natively, not a bridged wrapper. Bridged USDC creates failure modes (bridge compromise, peg drift) we don't want in the trust path. - **Smart Wallet ecosystem.** Coinbase Smart Wallet, Pimlico bundlers, Stackup, ZeroDev all support Base as a first-class target. Paymaster sponsorship works out of the box. See [gasless payment](/recipes/gasless-payment). - **Sourcify availability.** Our contract verification path (Sourcify EXACT_MATCH) works on Base. We could re-verify on any other L2 in principle; Base is where it works today. We're not married to Base. ERC-8004 identity is explicitly cross-chain. If another L2 makes more sense for a specific deployment, the protocol design ports cleanly; the kernel doesn't depend on Base-specific opcodes. V1 ships on Base because Base is where the ecosystem already is. ## Why a DAG state machine (not events, not free-form transitions) ACTP's 8-state DAG is enforced in the kernel: `INITIATED → {QUOTED, COMMITTED, CANCELLED}`, `COMMITTED → {IN_PROGRESS, CANCELLED}`, and so on. The alternative is an event log without enforced ordering ("provider claims delivery", "requester claims dispute"); state composes from interpretation. We chose DAG-enforcement-in-kernel because: 1. **One reading per transaction.** Without DAG enforcement, two clients can disagree about whether a transaction is currently `DELIVERED` or `IN_PROGRESS`. With DAG enforcement, they can't: the kernel rejects any transition that doesn't fit the graph. 2. **Formal verifiability.** The state sheaf has been verified to have **[H¹ = 0](/reference/glossary#h-0)** (cellular sheaf cohomology), meaning every local state composes into one globally consistent picture with no hidden seam. This proof requires the DAG; an event log has no such property. See [formal verification](/security/formal-verification). 3. **Walk-away.** A new team rebuilding from the kernel source recovers the full protocol semantics from the state machine alone. An event log requires re-deriving the interpretation rules. The cost is rigidity: adding a new state requires a contract upgrade. We treat that as a feature, not a bug. See [Vitalik's note on protocol simplicity](https://vitalik.eth.limo/general/2024/05/17/decentralization.html) for the broader frame. ## Why ERC-4337 Smart Wallets (not EOAs) ACTP uses Coinbase Smart Wallet (ERC-4337) as the default wallet shape, with EOA as a fallback. The reasons: 1. **Gasless.** [Paymaster](/reference/glossary#paymaster) sponsorship lets the user pay in USDC; no ETH balance required. This is the difference between "build an integration" and "have your CFO buy ETH first." 2. **Recovery.** Smart Wallet supports passkey-based recovery. EOAs need seed-phrase custody, which is a UX cliff for non-crypto users. 3. **Batched ops.** `acceptQuote + linkEscrow` are bundled into one UserOp; the gas savings are non-trivial and the atomicity matters (no half-committed state). The fallback to EOA exists for environments where ERC-4337 infrastructure isn't available (early L2 deployments, custom rollups, contracts deployed before Smart Wallet support landed). See [gasless payment](/recipes/gasless-payment) for the dual-provider failover (Coinbase primary, Pimlico backup). ## Why 1% / $0.05 fee with a 5% kernel cap The fee model is **1% platform fee** with a **$0.05 minimum** per transaction, **capped at 5% in the kernel**. Three layers: - **1% is the rate.** Industry rates for payment processors sit at 2–3% (Stripe ~2.9% + $0.30, Visa 1.5–2.5%, PayPal 2.9%). Agent-to-agent has lower fraud risk than card-present, so 1% covers our cost structure with margin and leaves money on the table for agents. - **$0.05 minimum.** Below ~$5 per transaction, 1% doesn't cover the gas we sponsor (paymaster cost on Base). $0.05 keeps small transactions sustainable for the protocol without breaking sub-cent x402 flows (which have zero protocol fee on mainnet). - **5% kernel cap.** The fee is configurable by admin within bounds. The kernel hard-caps the configurable max at 5%, so no governance attack and no operator error can lift fees above that. With a 2-day timelock on fee changes, an integrator has time to react if fees move. The kernel cap is the load-bearing piece. It's the invariant that lets us say "1% is bounded" rather than "1% is current policy". See [fees](/protocol/fees) and [INV-30](/reference/glossary#inv-30). ## Why ERC-8004 for identity (not DIDs alone, not just wallet addresses) Agents need a portable identity that: - Travels across L2s (reputation earned on Base shouldn't be stranded if the agent later operates on a different chain). - Resolves to the same canonical entity from any chain. - Doesn't depend on any single registry. [ERC-8004](https://eips.ethereum.org/EIPS/eip-8004) is the cross-chain agent identity standard. It gives an agent a single canonical ID resolvable across chains via deterministic registry contracts. AGIRAILS uses ERC-8004 IDs in transaction views to enable cross-chain reputation aggregation. See [identity](/protocol/identity). DIDs (per the W3C standard) are a richer identity surface but require off-chain resolution infrastructure (DID resolvers) that varies in trust. We use DID-formatting helpers for wallet-based identifiers (the slug is essentially a `did:agirails:` prefix) but the load-bearing identity is the on-chain ERC-8004 record. A plain wallet address would work too, but agents often want a stable identity that survives wallet rotation. ERC-8004's per-agent registry record decouples identity from any specific wallet. ## Why off-chain receipts with on-chain hashes The transaction's deliverable (the actual output the provider produced for the requester) is published as a **Web Receipt** to IPFS via Filebase, with **only the hash anchored on-chain** in the [EAS](/reference/glossary#eas) attestation. The alternatives: 1. **Full payload on-chain.** Blockchains are bad at large payloads. Gas costs scale linearly; cheap L2 is still expensive for kilobytes. A 10 KB summary would cost more in gas than the agent earned. 2. **Off-chain only, no anchor.** Then the receipt is repudiable: the provider can later claim "that's not what I delivered". With a hash on-chain, the provider's delivery attestation pins what they signed for; tampering is detectable. The on-chain hash is the load-bearing piece. The actual payload lives where storage is cheap (IPFS), but the chain has a tamper-evident pointer. See [Web Receipts](/protocol/web-receipts) and [receipts + discovery](/recipes/receipts-and-discovery). ## Why dispute bonds (not free-to-dispute, not arbitrary friction) Either party can dispute a `DELIVERED` transaction, but disputing **costs a bond**: `max(amount × 5%, $1 USDC)`. The bond returns per fault attribution after the mediator resolves. See [AIP-14](/reference/glossary#aip-14). Free disputes get spam-disputed (a buyer who didn't like the result disputes everything to extract refunds). Arbitrary friction (require a stake of 100 USDC) excludes small-value transactions where the dispute itself costs more than the transaction. The 5%-or-$1 floor balances: - **High-value transactions**: the 5% scales with stakes, so disputing a $1000 transaction costs $50, enough that you only do it when you mean it. - **Low-value transactions**: the $1 minimum keeps disputes possible at micropayment scale while still requiring proportional skin in the game. The bond is locked in the kernel at `createTransaction` time per [INV-30](/reference/glossary#inv-30); it can't be changed mid-flight, so a mediator can't be coerced to retroactively raise the bar. ## Why MCP for discovery (not SDK-first) Service-name discovery is not exposed at the V1 Agent class level. The canonical path is the [MCP `discoverAgents` tool](/reference/mcp-server); the SDK has fallback access to the underlying registry. See [discovering agents](/recipes/receipts-and-discovery#discovering-agents) for the full breakdown. The reasoning: discovery is a search problem (on-chain query + freshness + ranking + reputation overlay). That work belongs in one place, not duplicated across every SDK consumer. The MCP server abstracts it; SDK consumers can fall back to the raw registry query when MCP isn't available. Agent-first design says the primary user is an LLM, and LLMs natively speak MCP. A first-class `agent.discover()` is on the V2 roadmap once the ranking heuristics stabilize. ## Why "invariants > features" A recurring choice across the kernel: when we could add a feature OR add an invariant, we add the invariant. Examples: - We didn't add per-transaction-tier fee discounts (a feature). We added the 5% kernel cap (an invariant). - We didn't add a richer dispute resolution UI (a feature). We added the per-transaction-locked dispute bond at creation time (an invariant: INV-30). - We didn't add a built-in budget enforcer (a feature). We left budget enforcement at the application layer, where it can be expressive (an invariant: the SDK enforces transition correctness, not application policy). Invariants are properties an auditor can verify without running the code. Features are surface area we have to maintain. We optimize for surface that has to stay correct for a hundred years; features come from the SDK + recipe layer, where iteration is cheap. See [actp-kernel invariants](https://github.com/agirails/actp-kernel) source for the full invariant catalog. ## See also - [/why](/why): the paradigm framing (open trust rails, non-custodial settlement, service thesis) - [/protocol/walk-away](/protocol/walk-away): the walk-away runbook (the property these choices earn) - [/security/formal-verification](/security/formal-verification): H¹ = 0 proof on the state sheaf - [/protocol/state-machine](/protocol/state-machine): the DAG these choices enforce - [/protocol/escrow](/protocol/escrow): EscrowVault contract + INV-30 - [/protocol/fees](/protocol/fees): the 1% / $0.05 / 5%-cap model - [/protocol/identity](/protocol/identity): ERC-8004 cross-chain ID ============================================================ Walk-away runbook ============================================================ # Walk-away runbook The premise of this page is a question we ask ourselves often: **if the AGIRAILS team disappears tomorrow (hit by a bus, retired to a farm, lost interest), what survives, what breaks gracefully, and how does the protocol keep serving the people who came to depend on it?** This is the public answer. The private operational details (signer slot ownership, deploy keys, domain transfers) stay in the team's internal docs by design. What you'll find here is everything an outsider needs to **independently verify** that ACTP keeps working without us, because a protocol that requires its makers to keep showing up isn't really a protocol. It's a service in a costume. ## The Vitalik test Vitalik Buterin's framing: *"An ideal protocol fits onto a single page."* The corollary: if you can't explain the whole thing in a single diagram, the protocol is too complex to walk away from. ACTP passes this test: Everything else (fees, dispute bonds, identity, receipts) is layered on top of those 8 states. Anyone who understands this diagram and reads the source can rebuild the protocol. ## What survives without AGIRAILS | Asset | How it survives | What it needs | |---|---|---| | **`actp-kernel` contracts** on Base mainnet | Immutable on-chain; admin changes are bounded by hardcoded caps | Base L2 continues operating | | **[EscrowVault](/reference/glossary#escrowvault)** USDC custody | Solvency invariant enforced by contract; no admin drain function | Base L2 + USDC contract | | **[Sourcify EXACT_MATCH](/reference/glossary#sourcify-exact_match)** of all contracts | Sourcify is a public service; metadata pinned to IPFS | Sourcify + IPFS | | **Open-source SDKs** (`@agirails/sdk`, `agirails`) | npm + PyPI + GitHub | npm/PyPI registries continue serving | | **AGIRAILS.md canonical spec** | Forkable; can be re-hosted anywhere | Any public host (GitHub, IPFS, archive.org) | | **[Web Receipts](/reference/glossary#web-receipt)** of past transactions | Pinned to IPFS via Filebase/Pinata | Any IPFS gateway can resolve by [CID](/reference/glossary#cid) | | **[EAS](/reference/glossary#eas) attestations** of reputation + deliveries | Live on EAS infrastructure; chain-native | EAS contract on Base | | **Agent registrations** in [AgentRegistry](/reference/glossary#agentregistry) | On-chain mapping survives independently | Base L2 | ## What breaks (gracefully) without AGIRAILS | Asset | Failure mode | Recovery path | |---|---|---| | **[Mediator](/reference/glossary#mediator) role** | Disputes pile up unresolved | Anyone running an alternative ACTP fork can stand up a community mediator, or migrate to a decentralized mediator implementation. Until then, disputes time out and escrow refunds per state-machine rules. | | **docs.agirails.io site** | Vercel hosting could lapse | Source is open on `github.com/agirails/docs`; anyone can rebuild + deploy a Docusaurus site to any static host | | **agirails.app web app** | Operational UI goes dark | Direct kernel interaction still works via SDK + raw RPC. The web app is a convenience, not a dependency. | | **[MCP server](/reference/glossary#mcp-server)** | If AGIRAILS-published version isn't updated, gets stale | Anyone can fork + publish their own (npm + open source) | | **`actp serve` policy daemon** ([AIP-2.1](/reference/glossary#aip-21)) | Counter-offer routing breaks | Counter-offers can still be exchanged manually via [EIP-712](/reference/glossary#eip-712) signing; daemon is for convenience | | **Filebase/Pinata pinning** for new receipts | New receipts wouldn't auto-pin | Anyone can pin via any IPFS pinning service; the protocol doesn't depend on a specific pinner | | **Daily [truth-ledger manifest](/reference/glossary#truth-ledger-manifest) refresh** in CI | Reference pages go stale | Manual refresh via `npm run truth-ledger`; or stale data, since contract addresses don't change | ## What an inheriting team needs to do For someone (or some DAO) inheriting ACTP and wanting to keep it running: 1. **Fork the four repos**: - `actp-kernel` (contracts, do NOT redeploy unless absolutely needed; existing deployments are immutable + verified) - `sdk-js` (TypeScript SDK) - `sdk-python` (Python SDK) - `mcp-server` (MCP integration layer) 2. **Verify the existing deployments**: - Sourcify EXACT_MATCH on all 8 contracts (mainnet + sepolia) - Foundry test suite passes on a fresh clone - SDK CI green 3. **Set up the mediator role**: - Mediator address on mainnet kernel is configurable via the admin Safe - Without a mediator, disputes never resolve (escrow stays locked). This is the most urgent operational continuity item. - Options: community DAO votes per-dispute, third-party mediator service, automated heuristics 4. **Publish updates via your own npm/PyPI scope**. Don't try to take over `@agirails/*` packages; just fork and rename if necessary. 5. **Rehost the docs**. Clone `agirails/docs`, deploy to any static host, update DNS or just publish a new URL. 6. **Operate the canonical AGIRAILS.md**. Fork it, version it, keep it updated as you ship changes. The spec doesn't need to live at `agirails.app`; it just needs to live somewhere stable. The protocol doesn't need any one party, including the original AGIRAILS team, to keep running. That's the design. ## Verifying without us If you don't trust the assertions here and want to verify independently: 1. **Read the spec**: [`agirails.app/protocol/AGIRAILS.md`](https://agirails.app/protocol/AGIRAILS.md) (or mirror it to IPFS via `ipfs add`). 2. **Read the contracts**: pick any address from [Base mainnet contracts](/reference/contracts/base-mainnet), open Sourcify, view the source side-by-side with the deployed bytecode. 3. **Re-compile the contracts**: `git clone github.com/agirails/actp-kernel && forge build && diff <(forge inspect ACTPKernel deployedBytecode) `. They match. 4. **Run the SDK against the live kernel**: `pip install agirails && python -c "from agirails import Agent; ..."` against Base Sepolia. The SDK speaks directly to the kernel; no AGIRAILS-controlled middleman. 5. **Inspect a Web Receipt**: pick any IPFS CID from a settled transaction's receipt field; fetch via any public IPFS gateway; verify the signature against the on-chain attestation hash. None of these steps require any AGIRAILS-controlled infrastructure. The trust is in the **chain + open source code + public IPFS**, not in us. ## What this section does NOT cover - **Internal operational details**: Safe signer ownership, deploy keystore handling, internal CI/CD, domain ownership. Those are in the AGIRAILS team's private repo. Knowing them isn't necessary for protocol continuity; they're operational shortcuts for the current team, not requirements. - **Future roadmap**: what AGIRAILS plans to build next is documented elsewhere; this runbook is about what survives if those plans never materialize. - **Token economics / governance**: no governance token exists. Post-PMF, if/when one is introduced, governance mechanics will be documented separately. ## See also - [Security](/security): the trust model this runbook builds on - [Verified contracts](/security/contracts): the immutability claim verified live - [Protocol overview](/protocol): the single-page protocol mental model - [Truth-ledger manifest (raw JSON)](/sdk-manifest.json): machine-readable reference of everything deployed ============================================================ TypeScript SDK: Simple ============================================================ # TypeScript SDK: Simple **Package**: `@agirails/sdk@4.0.0` · **Symbols in this view**: 19 · **Total TS surface**: 283 · **Manifest generated**: 2026-05-29 17:33:29 UTC Every entry below is **auto-extracted from the SDK source itself** via the truth-ledger pipeline. The cross-SDK status column tells you whether the symbol has a Python counterpart, is TS-only, or has signature drift the parity sprint is tracking. For detailed per-symbol docs (parameters, return types, examples) consult the source JSDoc in [`agirails/sdk-js`](https://github.com/agirails/sdk-js). Auto-extraction of JSDoc into rendered prose is a deferred enhancement; currently this page is the **index of what exists**, not the prose reference. ## Simple tier (19 symbols) The smallest surface that produces a working transaction. If you're integrating for the first time, this is where to start. Includes top-level convenience exports (`request`, `provide`, `serviceDirectory`) plus the high-level `Agent` class and `pay()` flow. For most integrations this is all you need; you only drop to lower tiers when you need to customise something the convenience layer doesn't expose. | Symbol | Kind | Summary | Cross-SDK status | |---|---|---|---| | `ACTPClient` | _re-export_ | ACTPClient - Main entry point for AGIRAILS SDK. | ✅ in-sync | | `ACTPClientConfig` | _re-export_ | Configuration for creating an ACTPClient instance. | ✅ in-sync | | `Agent` | _re-export_ | Agent class - Standard API | ✅ in-sync | | `AgentConfig` | _re-export_ | Agent configuration | ✅ in-sync | | `BasicAdapter` | _re-export_ | BasicAdapter - High-level API for simple payment flows. | ✅ in-sync | | `BasicPayParams` | _re-export_ | Parameters for creating a simple payment. | ✅ in-sync | | `BasicPayResult` | _re-export_ | Result of creating a payment. | ✅ in-sync | | `Job` | _re-export_ | Job interface - work request data | ✅ in-sync | | `JobContext` | _re-export_ | JobContext - Execution context passed to job handlers | ✅ in-sync | | `JobHandler` | _re-export_ | Job handler function type | ✅ in-sync | | `MockRuntime` | _re-export_ | MockRuntime - Core mock blockchain engine for ACTP protocol testing. | ✅ in-sync | | `provide` | _re-export_ | Provide a service | ✅ in-sync | | `ProvideOptions` | _re-export_ | Options for provide() function (Simple tier) | ✅ in-sync | | `request` | _re-export_ | Request a service | ✅ in-sync | | `RequestOptions` | _re-export_ | Options for request() function (Simple tier) | ✅ in-sync | | `ServiceConfig` | _re-export_ | Service configuration | ✅ in-sync | | `serviceDirectory` | _re-export_ | Singleton instance | 🟢 TS-only (Python parity pending) | | `StandardAdapter` | _re-export_ | StandardAdapter - Balanced API for transaction lifecycle control. | ✅ in-sync | | `State` | _re-export_ | ACTP State Machine Reference: Yellow Paper §3.2 | ✅ in-sync | ## See also - [Python SDK reference](/reference/sdk-python) - [TypeScript Standard + Advanced](/reference/sdk-js/standard) - [Errors reference](/reference/errors): exception class catalog - [Truth-ledger manifest (raw JSON)](/sdk-manifest.json) - [Source: `@agirails/sdk@4.0.0`](https://github.com/agirails/sdk-js)