arc-devkit 0.4.7__tar.gz → 0.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/PKG-INFO +13 -7
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/README.md +2 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/agents/base_agent.py +9 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/agents/coordinator.py +39 -0
- arc_devkit-0.8.0/arc_devkit/agents/identity.py +205 -0
- arc_devkit-0.8.0/arc_devkit/agents/job_agent.py +88 -0
- arc_devkit-0.8.0/arc_devkit/agents/jobs.py +321 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/agents/monitor_agent.py +42 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/agents/payment_agent.py +20 -1
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/analytics/portfolio.py +100 -1
- arc_devkit-0.8.0/arc_devkit/api/auth.py +24 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/api/main.py +36 -16
- arc_devkit-0.8.0/arc_devkit/api/routes/agent_economy.py +195 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/api/routes/agents.py +10 -2
- arc_devkit-0.8.0/arc_devkit/api/routes/bridge.py +93 -0
- arc_devkit-0.8.0/arc_devkit/api/routes/fees.py +49 -0
- arc_devkit-0.8.0/arc_devkit/bridge/__init__.py +14 -0
- arc_devkit-0.8.0/arc_devkit/bridge/cctp.py +354 -0
- arc_devkit-0.8.0/arc_devkit/bridge/models.py +78 -0
- arc_devkit-0.8.0/arc_devkit/bridge/store.py +47 -0
- arc_devkit-0.8.0/arc_devkit/cli/commands/agent.py +542 -0
- arc_devkit-0.8.0/arc_devkit/cli/commands/bridge.py +148 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/cli/commands/debug.py +80 -0
- arc_devkit-0.8.0/arc_devkit/cli/commands/fees.py +55 -0
- arc_devkit-0.8.0/arc_devkit/cli/commands/mcp.py +31 -0
- arc_devkit-0.8.0/arc_devkit/cli/commands/network.py +130 -0
- arc_devkit-0.8.0/arc_devkit/cli/commands/oracle.py +43 -0
- arc_devkit-0.8.0/arc_devkit/cli/commands/privacy.py +56 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/cli/flat.py +4 -1
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/cli/main.py +10 -1
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/config.py +43 -3
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/copilot/agent.py +31 -7
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/copilot/tools.py +168 -0
- arc_devkit-0.8.0/arc_devkit/core/gas.py +157 -0
- arc_devkit-0.8.0/arc_devkit/core/signer.py +121 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/debugger/tx_analyzer.py +39 -0
- arc_devkit-0.8.0/arc_devkit/mcp_server.py +116 -0
- arc_devkit-0.8.0/arc_devkit/networks.py +67 -0
- arc_devkit-0.8.0/arc_devkit/oracle/__init__.py +5 -0
- arc_devkit-0.8.0/arc_devkit/oracle/price_feed.py +86 -0
- arc_devkit-0.8.0/arc_devkit/paymaster/__init__.py +16 -0
- arc_devkit-0.8.0/arc_devkit/paymaster/detector.py +42 -0
- arc_devkit-0.8.0/arc_devkit/paymaster/user_operation.py +92 -0
- arc_devkit-0.8.0/arc_devkit/privacy/__init__.py +23 -0
- arc_devkit-0.8.0/arc_devkit/privacy/confidential_transfer.py +86 -0
- arc_devkit-0.8.0/arc_devkit/privacy/view_key.py +100 -0
- arc_devkit-0.8.0/arc_devkit/stablecoins/__init__.py +5 -0
- {arc_devkit-0.4.7/arc_devkit/usdc → arc_devkit-0.8.0/arc_devkit/stablecoins}/token.py +49 -24
- arc_devkit-0.8.0/arc_devkit/usdc/token.py +18 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit.egg-info/PKG-INFO +13 -7
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit.egg-info/SOURCES.txt +52 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit.egg-info/requires.txt +6 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/pyproject.toml +17 -7
- arc_devkit-0.8.0/tests/test_agent_identity.py +167 -0
- arc_devkit-0.8.0/tests/test_agent_jobs.py +196 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/tests/test_agents.py +73 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/tests/test_analytics.py +73 -1
- arc_devkit-0.8.0/tests/test_api_agent_economy.py +168 -0
- arc_devkit-0.8.0/tests/test_api_bridge.py +70 -0
- arc_devkit-0.8.0/tests/test_api_fees.py +61 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/tests/test_async_agents.py +37 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/tests/test_autonomous.py +35 -0
- arc_devkit-0.8.0/tests/test_bridge.py +448 -0
- arc_devkit-0.8.0/tests/test_cli_agent_economy.py +174 -0
- arc_devkit-0.8.0/tests/test_cli_bridge.py +106 -0
- arc_devkit-0.8.0/tests/test_cli_debug_advanced.py +79 -0
- arc_devkit-0.8.0/tests/test_cli_fees.py +31 -0
- arc_devkit-0.8.0/tests/test_cli_network.py +34 -0
- arc_devkit-0.8.0/tests/test_cli_sprint6.py +80 -0
- arc_devkit-0.8.0/tests/test_config.py +69 -0
- arc_devkit-0.8.0/tests/test_connection_regression.py +34 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/tests/test_copilot_tools.py +97 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/tests/test_debugger.py +45 -0
- arc_devkit-0.8.0/tests/test_gas.py +88 -0
- arc_devkit-0.8.0/tests/test_job_agent.py +100 -0
- arc_devkit-0.8.0/tests/test_mcp_server.py +119 -0
- arc_devkit-0.8.0/tests/test_oracle.py +56 -0
- arc_devkit-0.8.0/tests/test_paymaster.py +89 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/tests/test_payment_security.py +24 -0
- arc_devkit-0.8.0/tests/test_privacy.py +118 -0
- arc_devkit-0.8.0/tests/test_signer.py +78 -0
- arc_devkit-0.8.0/tests/test_stablecoins.py +111 -0
- arc_devkit-0.4.7/arc_devkit/cli/commands/agent.py +0 -278
- arc_devkit-0.4.7/arc_devkit/core/gas.py +0 -64
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/__init__.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/agents/__init__.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/agents/async_base.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/agents/async_monitor.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/agents/autonomous.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/agents/dashboard.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/agents/event_bus.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/agents/guardrails.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/analytics/__init__.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/api/__init__.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/api/rate_limit.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/api/routes/__init__.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/api/routes/copilot.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/api/routes/debugger.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/cli/__init__.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/cli/commands/__init__.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/cli/commands/copilot.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/contracts/__init__.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/contracts/loader.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/copilot/__init__.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/core/__init__.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/core/connection.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/core/validation.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/core/wallet.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/debugger/__init__.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/deploy/__init__.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/deploy/deployer.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/events/__init__.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/events/listener.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit/usdc/__init__.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit.egg-info/dependency_links.txt +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit.egg-info/entry_points.txt +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/arc_devkit.egg-info/top_level.txt +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/setup.cfg +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/tests/test_api.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/tests/test_api_security.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/tests/test_cli.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/tests/test_connection.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/tests/test_contracts.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/tests/test_copilot.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/tests/test_deploy.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/tests/test_events.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/tests/test_guardrails.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/tests/test_integration.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/tests/test_usdc.py +0 -0
- {arc_devkit-0.4.7 → arc_devkit-0.8.0}/tests/test_validation.py +0 -0
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: arc-devkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: Plataforma open source de ferramentas para desenvolvedores da Arc blockchain (Circle)
|
|
5
|
-
Author:
|
|
6
|
-
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/Jeielsantosdev/
|
|
8
|
-
Project-URL: Repository, https://github.com/Jeielsantosdev/
|
|
9
|
-
Project-URL:
|
|
5
|
+
Author-email: Jeielsantosdev <jeielsantos.ti@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Jeielsantosdev/arc-devkit
|
|
8
|
+
Project-URL: Repository, https://github.com/Jeielsantosdev/arc-devkit
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/Jeielsantosdev/arc-devkit/issues
|
|
10
10
|
Keywords: arc,blockchain,circle,usdc,web3,ai,anthropic
|
|
11
11
|
Classifier: Development Status :: 3 - Alpha
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
13
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
14
13
|
Classifier: Programming Language :: Python :: 3
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.11
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -39,8 +38,13 @@ Requires-Dist: mypy>=1.8; extra == "dev"
|
|
|
39
38
|
Requires-Dist: bandit>=1.7; extra == "dev"
|
|
40
39
|
Requires-Dist: pip-audit>=2.7; extra == "dev"
|
|
41
40
|
Requires-Dist: httpx>=0.27; extra == "dev"
|
|
41
|
+
Requires-Dist: vcrpy>=6.0; extra == "dev"
|
|
42
|
+
Requires-Dist: mcp>=1.2; extra == "dev"
|
|
43
|
+
Requires-Dist: locust>=2.29; extra == "dev"
|
|
42
44
|
Provides-Extra: security
|
|
43
45
|
Requires-Dist: keyring>=24; extra == "security"
|
|
46
|
+
Provides-Extra: mcp
|
|
47
|
+
Requires-Dist: mcp>=1.2; extra == "mcp"
|
|
44
48
|
|
|
45
49
|
# Arc DevKit
|
|
46
50
|
|
|
@@ -53,6 +57,8 @@ Requires-Dist: keyring>=24; extra == "security"
|
|
|
53
57
|
|
|
54
58
|
**Arc DevKit** is a complete Python SDK for developers building on the **Arc blockchain** — Circle's EVM-compatible Layer 1 with USDC as the gas token and sub-second finality.
|
|
55
59
|
|
|
60
|
+
**link to project** https://arc-dev-kit-uxun.vercel.app/
|
|
61
|
+
|
|
56
62
|
It solves the practical friction of building on Arc: USDC gas accounting, PoA middleware, ERC-20 monitoring, async agents, WebSocket streaming, and AI-assisted debugging — all packaged and ready to use.
|
|
57
63
|
|
|
58
64
|
---
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
**Arc DevKit** is a complete Python SDK for developers building on the **Arc blockchain** — Circle's EVM-compatible Layer 1 with USDC as the gas token and sub-second finality.
|
|
11
11
|
|
|
12
|
+
**link to project** https://arc-dev-kit-uxun.vercel.app/
|
|
13
|
+
|
|
12
14
|
It solves the practical friction of building on Arc: USDC gas accounting, PoA middleware, ERC-20 monitoring, async agents, WebSocket streaming, and AI-assisted debugging — all packaged and ready to use.
|
|
13
15
|
|
|
14
16
|
---
|
|
@@ -74,6 +74,14 @@ class BaseAgent(ABC):
|
|
|
74
74
|
account = Account.from_key(_key)
|
|
75
75
|
self._address: str | None = account.address
|
|
76
76
|
self._private_key: str | None = _key
|
|
77
|
+
|
|
78
|
+
from arc_devkit.core.signer import LocalKeySigner
|
|
79
|
+
|
|
80
|
+
# Prepares the pluggable Signer interface (core/signer.py) for
|
|
81
|
+
# future hardware-wallet/post-quantum backends — subclasses still
|
|
82
|
+
# sign via self._private_key today; this is additive, not a
|
|
83
|
+
# behavior change.
|
|
84
|
+
self._signer: LocalKeySigner | None = LocalKeySigner(_key)
|
|
77
85
|
logger.info(
|
|
78
86
|
"[%s] Initialized with wallet %s",
|
|
79
87
|
self.__class__.__name__,
|
|
@@ -82,6 +90,7 @@ class BaseAgent(ABC):
|
|
|
82
90
|
else:
|
|
83
91
|
self._address = None
|
|
84
92
|
self._private_key = None
|
|
93
|
+
self._signer = None
|
|
85
94
|
logger.warning(
|
|
86
95
|
"[%s] No private key — read-only mode.",
|
|
87
96
|
self.__class__.__name__,
|
|
@@ -10,6 +10,7 @@ import logging
|
|
|
10
10
|
|
|
11
11
|
from arc_devkit.agents.event_bus import EventBus
|
|
12
12
|
from arc_devkit.agents.guardrails import Guardrails
|
|
13
|
+
from arc_devkit.agents.jobs import Job, JobRegistry
|
|
13
14
|
from arc_devkit.agents.monitor_agent import MonitorAgent
|
|
14
15
|
from arc_devkit.agents.payment_agent import PaymentAgent
|
|
15
16
|
|
|
@@ -93,6 +94,44 @@ class CoordinatorAgent:
|
|
|
93
94
|
self.bus.publish_sync("coordinator.transfer", {"to": to, "result": result})
|
|
94
95
|
return result
|
|
95
96
|
|
|
97
|
+
def hire_agent(
|
|
98
|
+
self,
|
|
99
|
+
job_registry: JobRegistry,
|
|
100
|
+
agent_address: str,
|
|
101
|
+
amount_usdc: float,
|
|
102
|
+
spec: str,
|
|
103
|
+
) -> Job:
|
|
104
|
+
"""
|
|
105
|
+
Create a job (with USDC escrow) hiring another agent — planning a
|
|
106
|
+
workflow that contracts other agents via ERC-8183 jobs.
|
|
107
|
+
|
|
108
|
+
Requires a PaymentAgent registered with a private key (its wallet
|
|
109
|
+
signs the job-creation tx and funds the escrow). The caller must have
|
|
110
|
+
already approved `job_registry`'s contract to spend `amount_usdc` USDC.
|
|
111
|
+
"""
|
|
112
|
+
from decimal import Decimal
|
|
113
|
+
|
|
114
|
+
if self._payment_agent is None or self._payment_agent._private_key is None:
|
|
115
|
+
return Job(
|
|
116
|
+
job_id=0,
|
|
117
|
+
requester="",
|
|
118
|
+
agent=agent_address,
|
|
119
|
+
amount_usdc=Decimal(str(amount_usdc)),
|
|
120
|
+
spec=spec,
|
|
121
|
+
error="No PaymentAgent with a private key registered.",
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
job = job_registry.create_job(
|
|
125
|
+
agent_address,
|
|
126
|
+
Decimal(str(amount_usdc)),
|
|
127
|
+
spec,
|
|
128
|
+
self._payment_agent._private_key,
|
|
129
|
+
)
|
|
130
|
+
self.bus.publish_sync(
|
|
131
|
+
"coordinator.hire", {"agent": agent_address, "job_id": job.job_id, "spec": spec}
|
|
132
|
+
)
|
|
133
|
+
return job
|
|
134
|
+
|
|
96
135
|
def maintain_balance(
|
|
97
136
|
self,
|
|
98
137
|
address: str,
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""ERC-8004 (Trustless Agents) identity + reputation registry client.
|
|
2
|
+
|
|
3
|
+
ERC-8004 is a very recent EIP (2025) for on-chain agent identity and
|
|
4
|
+
reputation, still evolving — no canonical registry deployment address is
|
|
5
|
+
published for Arc (or confirmed cross-chain) yet, so `AgentRegistry` always
|
|
6
|
+
requires an explicit `identity_registry_address` (and, for reputation,
|
|
7
|
+
`reputation_registry_address`). The default ABIs below are this SDK's
|
|
8
|
+
best-effort reconstruction of the registries described in the spec
|
|
9
|
+
(`registerAgent`/`resolveAgent`, `giveFeedback`/`getReputation`) — pass
|
|
10
|
+
`abi=`/`reputation_abi=` to override once a reference ABI is published.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from decimal import Decimal
|
|
16
|
+
|
|
17
|
+
from web3 import Web3
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
# Best-effort default ABIs — see module docstring.
|
|
22
|
+
_DEFAULT_IDENTITY_ABI = [
|
|
23
|
+
{
|
|
24
|
+
"inputs": [
|
|
25
|
+
{"name": "domain", "type": "string"},
|
|
26
|
+
{"name": "agentAddress", "type": "address"},
|
|
27
|
+
],
|
|
28
|
+
"name": "registerAgent",
|
|
29
|
+
"outputs": [{"name": "agentId", "type": "uint256"}],
|
|
30
|
+
"stateMutability": "nonpayable",
|
|
31
|
+
"type": "function",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"inputs": [{"name": "agentId", "type": "uint256"}],
|
|
35
|
+
"name": "resolveAgent",
|
|
36
|
+
"outputs": [
|
|
37
|
+
{"name": "domain", "type": "string"},
|
|
38
|
+
{"name": "agentAddress", "type": "address"},
|
|
39
|
+
],
|
|
40
|
+
"stateMutability": "view",
|
|
41
|
+
"type": "function",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"anonymous": False,
|
|
45
|
+
"inputs": [
|
|
46
|
+
{"indexed": True, "name": "agentId", "type": "uint256"},
|
|
47
|
+
{"indexed": False, "name": "domain", "type": "string"},
|
|
48
|
+
{"indexed": True, "name": "agentAddress", "type": "address"},
|
|
49
|
+
],
|
|
50
|
+
"name": "AgentRegistered",
|
|
51
|
+
"type": "event",
|
|
52
|
+
},
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
_DEFAULT_REPUTATION_ABI = [
|
|
56
|
+
{
|
|
57
|
+
"inputs": [
|
|
58
|
+
{"name": "agentId", "type": "uint256"},
|
|
59
|
+
{"name": "score", "type": "uint8"},
|
|
60
|
+
{"name": "tag", "type": "string"},
|
|
61
|
+
],
|
|
62
|
+
"name": "giveFeedback",
|
|
63
|
+
"outputs": [],
|
|
64
|
+
"stateMutability": "nonpayable",
|
|
65
|
+
"type": "function",
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"inputs": [{"name": "agentId", "type": "uint256"}],
|
|
69
|
+
"name": "getReputation",
|
|
70
|
+
"outputs": [
|
|
71
|
+
{"name": "totalScore", "type": "uint256"},
|
|
72
|
+
{"name": "feedbackCount", "type": "uint256"},
|
|
73
|
+
],
|
|
74
|
+
"stateMutability": "view",
|
|
75
|
+
"type": "function",
|
|
76
|
+
},
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass
|
|
81
|
+
class AgentIdentity:
|
|
82
|
+
"""An agent's on-chain identity (ERC-8004 Identity Registry)."""
|
|
83
|
+
|
|
84
|
+
agent_id: int
|
|
85
|
+
domain: str
|
|
86
|
+
agent_address: str
|
|
87
|
+
tx_hash: str | None = None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass
|
|
91
|
+
class ReputationScore:
|
|
92
|
+
"""Aggregated feedback for an agent (ERC-8004 Reputation Registry)."""
|
|
93
|
+
|
|
94
|
+
agent_id: int
|
|
95
|
+
total_score: int
|
|
96
|
+
feedback_count: int
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def average(self) -> Decimal:
|
|
100
|
+
if self.feedback_count == 0:
|
|
101
|
+
return Decimal("0")
|
|
102
|
+
return Decimal(self.total_score) / Decimal(self.feedback_count)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class AgentRegistry:
|
|
106
|
+
"""Client for the ERC-8004 Identity + Reputation registries."""
|
|
107
|
+
|
|
108
|
+
def __init__(
|
|
109
|
+
self,
|
|
110
|
+
w3: Web3,
|
|
111
|
+
identity_registry_address: str,
|
|
112
|
+
reputation_registry_address: str | None = None,
|
|
113
|
+
abi: list[dict] | None = None,
|
|
114
|
+
reputation_abi: list[dict] | None = None,
|
|
115
|
+
) -> None:
|
|
116
|
+
from arc_devkit.core.validation import validate_address
|
|
117
|
+
|
|
118
|
+
self._w3 = w3
|
|
119
|
+
self._identity_abi = abi or _DEFAULT_IDENTITY_ABI
|
|
120
|
+
self._identity = w3.eth.contract(
|
|
121
|
+
address=validate_address(identity_registry_address),
|
|
122
|
+
abi=self._identity_abi,
|
|
123
|
+
)
|
|
124
|
+
self._reputation_abi = reputation_abi or _DEFAULT_REPUTATION_ABI
|
|
125
|
+
self._reputation = None
|
|
126
|
+
if reputation_registry_address:
|
|
127
|
+
self._reputation = w3.eth.contract(
|
|
128
|
+
address=validate_address(reputation_registry_address),
|
|
129
|
+
abi=self._reputation_abi,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
def register(self, domain: str, private_key: str) -> AgentIdentity:
|
|
133
|
+
"""Register the caller's wallet as an agent under `domain`."""
|
|
134
|
+
from eth_account import Account
|
|
135
|
+
|
|
136
|
+
sender = Account.from_key(private_key).address
|
|
137
|
+
tx = self._identity.functions.registerAgent(domain, sender).build_transaction(
|
|
138
|
+
{
|
|
139
|
+
"from": sender,
|
|
140
|
+
"nonce": self._w3.eth.get_transaction_count(sender),
|
|
141
|
+
"gas": 200_000,
|
|
142
|
+
"gasPrice": self._w3.eth.gas_price,
|
|
143
|
+
"chainId": self._w3.eth.chain_id,
|
|
144
|
+
}
|
|
145
|
+
)
|
|
146
|
+
signed = self._w3.eth.account.sign_transaction(tx, private_key)
|
|
147
|
+
tx_hash = self._w3.eth.send_raw_transaction(signed.raw_transaction)
|
|
148
|
+
tx_hash_hex = tx_hash.hex()
|
|
149
|
+
|
|
150
|
+
receipt = self._w3.eth.wait_for_transaction_receipt(tx_hash)
|
|
151
|
+
agent_id = 0
|
|
152
|
+
logs = self._identity.events.AgentRegistered().process_receipt(receipt)
|
|
153
|
+
if logs:
|
|
154
|
+
agent_id = logs[0]["args"]["agentId"]
|
|
155
|
+
|
|
156
|
+
logger.info("Agent registered: domain=%s address=%s id=%s", domain, sender, agent_id)
|
|
157
|
+
return AgentIdentity(
|
|
158
|
+
agent_id=agent_id, domain=domain, agent_address=sender, tx_hash=tx_hash_hex
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
def resolve(self, agent_id: int) -> AgentIdentity | None:
|
|
162
|
+
"""Look up an agent's domain and address by id."""
|
|
163
|
+
try:
|
|
164
|
+
domain, agent_address = self._identity.functions.resolveAgent(agent_id).call()
|
|
165
|
+
except Exception as exc:
|
|
166
|
+
logger.warning("resolveAgent(%d) failed: %s", agent_id, exc)
|
|
167
|
+
return None
|
|
168
|
+
return AgentIdentity(agent_id=agent_id, domain=domain, agent_address=agent_address)
|
|
169
|
+
|
|
170
|
+
def get_reputation(self, agent_id: int) -> ReputationScore | None:
|
|
171
|
+
"""Return aggregated feedback for an agent. None if unavailable."""
|
|
172
|
+
if self._reputation is None:
|
|
173
|
+
logger.warning("No reputation_registry_address configured.")
|
|
174
|
+
return None
|
|
175
|
+
try:
|
|
176
|
+
total_score, feedback_count = self._reputation.functions.getReputation(agent_id).call()
|
|
177
|
+
except Exception as exc:
|
|
178
|
+
logger.warning("getReputation(%d) failed: %s", agent_id, exc)
|
|
179
|
+
return None
|
|
180
|
+
return ReputationScore(
|
|
181
|
+
agent_id=agent_id, total_score=total_score, feedback_count=feedback_count
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
def give_feedback(self, agent_id: int, score: int, private_key: str, tag: str = "") -> str:
|
|
185
|
+
"""Submit feedback (0-100) for an agent. Returns the tx hash."""
|
|
186
|
+
if not 0 <= score <= 100:
|
|
187
|
+
raise ValueError("score must be between 0 and 100.")
|
|
188
|
+
if self._reputation is None:
|
|
189
|
+
raise ValueError("No reputation_registry_address configured.")
|
|
190
|
+
|
|
191
|
+
from eth_account import Account
|
|
192
|
+
|
|
193
|
+
sender = Account.from_key(private_key).address
|
|
194
|
+
tx = self._reputation.functions.giveFeedback(agent_id, score, tag).build_transaction(
|
|
195
|
+
{
|
|
196
|
+
"from": sender,
|
|
197
|
+
"nonce": self._w3.eth.get_transaction_count(sender),
|
|
198
|
+
"gas": 150_000,
|
|
199
|
+
"gasPrice": self._w3.eth.gas_price,
|
|
200
|
+
"chainId": self._w3.eth.chain_id,
|
|
201
|
+
}
|
|
202
|
+
)
|
|
203
|
+
signed = self._w3.eth.account.sign_transaction(tx, private_key)
|
|
204
|
+
tx_hash = self._w3.eth.send_raw_transaction(signed.raw_transaction)
|
|
205
|
+
return tx_hash.hex()
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""JobAgent — accepts and executes ERC-8183 jobs autonomously, within guardrails."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
|
|
6
|
+
from arc_devkit.agents.base_agent import BaseAgent
|
|
7
|
+
from arc_devkit.agents.guardrails import Guardrails, GuardrailViolation
|
|
8
|
+
from arc_devkit.agents.jobs import Job, JobRegistry
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class JobAgent(BaseAgent):
|
|
14
|
+
"""
|
|
15
|
+
Autonomously accepts an ERC-8183 job, runs a handler to produce the
|
|
16
|
+
deliverable, and submits it — every step gated by the kill switch.
|
|
17
|
+
|
|
18
|
+
Example:
|
|
19
|
+
agent = JobAgent(registry_address="0x...", private_key=key, guardrails=guardrails)
|
|
20
|
+
result = agent.execute(job_id=42, handler=lambda job: "ipfs://Qm...")
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
registry_address: str,
|
|
26
|
+
job_abi: list[dict] | None = None,
|
|
27
|
+
guardrails: Guardrails | None = None,
|
|
28
|
+
**kwargs: object,
|
|
29
|
+
) -> None:
|
|
30
|
+
super().__init__(**kwargs)
|
|
31
|
+
self._guardrails = guardrails
|
|
32
|
+
self._registry = JobRegistry(self._w3, registry_address, abi=job_abi, guardrails=guardrails)
|
|
33
|
+
|
|
34
|
+
def get_balance(self) -> dict:
|
|
35
|
+
if not self._address:
|
|
36
|
+
return {"error": "No private key configured — read-only mode."}
|
|
37
|
+
wei = self._w3.eth.get_balance(self._address)
|
|
38
|
+
return {"address": self._address, "balance_wei": str(wei)}
|
|
39
|
+
|
|
40
|
+
def execute(self, job_id: int, handler: Callable[[Job], str]) -> dict: # type: ignore[override]
|
|
41
|
+
"""
|
|
42
|
+
Accept `job_id`, run `handler(job)` to produce a deliverable URI/string,
|
|
43
|
+
and deliver it.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
job_id: The job to accept.
|
|
47
|
+
handler: Called with the accepted Job; must return the deliverable
|
|
48
|
+
(e.g. an IPFS URI or a result string) to submit on-chain.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Dict with status ("delivered"/"blocked"/"error") and the Job state.
|
|
52
|
+
"""
|
|
53
|
+
if not self._private_key:
|
|
54
|
+
return {"status": "error", "error": "Private key required."}
|
|
55
|
+
|
|
56
|
+
if self._guardrails:
|
|
57
|
+
try:
|
|
58
|
+
self._guardrails.check_kill_switch()
|
|
59
|
+
except GuardrailViolation as exc:
|
|
60
|
+
return {"status": "blocked", "error": str(exc)}
|
|
61
|
+
|
|
62
|
+
job = self._registry.accept_job(job_id, self._private_key)
|
|
63
|
+
if job.error:
|
|
64
|
+
self.log(f"Failed to accept job {job_id}: {job.error}")
|
|
65
|
+
return {"status": "error", "error": job.error, "job": job}
|
|
66
|
+
|
|
67
|
+
self.log(f"Accepted job {job_id} — running handler...")
|
|
68
|
+
try:
|
|
69
|
+
deliverable = handler(job)
|
|
70
|
+
except Exception as exc:
|
|
71
|
+
self.log(f"Handler failed for job {job_id}: {exc}")
|
|
72
|
+
return {"status": "error", "error": str(exc), "job": job}
|
|
73
|
+
|
|
74
|
+
delivered = self._registry.deliver_job(job_id, deliverable, self._private_key)
|
|
75
|
+
if delivered.error:
|
|
76
|
+
return {"status": "error", "error": delivered.error, "job": delivered}
|
|
77
|
+
|
|
78
|
+
if self._guardrails:
|
|
79
|
+
self._guardrails.audit(
|
|
80
|
+
agent="JobAgent",
|
|
81
|
+
trigger="manual",
|
|
82
|
+
action="job_deliver",
|
|
83
|
+
job_id=job_id,
|
|
84
|
+
deliverable_uri=deliverable,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
self.log(f"Delivered job {job_id}.")
|
|
88
|
+
return {"status": "delivered", "job": delivered}
|