@agentlayer.tech/wallet 0.1.32 → 0.1.34
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/dist/index.js +2 -59
- package/.openclaw/extensions/agent-wallet/index.ts +2 -59
- package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +1 -4
- package/.openclaw/extensions/agent-wallet/package.json +1 -1
- package/CHANGELOG.md +66 -0
- package/README.md +2 -5
- package/RELEASING.md +56 -29
- package/VERSION +1 -0
- package/agent-wallet/.env.example +0 -1
- package/agent-wallet/README.md +0 -8
- package/agent-wallet/agent_wallet/__init__.py +5 -0
- package/agent-wallet/agent_wallet/config.py +0 -1
- package/agent-wallet/agent_wallet/openclaw_adapter.py +25 -324
- package/agent-wallet/agent_wallet/openclaw_cli.py +0 -5
- package/agent-wallet/agent_wallet/providers/bags.py +1 -58
- package/agent-wallet/agent_wallet/providers/jupiter.py +1 -64
- package/agent-wallet/agent_wallet/update_check.py +191 -0
- package/agent-wallet/agent_wallet/wallet_layer/base.py +0 -44
- package/agent-wallet/agent_wallet/wallet_layer/solana.py +0 -236
- package/agent-wallet/openclaw.plugin.json +1 -5
- package/agent-wallet/pyproject.toml +1 -1
- package/agent-wallet/skills/wallet-operator/SKILL.md +2 -5
- package/bin/openclaw-agent-wallet.mjs +419 -33
- package/claude-code/plugins/agent-wallet/.claude-plugin/plugin.json +1 -1
- package/claude-code/plugins/agent-wallet/scripts/run_mcp.sh +14 -1
- package/codex/plugins/agent-wallet/.codex-plugin/plugin.json +1 -1
- package/codex/plugins/agent-wallet/scripts/run_mcp.sh +18 -0
- package/codex/plugins/agent-wallet/server.py +39 -5
- package/hermes/plugins/agent_wallet/plugin.yaml +1 -1
- package/package.json +5 -1
- package/scripts/check_release_version.mjs +50 -20
- package/scripts/version_targets.mjs +60 -0
- package/wdk-btc-wallet/package.json +1 -1
- package/wdk-evm-wallet/README.md +3 -3
- package/wdk-evm-wallet/package.json +1 -1
- package/wdk-evm-wallet/src/wdk_evm_wallet.js +17 -2
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""Non-blocking "a newer version is available" check for the agent wallet.
|
|
2
|
+
|
|
3
|
+
Distribution is the npm package ``@agentlayer.tech/wallet``; a new release is a
|
|
4
|
+
new npm publish. This module compares the installed version against the latest
|
|
5
|
+
version published on the npm registry and decides whether to surface a notice.
|
|
6
|
+
|
|
7
|
+
Design constraints (must all hold):
|
|
8
|
+
|
|
9
|
+
* **Never block startup.** The network refresh runs in a daemon thread and only
|
|
10
|
+
updates a cache file for the *next* start. The synchronous notice decision
|
|
11
|
+
reads that cache and returns instantly without touching the network.
|
|
12
|
+
* **At most once per usage cycle.** The notice is injected into the MCP server
|
|
13
|
+
``instructions`` string, which a client reads exactly once per session.
|
|
14
|
+
* **At most once per day per version across sessions.** A cached
|
|
15
|
+
``last_shown_*`` record throttles repeats to :data:`NAG_INTERVAL_SECONDS`.
|
|
16
|
+
* **Fail-open.** Any error (offline, timeout, malformed cache) results in no
|
|
17
|
+
notice and no exception — the wallet keeps working.
|
|
18
|
+
* **Opt-out.** Set ``AGENT_WALLET_DISABLE_UPDATE_CHECK=1`` to disable entirely.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
import os
|
|
25
|
+
import threading
|
|
26
|
+
import time
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from typing import Callable
|
|
29
|
+
|
|
30
|
+
from .config import resolve_openclaw_home
|
|
31
|
+
|
|
32
|
+
PACKAGE_NAME = "@agentlayer.tech/wallet"
|
|
33
|
+
# URL-encoded scoped package name for the npm registry endpoint.
|
|
34
|
+
_REGISTRY_URL = "https://registry.npmjs.org/@agentlayer.tech%2Fwallet/latest"
|
|
35
|
+
|
|
36
|
+
CACHE_TTL_SECONDS = 24 * 3600
|
|
37
|
+
NAG_INTERVAL_SECONDS = 24 * 3600
|
|
38
|
+
NET_TIMEOUT_SECONDS = 1.5
|
|
39
|
+
|
|
40
|
+
ENV_DISABLE = "AGENT_WALLET_DISABLE_UPDATE_CHECK"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def installed_version() -> str:
|
|
44
|
+
"""Return the installed agent-wallet version."""
|
|
45
|
+
from . import __version__
|
|
46
|
+
|
|
47
|
+
return __version__
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def is_disabled(env: dict | None = None) -> bool:
|
|
51
|
+
"""Return True when the update check is opted out via environment."""
|
|
52
|
+
env = os.environ if env is None else env
|
|
53
|
+
return str(env.get(ENV_DISABLE, "")).strip().lower() in {"1", "true", "yes", "on"}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def cache_path() -> Path:
|
|
57
|
+
"""Location of the shared update-check cache file."""
|
|
58
|
+
return resolve_openclaw_home() / "agent-wallet-runtime" / "update-check.json"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _read_cache() -> dict:
|
|
62
|
+
try:
|
|
63
|
+
return json.loads(cache_path().read_text(encoding="utf-8"))
|
|
64
|
+
except Exception:
|
|
65
|
+
return {}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _write_cache(data: dict) -> None:
|
|
69
|
+
try:
|
|
70
|
+
path = cache_path()
|
|
71
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
72
|
+
tmp = path.with_suffix(".json.tmp")
|
|
73
|
+
tmp.write_text(json.dumps(data), encoding="utf-8")
|
|
74
|
+
tmp.replace(path)
|
|
75
|
+
except Exception:
|
|
76
|
+
# Cache is best-effort; never let a write failure surface.
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _parse_semver(value: str) -> tuple[int, ...]:
|
|
81
|
+
parts: list[int] = []
|
|
82
|
+
for chunk in str(value).strip().split("."):
|
|
83
|
+
num = ""
|
|
84
|
+
for ch in chunk:
|
|
85
|
+
if ch.isdigit():
|
|
86
|
+
num += ch
|
|
87
|
+
else:
|
|
88
|
+
break
|
|
89
|
+
parts.append(int(num) if num else 0)
|
|
90
|
+
return tuple(parts)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _is_newer(latest: str, current: str) -> bool:
|
|
94
|
+
try:
|
|
95
|
+
return _parse_semver(latest) > _parse_semver(current)
|
|
96
|
+
except Exception:
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def pending_notice(
|
|
101
|
+
*,
|
|
102
|
+
mark_shown: bool,
|
|
103
|
+
current: str | None = None,
|
|
104
|
+
now: float | None = None,
|
|
105
|
+
) -> str | None:
|
|
106
|
+
"""Return a human-readable notice when a newer version should be surfaced.
|
|
107
|
+
|
|
108
|
+
Reads only the cache (no network, instant). Returns ``None`` when disabled,
|
|
109
|
+
when no newer version is known, or when the same version was already shown
|
|
110
|
+
within :data:`NAG_INTERVAL_SECONDS`. When ``mark_shown`` is True and a notice
|
|
111
|
+
is returned, the "last shown" record is persisted to throttle later sessions.
|
|
112
|
+
"""
|
|
113
|
+
if is_disabled():
|
|
114
|
+
return None
|
|
115
|
+
current = installed_version() if current is None else current
|
|
116
|
+
now = time.time() if now is None else now
|
|
117
|
+
|
|
118
|
+
cache = _read_cache()
|
|
119
|
+
latest = cache.get("latest_version")
|
|
120
|
+
if not latest or not _is_newer(str(latest), current):
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
if (
|
|
124
|
+
cache.get("last_shown_version") == latest
|
|
125
|
+
and isinstance(cache.get("last_shown_at"), (int, float))
|
|
126
|
+
and now - cache["last_shown_at"] < NAG_INTERVAL_SECONDS
|
|
127
|
+
):
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
if mark_shown:
|
|
131
|
+
cache["last_shown_version"] = latest
|
|
132
|
+
cache["last_shown_at"] = now
|
|
133
|
+
_write_cache(cache)
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
f"newer agent-wallet version {latest} is available (you have {current}). "
|
|
137
|
+
f"Update with: npx {PACKAGE_NAME} update --yes"
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _fetch_latest_version() -> str | None:
|
|
142
|
+
"""Fetch the latest published version from the npm registry (blocking)."""
|
|
143
|
+
import httpx
|
|
144
|
+
|
|
145
|
+
resp = httpx.get(_REGISTRY_URL, timeout=NET_TIMEOUT_SECONDS)
|
|
146
|
+
resp.raise_for_status()
|
|
147
|
+
version = resp.json().get("version")
|
|
148
|
+
return str(version) if version else None
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _refresh_now(
|
|
152
|
+
fetcher: Callable[[], str | None] | None = None,
|
|
153
|
+
now: float | None = None,
|
|
154
|
+
) -> None:
|
|
155
|
+
"""Refresh the cached latest version if stale. Synchronous, fail-open."""
|
|
156
|
+
if is_disabled():
|
|
157
|
+
return
|
|
158
|
+
now = time.time() if now is None else now
|
|
159
|
+
cache = _read_cache()
|
|
160
|
+
checked_at = cache.get("checked_at")
|
|
161
|
+
if isinstance(checked_at, (int, float)) and now - checked_at < CACHE_TTL_SECONDS:
|
|
162
|
+
return # cache fresh enough; skip network
|
|
163
|
+
fetcher = _fetch_latest_version if fetcher is None else fetcher
|
|
164
|
+
try:
|
|
165
|
+
latest = fetcher()
|
|
166
|
+
except Exception:
|
|
167
|
+
return # fail-open: leave cache untouched
|
|
168
|
+
if not latest:
|
|
169
|
+
return
|
|
170
|
+
cache["latest_version"] = str(latest)
|
|
171
|
+
cache["checked_at"] = now
|
|
172
|
+
_write_cache(cache)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def maybe_refresh_in_background(
|
|
176
|
+
fetcher: Callable[[], str | None] | None = None,
|
|
177
|
+
) -> threading.Thread | None:
|
|
178
|
+
"""Start a daemon thread to refresh the cache without blocking the caller.
|
|
179
|
+
|
|
180
|
+
Returns the started thread (for tests) or ``None`` when disabled.
|
|
181
|
+
"""
|
|
182
|
+
if is_disabled():
|
|
183
|
+
return None
|
|
184
|
+
thread = threading.Thread(
|
|
185
|
+
target=_refresh_now,
|
|
186
|
+
kwargs={"fetcher": fetcher},
|
|
187
|
+
name="agent-wallet-update-check",
|
|
188
|
+
daemon=True,
|
|
189
|
+
)
|
|
190
|
+
thread.start()
|
|
191
|
+
return thread
|
|
@@ -357,19 +357,6 @@ class AgentWalletBackend(ABC):
|
|
|
357
357
|
async def get_stake_account(self, stake_account: str) -> dict[str, Any]:
|
|
358
358
|
raise WalletBackendError(f"{self.name} does not support stake account lookup.")
|
|
359
359
|
|
|
360
|
-
async def get_jupiter_portfolio(
|
|
361
|
-
self,
|
|
362
|
-
address: str | None = None,
|
|
363
|
-
platforms: list[str] | None = None,
|
|
364
|
-
) -> dict[str, Any]:
|
|
365
|
-
raise WalletBackendError(f"{self.name} does not support Jupiter portfolio lookup.")
|
|
366
|
-
|
|
367
|
-
async def get_jupiter_portfolio_platforms(self) -> dict[str, Any]:
|
|
368
|
-
raise WalletBackendError(f"{self.name} does not support Jupiter portfolio platforms.")
|
|
369
|
-
|
|
370
|
-
async def get_jupiter_staked_jup(self, address: str | None = None) -> dict[str, Any]:
|
|
371
|
-
raise WalletBackendError(f"{self.name} does not support Jupiter staked JUP lookup.")
|
|
372
|
-
|
|
373
360
|
async def get_flash_trade_markets(
|
|
374
361
|
self,
|
|
375
362
|
pool_name: str | None = None,
|
|
@@ -804,37 +791,6 @@ class AgentWalletBackend(ABC):
|
|
|
804
791
|
}
|
|
805
792
|
return result
|
|
806
793
|
|
|
807
|
-
async def get_bags_claimable_positions(
|
|
808
|
-
self,
|
|
809
|
-
wallet: str | None = None,
|
|
810
|
-
) -> dict[str, Any]:
|
|
811
|
-
raise WalletBackendError(f"{self.name} does not support Bags claimable positions lookup.")
|
|
812
|
-
|
|
813
|
-
async def get_bags_fee_analytics(
|
|
814
|
-
self,
|
|
815
|
-
token_mint: str,
|
|
816
|
-
*,
|
|
817
|
-
include_claim_events: bool = False,
|
|
818
|
-
mode: str = "offset",
|
|
819
|
-
limit: int | None = None,
|
|
820
|
-
offset: int | None = None,
|
|
821
|
-
from_ts: int | None = None,
|
|
822
|
-
to_ts: int | None = None,
|
|
823
|
-
) -> dict[str, Any]:
|
|
824
|
-
raise WalletBackendError(f"{self.name} does not support Bags fee analytics lookup.")
|
|
825
|
-
|
|
826
|
-
async def preview_bags_fee_claim(self, token_mint: str) -> dict[str, Any]:
|
|
827
|
-
raise WalletBackendError(f"{self.name} does not support Bags fee claim previews.")
|
|
828
|
-
|
|
829
|
-
async def execute_bags_fee_claim(self, token_mint: str) -> dict[str, Any]:
|
|
830
|
-
raise WalletBackendError(f"{self.name} does not support Bags fee claims.")
|
|
831
|
-
|
|
832
|
-
async def execute_bags_fee_claim_from_preview(
|
|
833
|
-
self,
|
|
834
|
-
preview: dict[str, Any],
|
|
835
|
-
) -> dict[str, Any]:
|
|
836
|
-
return await self.execute_bags_fee_claim(str(preview["token_mint"]))
|
|
837
|
-
|
|
838
794
|
async def preview_bags_token_launch(
|
|
839
795
|
self,
|
|
840
796
|
*,
|
|
@@ -993,79 +993,6 @@ class SolanaWalletBackend(AgentWalletBackend):
|
|
|
993
993
|
"source": "lifi",
|
|
994
994
|
}
|
|
995
995
|
|
|
996
|
-
async def get_bags_claimable_positions(
|
|
997
|
-
self,
|
|
998
|
-
wallet: str | None = None,
|
|
999
|
-
) -> dict[str, Any]:
|
|
1000
|
-
self._require_mainnet_bags("Bags fee claims")
|
|
1001
|
-
wallet_address = wallet or self.address
|
|
1002
|
-
if not wallet_address:
|
|
1003
|
-
raise WalletBackendError("A wallet address is required for Bags claimable positions.")
|
|
1004
|
-
wallet_address = validate_solana_address(wallet_address)
|
|
1005
|
-
raw = await bags.fetch_claimable_positions(wallet_address)
|
|
1006
|
-
positions = self._bags_claim_positions_list(raw)
|
|
1007
|
-
return {
|
|
1008
|
-
"chain": "solana",
|
|
1009
|
-
"network": self.network,
|
|
1010
|
-
"wallet": wallet_address,
|
|
1011
|
-
"position_count": len(positions),
|
|
1012
|
-
"positions": positions,
|
|
1013
|
-
"raw": raw,
|
|
1014
|
-
"source": "bags",
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
async def get_bags_fee_analytics(
|
|
1018
|
-
self,
|
|
1019
|
-
token_mint: str,
|
|
1020
|
-
*,
|
|
1021
|
-
include_claim_events: bool = False,
|
|
1022
|
-
mode: str = "offset",
|
|
1023
|
-
limit: int | None = None,
|
|
1024
|
-
offset: int | None = None,
|
|
1025
|
-
from_ts: int | None = None,
|
|
1026
|
-
to_ts: int | None = None,
|
|
1027
|
-
) -> dict[str, Any]:
|
|
1028
|
-
self._require_mainnet_bags("Bags fee analytics")
|
|
1029
|
-
normalized_mint = validate_solana_mint(token_mint)
|
|
1030
|
-
if mode not in {"offset", "time"}:
|
|
1031
|
-
raise WalletBackendError("mode must be 'offset' or 'time'.")
|
|
1032
|
-
if limit is not None and limit <= 0:
|
|
1033
|
-
raise WalletBackendError("limit must be greater than zero when provided.")
|
|
1034
|
-
if offset is not None and offset < 0:
|
|
1035
|
-
raise WalletBackendError("offset must be greater than or equal to zero when provided.")
|
|
1036
|
-
if from_ts is not None and from_ts < 0:
|
|
1037
|
-
raise WalletBackendError("from_ts must be greater than or equal to zero.")
|
|
1038
|
-
if to_ts is not None and to_ts < 0:
|
|
1039
|
-
raise WalletBackendError("to_ts must be greater than or equal to zero.")
|
|
1040
|
-
|
|
1041
|
-
tasks = [
|
|
1042
|
-
bags.fetch_lifetime_fees(normalized_mint),
|
|
1043
|
-
bags.fetch_claim_stats(normalized_mint),
|
|
1044
|
-
]
|
|
1045
|
-
if include_claim_events:
|
|
1046
|
-
tasks.append(
|
|
1047
|
-
bags.fetch_claim_events(
|
|
1048
|
-
token_mint=normalized_mint,
|
|
1049
|
-
mode=mode,
|
|
1050
|
-
limit=limit,
|
|
1051
|
-
offset=offset,
|
|
1052
|
-
from_ts=from_ts,
|
|
1053
|
-
to_ts=to_ts,
|
|
1054
|
-
)
|
|
1055
|
-
)
|
|
1056
|
-
results = await asyncio.gather(*tasks)
|
|
1057
|
-
claim_events = results[2] if include_claim_events else None
|
|
1058
|
-
return {
|
|
1059
|
-
"chain": "solana",
|
|
1060
|
-
"network": self.network,
|
|
1061
|
-
"token_mint": normalized_mint,
|
|
1062
|
-
"lifetime_fees": results[0],
|
|
1063
|
-
"claim_stats": results[1],
|
|
1064
|
-
"claim_events": claim_events,
|
|
1065
|
-
"include_claim_events": include_claim_events,
|
|
1066
|
-
"source": "bags",
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
996
|
async def get_staking_validators(
|
|
1070
997
|
self,
|
|
1071
998
|
limit: int = 20,
|
|
@@ -1262,16 +1189,6 @@ class SolanaWalletBackend(AgentWalletBackend):
|
|
|
1262
1189
|
raise WalletBackendError("basis_points must sum to exactly 10000.")
|
|
1263
1190
|
return normalized
|
|
1264
1191
|
|
|
1265
|
-
def _bags_claim_positions_list(self, payload: Any) -> list[dict[str, Any]]:
|
|
1266
|
-
if isinstance(payload, list):
|
|
1267
|
-
return [item for item in payload if isinstance(item, dict)]
|
|
1268
|
-
if isinstance(payload, dict):
|
|
1269
|
-
for key in ("positions", "claimablePositions", "items", "data"):
|
|
1270
|
-
value = payload.get(key)
|
|
1271
|
-
if isinstance(value, list):
|
|
1272
|
-
return [item for item in value if isinstance(item, dict)]
|
|
1273
|
-
return []
|
|
1274
|
-
|
|
1275
1192
|
def _bags_decode_serialized_transaction_bytes(self, serialized_transaction: str) -> bytes:
|
|
1276
1193
|
serialized = str(serialized_transaction).strip()
|
|
1277
1194
|
if not serialized:
|
|
@@ -1533,75 +1450,6 @@ class SolanaWalletBackend(AgentWalletBackend):
|
|
|
1533
1450
|
if self.network != "mainnet":
|
|
1534
1451
|
raise WalletBackendError(f"{feature} is only enabled for Solana mainnet.")
|
|
1535
1452
|
|
|
1536
|
-
async def get_jupiter_portfolio_platforms(self) -> dict[str, Any]:
|
|
1537
|
-
self._require_mainnet_jupiter("Jupiter portfolio")
|
|
1538
|
-
data = await jupiter.fetch_portfolio_platforms()
|
|
1539
|
-
platforms = data.get("platforms")
|
|
1540
|
-
if not isinstance(platforms, list):
|
|
1541
|
-
platforms = data.get("data") if isinstance(data.get("data"), list) else []
|
|
1542
|
-
return {
|
|
1543
|
-
"chain": "solana",
|
|
1544
|
-
"network": self.network,
|
|
1545
|
-
"platform_count": len(platforms),
|
|
1546
|
-
"platforms": platforms,
|
|
1547
|
-
"raw": data,
|
|
1548
|
-
"source": "jupiter-portfolio",
|
|
1549
|
-
}
|
|
1550
|
-
|
|
1551
|
-
async def get_jupiter_portfolio(
|
|
1552
|
-
self,
|
|
1553
|
-
address: str | None = None,
|
|
1554
|
-
platforms: list[str] | None = None,
|
|
1555
|
-
) -> dict[str, Any]:
|
|
1556
|
-
self._require_mainnet_jupiter("Jupiter portfolio")
|
|
1557
|
-
wallet_address = address or self.address
|
|
1558
|
-
if not wallet_address:
|
|
1559
|
-
raise WalletBackendError(
|
|
1560
|
-
"No Solana wallet address configured. Set SOLANA_AGENT_PUBLIC_KEY or a signer."
|
|
1561
|
-
)
|
|
1562
|
-
wallet_address = validate_solana_address(wallet_address)
|
|
1563
|
-
platform_filter: list[str] | None = None
|
|
1564
|
-
if platforms is not None:
|
|
1565
|
-
platform_filter = []
|
|
1566
|
-
for platform in platforms:
|
|
1567
|
-
if not isinstance(platform, str) or not platform.strip():
|
|
1568
|
-
raise WalletBackendError("Each platform must be a non-empty string.")
|
|
1569
|
-
platform_filter.append(platform.strip())
|
|
1570
|
-
data = await jupiter.fetch_portfolio_positions(
|
|
1571
|
-
address=wallet_address,
|
|
1572
|
-
platforms=platform_filter,
|
|
1573
|
-
)
|
|
1574
|
-
positions = data.get("positions")
|
|
1575
|
-
if not isinstance(positions, list):
|
|
1576
|
-
positions = data.get("data") if isinstance(data.get("data"), list) else []
|
|
1577
|
-
return {
|
|
1578
|
-
"chain": "solana",
|
|
1579
|
-
"network": self.network,
|
|
1580
|
-
"address": wallet_address,
|
|
1581
|
-
"platforms": platform_filter or [],
|
|
1582
|
-
"position_count": len(positions),
|
|
1583
|
-
"positions": positions,
|
|
1584
|
-
"raw": data,
|
|
1585
|
-
"source": "jupiter-portfolio",
|
|
1586
|
-
}
|
|
1587
|
-
|
|
1588
|
-
async def get_jupiter_staked_jup(self, address: str | None = None) -> dict[str, Any]:
|
|
1589
|
-
self._require_mainnet_jupiter("Jupiter staked JUP")
|
|
1590
|
-
wallet_address = address or self.address
|
|
1591
|
-
if not wallet_address:
|
|
1592
|
-
raise WalletBackendError(
|
|
1593
|
-
"No Solana wallet address configured. Set SOLANA_AGENT_PUBLIC_KEY or a signer."
|
|
1594
|
-
)
|
|
1595
|
-
wallet_address = validate_solana_address(wallet_address)
|
|
1596
|
-
data = await jupiter.fetch_staked_jup(address=wallet_address)
|
|
1597
|
-
return {
|
|
1598
|
-
"chain": "solana",
|
|
1599
|
-
"network": self.network,
|
|
1600
|
-
"address": wallet_address,
|
|
1601
|
-
"raw": data,
|
|
1602
|
-
"source": "jupiter-portfolio",
|
|
1603
|
-
}
|
|
1604
|
-
|
|
1605
1453
|
async def get_flash_trade_markets(
|
|
1606
1454
|
self,
|
|
1607
1455
|
pool_name: str | None = None,
|
|
@@ -4599,90 +4447,6 @@ class SolanaWalletBackend(AgentWalletBackend):
|
|
|
4599
4447
|
"source": "solana-rpc",
|
|
4600
4448
|
}
|
|
4601
4449
|
|
|
4602
|
-
async def preview_bags_fee_claim(self, token_mint: str) -> dict[str, Any]:
|
|
4603
|
-
self._require_mainnet_bags("Bags fee claims")
|
|
4604
|
-
owner = await self.get_address()
|
|
4605
|
-
if not owner:
|
|
4606
|
-
raise WalletBackendError(
|
|
4607
|
-
"No Solana wallet address configured. Set SOLANA_AGENT_PUBLIC_KEY or a signer."
|
|
4608
|
-
)
|
|
4609
|
-
normalized_mint = validate_solana_mint(token_mint)
|
|
4610
|
-
positions_payload = await self.get_bags_claimable_positions(owner)
|
|
4611
|
-
positions = [
|
|
4612
|
-
item
|
|
4613
|
-
for item in positions_payload["positions"]
|
|
4614
|
-
if str(item.get("tokenMint") or item.get("token_mint") or "").strip() == normalized_mint
|
|
4615
|
-
]
|
|
4616
|
-
return {
|
|
4617
|
-
"chain": "solana",
|
|
4618
|
-
"network": self.network,
|
|
4619
|
-
"mode": "preview",
|
|
4620
|
-
"asset_type": "bags-fee-claim",
|
|
4621
|
-
"owner": owner,
|
|
4622
|
-
"fee_claimer": owner,
|
|
4623
|
-
"token_mint": normalized_mint,
|
|
4624
|
-
"claimable_position_count": len(positions),
|
|
4625
|
-
"claimable_positions": positions,
|
|
4626
|
-
"sign_only": self.sign_only,
|
|
4627
|
-
"can_send": self.get_capabilities().can_send_transaction,
|
|
4628
|
-
"source": "bags",
|
|
4629
|
-
}
|
|
4630
|
-
|
|
4631
|
-
async def execute_bags_fee_claim(
|
|
4632
|
-
self,
|
|
4633
|
-
token_mint: str,
|
|
4634
|
-
) -> dict[str, Any]:
|
|
4635
|
-
preview = await self.preview_bags_fee_claim(token_mint)
|
|
4636
|
-
return await self.execute_bags_fee_claim_from_preview(preview)
|
|
4637
|
-
|
|
4638
|
-
async def execute_bags_fee_claim_from_preview(
|
|
4639
|
-
self,
|
|
4640
|
-
preview: dict[str, Any],
|
|
4641
|
-
) -> dict[str, Any]:
|
|
4642
|
-
if not self.signer:
|
|
4643
|
-
raise WalletBackendError("Solana signer is not configured.")
|
|
4644
|
-
owner = await self.get_address()
|
|
4645
|
-
if not owner:
|
|
4646
|
-
raise WalletBackendError(
|
|
4647
|
-
"No Solana wallet address configured. Set SOLANA_AGENT_PUBLIC_KEY or a signer."
|
|
4648
|
-
)
|
|
4649
|
-
if str(preview.get("asset_type") or "").strip().lower() != "bags-fee-claim":
|
|
4650
|
-
raise WalletBackendError("preview payload is not a Bags fee claim preview.")
|
|
4651
|
-
if str(preview.get("network") or self.network).strip().lower() != self.network:
|
|
4652
|
-
raise WalletBackendError("preview payload network does not match the wallet backend.")
|
|
4653
|
-
if str(preview.get("owner") or owner) != owner:
|
|
4654
|
-
raise WalletBackendError("preview payload owner does not match the connected wallet.")
|
|
4655
|
-
if int(preview.get("claimable_position_count") or 0) <= 0:
|
|
4656
|
-
raise WalletBackendError("No claimable Bags fee positions were found for this token.")
|
|
4657
|
-
|
|
4658
|
-
token_mint = validate_solana_mint(str(preview.get("token_mint") or ""))
|
|
4659
|
-
claim_payload = await bags.build_claim_transactions(
|
|
4660
|
-
{
|
|
4661
|
-
"feeClaimer": owner,
|
|
4662
|
-
"tokenMint": token_mint,
|
|
4663
|
-
}
|
|
4664
|
-
)
|
|
4665
|
-
transactions = self._bags_extract_transaction_base64s(claim_payload)
|
|
4666
|
-
prepared = await self._prepare_bags_transactions(
|
|
4667
|
-
transaction_base64s=transactions,
|
|
4668
|
-
token_mint=token_mint,
|
|
4669
|
-
action="Bags fee claim",
|
|
4670
|
-
owner=owner,
|
|
4671
|
-
asset_type="bags-fee-claim",
|
|
4672
|
-
extra={
|
|
4673
|
-
"fee_claimer": owner,
|
|
4674
|
-
"claimable_position_count": int(preview.get("claimable_position_count") or 0),
|
|
4675
|
-
"claimable_positions": preview.get("claimable_positions"),
|
|
4676
|
-
"claim_response": claim_payload,
|
|
4677
|
-
},
|
|
4678
|
-
)
|
|
4679
|
-
result = await self._execute_prepared_bags_transactions(prepared)
|
|
4680
|
-
result["fee_claimer"] = owner
|
|
4681
|
-
result["claimable_position_count"] = int(preview.get("claimable_position_count") or 0)
|
|
4682
|
-
result["claimable_positions"] = preview.get("claimable_positions")
|
|
4683
|
-
result["claim_response"] = claim_payload
|
|
4684
|
-
return result
|
|
4685
|
-
|
|
4686
4450
|
async def preview_bags_token_launch(
|
|
4687
4451
|
self,
|
|
4688
4452
|
*,
|
|
@@ -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.34",
|
|
6
6
|
"skills": ["skills/wallet-operator"],
|
|
7
7
|
"configSchema": {
|
|
8
8
|
"type": "object",
|
|
@@ -113,10 +113,6 @@
|
|
|
113
113
|
"type": "string",
|
|
114
114
|
"description": "Optional Jupiter Price API base URL for token price lookup."
|
|
115
115
|
},
|
|
116
|
-
"jupiterPortfolioBaseUrl": {
|
|
117
|
-
"type": "string",
|
|
118
|
-
"description": "Optional Jupiter Portfolio API base URL for Jupiter-specific positions and staking data."
|
|
119
|
-
},
|
|
120
116
|
"jupiterApiKey": {
|
|
121
117
|
"type": "string",
|
|
122
118
|
"description": "Optional Jupiter API key if your deployment uses a keyed endpoint."
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: wallet-operator
|
|
3
|
-
description: Use when operating OpenClaw wallet tools: balances, transfers, swaps, LI.FI cross-chain swaps, Jupiter swaps, Velora EVM swaps, BTC transfers, staking, Kamino lending, Bags
|
|
3
|
+
description: Use when operating OpenClaw wallet tools: balances, transfers, swaps, LI.FI cross-chain swaps, Jupiter swaps, Velora EVM swaps, BTC transfers, staking, Kamino lending, Bags launches, and wallet execution safety.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Wallet Operator
|
|
@@ -30,7 +30,7 @@ Use this skill before calling OpenClaw wallet tools. It is the routing guide for
|
|
|
30
30
|
- BTC transfer: `transfer_btc`.
|
|
31
31
|
- Solana staking: `stake_sol_native`, `deactivate_solana_stake`, `withdraw_solana_stake`.
|
|
32
32
|
- Kamino: `kamino_lend_deposit`, `kamino_lend_withdraw`, `kamino_lend_borrow`, `kamino_lend_repay`.
|
|
33
|
-
- Bags: `
|
|
33
|
+
- Bags: `launch_bags_token`.
|
|
34
34
|
|
|
35
35
|
## Common Token IDs
|
|
36
36
|
|
|
@@ -108,8 +108,6 @@ Use this skill before calling OpenClaw wallet tools. It is the routing guide for
|
|
|
108
108
|
- `withdraw_solana_stake`: `stake_account`, `amount` in SOL, optional `recipient`, `mode`, `purpose`.
|
|
109
109
|
- Before Kamino writes, use `get_kamino_lend_markets`, `get_kamino_lend_market_reserves`, `get_kamino_lend_user_obligations`, and `get_kamino_lend_user_rewards`.
|
|
110
110
|
- Kamino write params: `market`, `reserve`, `amount_ui` decimal string, `mode`, `purpose`.
|
|
111
|
-
- Bags reads: `get_bags_claimable_positions`, `get_bags_fee_analytics`.
|
|
112
|
-
- `claim_bags_fees`: `token_mint`, `mode`, `purpose`.
|
|
113
111
|
- `launch_bags_token`: `name`, `symbol`, `description`, `base_mint`, `claimers`, `basis_points`, `initial_buy_sol`, `mode`, `purpose`; optional socials/image/config type.
|
|
114
112
|
- `close_empty_token_accounts`: `limit`, `mode` (`preview` or `execute`), `purpose`.
|
|
115
113
|
|
|
@@ -123,6 +121,5 @@ Use this skill before calling OpenClaw wallet tools. It is the routing guide for
|
|
|
123
121
|
|
|
124
122
|
## Disabled Or Avoided Paths
|
|
125
123
|
|
|
126
|
-
- Do not call Jupiter Portfolio tools if they are not listed by `get_wallet_capabilities`; they may be temporarily disabled.
|
|
127
124
|
- Do not invent generic calldata, arbitrary contract calls, token approvals, or non-listed bridge providers.
|
|
128
125
|
- If a requested tool is absent from `list_tools` or capabilities, say the wallet runtime does not expose it.
|