@agentlayer.tech/wallet 0.1.88 → 0.1.90

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 (26) hide show
  1. package/.openclaw/extensions/agent-wallet/dist/index.js +5 -5
  2. package/.openclaw/extensions/agent-wallet/index.ts +5 -5
  3. package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +2 -1
  4. package/.openclaw/extensions/agent-wallet/package.json +1 -1
  5. package/VERSION +1 -1
  6. package/agent-wallet/agent_wallet/__init__.py +1 -1
  7. package/agent-wallet/agent_wallet/autonomous_permissions.py +43 -1
  8. package/agent-wallet/agent_wallet/openclaw_adapter.py +696 -375
  9. package/agent-wallet/agent_wallet/providers/dexscreener.py +145 -0
  10. package/agent-wallet/agent_wallet/providers/x402.py +84 -0
  11. package/agent-wallet/agent_wallet/wallet_layer/base.py +12 -0
  12. package/agent-wallet/agent_wallet/wallet_layer/wdk_evm.py +38 -1
  13. package/agent-wallet/openclaw.plugin.json +1 -1
  14. package/agent-wallet/pyproject.toml +1 -1
  15. package/claude-code/plugins/agent-wallet/.claude-plugin/plugin.json +1 -1
  16. package/claude-code/plugins/agent-wallet/README.md +3 -1
  17. package/claude-code/plugins/agent-wallet/commands/agentlayer-autonomous-approve.md +7 -8
  18. package/claude-code/plugins/agent-wallet/commands/agentlayer-autonomous-revoke.md +2 -2
  19. package/claude-code/plugins/agent-wallet/commands/wallet-base.md +38 -15
  20. package/codex/plugins/agent-wallet/.codex-plugin/plugin.json +1 -1
  21. package/codex/plugins/agent-wallet/README.md +11 -6
  22. package/codex/plugins/agent-wallet/skills/wallet-base/SKILL.md +56 -0
  23. package/hermes/plugins/agent_wallet/plugin.yaml +1 -1
  24. package/package.json +1 -1
  25. package/wdk-btc-wallet/package.json +1 -1
  26. package/wdk-evm-wallet/package.json +1 -1
