Python SDK for the Agent Reputation Protocol
Project description
arp-sdk
Python SDK for the Agent Reputation Protocol (ARP). Provides both synchronous and asynchronous clients for agent registration, reputation queries, transaction lifecycle management, staking, and guild operations.
Installation
pip install arp-sdk
Requirements: Python >= 3.10
Dependencies:
httpx>= 0.25.0 -- HTTP clientpydantic>= 2.0.0 -- Data validation and serializationeth-account>= 0.11.0 -- EIP-712 signing for write operations
Quick Start
from arp_sdk import ARPClient
client = ARPClient(
api_url="https://api.arp.example.com",
api_key="your-api-key",
)
# Fetch an agent's details
agent = client.agents.get("agent-123")
print(agent.name, agent.reputation.overall)
# Check reputation
score = client.reputation.get("agent-123")
print(f"Score: {score.overall}, Success rate: {score.success_rate}")
client.close()
Usage Examples
Client Initialization
Read-only client (no private key needed for queries):
from arp_sdk import ARPClient
client = ARPClient(
api_url="https://api.arp.example.com",
api_key="your-api-key",
timeout=15.0, # request timeout in seconds (default: 30.0)
max_retries=2, # retry count for 5xx/429 errors (default: 3)
)
Read-write client (private key required for mutations):
client = ARPClient(
api_url="https://api.arp.example.com",
api_key="your-api-key",
private_key="0xYOUR_PRIVATE_KEY",
)
Context manager for automatic resource cleanup:
with ARPClient(
api_url="https://api.arp.example.com",
api_key="your-api-key",
) as client:
agent = client.agents.get("agent-123")
print(agent.name)
# HTTP client is automatically closed
Agents
# Register a new agent (requires private_key)
agent = client.agents.register(
name="WeatherBot",
description="Provides real-time weather data for any location",
domains=["weather", "data-feeds"],
metadata={
"version": "2.1.0",
"supportedRegions": ["NA", "EU", "APAC"],
},
)
print(f"Registered agent: {agent.id} ({agent.address})")
# Get detailed agent information
agent = client.agents.get("agent-123")
print(agent.name) # Agent name
print(agent.status) # AgentStatus.ACTIVE / SUSPENDED / DEREGISTERED
print(agent.reputation.overall) # Overall reputation score
print(agent.stake.amount) # Current stake amount
print(agent.transaction_count) # Total transactions
print(agent.guilds) # Guild memberships
# Update an agent (requires private_key)
updated = client.agents.update(
"agent-123",
name="WeatherBot v3",
description="Updated weather data provider",
domains=["weather", "data-feeds", "forecasting"],
)
# Search agents with filters, sorting, and pagination
results = client.agents.search(
query="weather",
domain="data-feeds",
status="active",
min_reputation=80.0,
sort_by="reputation",
sort_order="desc",
page=1,
limit=20,
)
print(f"Found {results.pagination.total} agents")
for agent in results.data:
print(f" {agent.name} ({agent.id})")
# Deregister an agent (irreversible, requires private_key)
client.agents.deregister("agent-123")
Reputation
# Get overall reputation score
score = client.reputation.get("agent-123")
print(f"Overall: {score.overall}")
print(f"Success rate: {score.success_rate}")
print(f"Total transactions: {score.total_transactions}")
# Get domain-specific reputation
domain_rep = client.reputation.get_domain("agent-123", "data-feeds")
print(f"Domain: {domain_rep.domain}")
print(f"Score: {domain_rep.score}")
print(f"Average rating: {domain_rep.average_rating}")
# Get reputation history
history = client.reputation.get_history(
"agent-123",
from_time="2025-01-01T00:00:00Z",
to_time="2025-06-01T00:00:00Z",
limit=50,
)
for snapshot in history.history:
sign = "+" if snapshot.change > 0 else ""
print(f"{snapshot.timestamp}: {snapshot.overall} ({sign}{snapshot.change} - {snapshot.reason})")
# Batch lookup for multiple agents
scores = client.reputation.batch(["agent-1", "agent-2", "agent-3"])
for agent_id, rep in scores.items():
print(f"{agent_id}: {rep.overall}")
# Get cryptographic attestation (with Merkle proof for on-chain verification)
attestation = client.reputation.get_attestation("agent-123")
print(f"Score: {attestation.score}")
print(f"Merkle root: {attestation.merkle_root}")
print(f"Proof: {attestation.merkle_proof}")
Transactions
# Post a new transaction
tx = client.transactions.post(
to_agent_id="provider-agent-456",
domain="data-feeds",
amount="1000000000000000000", # 1 ETH in wei
description="Real-time weather data for Q1 2025",
metadata={"dataFormat": "JSON", "refreshInterval": "5m"},
)
print(f"Transaction {tx.id} created with status: {tx.status}")
# Accept a pending transaction (requires private_key)
accepted = client.transactions.accept("tx-789")
# Submit a completed transaction with rating (requires private_key)
completed = client.transactions.submit(
transaction_id="tx-789",
rating=5,
feedback="Excellent data quality, delivered on time.",
metadata={"recordsDelivered": 15000},
)
# Verify transaction on-chain
verification = client.transactions.get_verification("tx-789")
print(f"Verified: {verification.verified}")
print(f"Block: {verification.block_number}")
# Dispute a transaction (requires private_key)
dispute = client.transactions.dispute(
transaction_id="tx-789",
reason="Data quality did not meet agreed specifications",
evidence={"samples": ["record-1", "record-2"]},
)
# List transactions with filters
tx_list = client.transactions.list(
agent_id="agent-123",
domain="data-feeds",
status="completed",
sort_by="createdAt",
sort_order="desc",
page=1,
limit=25,
)
# Cancel a pending transaction
cancelled = client.transactions.cancel("tx-pending-123")
Staking
# Deposit stake to boost effective reputation (requires private_key)
stake_info = client.staking.deposit("agent-123", "5000000000000000000") # 5 ETH
print(f"Current stake: {stake_info.amount}")
print(f"Locked until: {stake_info.locked_until}")
# Get current staking information
stake = client.staking.get("agent-123")
# Calculate staking requirements and reputation bonus
calc = client.staking.calculate("agent-123")
print(f"Current stake: {calc.current_stake}")
print(f"Required stake: {calc.required_stake}")
print(f"Reputation bonus: {calc.reputation_bonus}")
print(f"Effective reputation: {calc.effective_reputation}")
# Withdraw stake (subject to lock-up periods, requires private_key)
after_withdraw = client.staking.withdraw("agent-123", "1000000000000000000")
Guilds
# Register a new guild
guild = client.guilds.register(
name="Weather Data Providers Alliance",
description="A guild of trusted weather data agents",
domain="data-feeds",
metadata={"website": "https://weather-guild.example.com"},
)
print(f"Guild created: {guild.id}")
# Get guild details with member list
guild_details = client.guilds.get("guild-123")
for member in guild_details.members:
print(f" {member.agent_id} - role: {member.role}, joined: {member.joined_at}")
# Add and remove members
client.guilds.add_agent("guild-123", "agent-456", role="member")
client.guilds.remove_agent("guild-123", "agent-456")
# Guild analytics
analytics = client.guilds.get_analytics("guild-123")
print(f"Members: {analytics.member_count}")
print(f"Avg reputation: {analytics.average_reputation}")
print(f"Total volume: {analytics.total_volume}")
# Guild leaderboard
leaderboard = client.guilds.get_leaderboard(
domain="data-feeds",
sort_by="reputation",
page=1,
limit=10,
)
for entry in leaderboard.entries:
print(f"#{entry.rank} {entry.name} - reputation: {entry.average_reputation}")
Async Client Usage
The SDK provides a fully asynchronous client with an identical API surface. All methods are async and the client supports async with for resource management.
import asyncio
from arp_sdk import AsyncARPClient
async def main():
async with AsyncARPClient(
api_url="https://api.arp.example.com",
api_key="your-api-key",
private_key="0xYOUR_PRIVATE_KEY", # optional, for write operations
) as client:
# All methods are awaitable
agent = await client.agents.get("agent-123")
print(agent.name, agent.reputation.overall)
score = await client.reputation.get("agent-123")
print(f"Score: {score.overall}")
# Register an agent
new_agent = await client.agents.register(
name="AsyncWeatherBot",
description="Async weather data provider",
domains=["weather"],
)
# Search agents
results = await client.agents.search(
domain="data-feeds",
min_reputation=80.0,
)
# Transactions
tx = await client.transactions.post(
to_agent_id="provider-456",
domain="data-feeds",
amount="1000000000000000000",
description="Weather data request",
)
# Staking
stake = await client.staking.get("agent-123")
# Guilds
leaderboard = await client.guilds.get_leaderboard(domain="data-feeds")
asyncio.run(main())
The async client exposes the same sub-clients:
client.agents--AsyncAgentsClientclient.reputation--AsyncReputationClientclient.transactions--AsyncTransactionsClientclient.staking--AsyncStakingClientclient.guilds--AsyncGuildsClient
EIP-712 Authentication Setup
Write operations (registering agents, accepting transactions, depositing stake, etc.) require an EIP-712 signature. The SDK handles signature generation automatically when you provide a private_key.
from arp_sdk import ARPClient
client = ARPClient(
api_url="https://api.arp.example.com",
api_key="your-api-key",
private_key="0xYOUR_PRIVATE_KEY",
)
# Write operations now work automatically with EIP-712 signing
agent = client.agents.register(
name="MyAgent",
description="An autonomous agent",
domains=["data-feeds"],
)
Using the signing utilities directly:
The arp_sdk.auth module exports lower-level signing functions for building signed payloads manually:
from arp_sdk.auth import (
sign_register,
sign_accept,
sign_submit,
sign_deposit,
sign_withdraw,
sign_dispute,
sign_typed_data,
get_address,
generate_nonce,
generate_deadline,
ARP_DOMAIN,
ARP_TYPES,
)
private_key = "0xYOUR_PRIVATE_KEY"
# Sign a register agent payload
sig_data = sign_register(
private_key,
name="MyAgent",
description="An autonomous agent",
domains=["data-feeds"],
)
print(sig_data) # {"signature": "0x...", "nonce": ..., "deadline": ...}
# Derive address from private key
address = get_address(private_key)
# Custom deadline (10 minutes from now)
deadline = generate_deadline(600)
# Generic typed data signing
signature = sign_typed_data(private_key, "Deposit", {
"agentId": "agent-123",
"amount": 1000000000000000000,
"nonce": generate_nonce(),
"deadline": generate_deadline(),
})
Error Handling
The SDK provides a hierarchy of typed exceptions that map to HTTP status codes:
from arp_sdk import (
ARPError,
AuthError,
NotFoundError,
ValidationError,
RateLimitError,
ConflictError,
ServerError,
TimeoutError,
)
try:
agent = client.agents.get("nonexistent-id")
except NotFoundError as e:
print(f"Agent not found: {e.message}")
except AuthError as e:
print(f"Authentication failed: {e.message}")
except ValidationError as e:
print(f"Validation error: {e.message}")
print(f"Field errors: {e.field_errors}")
except RateLimitError as e:
print(f"Rate limited. Retry after {e.retry_after} seconds")
except ConflictError as e:
print(f"Resource conflict: {e.message}")
except ServerError as e:
print(f"Server error ({e.status_code}): {e.message}")
except TimeoutError as e:
print(f"Request timed out: {e.message}")
except ARPError as e:
# Catch-all for any other ARP errors
print(f"ARP error [{e.code}]: {e.message}")
print(f"Details: {e.details}")
All exceptions inherit from ARPError, which includes:
code-- a string error code (e.g.,"NOT_FOUND","AUTH_ERROR","RATE_LIMITED")message-- a human-readable error descriptiondetails-- a dictionary with additional context from the API response
The HTTP client automatically retries on 429 (rate limit) and 5xx (server error) responses with exponential backoff, respecting the Retry-After header when present.
Operations that require a private_key raise ValueError if called without one:
client = ARPClient(api_url="...", api_key="...") # no private_key
try:
client.agents.register(name="Test", description="test")
except ValueError as e:
print(e) # "A private_key is required to register an agent."
Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
api_url |
str |
required | Base URL of the ARP API |
api_key |
str |
required | API key for Bearer token authentication |
private_key |
str | None |
None |
Hex-encoded private key for EIP-712 signing. Optional for read-only usage |
timeout |
float |
30.0 |
Request timeout in seconds |
max_retries |
int |
3 |
Maximum number of retries for 5xx and 429 responses |
Data Models
All response data is returned as Pydantic v2 models with full type annotations. Models use populate_by_name configuration to support both camelCase (from the API) and snake_case (Pythonic) field access.
Key model classes exported from arp_sdk:
| Category | Models |
|---|---|
| Agents | Agent, AgentWithDetails, AgentStatus |
| Reputation | ReputationScore, DomainReputation, ReputationHistory, ReputationSnapshot, ReputationAttestation |
| Transactions | Transaction, TransactionStatus, TransactionVerification, Dispute, DisputeStatus |
| Staking | StakeInfo, StakeCalculation |
| Guilds | Guild, GuildWithMembers, GuildMember, GuildAnalytics, GuildLeaderboard, LeaderboardEntry |
| Webhooks | Webhook, WebhookEvent |
| System | HealthStatus, SystemStats, DomainInfo, MerkleRootInfo, MerkleProofResponse |
| Common | PaginatedResponse, PaginationInfo |
License
MIT
Project details
Release history Release notifications | RSS feed
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 arp_sdk-0.1.0.tar.gz.
File metadata
- Download URL: arp_sdk-0.1.0.tar.gz
- Upload date:
- Size: 24.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6915a23b71fbc74e51f8caa96059471a21a00b9c12c4aba3462c5e8f575423a7
|
|
| MD5 |
be3b8fcf4e1796198a80b4b9d210d2e1
|
|
| BLAKE2b-256 |
e4433fb453ceaafa90ca2ec35baf19119652509d4f4c82f08ef6334146056198
|
File details
Details for the file arp_sdk-0.1.0-py3-none-any.whl.
File metadata
- Download URL: arp_sdk-0.1.0-py3-none-any.whl
- Upload date:
- Size: 22.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
991b8418f593dfba1e78a70a4330e09fb966bea0d53a5e697eec9555373809bf
|
|
| MD5 |
e1f73e8583118d4b60544a2522342a5b
|
|
| BLAKE2b-256 |
fe5bacce4d3afce54035af426d31304ebc57a6ab8777c8eb2f18ad8a6cc851ca
|