@agentlayer.tech/wallet 0.1.94 → 0.1.96
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.
- package/.openclaw/extensions/agent-wallet/README.md +2 -2
- package/.openclaw/extensions/agent-wallet/dist/index.js +70 -14
- package/.openclaw/extensions/agent-wallet/index.ts +70 -14
- package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +4 -2
- package/.openclaw/extensions/agent-wallet/package.json +1 -1
- package/VERSION +1 -1
- package/agent-wallet/README.md +3 -3
- package/agent-wallet/agent_wallet/__init__.py +1 -1
- package/agent-wallet/agent_wallet/autonomous_policy.py +2 -1
- package/agent-wallet/agent_wallet/config.py +7 -11
- package/agent-wallet/agent_wallet/networks.py +25 -0
- package/agent-wallet/agent_wallet/openclaw_adapter.py +171 -27
- package/agent-wallet/agent_wallet/providers/evm_portfolio.py +9 -2
- package/agent-wallet/agent_wallet/wallet_layer/base.py +19 -0
- package/agent-wallet/agent_wallet/wallet_layer/wdk_evm.py +61 -1
- package/agent-wallet/openclaw.plugin.json +1 -1
- package/agent-wallet/pyproject.toml +1 -1
- package/agent-wallet/scripts/install_agent_wallet.py +29 -1
- package/claude-code/plugins/agent-wallet/.claude-plugin/plugin.json +1 -1
- package/claude-code/plugins/agent-wallet/AGENTLAYER_AGENT_GUIDE.md +282 -0
- package/claude-code/plugins/agent-wallet/README.md +2 -0
- package/claude-code/plugins/agent-wallet/commands/guide.md +40 -0
- package/codex/plugins/agent-wallet/.codex-plugin/plugin.json +1 -1
- package/codex/plugins/agent-wallet/server.py +15 -11
- package/codex/plugins/agent-wallet/skills/wallet-operator/SKILL.md +1 -1
- package/hermes/plugins/agent_wallet/plugin.yaml +1 -1
- package/package.json +1 -1
- package/wdk-btc-wallet/package.json +1 -1
- package/wdk-evm-wallet/.env.example +4 -1
- package/wdk-evm-wallet/README.md +18 -2
- package/wdk-evm-wallet/package.json +1 -1
- package/wdk-evm-wallet/src/config.js +34 -3
- package/wdk-evm-wallet/src/network_state.js +8 -2
- package/wdk-evm-wallet/src/server.js +12 -0
- package/wdk-evm-wallet/src/wdk_evm_wallet.js +132 -2
|
@@ -11,6 +11,13 @@ from agent_wallet.approval import inspect_approval_token, verify_approval_token
|
|
|
11
11
|
from agent_wallet.autonomous_policy import OperationRequest
|
|
12
12
|
from agent_wallet.exceptions import ProviderError
|
|
13
13
|
from agent_wallet.models import AgentToolResult, AgentToolSpec
|
|
14
|
+
from agent_wallet.networks import (
|
|
15
|
+
EVM_CORE_MAINNET_CAIP_IDS,
|
|
16
|
+
EVM_CORE_MAINNETS,
|
|
17
|
+
EVM_CORE_NETWORK_ALIASES,
|
|
18
|
+
EVM_CORE_TESTNETS,
|
|
19
|
+
GOAT_EVM_NETWORK_IDENTIFIERS,
|
|
20
|
+
)
|
|
14
21
|
from agent_wallet.providers import x402
|
|
15
22
|
from agent_wallet.wallet_layer.base import AgentWalletBackend, WalletBackendError
|
|
16
23
|
|
|
@@ -74,7 +81,7 @@ class OpenClawWalletAdapter:
|
|
|
74
81
|
if chain == "bitcoin":
|
|
75
82
|
return normalized == "bitcoin"
|
|
76
83
|
if chain == "evm":
|
|
77
|
-
return normalized in
|
|
84
|
+
return normalized in EVM_CORE_MAINNETS | EVM_CORE_MAINNET_CAIP_IDS
|
|
78
85
|
if chain == "solana":
|
|
79
86
|
return normalized in {"mainnet", "solana:5eykt4usfv8p8njdtrepy1vzkqzkvdp"}
|
|
80
87
|
return normalized == "mainnet"
|
|
@@ -85,6 +92,10 @@ class OpenClawWalletAdapter:
|
|
|
85
92
|
def _is_mainnet_for_backend(self, backend: AgentWalletBackend) -> bool:
|
|
86
93
|
return self._is_mainnet_network(getattr(backend, "network", ""))
|
|
87
94
|
|
|
95
|
+
@staticmethod
|
|
96
|
+
def _is_goat_evm_network(network: Any) -> bool:
|
|
97
|
+
return str(network or "").strip().lower() in GOAT_EVM_NETWORK_IDENTIFIERS
|
|
98
|
+
|
|
88
99
|
def _supports_evm_velora(self) -> bool:
|
|
89
100
|
return str(getattr(self.backend, "chain", "")).strip().lower() == "evm" and self._is_mainnet()
|
|
90
101
|
|
|
@@ -93,17 +104,13 @@ class OpenClawWalletAdapter:
|
|
|
93
104
|
|
|
94
105
|
def _normalize_evm_tool_network(self, value: Any) -> str:
|
|
95
106
|
network = str(value or "").strip().lower()
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if network in {"sepolia", "base-sepolia", "base_sepolia"}:
|
|
104
|
-
raise WalletBackendError("EVM testnets are no longer supported. Use ethereum, base, or robinhood.")
|
|
105
|
-
if network not in {"ethereum", "base", "robinhood"}:
|
|
106
|
-
raise WalletBackendError("EVM network must be 'ethereum', 'base', or 'robinhood'.")
|
|
107
|
+
network = EVM_CORE_NETWORK_ALIASES.get(network, network)
|
|
108
|
+
if network in EVM_CORE_TESTNETS:
|
|
109
|
+
raise WalletBackendError(
|
|
110
|
+
"EVM testnets are no longer supported. Use ethereum, base, robinhood, or goat."
|
|
111
|
+
)
|
|
112
|
+
if network not in EVM_CORE_MAINNETS:
|
|
113
|
+
raise WalletBackendError("EVM network must be 'ethereum', 'base', 'robinhood', or 'goat'.")
|
|
107
114
|
return network
|
|
108
115
|
|
|
109
116
|
def _resolve_backend_for_args(self, args: dict[str, Any]) -> AgentWalletBackend:
|
|
@@ -1229,7 +1236,7 @@ class OpenClawWalletAdapter:
|
|
|
1229
1236
|
"properties": {
|
|
1230
1237
|
"network": {
|
|
1231
1238
|
"type": "string",
|
|
1232
|
-
"enum": ["ethereum", "base", "robinhood"],
|
|
1239
|
+
"enum": ["ethereum", "base", "robinhood", "goat"],
|
|
1233
1240
|
"description": "Optional EVM network override for this request.",
|
|
1234
1241
|
},
|
|
1235
1242
|
},
|
|
@@ -1246,7 +1253,7 @@ class OpenClawWalletAdapter:
|
|
|
1246
1253
|
"properties": {
|
|
1247
1254
|
"network": {
|
|
1248
1255
|
"type": "string",
|
|
1249
|
-
"enum": ["ethereum", "base", "robinhood"],
|
|
1256
|
+
"enum": ["ethereum", "base", "robinhood", "goat"],
|
|
1250
1257
|
"description": "Optional EVM network override for this request.",
|
|
1251
1258
|
},
|
|
1252
1259
|
},
|
|
@@ -1271,7 +1278,7 @@ class OpenClawWalletAdapter:
|
|
|
1271
1278
|
},
|
|
1272
1279
|
"network": {
|
|
1273
1280
|
"type": "string",
|
|
1274
|
-
"enum": ["ethereum", "base", "robinhood"],
|
|
1281
|
+
"enum": ["ethereum", "base", "robinhood", "goat"],
|
|
1275
1282
|
"description": "Optional EVM network override for this request.",
|
|
1276
1283
|
},
|
|
1277
1284
|
},
|
|
@@ -1352,7 +1359,7 @@ class OpenClawWalletAdapter:
|
|
|
1352
1359
|
"properties": {
|
|
1353
1360
|
"network": {
|
|
1354
1361
|
"type": "string",
|
|
1355
|
-
"enum": ["ethereum", "base", "robinhood"],
|
|
1362
|
+
"enum": ["ethereum", "base", "robinhood", "goat"],
|
|
1356
1363
|
"description": "Optional EVM network override for this request.",
|
|
1357
1364
|
},
|
|
1358
1365
|
},
|
|
@@ -1365,7 +1372,7 @@ class OpenClawWalletAdapter:
|
|
|
1365
1372
|
name="set_evm_network",
|
|
1366
1373
|
description=(
|
|
1367
1374
|
"Select the active EVM network for subsequent wallet tool calls in this "
|
|
1368
|
-
"runtime session. Use this to switch between ethereum, base, and
|
|
1375
|
+
"runtime session. Use this to switch between ethereum, base, robinhood, and goat instead "
|
|
1369
1376
|
"of editing code or plugin configuration."
|
|
1370
1377
|
),
|
|
1371
1378
|
input_schema={
|
|
@@ -1373,7 +1380,7 @@ class OpenClawWalletAdapter:
|
|
|
1373
1380
|
"properties": {
|
|
1374
1381
|
"network": {
|
|
1375
1382
|
"type": "string",
|
|
1376
|
-
"enum": ["ethereum", "base", "robinhood"],
|
|
1383
|
+
"enum": ["ethereum", "base", "robinhood", "goat"],
|
|
1377
1384
|
"description": "EVM network to make active for subsequent calls.",
|
|
1378
1385
|
},
|
|
1379
1386
|
},
|
|
@@ -1395,7 +1402,7 @@ class OpenClawWalletAdapter:
|
|
|
1395
1402
|
},
|
|
1396
1403
|
"network": {
|
|
1397
1404
|
"type": "string",
|
|
1398
|
-
"enum": ["ethereum", "base", "robinhood"],
|
|
1405
|
+
"enum": ["ethereum", "base", "robinhood", "goat"],
|
|
1399
1406
|
"description": "Optional EVM network override for this request.",
|
|
1400
1407
|
},
|
|
1401
1408
|
},
|
|
@@ -1417,7 +1424,7 @@ class OpenClawWalletAdapter:
|
|
|
1417
1424
|
},
|
|
1418
1425
|
"network": {
|
|
1419
1426
|
"type": "string",
|
|
1420
|
-
"enum": ["ethereum", "base", "robinhood"],
|
|
1427
|
+
"enum": ["ethereum", "base", "robinhood", "goat"],
|
|
1421
1428
|
"description": "Optional EVM network override for this request.",
|
|
1422
1429
|
},
|
|
1423
1430
|
},
|
|
@@ -1435,7 +1442,7 @@ class OpenClawWalletAdapter:
|
|
|
1435
1442
|
"properties": {
|
|
1436
1443
|
"network": {
|
|
1437
1444
|
"type": "string",
|
|
1438
|
-
"enum": ["ethereum", "base", "robinhood"],
|
|
1445
|
+
"enum": ["ethereum", "base", "robinhood", "goat"],
|
|
1439
1446
|
"description": "Optional EVM network override for this request.",
|
|
1440
1447
|
},
|
|
1441
1448
|
},
|
|
@@ -1446,7 +1453,10 @@ class OpenClawWalletAdapter:
|
|
|
1446
1453
|
),
|
|
1447
1454
|
AgentToolSpec(
|
|
1448
1455
|
name="get_evm_transaction_receipt",
|
|
1449
|
-
description=
|
|
1456
|
+
description=(
|
|
1457
|
+
"Get the transaction receipt for a broadcast EVM transaction hash. On GOAT, a receipt "
|
|
1458
|
+
"confirms L2 inclusion; it does not by itself prove Bitcoin-backed finality."
|
|
1459
|
+
),
|
|
1450
1460
|
input_schema={
|
|
1451
1461
|
"type": "object",
|
|
1452
1462
|
"properties": {
|
|
@@ -1456,7 +1466,7 @@ class OpenClawWalletAdapter:
|
|
|
1456
1466
|
},
|
|
1457
1467
|
"network": {
|
|
1458
1468
|
"type": "string",
|
|
1459
|
-
"enum": ["ethereum", "base", "robinhood"],
|
|
1469
|
+
"enum": ["ethereum", "base", "robinhood", "goat"],
|
|
1460
1470
|
"description": "Optional EVM network override for this request.",
|
|
1461
1471
|
},
|
|
1462
1472
|
},
|
|
@@ -1489,7 +1499,7 @@ class OpenClawWalletAdapter:
|
|
|
1489
1499
|
"approval_token": {"type": "string"},
|
|
1490
1500
|
"network": {
|
|
1491
1501
|
"type": "string",
|
|
1492
|
-
"enum": ["ethereum", "base", "robinhood"],
|
|
1502
|
+
"enum": ["ethereum", "base", "robinhood", "goat"],
|
|
1493
1503
|
"description": "Optional EVM network override for this request.",
|
|
1494
1504
|
},
|
|
1495
1505
|
},
|
|
@@ -1524,7 +1534,7 @@ class OpenClawWalletAdapter:
|
|
|
1524
1534
|
"approval_token": {"type": "string"},
|
|
1525
1535
|
"network": {
|
|
1526
1536
|
"type": "string",
|
|
1527
|
-
"enum": ["ethereum", "base", "robinhood"],
|
|
1537
|
+
"enum": ["ethereum", "base", "robinhood", "goat"],
|
|
1528
1538
|
"description": "Optional EVM network override for this request.",
|
|
1529
1539
|
},
|
|
1530
1540
|
},
|
|
@@ -1746,12 +1756,62 @@ class OpenClawWalletAdapter:
|
|
|
1746
1756
|
)
|
|
1747
1757
|
tools.insert(
|
|
1748
1758
|
13,
|
|
1759
|
+
AgentToolSpec(
|
|
1760
|
+
name="get_evm_uniswap_pools",
|
|
1761
|
+
description=(
|
|
1762
|
+
"Find existing Uniswap V3/V4 pool metadata on ethereum, base, or robinhood using the official "
|
|
1763
|
+
"Uniswap Pool Info API. Returns the canonical poolReferenceIdentifier required to create liquidity; "
|
|
1764
|
+
"this is read-only and does not approve, sign, or execute anything."
|
|
1765
|
+
),
|
|
1766
|
+
input_schema={
|
|
1767
|
+
"type": "object",
|
|
1768
|
+
"properties": {
|
|
1769
|
+
"protocol": {"type": "string", "enum": ["V3", "V4"]},
|
|
1770
|
+
"pool_parameters": {"type": "object", "description": "Official Pool Info API token-pair parameters, such as tokenAddressA, tokenAddressB and optional fee/tickSpacing/hooks.", "additionalProperties": True},
|
|
1771
|
+
"pool_references": {"type": "array", "maxItems": 20, "items": {"type": "object", "additionalProperties": True}, "description": "One to twenty official Pool Info API pool reference objects."},
|
|
1772
|
+
"page_size": {"type": "integer", "minimum": 1, "maximum": 20},
|
|
1773
|
+
"current_page": {"type": "integer", "minimum": 1},
|
|
1774
|
+
"network": {"type": "string", "enum": ["ethereum", "base", "robinhood"]},
|
|
1775
|
+
},
|
|
1776
|
+
"required": ["protocol"],
|
|
1777
|
+
"additionalProperties": False,
|
|
1778
|
+
},
|
|
1779
|
+
read_only=True,
|
|
1780
|
+
risk_level="low",
|
|
1781
|
+
),
|
|
1782
|
+
)
|
|
1783
|
+
tools.insert(
|
|
1784
|
+
14,
|
|
1785
|
+
AgentToolSpec(
|
|
1786
|
+
name="get_evm_uniswap_positions",
|
|
1787
|
+
description=(
|
|
1788
|
+
"List read-only Uniswap V3 LP position NFTs owned by the active wallet on ethereum, base, or robinhood. "
|
|
1789
|
+
"Returns NFT token ids, tokens, fee tier, tick range, liquidity, and currently owed fees. V4 discovery is "
|
|
1790
|
+
"not exposed until a verified indexed source is configured because V4 PositionManager is not enumerable."
|
|
1791
|
+
),
|
|
1792
|
+
input_schema={
|
|
1793
|
+
"type": "object",
|
|
1794
|
+
"properties": {
|
|
1795
|
+
"protocol": {"type": "string", "enum": ["V3"], "description": "V3 is the currently enumerable, on-chain-supported position scanner."},
|
|
1796
|
+
"limit": {"type": "integer", "minimum": 1, "maximum": 100},
|
|
1797
|
+
"network": {"type": "string", "enum": ["ethereum", "base", "robinhood"]},
|
|
1798
|
+
},
|
|
1799
|
+
"additionalProperties": False,
|
|
1800
|
+
},
|
|
1801
|
+
read_only=True,
|
|
1802
|
+
risk_level="low",
|
|
1803
|
+
),
|
|
1804
|
+
)
|
|
1805
|
+
tools.insert(
|
|
1806
|
+
15,
|
|
1749
1807
|
AgentToolSpec(
|
|
1750
1808
|
name="manage_evm_uniswap_liquidity",
|
|
1751
1809
|
description=(
|
|
1752
1810
|
"Preview, prepare, or execute a Uniswap V3/V4 liquidity action on ethereum, base, or robinhood. "
|
|
1753
1811
|
"Supported actions are create, increase, decrease, and claim_fees. The request is passed to the "
|
|
1754
|
-
"official Uniswap Liquidity API; execute refreshes the transaction immediately before signing."
|
|
1812
|
+
"official Uniswap Liquidity API; execute refreshes the transaction immediately before signing. "
|
|
1813
|
+
"Create requires existingPool.poolReference; increase/decrease/claim_fees require a position NFT token id. "
|
|
1814
|
+
"Use get_evm_uniswap_pools or get_evm_uniswap_positions to obtain these identifiers; never guess them."
|
|
1755
1815
|
),
|
|
1756
1816
|
input_schema={
|
|
1757
1817
|
"type": "object",
|
|
@@ -4270,6 +4330,14 @@ class OpenClawWalletAdapter:
|
|
|
4270
4330
|
return AgentToolResult(tool=tool_name, ok=True, data=data)
|
|
4271
4331
|
|
|
4272
4332
|
if tool_name == "x402_pay_request":
|
|
4333
|
+
if (
|
|
4334
|
+
str(getattr(active_backend, "chain", "")).strip().lower() == "evm"
|
|
4335
|
+
and self._is_goat_evm_network(getattr(active_backend, "network", ""))
|
|
4336
|
+
):
|
|
4337
|
+
raise WalletBackendError(
|
|
4338
|
+
"GOAT x402 payments are not enabled in this wallet surface. "
|
|
4339
|
+
"Use the supported core GOAT wallet operations instead."
|
|
4340
|
+
)
|
|
4273
4341
|
url = args.get("url")
|
|
4274
4342
|
method = args.get("method", "GET")
|
|
4275
4343
|
headers = args.get("headers")
|
|
@@ -5682,6 +5750,46 @@ class OpenClawWalletAdapter:
|
|
|
5682
5750
|
)
|
|
5683
5751
|
return AgentToolResult(tool=tool_name, ok=True, data=data)
|
|
5684
5752
|
|
|
5753
|
+
if tool_name == "get_evm_uniswap_pools":
|
|
5754
|
+
protocol = args.get("protocol")
|
|
5755
|
+
pool_parameters = args.get("pool_parameters")
|
|
5756
|
+
pool_references = args.get("pool_references")
|
|
5757
|
+
page_size = args.get("page_size", 20)
|
|
5758
|
+
current_page = args.get("current_page", 1)
|
|
5759
|
+
if protocol not in {"V3", "V4"}:
|
|
5760
|
+
raise WalletBackendError("protocol must be V3 or V4.")
|
|
5761
|
+
if (pool_parameters is None) == (pool_references is None):
|
|
5762
|
+
raise WalletBackendError("Provide exactly one of pool_parameters or pool_references.")
|
|
5763
|
+
if pool_parameters is not None and not isinstance(pool_parameters, dict):
|
|
5764
|
+
raise WalletBackendError("pool_parameters must be an object.")
|
|
5765
|
+
if pool_references is not None:
|
|
5766
|
+
if not isinstance(pool_references, list) or not 1 <= len(pool_references) <= 20:
|
|
5767
|
+
raise WalletBackendError("pool_references must contain between 1 and 20 objects.")
|
|
5768
|
+
if any(not isinstance(reference, dict) for reference in pool_references):
|
|
5769
|
+
raise WalletBackendError("pool_references must contain only objects.")
|
|
5770
|
+
if not isinstance(page_size, int) or not 1 <= page_size <= 20:
|
|
5771
|
+
raise WalletBackendError("page_size must be an integer between 1 and 20.")
|
|
5772
|
+
if not isinstance(current_page, int) or current_page < 1:
|
|
5773
|
+
raise WalletBackendError("current_page must be a positive integer.")
|
|
5774
|
+
data = await active_backend.get_uniswap_liquidity_pools(
|
|
5775
|
+
protocol=protocol,
|
|
5776
|
+
pool_parameters=pool_parameters,
|
|
5777
|
+
pool_references=pool_references,
|
|
5778
|
+
page_size=page_size,
|
|
5779
|
+
current_page=current_page,
|
|
5780
|
+
)
|
|
5781
|
+
return AgentToolResult(tool=tool_name, ok=True, data=data)
|
|
5782
|
+
|
|
5783
|
+
if tool_name == "get_evm_uniswap_positions":
|
|
5784
|
+
protocol = args.get("protocol", "V3")
|
|
5785
|
+
limit = args.get("limit", 20)
|
|
5786
|
+
if protocol != "V3":
|
|
5787
|
+
raise WalletBackendError("Only V3 position discovery is currently available; V4 PositionManager is not enumerable.")
|
|
5788
|
+
if not isinstance(limit, int) or not 1 <= limit <= 100:
|
|
5789
|
+
raise WalletBackendError("limit must be an integer between 1 and 100.")
|
|
5790
|
+
data = await active_backend.get_uniswap_liquidity_positions(protocol=protocol, limit=limit)
|
|
5791
|
+
return AgentToolResult(tool=tool_name, ok=True, data=data)
|
|
5792
|
+
|
|
5685
5793
|
if tool_name == "swap_evm_uniswap_tokens":
|
|
5686
5794
|
token_in = args.get("token_in")
|
|
5687
5795
|
token_out = args.get("token_out")
|
|
@@ -5826,13 +5934,49 @@ class OpenClawWalletAdapter:
|
|
|
5826
5934
|
raise WalletBackendError("mode must be 'preview', 'prepare' or 'execute'.")
|
|
5827
5935
|
if not isinstance(purpose, str) or not purpose.strip():
|
|
5828
5936
|
raise WalletBackendError("purpose is required.")
|
|
5937
|
+
normalized_request = dict(request)
|
|
5938
|
+
if action == "create":
|
|
5939
|
+
existing_pool = normalized_request.get("existingPool")
|
|
5940
|
+
if not isinstance(existing_pool, dict):
|
|
5941
|
+
raise WalletBackendError(
|
|
5942
|
+
"create requires existingPool with token0Address, token1Address, and poolReference. "
|
|
5943
|
+
"Use get_evm_uniswap_pools to obtain the exact existing pool; do not infer it from market search data."
|
|
5944
|
+
)
|
|
5945
|
+
missing_pool_fields = [
|
|
5946
|
+
field
|
|
5947
|
+
for field in ("token0Address", "token1Address", "poolReference")
|
|
5948
|
+
if not isinstance(existing_pool.get(field), str) or not existing_pool[field].strip()
|
|
5949
|
+
]
|
|
5950
|
+
if missing_pool_fields:
|
|
5951
|
+
raise WalletBackendError(
|
|
5952
|
+
"create existingPool is missing "
|
|
5953
|
+
f"{', '.join(missing_pool_fields)}. Ask the user for the exact existing Uniswap pool; do not infer it from market search data."
|
|
5954
|
+
)
|
|
5955
|
+
elif action in {"increase", "decrease"}:
|
|
5956
|
+
nft_token_id = normalized_request.get("nftTokenId")
|
|
5957
|
+
if not isinstance(nft_token_id, str) or not nft_token_id.strip().isdigit() or int(nft_token_id.strip()) <= 0:
|
|
5958
|
+
raise WalletBackendError(
|
|
5959
|
+
f"{action} requires a positive nftTokenId. Use get_evm_uniswap_positions for V3 or request a verified V4 token id; never infer it."
|
|
5960
|
+
)
|
|
5961
|
+
normalized_request["nftTokenId"] = nft_token_id.strip()
|
|
5962
|
+
else: # claim_fees
|
|
5963
|
+
token_id = normalized_request.get("tokenId", normalized_request.get("nftTokenId"))
|
|
5964
|
+
if not isinstance(token_id, str) or not token_id.strip().isdigit() or int(token_id.strip()) <= 0:
|
|
5965
|
+
raise WalletBackendError(
|
|
5966
|
+
"claim_fees requires the user's positive tokenId (the LP position NFT id). "
|
|
5967
|
+
"Use get_evm_uniswap_positions for V3 or request a verified V4 token id; never infer it."
|
|
5968
|
+
)
|
|
5969
|
+
# The Liquidity API names this field tokenId for fee claims;
|
|
5970
|
+
# accept nftTokenId as the user-facing alias used by other LP actions.
|
|
5971
|
+
normalized_request["tokenId"] = token_id.strip()
|
|
5972
|
+
normalized_request.pop("nftTokenId", None)
|
|
5829
5973
|
# The caller must not override values that are derived from the
|
|
5830
5974
|
# active wallet/network in the WDK layer.
|
|
5831
5975
|
forbidden = {"walletAddress", "chainId", "protocol", "simulateTransaction", "signature", "batchPermitData", "v4BatchPermitData", "v3NftPermitData"}
|
|
5832
5976
|
overlap = forbidden.intersection(request)
|
|
5833
5977
|
if overlap:
|
|
5834
5978
|
raise WalletBackendError(f"request must not set wallet-controlled fields: {', '.join(sorted(overlap))}.")
|
|
5835
|
-
preview_kwargs = {"action": action, "protocol": protocol, "request":
|
|
5979
|
+
preview_kwargs = {"action": action, "protocol": protocol, "request": normalized_request}
|
|
5836
5980
|
if mode == "preview":
|
|
5837
5981
|
preview = await active_backend.preview_uniswap_liquidity(**preview_kwargs)
|
|
5838
5982
|
return AgentToolResult(tool=tool_name, ok=True, data=self._annotate_sensitive_payload(preview, action_label="Uniswap liquidity", mode="preview"))
|
|
@@ -117,6 +117,7 @@ TOKEN_METADATA: dict[str, dict[str, dict[str, Any]]] = {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
COINGECKO_IDS = {
|
|
120
|
+
"BTC": "bitcoin",
|
|
120
121
|
"ETH": "ethereum",
|
|
121
122
|
"WETH": "ethereum",
|
|
122
123
|
"USDC": "usd-coin",
|
|
@@ -142,7 +143,7 @@ _PRICE_CACHE: dict[str, tuple[float, float]] = {}
|
|
|
142
143
|
|
|
143
144
|
def _normalize_network(network: str) -> str:
|
|
144
145
|
normalized = str(network or "").strip().lower()
|
|
145
|
-
if normalized not in {"ethereum", "base", "robinhood"}:
|
|
146
|
+
if normalized not in {"ethereum", "base", "robinhood", "goat"}:
|
|
146
147
|
raise ProviderError("evm-portfolio", f"Unsupported EVM portfolio network: {network}")
|
|
147
148
|
return normalized
|
|
148
149
|
|
|
@@ -207,7 +208,7 @@ async def _gateway_rpc_call(network: str, method: str, params: list[Any]) -> dic
|
|
|
207
208
|
if not gateway_url:
|
|
208
209
|
raise ProviderError(
|
|
209
210
|
"evm-portfolio",
|
|
210
|
-
"Provider gateway URL is required for EVM portfolio lookup on
|
|
211
|
+
"Provider gateway URL is required for EVM portfolio lookup on supported EVM networks.",
|
|
211
212
|
)
|
|
212
213
|
try:
|
|
213
214
|
response = await client.post(
|
|
@@ -230,6 +231,12 @@ async def _gateway_rpc_call(network: str, method: str, params: list[Any]) -> dic
|
|
|
230
231
|
|
|
231
232
|
async def fetch_token_balances(address: str, network: str) -> list[dict[str, Any]]:
|
|
232
233
|
normalized_network = _normalize_network(network)
|
|
234
|
+
# GOAT uses the shared RPC gateway rather than Alchemy. Its upstream does
|
|
235
|
+
# not provide Alchemy's account-wide ERC-20 index, so return a truthful
|
|
236
|
+
# native-only portfolio. Explicit per-token balance and metadata tools keep
|
|
237
|
+
# working through ordinary EVM RPC calls.
|
|
238
|
+
if normalized_network == "goat":
|
|
239
|
+
return []
|
|
233
240
|
cache_key = f"{normalized_network}:{address.lower()}"
|
|
234
241
|
cached = _cache_get_token_balances(cache_key)
|
|
235
242
|
if cached is not None:
|
|
@@ -337,6 +337,25 @@ class AgentWalletBackend(ABC):
|
|
|
337
337
|
) -> dict[str, Any]:
|
|
338
338
|
raise WalletBackendError(f"{self.name} does not support Uniswap pair search.")
|
|
339
339
|
|
|
340
|
+
async def get_uniswap_liquidity_pools(
|
|
341
|
+
self,
|
|
342
|
+
*,
|
|
343
|
+
protocol: str,
|
|
344
|
+
pool_parameters: dict[str, Any] | None = None,
|
|
345
|
+
pool_references: list[dict[str, Any]] | None = None,
|
|
346
|
+
page_size: int = 20,
|
|
347
|
+
current_page: int = 1,
|
|
348
|
+
) -> dict[str, Any]:
|
|
349
|
+
raise WalletBackendError(f"{self.name} does not support Uniswap liquidity pool discovery.")
|
|
350
|
+
|
|
351
|
+
async def get_uniswap_liquidity_positions(
|
|
352
|
+
self,
|
|
353
|
+
*,
|
|
354
|
+
protocol: str = "V3",
|
|
355
|
+
limit: int = 20,
|
|
356
|
+
) -> dict[str, Any]:
|
|
357
|
+
raise WalletBackendError(f"{self.name} does not support Uniswap liquidity position discovery.")
|
|
358
|
+
|
|
340
359
|
async def send_uniswap_swap(
|
|
341
360
|
self,
|
|
342
361
|
*,
|
|
@@ -668,7 +668,7 @@ class WdkEvmLocalWalletBackend(AgentWalletBackend):
|
|
|
668
668
|
"configured_network": self.network,
|
|
669
669
|
"service_active_network": str(data.get("activeNetwork") or "").strip() or None,
|
|
670
670
|
"available_networks": sorted(str(key) for key in profiles.keys()),
|
|
671
|
-
"agent_selectable_networks": ["ethereum", "base", "robinhood"],
|
|
671
|
+
"agent_selectable_networks": ["ethereum", "base", "robinhood", "goat"],
|
|
672
672
|
"swap_supported_networks": ["ethereum", "base", "robinhood"],
|
|
673
673
|
"network_profiles": {
|
|
674
674
|
str(network): {
|
|
@@ -1957,6 +1957,66 @@ class WdkEvmLocalWalletBackend(AgentWalletBackend):
|
|
|
1957
1957
|
"source": "dexscreener",
|
|
1958
1958
|
}
|
|
1959
1959
|
|
|
1960
|
+
async def get_uniswap_liquidity_pools(
|
|
1961
|
+
self,
|
|
1962
|
+
*,
|
|
1963
|
+
protocol: str,
|
|
1964
|
+
pool_parameters: dict[str, Any] | None = None,
|
|
1965
|
+
pool_references: list[dict[str, Any]] | None = None,
|
|
1966
|
+
page_size: int = 20,
|
|
1967
|
+
current_page: int = 1,
|
|
1968
|
+
) -> dict[str, Any]:
|
|
1969
|
+
if protocol not in {"V3", "V4"}:
|
|
1970
|
+
raise WalletBackendError("protocol must be V3 or V4.")
|
|
1971
|
+
if (pool_parameters is None) == (pool_references is None):
|
|
1972
|
+
raise WalletBackendError("Provide exactly one of pool_parameters or pool_references.")
|
|
1973
|
+
if pool_parameters is not None and not isinstance(pool_parameters, dict):
|
|
1974
|
+
raise WalletBackendError("pool_parameters must be an object.")
|
|
1975
|
+
if pool_references is not None:
|
|
1976
|
+
if not isinstance(pool_references, list) or not 1 <= len(pool_references) <= 20:
|
|
1977
|
+
raise WalletBackendError("pool_references must contain between 1 and 20 objects.")
|
|
1978
|
+
if any(not isinstance(reference, dict) for reference in pool_references):
|
|
1979
|
+
raise WalletBackendError("pool_references must contain only objects.")
|
|
1980
|
+
if not isinstance(page_size, int) or not 1 <= page_size <= 20:
|
|
1981
|
+
raise WalletBackendError("page_size must be an integer between 1 and 20.")
|
|
1982
|
+
if not isinstance(current_page, int) or current_page < 1:
|
|
1983
|
+
raise WalletBackendError("current_page must be a positive integer.")
|
|
1984
|
+
body: dict[str, Any] = {
|
|
1985
|
+
"network": self.network,
|
|
1986
|
+
"protocol": protocol,
|
|
1987
|
+
"pageSize": page_size,
|
|
1988
|
+
"currentPage": current_page,
|
|
1989
|
+
}
|
|
1990
|
+
if pool_parameters is not None:
|
|
1991
|
+
body["poolParameters"] = pool_parameters
|
|
1992
|
+
else:
|
|
1993
|
+
body["poolReferences"] = pool_references
|
|
1994
|
+
data = await self.client.post("/v1/evm/uniswap/liquidity/pools", body)
|
|
1995
|
+
return dict(data)
|
|
1996
|
+
|
|
1997
|
+
async def get_uniswap_liquidity_positions(
|
|
1998
|
+
self,
|
|
1999
|
+
*,
|
|
2000
|
+
protocol: str = "V3",
|
|
2001
|
+
limit: int = 20,
|
|
2002
|
+
) -> dict[str, Any]:
|
|
2003
|
+
if protocol not in {"V3", "V4"}:
|
|
2004
|
+
raise WalletBackendError("protocol must be V3 or V4.")
|
|
2005
|
+
if not isinstance(limit, int) or not 1 <= limit <= 100:
|
|
2006
|
+
raise WalletBackendError("limit must be an integer between 1 and 100.")
|
|
2007
|
+
data = await self.client.post(
|
|
2008
|
+
"/v1/evm/uniswap/liquidity/positions",
|
|
2009
|
+
{
|
|
2010
|
+
"walletId": self.wallet_id,
|
|
2011
|
+
"address": await self.get_address(),
|
|
2012
|
+
"accountIndex": self.account_index,
|
|
2013
|
+
"network": self.network,
|
|
2014
|
+
"protocol": protocol,
|
|
2015
|
+
"limit": limit,
|
|
2016
|
+
},
|
|
2017
|
+
)
|
|
2018
|
+
return dict(data)
|
|
2019
|
+
|
|
1960
2020
|
async def send_uniswap_swap(
|
|
1961
2021
|
self,
|
|
1962
2022
|
*,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "agent-wallet",
|
|
3
3
|
"name": "Agent Wallet",
|
|
4
4
|
"description": "Plugin-friendly wallet backend for OpenClaw agents with safe wallet tools and runtime instructions across Solana, local BTC, and local EVM.",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.96",
|
|
6
6
|
"skills": ["skills/wallet-operator"],
|
|
7
7
|
"configSchema": {
|
|
8
8
|
"type": "object",
|
|
@@ -513,6 +513,26 @@ def _pip_install_editable(python_bin: Path, package_root: Path) -> None:
|
|
|
513
513
|
)
|
|
514
514
|
|
|
515
515
|
|
|
516
|
+
def _python_runtime_is_healthy(python_bin: Path) -> bool:
|
|
517
|
+
"""Return whether a reusable venv has the installer dependencies available.
|
|
518
|
+
|
|
519
|
+
A shared runtime can be left half-created when an install is interrupted
|
|
520
|
+
between ``venv`` creation and the editable pip install. The interpreter
|
|
521
|
+
itself then exists, but the next update must repair it rather than treating
|
|
522
|
+
it as a valid cache entry.
|
|
523
|
+
"""
|
|
524
|
+
try:
|
|
525
|
+
result = subprocess.run(
|
|
526
|
+
[str(python_bin), "-c", "import pydantic, pydantic_settings"],
|
|
527
|
+
capture_output=True,
|
|
528
|
+
text=True,
|
|
529
|
+
check=False,
|
|
530
|
+
)
|
|
531
|
+
except OSError:
|
|
532
|
+
return False
|
|
533
|
+
return result.returncode == 0
|
|
534
|
+
|
|
535
|
+
|
|
516
536
|
def _ensure_python_runtime(
|
|
517
537
|
venv_path: Path,
|
|
518
538
|
package_root: Path,
|
|
@@ -529,9 +549,17 @@ def _ensure_python_runtime(
|
|
|
529
549
|
created = True
|
|
530
550
|
_bootstrap_venv_pip(python_bin)
|
|
531
551
|
_pip_install_editable(python_bin, package_root)
|
|
552
|
+
elif not _python_runtime_is_healthy(python_bin):
|
|
553
|
+
# A prior update may have been interrupted after creating the venv
|
|
554
|
+
# but before pip finished. Repair the cache in place; release links
|
|
555
|
+
# continue to point at one verified shared environment.
|
|
556
|
+
_bootstrap_venv_pip(python_bin)
|
|
557
|
+
_pip_install_editable(python_bin, package_root)
|
|
558
|
+
plan["action"] = "repair"
|
|
532
559
|
shared_wrapper = _ensure_python_wrapper(shared_venv_path)
|
|
533
560
|
_replace_with_directory_symlink(venv_path, shared_venv_path)
|
|
534
|
-
|
|
561
|
+
if created:
|
|
562
|
+
plan["action"] = "create"
|
|
535
563
|
plan["exists"] = True
|
|
536
564
|
return (
|
|
537
565
|
venv_path / shared_wrapper.relative_to(shared_venv_path),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-wallet",
|
|
3
3
|
"displayName": "Agent Wallet",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.96",
|
|
5
5
|
"description": "Claude Code bridge for the existing AgentLayer wallet runtime. Connects to Solana, Bitcoin, and EVM wallets without creating a new one.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "AgentLayer"
|