@@ -0,0 +1,145 @@
1
+ """DexScreener provider for onchain token/pair discovery (price, volume, liquidity).
2
+
3
+ Public API, no key required: https://docs.dexscreener.com/api/reference. Used to
4
+ answer "which pairs exist for this token/ticker" the way app.uniswap.org's own
5
+ search box does, since the Uniswap Trading API itself only exposes quote/order/swap
6
+ for a pair you already know, not a search-by-name/address endpoint.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import time
12
+ from typing import Any
13
+
14
+ from agent_wallet.exceptions import ProviderError
15
+ from agent_wallet.http_client import get_client
16
+
17
+ DEXSCREENER_BASE_URL = "https://api.dexscreener.com"
18
+
19
+ # DexScreener's own responses are served with `cache-control: public, max-age=30`;
20
+ # mirroring that TTL avoids redundant lookups within a session without serving
21
+ # data staler than DexScreener itself would.
22
+ _CACHE_TTL_SECONDS = 30
23
+ _CACHE_MAX_ENTRIES = 128
24
+ _cache: dict[str, tuple[float, list[dict[str, Any]]]] = {}
25
+
26
+
27
+ def _cache_get(key: str) -> list[dict[str, Any]] | None:
28
+ entry = _cache.get(key)
29
+ if entry is None:
30
+ return None
31
+ stored_at, pairs = entry
32
+ if time.monotonic() - stored_at > _CACHE_TTL_SECONDS:
33
+ _cache.pop(key, None)
34
+ return None
35
+ return pairs
36
+
37
+
38
+ def _cache_set(key: str, pairs: list[dict[str, Any]]) -> None:
39
+ if len(_cache) >= _CACHE_MAX_ENTRIES and key not in _cache:
40
+ oldest_key = min(_cache, key=lambda existing: _cache[existing][0])
41
+ _cache.pop(oldest_key, None)
42
+ _cache[key] = (time.monotonic(), pairs)
43
+
44
+
45
+ async def _get(path: str, *, params: dict[str, Any] | None = None) -> Any:
46
+ client = get_client()
47
+ response = await client.get(f"{DEXSCREENER_BASE_URL}{path}", params=params)
48
+ if response.status_code != 200:
49
+ raise ProviderError("dexscreener", f"HTTP {response.status_code}: {response.text[:300]}")
50
+ try:
51
+ return response.json()
52
+ except ValueError as exc:
53
+ raise ProviderError("dexscreener", "Unexpected non-JSON response from DexScreener.") from exc
54
+
55
+
56
+ def _extract_pairs(data: Any) -> list[dict[str, Any]]:
57
+ if isinstance(data, dict):
58
+ pairs = data.get("pairs")
59
+ return pairs if isinstance(pairs, list) else []
60
+ if isinstance(data, list):
61
+ return [item for item in data if isinstance(item, dict)]
62
+ return []
63
+
64
+
65
+ async def search_pairs(query: str) -> list[dict[str, Any]]:
66
+ """Free-text search across every chain/DEX DexScreener indexes."""
67
+ query = query.strip()
68
+ if not query:
69
+ raise ProviderError("dexscreener", "query must not be empty.")
70
+ cache_key = f"search:{query.lower()}"
71
+ cached = _cache_get(cache_key)
72
+ if cached is not None:
73
+ return cached
74
+ data = await _get("/latest/dex/search", params={"q": query})
75
+ pairs = _extract_pairs(data)
76
+ _cache_set(cache_key, pairs)
77
+ return pairs
78
+
79
+
80
+ async def get_pairs_for_token(*, chain: str, token_address: str) -> list[dict[str, Any]]:
81
+ """All pairs for a single token address on one chain, across every DEX on it."""
82
+ chain = chain.strip().lower()
83
+ token_address = token_address.strip()
84
+ if not chain:
85
+ raise ProviderError("dexscreener", "chain must not be empty.")
86
+ if not token_address:
87
+ raise ProviderError("dexscreener", "token_address must not be empty.")
88
+ cache_key = f"token:{chain}:{token_address.lower()}"
89
+ cached = _cache_get(cache_key)
90
+ if cached is not None:
91
+ return cached
92
+ data = await _get(f"/tokens/v1/{chain}/{token_address}")
93
+ pairs = _extract_pairs(data)
94
+ _cache_set(cache_key, pairs)
95
+ return pairs
96
+
97
+
98
+ def normalize_pair(pair: dict[str, Any]) -> dict[str, Any]:
99
+ """Flatten a DexScreener pair payload into the shape handed back to the agent."""
100
+ base_token = pair.get("baseToken") or {}
101
+ quote_token = pair.get("quoteToken") or {}
102
+ volume = pair.get("volume") or {}
103
+ liquidity = pair.get("liquidity") or {}
104
+ price_change = pair.get("priceChange") or {}
105
+ txns = pair.get("txns") or {}
106
+ return {
107
+ "chain_id": pair.get("chainId"),
108
+ "dex_id": pair.get("dexId"),
109
+ "pair_address": pair.get("pairAddress"),
110
+ "url": pair.get("url"),
111
+ "labels": pair.get("labels") or [],
112
+ "base_token": {
113
+ "address": base_token.get("address"),
114
+ "name": base_token.get("name"),
115
+ "symbol": base_token.get("symbol"),
116
+ },
117
+ "quote_token": {
118
+ "address": quote_token.get("address"),
119
+ "name": quote_token.get("name"),
120
+ "symbol": quote_token.get("symbol"),
121
+ },
122
+ "price_usd": pair.get("priceUsd"),
123
+ "price_native": pair.get("priceNative"),
124
+ "price_change_pct": {
125
+ "m5": price_change.get("m5"),
126
+ "h1": price_change.get("h1"),
127
+ "h6": price_change.get("h6"),
128
+ "h24": price_change.get("h24"),
129
+ },
130
+ "volume_usd": {
131
+ "m5": volume.get("m5"),
132
+ "h1": volume.get("h1"),
133
+ "h6": volume.get("h6"),
134
+ "h24": volume.get("h24"),
135
+ },
136
+ "txns": {
137
+ window: {"buys": counts.get("buys"), "sells": counts.get("sells")}
138
+ for window, counts in txns.items()
139
+ if isinstance(counts, dict)
140
+ },
141
+ "liquidity_usd": liquidity.get("usd"),
142
+ "fdv_usd": pair.get("fdv"),
143
+ "market_cap_usd": pair.get("marketCap"),
144
+ "pair_created_at": pair.get("pairCreatedAt"),
145
+ }
@@ -1600,6 +1600,90 @@ def _reusable_approved_preview(
1600
1600
  return dict(approved_preview)
1601
1601
 
1602
1602
 
1603
+ #: USD amount below which an x402 payment never requires host/session/
1604
+ #: permission approval, regardless of network (including mainnet) -- the
1605
+ #: same rationale as in-person card payments skipping a signature/PIN below
1606
+ #: a floor limit. Only ever applies when the payment asset is confidently
1607
+ #: identified as USDC (see _looks_like_usdc); any other asset always
1608
+ #: requires approval, since its USD value can't be determined here.
1609
+ DE_MINIMIS_USD_THRESHOLD = 2.0
1610
+
1611
+
1612
+ def de_minimis_usd_amount(preview: dict[str, Any]) -> float | None:
1613
+ """Return *preview*'s payment amount in USD, or None if it isn't confidently USDC.
1614
+
1615
+ ``x402_amount_display`` is only populated by normalize_payment_requirement
1616
+ when the asset is recognized as USDC (see _looks_like_usdc) -- for any
1617
+ other asset it's None, since its USD value is unknown here.
1618
+ """
1619
+ amount_display = preview.get("x402_amount_display")
1620
+ if not isinstance(amount_display, str) or not amount_display.strip():
1621
+ return None
1622
+ try:
1623
+ return float(amount_display)
1624
+ except ValueError:
1625
+ return None
1626
+
1627
+
1628
+ def is_de_minimis_payment(
1629
+ preview: dict[str, Any],
1630
+ *,
1631
+ threshold_usd: float | None = None,
1632
+ ) -> bool:
1633
+ """Whether *preview*'s payment is small enough to skip approval entirely.
1634
+
1635
+ threshold_usd defaults to the current DE_MINIMIS_USD_THRESHOLD, read at
1636
+ call time (not bound at import time) so tests can monkeypatch the module
1637
+ attribute directly.
1638
+ """
1639
+ usd_amount = de_minimis_usd_amount(preview)
1640
+ if usd_amount is None:
1641
+ return False
1642
+ effective_threshold = DE_MINIMIS_USD_THRESHOLD if threshold_usd is None else threshold_usd
1643
+ return usd_amount < effective_threshold
1644
+
1645
+
1646
+ async def resolve_payment_preview(
1647
+ *,
1648
+ backend: AgentWalletBackend,
1649
+ url: str,
1650
+ method: str = "GET",
1651
+ headers: dict[str, Any] | None = None,
1652
+ query: dict[str, Any] | None = None,
1653
+ json_body: Any | None = None,
1654
+ text_body: str | None = None,
1655
+ approved_preview: dict[str, Any] | None = None,
1656
+ ) -> dict[str, Any]:
1657
+ """Return the payment preview for this exact request.
1658
+
1659
+ Reuses *approved_preview* when it still matches (same fingerprint check
1660
+ ``pay_and_fetch`` applies before deciding whether to skip a fresh probe),
1661
+ otherwise makes a fresh unpaid probe. Callers that need a summary to bind
1662
+ an approval token to -- before paying -- can call this instead of
1663
+ duplicating the reuse-or-probe logic ``pay_and_fetch`` already has.
1664
+ """
1665
+ request = _build_request_metadata(
1666
+ url=url,
1667
+ method=method,
1668
+ headers=headers,
1669
+ query=query,
1670
+ json_body=json_body,
1671
+ text_body=text_body,
1672
+ )
1673
+ reused = _reusable_approved_preview(approved_preview, request=request)
1674
+ if reused is not None:
1675
+ return reused
1676
+ return await preview_request(
1677
+ backend=backend,
1678
+ url=url,
1679
+ method=method,
1680
+ headers=headers,
1681
+ query=query,
1682
+ json_body=json_body,
1683
+ text_body=text_body,
1684
+ )
1685
+
1686
+
1603
1687
  async def pay_and_fetch(
1604
1688
  *,
1605
1689
  backend: AgentWalletBackend,
@@ -325,6 +325,18 @@ class AgentWalletBackend(ABC):
325
325
  ) -> dict[str, Any]:
326
326
  raise WalletBackendError(f"{self.name} does not support Uniswap swap previews.")
327
327
 
328
+ async def search_uniswap_pairs(
329
+ self,
330
+ *,
331
+ query: str | None = None,
332
+ token_address: str | None = None,
333
+ chain: str | None = None,
334
+ dex_id: str | None = None,
335
+ all_chains: bool = False,
336
+ limit: int = 10,
337
+ ) -> dict[str, Any]:
338
+ raise WalletBackendError(f"{self.name} does not support Uniswap pair search.")
339
+
328
340
  async def send_uniswap_swap(
329
341
  self,
330
342
  *,
@@ -7,7 +7,7 @@ from typing import Any
7
7
 
8
8
  from agent_wallet.config import normalize_evm_network
9
9
  from agent_wallet.providers.evm_portfolio import build_portfolio_snapshot
10
- from agent_wallet.providers import lifi
10
+ from agent_wallet.providers import dexscreener, lifi
11
11
  from agent_wallet.providers.wdk_evm_local import WdkEvmLocalClient
12
12
  from agent_wallet.wallet_layer.base import AgentWalletBackend, WalletBackendError, WalletCapabilities
13
13
 
@@ -1920,6 +1920,43 @@ class WdkEvmLocalWalletBackend(AgentWalletBackend):
1920
1920
  "swap_transaction": swap_transaction,
1921
1921
  }
1922
1922
 
1923
+ async def search_uniswap_pairs(
1924
+ self,
1925
+ *,
1926
+ query: str | None = None,
1927
+ token_address: str | None = None,
1928
+ chain: str | None = None,
1929
+ dex_id: str | None = None,
1930
+ all_chains: bool = False,
1931
+ limit: int = 10,
1932
+ ) -> dict[str, Any]:
1933
+ query = query.strip() if isinstance(query, str) else None
1934
+ token_address = token_address.strip() if isinstance(token_address, str) else None
1935
+ if not query and not token_address:
1936
+ raise WalletBackendError("Either query or token_address is required.")
1937
+ resolved_chain = (chain or self.network).strip().lower()
1938
+ if token_address:
1939
+ pairs = await dexscreener.get_pairs_for_token(chain=resolved_chain, token_address=token_address)
1940
+ else:
1941
+ pairs = await dexscreener.search_pairs(query)
1942
+ if not all_chains:
1943
+ pairs = [p for p in pairs if str(p.get("chainId") or "").lower() == resolved_chain]
1944
+ if dex_id:
1945
+ wanted_dex = dex_id.strip().lower()
1946
+ pairs = [p for p in pairs if str(p.get("dexId") or "").lower() == wanted_dex]
1947
+ bounded_limit = max(1, min(int(limit or 10), 30))
1948
+ normalized = [dexscreener.normalize_pair(pair) for pair in pairs[:bounded_limit]]
1949
+ return {
1950
+ "chain": None if all_chains else resolved_chain,
1951
+ "all_chains": all_chains,
1952
+ "query": query,
1953
+ "token_address": token_address,
1954
+ "dex_id": dex_id,
1955
+ "count": len(normalized),
1956
+ "pairs": normalized,
1957
+ "source": "dexscreener",
1958
+ }
1959
+
1923
1960
  async def send_uniswap_swap(
1924
1961
  self,
1925
1962
  *,
@@ -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.88",
5
+ "version": "0.1.90",
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.88"
7
+ version = "0.1.90"
8
8
  description = "Plugin-friendly wallet backend for OpenClaw agents"
9
9
  requires-python = ">=3.10"
10
10
  dependencies = [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agent-wallet",
3
3
  "displayName": "Agent Wallet",
4
- "version": "0.1.88",
4
+ "version": "0.1.90",
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"
@@ -54,7 +54,9 @@ no-op once the backend is healthy.
54
54
  - `/wallet-sol` — print the current Solana wallet overview directly in chat.
55
55
  - `/wallet-evm` — print the wallet overview for the current/default EVM network
56
56
  directly in chat.
57
- - `/wallet-base` — print the Base EVM wallet overview directly in chat.
57
+ - `/wallet-base` — print the Base EVM wallet overview directly in chat and
58
+ switch the session's active wallet backend to Base so follow-up wallet
59
+ requests default to it.
58
60
  - `/wallet-ethereum` — print the Ethereum EVM wallet overview directly in chat.
59
61
  - `/agentlayer-autonomous-approve` — enable high-trust autonomous Base swaps
60
62
  (`swap_evm_tokens` / `swap_evm_uniswap_tokens` on Base only) without
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: Enable AgentLayer autonomous Base swaps and EVM DeFi tools without per-transaction approvals.
2
+ description: Enable AgentLayer autonomous wallet execution (every write tool) without per-transaction approvals.
3
3
  allowed-tools: AskUserQuestion, mcp__agent_wallet__agentlayer_autonomous_approve, mcp__agent_wallet__agentlayer_autonomous_status
4
4
  disable-model-invocation: true
5
5
  ---
@@ -13,17 +13,17 @@ If both `base_swaps` and `defi_tools` are already enabled, report that the combi
13
13
  Otherwise, use `AskUserQuestion` to confirm:
14
14
 
15
15
  - header: `Autonomy`
16
- - question: `Enable AgentLayer autonomous Base swaps and EVM DeFi tools without per-transaction approvals?`
16
+ - question: `Enable AgentLayer autonomous execution for every wallet write tool (transfers, swaps, bridges, staking, x402 payments, generic contract calls, DeFi management) without per-transaction approvals, with no spend cap?`
17
17
  - options:
18
- - `Enable` — `Turns on the combined autonomous permission group until revoked.`
18
+ - `Enable` — `Turns on unbounded autonomous execution for every wallet write tool until revoked.`
19
19
  - `Cancel` — `Leaves per-transaction approvals in place and does not change permissions.`
20
20
 
21
21
  Only if the user selects `Enable`, call `agentlayer_autonomous_approve` with:
22
22
 
23
23
  ```json
24
24
  {
25
- "scope": "base_swaps",
26
- "purpose": "User requested autonomous Base swaps and EVM DeFi tools from Claude Code.",
25
+ "scope": "all",
26
+ "purpose": "User requested autonomous wallet execution from Claude Code.",
27
27
  "user_intent": true
28
28
  }
29
29
  ```
@@ -34,7 +34,6 @@ If the user selects `Cancel`, do not call any write tool. State that autonomous
34
34
 
35
35
  Be explicit in the response:
36
36
 
37
- - This removes per-transaction approvals for Base Velora/Uniswap swap execute calls and supported EVM DeFi management tools.
38
- - The `scope=base_swaps` argument is a compatibility value; this command enables the combined autonomous permission group.
39
- - It does not authorize transfers, bridges, Solana swaps, or generic contract calls.
37
+ - This removes per-transaction approvals for every wallet write tool: transfers, bridges, Solana swaps, staking, x402 payments, generic contract calls, Base Velora/Uniswap swap execute calls, and supported EVM DeFi management tools.
38
+ - This command enables the combined autonomous permission group, which has no per-tool allow-list, spend cap, or session TTL -- it is unbounded by amount within its scope.
40
39
  - The user can run `/agentlayer-autonomous-revoke` to disable it.
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: Disable AgentLayer autonomous Base swaps and EVM DeFi tools.
2
+ description: Disable AgentLayer autonomous wallet execution (every write tool).
3
3
  allowed-tools: mcp__agent_wallet__agentlayer_autonomous_revoke, mcp__agent_wallet__agentlayer_autonomous_status
4
4
  disable-model-invocation: true
5
5
  ---
@@ -10,7 +10,7 @@ Call `agentlayer_autonomous_revoke` with:
10
10
 
11
11
  ```json
12
12
  {
13
- "scope": "base_swaps"
13
+ "scope": "all"
14
14
  }
15
15
  ```
16
16
 
@@ -1,37 +1,60 @@
1
1
  ---
2
- description: Show the connected EVM wallet overview for Base directly in chat.
3
- allowed-tools: mcp__agent_wallet__get_wallet_overview
2
+ description: Show the connected Base EVM wallet portfolio directly in chat and switch the active wallet backend to Base.
3
+ allowed-tools: mcp__agent_wallet__get_wallet_overview, mcp__agent_wallet__set_wallet_backend
4
4
  disable-model-invocation: true
5
5
  ---
6
6
 
7
- Show the connected EVM wallet overview for Base directly in chat.
7
+ Show the connected Base EVM wallet portfolio directly in chat, and leave the
8
+ session's active wallet backend switched to Base so follow-up wallet calls in
9
+ this conversation don't need the backend specified again.
8
10
 
9
- 1. Call `get_wallet_overview` with:
11
+ 1. Call `get_wallet_overview` and `set_wallet_backend` together — they are
12
+ independent, so issue both in the same turn rather than sequentially:
10
13
 
11
14
  ```json
15
+ // get_wallet_overview
12
16
  {
13
17
  "backend": "evm",
14
18
  "network": "base"
15
19
  }
16
20
  ```
17
21
 
18
- 2. Format the response as a compact wallet report:
22
+ ```json
23
+ // set_wallet_backend
24
+ {
25
+ "backend": "base",
26
+ "network": "base"
27
+ }
28
+ ```
29
+
30
+ 2. Format the `get_wallet_overview` response as a compact wallet report:
19
31
 
20
32
  - state the wallet as `EVM (Base)`
21
- - include `chain`, `network` (or `requested_network`), `address`, and `total_value_usd` when present
33
+ - include `chain`, `network` (or `requested_network` when `network` is absent), `address`, and `total_value_usd` when present
22
34
  - render a Markdown table with columns: `Asset | Type | Amount | USD Value`
23
35
  - use `assets` when present
24
36
  - for each asset row, prefer:
25
- - asset label: `symbol`, then `token_address`, then `asset_type`
37
+ - asset label: `symbol`, then `name`, then `token_address`, then `asset_type`
38
+ - when the label came from `symbol`/`name` (not the token_address itself) and a `token_address` is present, append it shortened in parentheses next to the label as `first6…last4` (e.g. `USDC (0x833589…029139)`) — keep the contract visible but compact, never show it twice
26
39
  - amount: `amount_ui`, then `balance_ui`, then `balance_native`, then `amount_raw`
27
40
  - usd value: `value_usd`, then `balance_usd`
28
41
  - omit zero-value rows only when both the amount and USD value are clearly zero
29
42
  - if no asset rows are available, still report the native balance summary in prose
30
-
31
- 3. After the table, add one short metadata line with any available sources:
32
-
33
- - `source`
34
- - `token_discovery_source`
35
- - `pricing_source`
36
-
37
- 4. Do not suggest transfers, swaps, or other write actions unless the user explicitly asks for them.
43
+ - do not include source metadata lines or footer fields such as `source`, `token_discovery_source`, or `pricing_source`
44
+
45
+ 3. Assets with no `symbol`/`name` and no `price_usd` are unverified contracts
46
+ (commonly spam or airdropped tokens on Base). Do not list them individually
47
+ in the table — instead add one short line after the table noting how many
48
+ were omitted, e.g. "Plus 46 unverified ERC-20 contracts with no symbol or
49
+ price data (not included in the total)."
50
+
51
+ 4. After the table, add one short line confirming the session wallet backend
52
+ is now Base so the user knows follow-up wallet requests will target it by
53
+ default. If `set_wallet_backend` failed, add one short warning line instead
54
+ (e.g. a stale local EVM daemon holding its port after a plugin upgrade) —
55
+ note that the portfolio above is still accurate, and that the backend
56
+ switch can be retried. Never drop the portfolio report because the switch
57
+ failed.
58
+
59
+ 5. Do not suggest transfers, swaps, bridging, or other write actions unless
60
+ the user explicitly asks for them.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-wallet",
3
- "version": "0.1.88",
3
+ "version": "0.1.90",
4
4
  "description": "Codex plugin bridge for the AgentLayer wallet runtime.",
5
5
  "author": {
6
6
  "name": "AgentLayer"
@@ -22,7 +22,8 @@ Primary design rules:
22
22
  - EVM network selection with `set_evm_network`
23
23
  - auto-managed approval binding for `preview -> execute` write flows
24
24
  - bundled Codex skills, including `wallet-sol` for showing the Solana wallet
25
- portfolio directly in chat
25
+ portfolio directly in chat and `wallet-base` for showing the Base EVM
26
+ wallet portfolio and switching the session's active backend to Base
26
27
 
27
28
  ## Runtime requirements
28
29
 
@@ -30,12 +31,16 @@ Primary design rules:
30
31
  - keep the local wallet files and `~/.openclaw/sealed_keys.json` in place
31
32
  - use `wallet codex install --yes` to install this plugin into Codex
32
33
 
33
- ## Bundled skill
34
+ ## Bundled skills
34
35
 
35
- After `wallet codex install --yes` and a Codex restart, the plugin ships a
36
- bundled `wallet-sol` skill. In Codex you can invoke it from the slash menu or
37
- explicitly as `$wallet-sol` to render the connected Solana wallet portfolio as a
38
- compact chat table.
36
+ After `wallet codex install --yes` and a Codex restart, the plugin ships
37
+ bundled skills:
38
+
39
+ - `wallet-sol` — invoke from the slash menu or explicitly as `$wallet-sol`
40
+ to render the connected Solana wallet portfolio as a compact chat table.
41
+ - `wallet-base` — invoke from the slash menu or explicitly as `$wallet-base`
42
+ to render the connected Base EVM wallet portfolio as a compact chat table
43
+ and switch the session's active wallet backend to Base.
39
44
 
40
45
  ## Path resolution
41
46
 
@@ -0,0 +1,56 @@
1
+ ---
2
+ name: "wallet-base"
3
+ description: "Show the current Base EVM wallet portfolio from the local AgentLayer wallet and switch the session's active wallet backend to Base. Use when the user asks for /wallet-base or wants their Base wallet shown directly in chat."
4
+ ---
5
+
6
+ # Base EVM Wallet Portfolio
7
+
8
+ Use the local AgentLayer wallet MCP tools only.
9
+
10
+ Workflow:
11
+
12
+ 1. Call `get_wallet_overview` with `backend=evm, network=base` and
13
+ `set_wallet_backend` with `backend=base, network=base` — they are
14
+ independent, so issue both in the same turn rather than sequentially.
15
+ 2. Render the `get_wallet_overview` result directly in chat in this shape:
16
+ - title: `EVM (Base) Wallet Portfolio`
17
+ - bullets for `Chain`, `Network` (or `requested_network` when `network`
18
+ is absent), `Address`, and `Total Value (USD)` when available
19
+ - one compact Markdown table with columns: `Asset | Type | Amount | USD Value`
20
+
21
+ Formatting rules:
22
+
23
+ - Use `assets` when present.
24
+ - For the asset label, prefer `symbol`, then `name`, then `token_address`,
25
+ then `asset_type`.
26
+ - When the label came from `symbol`/`name` (not the token_address itself)
27
+ and a `token_address` is present, append it shortened in parentheses next
28
+ to the label as `first6…last4` (e.g. `USDC (0x833589…029139)`) — keep the
29
+ contract visible but compact, never show it twice.
30
+ - For the amount, prefer `amount_ui`, then `balance_ui`, then
31
+ `balance_native`, then `amount_raw`.
32
+ - For the USD value, prefer `value_usd`, then `balance_usd`.
33
+ - Omit zero-value rows only when both the amount and USD value are clearly
34
+ zero. If no asset rows are available, still report the native balance
35
+ summary in prose.
36
+ - Assets with no `symbol`/`name` and no `price_usd` are unverified
37
+ contracts (commonly spam or airdropped tokens on Base). Do not list them
38
+ individually in the table — instead add one short line after the table
39
+ noting how many were omitted, e.g. "Plus 46 unverified ERC-20 contracts
40
+ with no symbol or price data (not included in the total)."
41
+ - Do not include source metadata lines or footer fields such as `source`,
42
+ `token_discovery_source`, or `pricing_source`.
43
+
44
+ After the table:
45
+
46
+ - Add one short line confirming the session wallet backend is now Base, so
47
+ the user knows follow-up wallet requests will target it by default. If
48
+ `set_wallet_backend` failed, add one short warning line instead (e.g. a
49
+ stale local EVM daemon holding its port after a plugin upgrade) — note
50
+ that the portfolio above is still accurate, and that the backend switch
51
+ can be retried. Never drop the portfolio report because the switch
52
+ failed.
53
+
54
+ Keep the response concise. Do not suggest transfers, swaps, bridging, or
55
+ other write actions unless the user explicitly asks for them. If
56
+ `get_wallet_overview` fails, surface the tool error plainly and stop.
@@ -1,5 +1,5 @@
1
1
  name: agent-wallet
2
- version: 0.1.88
2
+ version: 0.1.90
3
3
  description: Thin Hermes Agent bridge to the existing AgentLayer/OpenClaw wallet backend
4
4
  provides_tools:
5
5
  - agent_wallet_tools
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentlayer.tech/wallet",
3
- "version": "0.1.88",
3
+ "version": "0.1.90",
4
4
  "description": "NPM installer for the OpenClaw Agent Wallet local runtime.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wdk-btc-wallet",
3
- "version": "0.1.88",
3
+ "version": "0.1.90",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "Separate BTC-only wallet service built on Tether WDK.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wdk-evm-wallet",
3
- "version": "0.1.88",
3
+ "version": "0.1.90",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "Separate EVM wallet service built on Tether WDK.",