@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.
Files changed (35) hide show
  1. package/.openclaw/extensions/agent-wallet/README.md +2 -2
  2. package/.openclaw/extensions/agent-wallet/dist/index.js +70 -14
  3. package/.openclaw/extensions/agent-wallet/index.ts +70 -14
  4. package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +4 -2
  5. package/.openclaw/extensions/agent-wallet/package.json +1 -1
  6. package/VERSION +1 -1
  7. package/agent-wallet/README.md +3 -3
  8. package/agent-wallet/agent_wallet/__init__.py +1 -1
  9. package/agent-wallet/agent_wallet/autonomous_policy.py +2 -1
  10. package/agent-wallet/agent_wallet/config.py +7 -11
  11. package/agent-wallet/agent_wallet/networks.py +25 -0
  12. package/agent-wallet/agent_wallet/openclaw_adapter.py +171 -27
  13. package/agent-wallet/agent_wallet/providers/evm_portfolio.py +9 -2
  14. package/agent-wallet/agent_wallet/wallet_layer/base.py +19 -0
  15. package/agent-wallet/agent_wallet/wallet_layer/wdk_evm.py +61 -1
  16. package/agent-wallet/openclaw.plugin.json +1 -1
  17. package/agent-wallet/pyproject.toml +1 -1
  18. package/agent-wallet/scripts/install_agent_wallet.py +29 -1
  19. package/claude-code/plugins/agent-wallet/.claude-plugin/plugin.json +1 -1
  20. package/claude-code/plugins/agent-wallet/AGENTLAYER_AGENT_GUIDE.md +282 -0
  21. package/claude-code/plugins/agent-wallet/README.md +2 -0
  22. package/claude-code/plugins/agent-wallet/commands/guide.md +40 -0
  23. package/codex/plugins/agent-wallet/.codex-plugin/plugin.json +1 -1
  24. package/codex/plugins/agent-wallet/server.py +15 -11
  25. package/codex/plugins/agent-wallet/skills/wallet-operator/SKILL.md +1 -1
  26. package/hermes/plugins/agent_wallet/plugin.yaml +1 -1
  27. package/package.json +1 -1
  28. package/wdk-btc-wallet/package.json +1 -1
  29. package/wdk-evm-wallet/.env.example +4 -1
  30. package/wdk-evm-wallet/README.md +18 -2
  31. package/wdk-evm-wallet/package.json +1 -1
  32. package/wdk-evm-wallet/src/config.js +34 -3
  33. package/wdk-evm-wallet/src/network_state.js +8 -2
  34. package/wdk-evm-wallet/src/server.js +12 -0
  35. 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 {"ethereum", "base", "robinhood", "eip155:1", "eip155:8453", "eip155:4663"}
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
- aliases = {
97
- "mainnet": "ethereum",
98
- "eth": "ethereum",
99
- "eth-mainnet": "ethereum",
100
- "base-mainnet": "base",
101
- }
102
- network = aliases.get(network, network)
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 robinhood instead "
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="Get the transaction receipt for a broadcast EVM transaction hash.",
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": dict(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 ethereum/base/robinhood.",
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.94",
5
+ "version": "0.1.96",
6
6
  "skills": ["skills/wallet-operator"],
7
7
  "configSchema": {
8
8
  "type": "object",
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "openclaw-agent-wallet"
7
- version = "0.1.94"
7
+ version = "0.1.96"
8
8
  description = "Plugin-friendly wallet backend for OpenClaw agents"
9
9
  requires-python = ">=3.10"
10
10
  dependencies = [
@@ -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
- plan["action"] = "create" if created else "reuse"
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.94",
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"