arc-agent-pay
Python SDK for AI agents that autonomously pay for API services using USDC nanopayments on Arc via the x402 protocol.
An agent discovers paid APIs, hits a real HTTP 402, signs an EIP-3009 authorization off-chain, retries — and gets the data. Every payment settles as a real on-chain transferWithAuthorization transaction on Arc Testnet. No wallets to manage, no approval prompts, no pre-funded accounts in the hot path.
Live playground: agentpay.bond Docs: agentpay.bond/docs
Quick start
pip install arc-agent-pay
from arc_agent_pay import PaymentClient, ServiceRegistry
from arc_agent_pay.models import Chain
from eth_account import Account
registry = ServiceRegistry()
services = registry.search("crypto prices")
account = Account.from_key("0x" + private_key)
async with PaymentClient(account=account, budget_usdc="0.05", chain=Chain.ARC_TESTNET) as client:
response = await client.get(services[0].url) # 402 → pay → retry, all automatic
data = response.json()
print(client.summary())
That's the whole loop: PaymentClient wraps httpx, intercepts the 402 Payment Required, checks the BudgetGuard, signs an EIP-3009 transferWithAuthorization off-chain, retries with the X-PAYMENT header, and hands you the data plus the settlement tx hash.
Prerequisites: Python 3.11+, a funded Arc Testnet EOA wallet (see Wallets).
How it works
sequenceDiagram
autonumber
participant A as Agent<br/>(PaymentClient)
participant R as ServiceRegistry
participant S as x402 API Server
participant C as Arc Testnet
A->>R: search("USDC payments")
R-->>A: Web Research API, Whale Tracker, Price Feed
A->>S: GET /whales
S-->>A: 402 Payment Required<br/>(price 0.010 USDC, pay_to)
Note over A: BudgetGuard checks session spend limit
Note over A: Sign EIP-3009 TransferWithAuthorization<br/>(off-chain — no gas, no chain call yet)
A->>S: retry GET /whales + X-PAYMENT header
S->>C: transferWithAuthorization(...)
C-->>S: tx confirmed
S-->>A: 200 OK + PAYMENT-RESPONSE header<br/>(tx hash)
Chain: Arc Testnet — chain ID 5042002
USDC: 0x3600000000000000000000000000000000000000 (native, EIP-3009 v2)
Explorer: https://explorer.testnet.arc.network
Install
The core package (PaymentClient, ServiceRegistry, BudgetGuard) has minimal dependencies. Optional extras add heavier features — install only what you need:
| Extra | Adds | Use when |
|---|---|---|
[agent] |
langgraph, langchain-core, langchain-openai, openai |
Building the LangGraph tool-calling research agent |
[llm] |
openai |
Just the LLM synthesis layer (provider-agnostic) |
[rag] |
chromadb, fastembed |
Semantic (embedding-based) service discovery |
[onchain] |
web3 |
ERC-8004 onchain agent identity + reputation |
[observability] |
langfuse |
Trace agent runs (Langfuse; optional, no-op without keys) |
[mcp] |
mcp |
Expose discovery + pay-and-fetch as an MCP server |
[all] |
every extra above | Trying out everything at once |
pip install "arc-agent-pay[agent]" # the paying research agent
pip install "arc-agent-pay[all]" # every feature
import arc_agent_pay stays light regardless — heavy dependencies are lazy-imported by the modules that need them.
Architecture
arc_agent_pay/ core SDK — minimal deps
models.py Service, Chain, PaymentRecord — core data types
budget.py BudgetGuard — session spend enforcement
interceptor.py PaymentClient — httpx wrapper, handles 402 → sign → retry
registry/ service discovery
__init__.py ServiceRegistry — keyword/tag search (default)
catalog.py external HTTP service catalog sync + TTL cache
semantic.py SemanticServiceRegistry — embeddings + Chroma ([rag] extra)
llm/ provider-agnostic LLM layer (OpenAI / ArcAPIs / template)
identity/ ERC-8004 onchain agent identity + reputation ([onchain] extra)
workflow/ work orders + escrow funding/settlement clients
onchain/ verified ERC-8004 addresses + ABIs (Arc Testnet)
observability/ Langfuse tracing (no-op fallback) + offline eval harness
mcp_server/ Model Context Protocol server ([mcp] extra)
agent/ ResearchAgent
graph.py real LangGraph tool-calling agent (the [agent] extra)
linear.py dependency-light plan → fetch → synthesize fallback
trust.py ReputationGate — reputation-gated spending policy
contracts/
ValidationEscrow.vy contract-enforced release/rejection/timeout state machine
The research agent
ResearchAgent runs the full loop — discover → pay → fetch → synthesize — behind one interface, with two execution paths: a real LangGraph tool-calling agent (the LLM decides which services to discover and pay for) when the [agent] extra and an LLM key are present, and a dependency-light linear pipeline otherwise (runs with zero API keys, template-mode synthesis).
from arc_agent_pay.agent import ResearchAgent
agent = ResearchAgent(private_key=key, budget_usdc="0.10")
report = await agent.run("USDC payments on Arc network")
Scope: what the agent can answer
The agent is not hardcoded to any subject. The loop is topic-agnostic; the agent is only as broad as the catalog of services it can reach. Point ARC_REGISTRY_CATALOG_URL at an external catalog and the registry syncs + caches it on startup (builtins become a fallback). The architecture consumes any x402-priced service through the same Discovery protocol and PaymentClient — the constraint is purely which services settle on the chain this agent pays on. As Arc-settling x402 services appear, they become reachable here with no code change.
Spending controls
An agent that spends on its own needs guardrails, all enforced before any payment is signed:
BudgetGuard— hard per-session budget cap; once hit, further payments are blocked.ReputationGate(agent/trust.py) — with a trust policy set, the agent reads a provider's on-chain ERC-8004 reputation and refuses to pay anyone below the floor. Off by default, fail-open; strict mode viarequire_provider_identity.- Allowlist / denylist by provider agent id, and a kill switch (
payments_disabled) to stop all spending instantly.
agent = ResearchAgent(
private_key=key,
budget_usdc="0.10",
min_provider_reputation=3.0, # refuse providers rated below 3.0
provider_denylist=[999000001], # never pay this provider
)
Validation-gated workflow protocol
The SDK defines partner-neutral workflow messages and ships a matching Vyper escrow contract without coupling validation to one service:
WorkOrderfixes the escrow, parties, asset, amount, task hash, validator, delivery deadline, refund deadline, chain, and unique nonce before work starts.DeliveryEvidencebinds the delivered content hash to that order.ValidationVerdictbinds approve/reject, score, reason hash, and validity window to the exact order and complete delivery commitment (content, URI, and time).Verifieris the async interface an independent validation service implements.EscrowClientfunds and resolves those orders againstValidationEscrow.vy.
Validator verdicts use EIP-712 domain separation by chain and escrow contract.
Strict verification checks every order and delivery binding, timing constraint,
validator identity, and canonical signature before a verdict can authorize
release. Funding uses EIP-3009 receiveWithAuthorization: the payer signs once,
and only the named escrow can pull the exact order amount.
import secrets
import time
from arc_agent_pay import (
DeliveryEvidence,
EscrowClient,
ValidationVerdict,
WorkOrder,
hash_content,
sign_funding_authorization,
sign_verdict,
verify_signed_verdict,
)
now = int(time.time())
order = WorkOrder(
escrow=escrow_address,
payer=payer_address,
provider=provider_address,
validator=validator_address,
asset=usdc_address,
amount=100_000, # 0.10 USDC in 6-decimal base units
chain_id=5_042_002,
delivery_deadline=now + 3_600,
refund_after=now + 7_200,
task_hash=hash_content(task_text),
nonce="0x" + secrets.token_hex(32),
)
escrow = EscrowClient(order.escrow, account=relayer_account)
funding = sign_funding_authorization(order, private_key=payer_private_key)
escrow.fund(order, funding)
delivery = DeliveryEvidence(
order_hash=order.order_hash,
evidence_hash=hash_content(report_bytes),
evidence_uri="ipfs://...",
delivered_at=now + 600,
)
verdict = ValidationVerdict.for_delivery(
delivery,
approved=True,
score=95,
reason="Meets the acceptance criteria",
issued_at=now + 900,
valid_until=now + 1_800,
)
signed = sign_verdict(verdict, private_key=validator_key, order=order)
verify_signed_verdict(
signed,
order=order,
delivery=delivery,
now=now + 1_000,
require_approval=True,
)
escrow.release(order, delivery, signed)
An approving verdict releases the fixed amount to the provider. A rejecting
verdict refunds immediately, and escrow.refund_timeout(order) returns funds
after refund_after if no validator responds. The source, full state-machine
tests, and deployment instructions live in contracts/.
An unaudited Arc Testnet deployment and its successful low-value lifecycle
evidence are recorded in
contracts/deployments/arc-testnet.json.
It remains testnet-only and is not presented as production-safe.
Wallets
The agent needs one funded Arc Testnet EOA (the payer):
python -c "from eth_account import Account; import secrets; a = Account.from_key('0x'+secrets.token_hex(32)); print('key:', a.key.hex()); print('addr:', a.address)"
Fund from a Circle wallet:
circle wallet transfer <ADDRESS> --amount 5 --address 0x40a2f3926fb79b91b8012c8f1dc3a1c6e4ded2cc --chain ARC-TESTNET --testnet
Use dedicated, low-balance testnet keys. Never reuse a key that holds mainnet funds.
Onchain agent identity (ERC-8004)
An agent can carry a verifiable onchain identity and track record across all three ERC-8004 registries:
- Identity — an ERC-721 "agent id" (
AgentIdentity). Who the agent is. - Reputation — feedback an agent accrues (
ReputationClient). Its track record. - Validation — attestations that an agent's work was checked (
ValidationClient). Its work was verified.
This is optional and independent of payments: PaymentClient works with no identity configured. Reads need no gas (just the [onchain] extra and an RPC); registering, leaving feedback, or recording a validation are writes that need a funded EOA.
from arc_agent_pay.identity import AgentIdentity, ReputationClient
identity = AgentIdentity() # read-only; uses Arc Testnet RPC
agent_id = identity.resolve("0x<agent-address>") # most recent id minted to an address
profile = identity.profile(agent_id, reputation=ReputationClient())
print(profile.agent_id, profile.address)
print(profile.reputation_score, profile.feedback_count)
Registering an identity (write — needs [onchain] and a funded account):
from eth_account import Account
from arc_agent_pay.identity import AgentIdentity
account = AgentIdentity(account=Account.from_key("0x" + private_key))
new_id = account.register("https://your-agent-metadata.example")
Validation is two-sided: the agent owner requests validation of a piece of work, then a validator (a different wallet) responds with a 0–100 score:
from arc_agent_pay.identity import ValidationClient
# owner (owns the agent id) requests; validator responds
ValidationClient(account=owner).request_validation(
validator_address=validator.address, agent_id=838889, request_hash=work_hash)
ValidationClient(account=validator).respond(
request_hash=work_hash, response=100, confirm_ready=True)
Contract addresses + ABIs are committed under arc_agent_pay/onchain/ (verified
Arc Testnet addresses) and overridable via ERC8004_IDENTITY_REGISTRY /
ERC8004_REPUTATION_REGISTRY / ERC8004_VALIDATION_REGISTRY.
Who plays which role (important)
ERC-8004 reputation and validation are only meaningful when they come from a party other than the agent — that's what makes them trustless. In a mature ecosystem these roles are separate businesses:
| Role | What it does | Who plays it in production |
|---|---|---|
| Agent | Does work, pays, holds an identity | You (this SDK) |
| Seller / provider | Sells the data/API/LLM the agent buys | Independent services |
| Reputation giver | Leaves feedback after transacting | The agent's counterparties |
| Validator | Independently verifies the agent's work | A separate auditor / validation service |
⚠️ In the hosted playground demo the same operator plays every role so the full three-registry flow can be shown end-to-end on Arc Testnet. That makes those records self-attested — they demonstrate the plumbing, not independent trust. In production you'd run only the agent, and consume reputation and validation from independent parties.
MCP server
Expose arc-agent-pay over the Model Context Protocol so Claude Desktop, Cursor, or any MCP client can discover and pay for x402 services through it. The wallet key and a session budget ceiling come from the environment (never tool args), and spend is capped across the whole session.
pip install "arc-agent-pay[mcp]"
export AGENT_PRIVATE_KEY=0x... # funded Arc Testnet EOA
export ARC_AGENT_PAY_BUDGET=0.50 # optional session ceiling (USDC)
arc-agent-pay-mcp # stdio transport
Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"arc-agent-pay": {
"command": "arc-agent-pay-mcp",
"env": { "AGENT_PRIVATE_KEY": "0x...", "ARC_AGENT_PAY_BUDGET": "0.50" }
}
}
}
Tools: discover_services, list_registered_services, pay_and_fetch,
get_budget_status, get_agent_identity.
LLM synthesis providers
The llm layer is provider-agnostic with three backends, picked by environment:
- ArcAPIs (
ARCAPIS_TOKEN_ID) — on-chain inference via arcapis.com; each call is authenticated with a per-call EIP-712 signature whose signer must own the packet NFT on-chain. - OpenAI (
OPENAI_API_KEY) — direct, off-chain. - Template — keyless deterministic fallback; the agent runs with zero API keys.
Observability & evals
Trace every agent run (discovery → each paid fetch → synthesis) with Langfuse — opt-in and self-hostable, a no-op when unconfigured:
pip install "arc-agent-pay[observability]"
export LANGFUSE_PUBLIC_KEY=... LANGFUSE_SECRET_KEY=... LANGFUSE_HOST=...
Evaluate service-discovery quality offline (deterministic, no keys, no spend):
python -m arc_agent_pay.observability.evals.run_evals # keyword
python -m arc_agent_pay.observability.evals.run_evals --semantic # RAG
Reports precision / recall / F1 / hit-rate over a labelled dataset.
Trust & safety
This SDK signs payments with a private key, so read the code — that's the point
of it being open. Safeguards in place: testnet-only funds, a hard budget
cap checked before every signature, reputation-gated spending, allow/deny
lists and a kill switch, single-use EIP-3009 nonces, and pip-audit +
gitleaks in CI. Full details, including honest limitations, in
SECURITY.md.
Development
git clone https://github.com/hamedkharazmi/arc-agent-pay
cd arc-agent-pay
uv sync --group dev --extra all
uv run pytest -q
uv run ruff check .
Related
- Hosted playground + API — agentpay.bond: run the agent from your browser against live Arc Testnet settlement, no install. The playground's server side (orchestration, SSE streaming, auth, hosting) lives in a separate repo; everything payment-critical is in this SDK.
- x402 protocol — the HTTP 402 payment standard this SDK implements the client side of.
- ERC-8004 — onchain agent identity/reputation/validation registries.
License
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file arc_agent_pay-0.2.0.tar.gz.
File metadata
- Download URL: arc_agent_pay-0.2.0.tar.gz
- Upload date:
- Size: 434.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c6a4d40accca6ac39ea28175a56c9eb546acea06c981c505fb4d241510da9d45
|
|
| MD5 |
1a0b235d47e0b6c83fc71a9cba56117e
|
|
| BLAKE2b-256 |
25c39af839b3aa8e261fa4e19a35aa664238738acc9af61d367dacdb03d88359
|
Provenance
The following attestation bundles were made for arc_agent_pay-0.2.0.tar.gz:
Publisher:
release.yml on hamedkharazmi/arc-agent-pay
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
arc_agent_pay-0.2.0.tar.gz -
Subject digest:
c6a4d40accca6ac39ea28175a56c9eb546acea06c981c505fb4d241510da9d45 - Sigstore transparency entry: 2385721408
- Sigstore integration time:
-
Permalink:
hamedkharazmi/arc-agent-pay@e9abeda0b4822245334ee6bd22b7f938a49f1a91 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/hamedkharazmi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e9abeda0b4822245334ee6bd22b7f938a49f1a91 -
Trigger Event:
push
-
Statement type:
File details
Details for the file arc_agent_pay-0.2.0-py3-none-any.whl.
File metadata
- Download URL: arc_agent_pay-0.2.0-py3-none-any.whl
- Upload date:
- Size: 91.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3597376fcb371b29f8f02cf5bab3f7fe8cde2ac7fede8740471c0a601e9917ba
|
|
| MD5 |
501b2679bd4c520ea9cc42436da0e649
|
|
| BLAKE2b-256 |
ca16d3d0f5a674d5315cd9abd6f070c49b4ed5a33fc0b3eda686589c9e02ac47
|
Provenance
The following attestation bundles were made for arc_agent_pay-0.2.0-py3-none-any.whl:
Publisher:
release.yml on hamedkharazmi/arc-agent-pay
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
arc_agent_pay-0.2.0-py3-none-any.whl -
Subject digest:
3597376fcb371b29f8f02cf5bab3f7fe8cde2ac7fede8740471c0a601e9917ba - Sigstore transparency entry: 2385721732
- Sigstore integration time:
-
Permalink:
hamedkharazmi/arc-agent-pay@e9abeda0b4822245334ee6bd22b7f938a49f1a91 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/hamedkharazmi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e9abeda0b4822245334ee6bd22b7f938a49f1a91 -
Trigger Event:
push
-
Statement type: