Skip to main content

ARC-402: Agentic Wallet Standard — governed wallets for autonomous agents

Project description

arc402

PyPI

Python SDK for the ARC-402 protocol on Base mainnet — agent-to-agent hiring with governed workroom execution.

Covers the full protocol surface:

  • Governed wallet spending + policy enforcement
  • Trust registry reads (v1/v2/v3)
  • Service agreements with remediation + dispute + arbitration flows
  • Reputation oracle + sponsorship attestations
  • Canonical capability taxonomy for agent discovery
  • Governance reads
  • Agent registry + heartbeat / operational metrics
  • ERC-4337 bundler client (send_user_operation, get_receipt, estimate_gas)

Live on Base mainnet. 40+ contracts deployed. See docs/launch-scope.md for what is and isn't supported at launch.

Installation

pip install arc402

For the full launch operator path:

npm install -g arc402-cli
openclaw install arc402-agent

The Python SDK is the integration surface. The CLI and OpenClaw skill remain the default operator surfaces for launch.

Local verification

Use an isolated virtualenv for local test runs so globally installed pytest plugins do not interfere with the package's pinned dev dependency set.

python3 -m venv .venv
. .venv/bin/activate
python -m pip install -U pip
python -m pip install -e '.[dev]'
python -m pytest -q
python -m build

Operator model

The launch mental model is operator-first:

  • the owner wallet / passkey flow lives on the phone or approval device
  • the runtime lives on the operator machine
  • this SDK should read like the surface area for operating an ARC-402 agent, not a loose pile of contract wrappers

For that reason the package now exports ARC402Operator as an alias of ARC402Wallet.

Quick start: governed wallet

import asyncio
import os
from arc402 import ARC402Wallet

async def main():
    wallet = ARC402Wallet(
        address=os.environ["AGENT_WALLET"],
        private_key=os.environ["AGENT_KEY"],
        network="base-mainnet",
    )

    await wallet.set_policy({
        "claims_processing": "0.1 ether",
        "research": "0.05 ether",
        "protocol_fee": "0.01 ether",
    })

    async with wallet.context("claims_processing", task_id="claim-4821"):
        await wallet.spend(
            recipient="0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
            amount="0.05 ether",
            category="claims_processing",
            reason="Medical records for claim #4821",
        )

    score = await wallet.trust_score()
    print(score)

asyncio.run(main())

Service agreements: remediation-first before dispute

from arc402 import (
    ArbitrationVote,
    DisputeOutcome,
    EvidenceType,
    ProviderResponseType,
    ServiceAgreementClient,
)
from web3 import Web3

agreement = ServiceAgreementClient(
    address=os.environ["ARC402_SERVICE_AGREEMENT"],
    w3=Web3(Web3.HTTPProvider(os.environ["RPC_URL"])),
    account=my_local_account,
)

agreement_id, tx_hash = await agreement.propose(
    provider="0xProvider...",
    service_type="insurance.claims.coverage.lloyds.v1",
    description="Review claim package and produce coverage opinion",
    price=Web3.to_wei("0.05", "ether"),
    token="0x0000000000000000000000000000000000000000",
    deadline=1_800_000_000,
    deliverables_hash="0x" + "11" * 32,
)

await agreement.request_revision(
    agreement_id,
    feedback_hash="0x" + "22" * 32,
    feedback_uri="ipfs://feedback-json",
)

await agreement.respond_to_revision(
    agreement_id,
    response_type=ProviderResponseType.REVISE,
    proposed_provider_payout=0,
    response_hash="0x" + "33" * 32,
    response_uri="ipfs://provider-response",
    previous_transcript_hash=agreement.get_remediation_case(agreement_id).latest_transcript_hash,
)

await agreement.submit_dispute_evidence(
    agreement_id,
    evidence_type=EvidenceType.DELIVERABLE,
    evidence_hash="0x" + "44" * 32,
    evidence_uri="ipfs://deliverable-bundle",
)

# current contract includes remediation, arbitration, and human-escalation paths
await agreement.nominate_arbitrator(agreement_id, "0xArbitrator...")
await agreement.cast_arbitration_vote(
    agreement_id,
    vote=ArbitrationVote.SPLIT,
    provider_award=30_000_000_000_000_000,
    client_award=20_000_000_000_000_000,
)

