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 +33 -0
- src/agent.py +239 -0
- src/bridge.py +504 -0
- src/chain.py +115 -0
- src/defi/__init__.py +476 -0
- src/llm.py +272 -0
- src/portfolio.py +326 -0
- src/sniper.py +511 -0
- src/utils/__init__.py +140 -0
- src/wallet.py +128 -0
- web3_agent_kit-0.3.0.dist-info/METADATA +333 -0
- web3_agent_kit-0.3.0.dist-info/RECORD +15 -0
- web3_agent_kit-0.3.0.dist-info/WHEEL +5 -0
- web3_agent_kit-0.3.0.dist-info/licenses/LICENSE +21 -0
- web3_agent_kit-0.3.0.dist-info/top_level.txt +1 -0
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())})"
|