web3-agent-kit 0.3.0__py3-none-any.whl

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.
src/__init__.py ADDED
@@ -0,0 +1,33 @@
1
+ """Web3 Agent Kit — Open-source framework for autonomous Web3 AI agents."""
2
+
3
+ __version__ = "0.3.0"
4
+ __author__ = "Maulana"
5
+
6
+ from .agent import Agent, AgentConfig
7
+ from .wallet import Wallet
8
+ from .chain import Chain, ChainManager
9
+ from .llm import LLM, LLMConfig
10
+ from .portfolio import PortfolioTracker, PortfolioSummary
11
+ from .bridge import BridgeAgent, BridgeRoute, BridgeResult
12
+ from .sniper import TokenSniper, SniperConfig, NewPair, RiskLevel
13
+
14
+ __all__ = [
15
+ # Core
16
+ "Agent",
17
+ "AgentConfig",
18
+ "Wallet",
19
+ "Chain",
20
+ "ChainManager",
21
+ "LLM",
22
+ "LLMConfig",
23
+ # Features
24
+ "PortfolioTracker",
25
+ "PortfolioSummary",
26
+ "BridgeAgent",
27
+ "BridgeRoute",
28
+ "BridgeResult",
29
+ "TokenSniper",
30
+ "SniperConfig",
31
+ "NewPair",
32
+ "RiskLevel",
33
+ ]
src/agent.py ADDED
@@ -0,0 +1,239 @@
1
+ """Core agent framework — goal-driven autonomous agents with LLM reasoning."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import logging
7
+ from dataclasses import dataclass, field
8
+ from typing import Any, Callable, Optional
9
+
10
+ from .wallet import Wallet
11
+ from .chain import Chain
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ # System prompt for the agent
17
+ AGENT_SYSTEM_PROMPT = """You are an autonomous Web3 agent. You observe blockchain state, reason about goals, and execute on-chain actions using available tools.
18
+
19
+ TOOLS:
20
+ {tools}
21
+
22
+ RESPONSE FORMAT:
23
+ You must respond with a JSON object:
24
+ {{
25
+ "thought": "your reasoning about what to do next",
26
+ "tool": "tool_name",
27
+ "args": {{"key": "value"}}
28
+ }}
29
+
30
+ When the goal is complete, respond with:
31
+ {{
32
+ "thought": "summary of what was accomplished",
33
+ "tool": "done",
34
+ "answer": "final answer to the user"
35
+ }}
36
+
37
+ RULES:
38
+ - Always use the cheapest available option
39
+ - Check balances before swaps
40
+ - Consider gas costs in your reasoning
41
+ - Never exceed the governor limits
42
+ - If a tool fails, try an alternative approach
43
+ - Be precise with token addresses and amounts
44
+ """
45
+
46
+
47
+ @dataclass
48
+ class AgentConfig:
49
+ """Configuration for an autonomous agent."""
50
+
51
+ wallet: Wallet
52
+ chains: list[Chain] = field(default_factory=lambda: [Chain.ETHEREUM])
53
+ llm: str = "auto" # "auto" for auto-detect, or specific model
54
+ max_steps: int = 20
55
+ tools: list[Any] = field(default_factory=list)
56
+ governor: Optional[Any] = None
57
+ confirm_fn: Optional[Callable] = None
58
+ verbose: bool = False
59
+
60
+
61
+ class Agent:
62
+ """
63
+ Autonomous AI agent for Web3 operations.
64
+
65
+ The agent observes blockchain state, reasons about goals using an LLM,
66
+ and executes on-chain transactions — all governed by safety caps.
67
+
68
+ Example:
69
+ agent = Agent(
70
+ wallet=Wallet.from_key("0x..."),
71
+ chains=[Chain.BASE],
72
+ tools=[Uniswap(), Aave()],
73
+ )
74
+ result = agent.run("Swap 0.1 ETH to USDC")
75
+ """
76
+
77
+ def __init__(self, config: Optional[AgentConfig] = None, **kwargs):
78
+ if config:
79
+ self.config = config
80
+ else:
81
+ self.config = AgentConfig(**kwargs)
82
+
83
+ self.wallet = self.config.wallet
84
+ self.chains = self.config.chains
85
+ self.tools = {t.name: t for t in self.config.tools}
86
+ self.history: list[dict] = []
87
+ self._llm = None
88
+
89
+ @property
90
+ def llm(self):
91
+ """Lazy-load LLM client."""
92
+ if self._llm is None:
93
+ from .llm import LLM, LLMConfig
94
+ self._llm = LLM()
95
+ return self._llm
96
+
97
+ def run(self, goal: str, max_steps: Optional[int] = None) -> str:
98
+ """
99
+ Run the agent toward a goal.
100
+
101
+ Args:
102
+ goal: Natural language description of what to accomplish
103
+ max_steps: Override default max steps
104
+
105
+ Returns:
106
+ Result string (transaction hash, summary, etc.)
107
+ """
108
+ steps = max_steps or self.config.max_steps
109
+ observation = self._observe()
110
+
111
+ for step in range(steps):
112
+ logger.info(f"Step {step + 1}/{steps}: Thinking...")
113
+
114
+ # Decide next action via LLM
115
+ action = self._decide(goal, observation)
116
+
117
+ if self.config.verbose:
118
+ thought = action.get("thought", "")
119
+ logger.info(f"Thought: {thought}")
120
+
121
+ if action.get("tool") == "done":
122
+ result = action.get("answer", "Task completed")
123
+ self.history.append({
124
+ "step": step + 1,
125
+ "action": action,
126
+ "result": result,
127
+ })
128
+ return result
129
+
130
+ # Execute action
131
+ result = self._act(action)
132
+ observation = result
133
+
134
+ # Log action
135
+ self.history.append({
136
+ "step": step + 1,
137
+ "action": action,
138
+ "result": result,
139
+ })
140
+
141
+ if self.config.verbose:
142
+ logger.info(f"Result: {result}")
143
+
144
+ return f"Max steps ({steps}) reached without completion"
145
+
146
+ def _observe(self) -> str:
147
+ """Observe current blockchain state."""
148
+ observations = []
149
+
150
+ for chain in self.chains:
151
+ try:
152
+ balance = self.wallet.get_balance(chain)
153
+ observations.append(f"{chain.name}: {balance} ETH")
154
+ except Exception as e:
155
+ observations.append(f"{chain.name}: error ({e})")
156
+
157
+ return " | ".join(observations)
158
+
159
+ def _decide(self, goal: str, observation: str) -> dict:
160
+ """
161
+ Decide next action using LLM.
162
+
163
+ Returns action dict: {"tool": "name", "args": {...}}
164
+ """
165
+ # Build tool descriptions
166
+ tool_descriptions = []
167
+ for name, tool in self.tools.items():
168
+ chains = [c.value for c in tool.supported_chains]
169
+ tool_descriptions.append(f"- {name}: chains={chains}")
170
+
171
+ if not tool_descriptions:
172
+ tool_descriptions = ["- No tools available"]
173
+
174
+ system_prompt = AGENT_SYSTEM_PROMPT.format(
175
+ tools="\n".join(tool_descriptions)
176
+ )
177
+
178
+ # Build conversation context
179
+ context_parts = [f"OBSERVATION: {observation}"]
180
+
181
+ if self.history:
182
+ for h in self.history[-3:]: # Last 3 steps
183
+ context_parts.append(
184
+ f"Step {h['step']}: tool={h['action'].get('tool')}, result={str(h['result'])[:200]}"
185
+ )
186
+
187
+ context_parts.append(f"GOAL: {goal}")
188
+
189
+ user_prompt = "\n".join(context_parts)
190
+
191
+ try:
192
+ response = self.llm.chat_json(user_prompt, system=system_prompt)
193
+
194
+ # Validate response
195
+ if "tool" not in response:
196
+ response["tool"] = "done"
197
+ response["answer"] = response.get("thought", "Invalid LLM response")
198
+
199
+ return response
200
+
201
+ except Exception as e:
202
+ logger.error(f"LLM failed: {e}")
203
+ # Fallback: return done with error
204
+ return {
205
+ "tool": "done",
206
+ "args": {},
207
+ "answer": f"LLM error: {e}",
208
+ }
209
+
210
+ def _act(self, action: dict) -> str:
211
+ """Execute an action using the appropriate tool."""
212
+ tool_name = action.get("tool")
213
+ args = action.get("args", {})
214
+
215
+ if tool_name not in self.tools:
216
+ return f"Unknown tool: {tool_name}. Available: {list(self.tools.keys())}"
217
+
218
+ tool = self.tools[tool_name]
219
+
220
+ # Check governor before executing
221
+ if self.config.governor:
222
+ decision = self.config.governor.authorize(action)
223
+ if not decision.allowed:
224
+ return f"Blocked by governor: {decision.reason}"
225
+
226
+ # Execute
227
+ try:
228
+ result = tool.execute(self.wallet, **args)
229
+ return str(result)
230
+ except Exception as e:
231
+ logger.error(f"Tool {tool_name} failed: {e}")
232
+ return f"Error: {e}"
233
+
234
+ def get_history(self) -> list[dict]:
235
+ """Get action history."""
236
+ return self.history
237
+
238
+ def __repr__(self) -> str:
239
+ return f"Agent(chains={[c.name for c in self.chains]}, tools={list(self.tools.keys())})"