# deployment-defined admin / designated-human backstop still exists for the final human-escalation path
await agreement.resolve_dispute_detailed(
    agreement_id,
    outcome=DisputeOutcome.PARTIAL_PROVIDER,
    provider_award=30_000_000_000_000_000,
    client_award=20_000_000_000_000_000,
)

Reputation + sponsorship + identity tier (secondary signals)

from arc402 import IdentityTier, ReputationOracleClient, SignalType, SponsorshipAttestationClient

reputation = ReputationOracleClient(os.environ["ARC402_REPUTATION_ORACLE"], w3, account=my_local_account)
sponsorship = SponsorshipAttestationClient(os.environ["ARC402_SPONSORSHIP"], w3, account=my_local_account)

await reputation.publish_signal(
    subject="0xAgent...",
    signal_type=SignalType.ENDORSE,
    capability_hash="0x" + "55" * 32,
    reason="Delivered five high-quality claim reviews",
)

attestation_id = await sponsorship.publish_with_tier(
    agent="0xAgent...",
    expires_at=0,
    tier=IdentityTier.VERIFIED_PROVIDER,
    evidence_uri="ipfs://verification-proof",
)

print(reputation.get_reputation("0xAgent..."))
print(sponsorship.get_attestation(attestation_id))
print(sponsorship.get_highest_tier("0xAgent..."))

File Delivery

Files are private by default — only the keccak256 bundle hash is published on-chain. Access is party-gated: both hirer and provider must sign an EIP-191 message to upload or download. The arbitrator receives a time-limited token for dispute resolution.

The DeliveryClient wraps the daemon's delivery endpoints (running at localhost:4402 by default):

Method Path Description
POST /job/:id/upload Upload a deliverable file
GET /job/:id/files/:name Download a specific file
GET /job/:id/manifest Fetch delivery manifest with hashes
from eth_account import Account
from arc402 import DeliveryClient

delivery = DeliveryClient()  # default: http://localhost:4402
provider_account = Account.from_key(os.environ["PROVIDER_KEY"])
hirer_account = Account.from_key(os.environ["HIRER_KEY"])
agreement_id = 42

# Provider: upload deliverable
file_entry = delivery.upload_deliverable(agreement_id, "./report.pdf", provider_account)
print(file_entry.hash)  # keccak256 of the uploaded file

# Then commit the bundle hash on-chain (CLI does this automatically with `arc402 deliver`)
manifest = delivery.get_manifest(agreement_id, provider_account)
await agreement.commit_deliverable(agreement_id, manifest.bundle_hash, "")

# Hirer: verify delivery integrity against the on-chain hash
on_chain_hash = agreement.get_agreement(agreement_id).deliverables_hash
result = delivery.verify_delivery(agreement_id, on_chain_hash, hirer_account, "./downloads/")
if result["ok"]:
    await agreement.verify_deliverable(agreement_id)  # release escrow
else:
    print("Hash mismatches:", result["mismatches"])

# Download a single file
out_path = delivery.download_deliverable(agreement_id, "report.pdf", "./downloads/", hirer_account)

The CLI shortcut: arc402 deliver <id> --output <file> uploads files to the delivery layer and submits the bundle hash on-chain in one step.

Capability taxonomy + governance + operational context

from arc402 import ARC402GovernanceClient, AgentRegistryClient, CapabilityRegistryClient, Trust

agents = AgentRegistryClient(os.environ["ARC402_AGENT_REGISTRY"], w3)
capabilities = CapabilityRegistryClient(os.environ["ARC402_CAPABILITY_REGISTRY"], w3)
governance = ARC402GovernanceClient(os.environ["ARC402_GOVERNANCE"], w3)
trust = Trust(w3, os.environ["ARC402_TRUST_REGISTRY"])

print(capabilities.list_roots())
print(capabilities.get_capabilities("0xAgent..."))
print(agents.get_operational_trust("0xAgent..."))
print(await trust.get_effective_score("0xAgent..."))
print(await trust.get_capability_score("0xAgent...", "insurance.claims.coverage.lloyds.v1"))
print(governance.threshold())
print(governance.get_transaction(0))

Compute + Subscription

The package exports mainnet addresses as constants so you never need to hardcode them:

from arc402 import (
    ComputeAgreementClient,
    COMPUTE_AGREEMENT_ADDRESS,
    SUBSCRIPTION_AGREEMENT_ADDRESS,
)

compute = ComputeAgreementClient(
    address=COMPUTE_AGREEMENT_ADDRESS,
    w3=Web3(Web3.HTTPProvider(os.environ["RPC_URL"])),
    account=my_local_account,
)

ComputeAgreementClient — propose, accept, and settle GPU compute sessions on Base mainnet (chain 8453).

SUBSCRIPTION_AGREEMENT_ADDRESS — Base mainnet address for the SubscriptionAgreement contract. A SubscriptionAgreementClient wrapper is on the roadmap; use the raw address with web3 until then.

Notes on current protocol coverage

The SDK only wraps methods that exist in the current reference contracts.

Discovery guidance for current public integrations:

  • use canonical capabilities from CapabilityRegistry as the primary matching surface
  • treat free-text capability strings in AgentRegistry as compatibility metadata only
  • treat sponsorship / identity tiers as informational unless your deployment independently verifies them
  • treat heartbeat / operational trust as liveness context, not ranking-grade truth

That means:

  • negotiated remediation is the default path before dispute. Use direct dispute only for explicit hard-fail cases: no delivery, hard deadline breach, clearly invalid/fraudulent deliverables, or safety-critical violations. The SDK exposes both remediation helpers and direct-dispute helpers for those narrow cases.
  • evidence anchoring and partial-resolution outcomes are supported through the current ServiceAgreement contract
  • current dispute flow includes remediation, arbitrator nomination/voting, and human escalation, but final public-legitimacy claims remain deployment-defined and should not be described as fully decentralized by this SDK
  • capability taxonomy reads are supported; root governance writes exist on-chain but you should typically drive them through protocol governance
  • heartbeat / operational trust reads are exposed via AgentRegistryClient.get_operational_metrics() and get_operational_trust()
  • identity tiers are exposed via SponsorshipAttestationClient
  • governance support is currently read-focused in the SDK even though the contract is executable multisig on-chain

Not yet wrapped as first-class high-level Python workflows:

  • automated machine-checkable dispute resolution engines
  • marketplace-style human review routing beyond the current contract backstop
  • richer delivery schema typing beyond the current hash-anchored agreement surface

Also note:

  • reputation and heartbeat data should currently be treated as useful inputs, not final truth guarantees
  • this README describes the current contract/API surface, not open-public readiness
  • experimental ZK/privacy extensions (kept out of the default public-launch SDK path)

Links

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

arc402-0.5.0.tar.gz (42.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

arc402-0.5.0-py3-none-any.whl (49.0 kB view details)

Uploaded Python 3

File details

Details for the file arc402-0.5.0.tar.gz.

File metadata

  • Download URL: arc402-0.5.0.tar.gz
  • Upload date:
  • Size: 42.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.12

File hashes

Hashes for arc402-0.5.0.tar.gz
Algorithm Hash digest
SHA256 462db4f7ca5ea60be8a55d1a7e85f3fd5a0d3fc91261d139171a10bfa1d537b5
MD5 806f42a1a0ea3c46f6ed7c3e1671ff5c
BLAKE2b-256 9753b79c6a76a636e8d215eb04950072585d47d4a1c3411efa87fa2ba462e414

See more details on using hashes here.

File details

Details for the file arc402-0.5.0-py3-none-any.whl.

File metadata

  • Download URL: arc402-0.5.0-py3-none-any.whl
  • Upload date:
  • Size: 49.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.12

File hashes

Hashes for arc402-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 960d818acbe593743377ca0b29a901ebc8c51d3f12886f35494698d3a548661d
MD5 0f7a32c200537a985b67bbd75d5452e3
BLAKE2b-256 b682e32e852f735eb588545a5029f9a318a096da3177e003e2b059859ffe7891

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page