@agentlayer.tech/wallet 0.1.48 → 0.1.50
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/index.ts +45 -0
- package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +4 -1
- package/.openclaw/extensions/agent-wallet/package.json +1 -1
- package/VERSION +1 -1
- package/agent-wallet/agent_wallet/__init__.py +1 -1
- package/agent-wallet/agent_wallet/autonomous_permissions.py +145 -0
- package/agent-wallet/agent_wallet/autonomous_policy.py +305 -0
- package/agent-wallet/agent_wallet/autonomous_session.py +236 -0
- package/agent-wallet/agent_wallet/openclaw_adapter.py +507 -29
- package/agent-wallet/agent_wallet/openclaw_cli.py +36 -0
- package/agent-wallet/agent_wallet/spending_limits.py +21 -3
- package/agent-wallet/openclaw.plugin.json +1 -1
- package/agent-wallet/pyproject.toml +2 -2
- package/bin/openclaw-agent-wallet.mjs +298 -7
- package/claude-code/plugins/agent-wallet/.claude-plugin/plugin.json +1 -1
- package/claude-code/plugins/agent-wallet/README.md +5 -0
- package/claude-code/plugins/agent-wallet/commands/agentlayer-autonomous-approve.md +24 -0
- package/claude-code/plugins/agent-wallet/commands/agentlayer-autonomous-revoke.md +16 -0
- package/claude-code/plugins/agent-wallet/scripts/bootstrap_backend.sh +2 -2
- package/codex/plugins/agent-wallet/.codex-plugin/plugin.json +1 -1
- package/codex/plugins/agent-wallet/server.py +18 -0
- 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/package.json +1 -1
|
@@ -220,6 +220,35 @@ async def _run_issue_approval(
|
|
|
220
220
|
}
|
|
221
221
|
|
|
222
222
|
|
|
223
|
+
def _run_autonomous_permission(action: str, scope: str) -> dict[str, Any]:
|
|
224
|
+
from agent_wallet import autonomous_permissions
|
|
225
|
+
|
|
226
|
+
normalized_scope = str(scope or "").strip()
|
|
227
|
+
if normalized_scope != autonomous_permissions.BASE_SWAP_SCOPE:
|
|
228
|
+
raise WalletBackendError("Only scope=base_swaps is currently supported.")
|
|
229
|
+
|
|
230
|
+
normalized_action = str(action or "").strip().lower()
|
|
231
|
+
if normalized_action == "approve":
|
|
232
|
+
return {
|
|
233
|
+
"ok": True,
|
|
234
|
+
"action": normalized_action,
|
|
235
|
+
"data": autonomous_permissions.approve_base_swaps(approved_by="openclaw_cli"),
|
|
236
|
+
}
|
|
237
|
+
if normalized_action == "revoke":
|
|
238
|
+
return {
|
|
239
|
+
"ok": True,
|
|
240
|
+
"action": normalized_action,
|
|
241
|
+
"data": autonomous_permissions.revoke_base_swaps(),
|
|
242
|
+
}
|
|
243
|
+
if normalized_action == "status":
|
|
244
|
+
return {
|
|
245
|
+
"ok": True,
|
|
246
|
+
"action": normalized_action,
|
|
247
|
+
"data": autonomous_permissions.status(),
|
|
248
|
+
}
|
|
249
|
+
raise WalletBackendError("action must be approve, revoke, or status.")
|
|
250
|
+
|
|
251
|
+
|
|
223
252
|
async def _run_btc_wallet_get(user_id: str, config: dict[str, Any]) -> dict[str, Any]:
|
|
224
253
|
from agent_wallet.btc_user_wallets import get_user_btc_wallet_binding
|
|
225
254
|
|
|
@@ -433,6 +462,11 @@ def main() -> int:
|
|
|
433
462
|
approval_parser.add_argument("--ttl-seconds", type=int)
|
|
434
463
|
approval_parser.add_argument("--config-json", default="{}")
|
|
435
464
|
|
|
465
|
+
autonomous_permission_parser = subparsers.add_parser("autonomous-permission")
|
|
466
|
+
autonomous_permission_parser.add_argument("--action", choices=["approve", "revoke", "status"], required=True)
|
|
467
|
+
autonomous_permission_parser.add_argument("--scope", choices=["base_swaps"], required=True)
|
|
468
|
+
autonomous_permission_parser.add_argument("--config-json", default="{}")
|
|
469
|
+
|
|
436
470
|
btc_get_parser = subparsers.add_parser("btc-wallet-get")
|
|
437
471
|
btc_get_parser.add_argument("--user-id", required=True)
|
|
438
472
|
btc_get_parser.add_argument("--config-json", default="{}")
|
|
@@ -506,6 +540,8 @@ def main() -> int:
|
|
|
506
540
|
ttl_seconds=args.ttl_seconds,
|
|
507
541
|
)
|
|
508
542
|
)
|
|
543
|
+
elif args.command == "autonomous-permission":
|
|
544
|
+
payload = _run_autonomous_permission(args.action, args.scope)
|
|
509
545
|
elif args.command == "btc-wallet-get":
|
|
510
546
|
payload = asyncio.run(_run_btc_wallet_get(args.user_id, config))
|
|
511
547
|
elif args.command == "btc-wallet-create":
|
|
@@ -15,6 +15,7 @@ from __future__ import annotations
|
|
|
15
15
|
|
|
16
16
|
import threading
|
|
17
17
|
import time
|
|
18
|
+
from collections.abc import Callable
|
|
18
19
|
from dataclasses import dataclass
|
|
19
20
|
|
|
20
21
|
from agent_wallet.wallet_layer.base import WalletBackendError
|
|
@@ -38,9 +39,20 @@ class SpendingConfig:
|
|
|
38
39
|
class SpendingLedger:
|
|
39
40
|
"""Records executed spends and rejects operations that exceed limits."""
|
|
40
41
|
|
|
41
|
-
def __init__(
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
config: SpendingConfig,
|
|
45
|
+
*,
|
|
46
|
+
clock: Callable[[], float] = time.monotonic,
|
|
47
|
+
entries: list[tuple[float, int]] | None = None,
|
|
48
|
+
) -> None:
|
|
42
49
|
self.config = config
|
|
43
|
-
|
|
50
|
+
# (timestamp, lamports) pairs in the unit returned by ``clock``.
|
|
51
|
+
# Defaults to ``time.monotonic`` for in-process use; pass
|
|
52
|
+
# ``time.time`` (wall-clock) when entries must be persisted and
|
|
53
|
+
# reloaded across processes.
|
|
54
|
+
self._clock = clock
|
|
55
|
+
self._entries: list[tuple[float, int]] = list(entries or [])
|
|
44
56
|
self._lock = threading.Lock()
|
|
45
57
|
|
|
46
58
|
# -- public API ----------------------------------------------------------
|
|
@@ -51,7 +63,7 @@ class SpendingLedger:
|
|
|
51
63
|
Raises ``WalletBackendError`` if any limit would be exceeded.
|
|
52
64
|
"""
|
|
53
65
|
with self._lock:
|
|
54
|
-
now =
|
|
66
|
+
now = self._clock()
|
|
55
67
|
self._cleanup(now)
|
|
56
68
|
|
|
57
69
|
# Per-transaction cap
|
|
@@ -94,6 +106,12 @@ class SpendingLedger:
|
|
|
94
106
|
|
|
95
107
|
self._entries.append((now, lamports))
|
|
96
108
|
|
|
109
|
+
def export(self) -> list[tuple[float, int]]:
|
|
110
|
+
"""Return a copy of the (timestamp, lamports) entries for persistence."""
|
|
111
|
+
with self._lock:
|
|
112
|
+
self._cleanup(self._clock())
|
|
113
|
+
return list(self._entries)
|
|
114
|
+
|
|
97
115
|
# -- internal ------------------------------------------------------------
|
|
98
116
|
|
|
99
117
|
def _cleanup(self, now: float) -> None:
|
|
@@ -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.50",
|
|
6
6
|
"skills": ["skills/wallet-operator"],
|
|
7
7
|
"configSchema": {
|
|
8
8
|
"type": "object",
|
|
@@ -4,13 +4,13 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "openclaw-agent-wallet"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.50"
|
|
8
8
|
description = "Plugin-friendly wallet backend for OpenClaw agents"
|
|
9
9
|
requires-python = ">=3.10"
|
|
10
10
|
dependencies = [
|
|
11
11
|
"fastmcp>=2.0.0",
|
|
12
12
|
"httpx>=0.27.0",
|
|
13
|
-
"pydantic>=2.0.0",
|
|
13
|
+
"pydantic>=2.0.0,<2.13.0",
|
|
14
14
|
"pydantic-settings>=2.0.0",
|
|
15
15
|
"pynacl>=1.5.0",
|
|
16
16
|
"python-dotenv>=1.0.0",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
4
4
|
import crypto from "node:crypto";
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import os from "node:os";
|
|
@@ -15,6 +15,14 @@ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
|
15
15
|
const packageVersion = packageJson.version;
|
|
16
16
|
const UPDATE_CLI_PATH_ENV = "OPENCLAW_AGENT_WALLET_UPDATE_CLI_PATH";
|
|
17
17
|
const UPDATE_PACKAGE_SPEC_ENV = "OPENCLAW_AGENT_WALLET_UPDATE_PACKAGE_SPEC";
|
|
18
|
+
const DEFAULT_PROVIDER_GATEWAY_URL = "https://agent-layer-production.up.railway.app";
|
|
19
|
+
const TELEMETRY_SPOOL_NAME = "telemetry_spool.jsonl";
|
|
20
|
+
const TELEMETRY_ID_NAME = "telemetry_id";
|
|
21
|
+
const TELEMETRY_LAST_FLUSH_NAME = "telemetry_last_flush";
|
|
22
|
+
const TELEMETRY_FLUSH_THROTTLE_SECONDS = 20;
|
|
23
|
+
const TELEMETRY_FORCE_LINES = 25;
|
|
24
|
+
const TELEMETRY_MAX_EVENTS_PER_FLUSH = 100;
|
|
25
|
+
const TELEMETRY_HTTP_TIMEOUT_MS = 1500;
|
|
18
26
|
|
|
19
27
|
function printHelp() {
|
|
20
28
|
console.log(`openclaw-agent-wallet
|
|
@@ -100,6 +108,211 @@ function updateCheckDisabled(env = process.env) {
|
|
|
100
108
|
return ["1", "true", "yes", "on"].includes(raw);
|
|
101
109
|
}
|
|
102
110
|
|
|
111
|
+
function telemetryDisabled(env = process.env) {
|
|
112
|
+
const raw = String(env.AGENT_WALLET_NO_TELEMETRY || "").trim().toLowerCase();
|
|
113
|
+
return ["1", "true", "yes", "on"].includes(raw);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function telemetryPath(name, env = process.env) {
|
|
117
|
+
return path.join(resolveOpenclawHome(env), name);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function telemetryInstallId(env = process.env) {
|
|
121
|
+
const idPath = telemetryPath(TELEMETRY_ID_NAME, env);
|
|
122
|
+
try {
|
|
123
|
+
const existing = fs.readFileSync(idPath, "utf8").trim();
|
|
124
|
+
if (existing) return existing;
|
|
125
|
+
} catch {
|
|
126
|
+
// Create below.
|
|
127
|
+
}
|
|
128
|
+
const next = crypto.randomUUID ? crypto.randomUUID().replaceAll("-", "") : crypto.randomBytes(16).toString("hex");
|
|
129
|
+
try {
|
|
130
|
+
fs.mkdirSync(path.dirname(idPath), { recursive: true });
|
|
131
|
+
fs.writeFileSync(idPath, next, { mode: 0o600 });
|
|
132
|
+
} catch {
|
|
133
|
+
// Telemetry must not affect install flow.
|
|
134
|
+
}
|
|
135
|
+
return next;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function telemetryHost(host = "", env = process.env) {
|
|
139
|
+
const raw = String(host || env.AGENT_WALLET_HOST || "").trim().toLowerCase();
|
|
140
|
+
return ["claude-code", "codex", "hermes", "openclaw"].includes(raw) ? raw : "unknown";
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function telemetrySource(env = process.env) {
|
|
144
|
+
const explicit = String(env.AGENT_WALLET_INSTALL_SOURCE || "").trim().toLowerCase();
|
|
145
|
+
if (explicit) return explicit.replace(/[^a-z0-9_]/g, "_").slice(0, 48);
|
|
146
|
+
return env.npm_execpath || String(env.npm_config_user_agent || "").includes("npm")
|
|
147
|
+
? "npx"
|
|
148
|
+
: "global_cli";
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function telemetryGatewayUrl(env = process.env) {
|
|
152
|
+
return String(env.PROVIDER_GATEWAY_URL || DEFAULT_PROVIDER_GATEWAY_URL).trim().replace(/\/+$/, "");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function telemetryAppend(payload, env = process.env) {
|
|
156
|
+
const spool = telemetryPath(TELEMETRY_SPOOL_NAME, env);
|
|
157
|
+
fs.mkdirSync(path.dirname(spool), { recursive: true });
|
|
158
|
+
fs.appendFileSync(spool, `${JSON.stringify(payload)}\n`, { encoding: "utf8" });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function telemetrySpoolLineCount(env = process.env) {
|
|
162
|
+
try {
|
|
163
|
+
const spool = telemetryPath(TELEMETRY_SPOOL_NAME, env);
|
|
164
|
+
return fs.readFileSync(spool, "utf8").split(/\r?\n/).filter(Boolean).length;
|
|
165
|
+
} catch {
|
|
166
|
+
return 0;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function telemetryShouldFlush(env = process.env) {
|
|
171
|
+
const count = telemetrySpoolLineCount(env);
|
|
172
|
+
if (count === 0) return false;
|
|
173
|
+
if (count >= TELEMETRY_FORCE_LINES) return true;
|
|
174
|
+
try {
|
|
175
|
+
const last = Number(fs.readFileSync(telemetryPath(TELEMETRY_LAST_FLUSH_NAME, env), "utf8").trim() || "0");
|
|
176
|
+
return Date.now() / 1000 - last >= TELEMETRY_FLUSH_THROTTLE_SECONDS;
|
|
177
|
+
} catch {
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function telemetryMarkFlush(env = process.env) {
|
|
183
|
+
try {
|
|
184
|
+
fs.writeFileSync(telemetryPath(TELEMETRY_LAST_FLUSH_NAME, env), String(Date.now() / 1000));
|
|
185
|
+
} catch {
|
|
186
|
+
// ignored
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function telemetrySpawnFlush(env = process.env, force = false) {
|
|
191
|
+
if (!force && !telemetryShouldFlush(env)) return;
|
|
192
|
+
telemetryMarkFlush(env);
|
|
193
|
+
try {
|
|
194
|
+
const child = spawn(process.execPath, [cliPath, "--telemetry-flush"], {
|
|
195
|
+
detached: true,
|
|
196
|
+
stdio: "ignore",
|
|
197
|
+
env,
|
|
198
|
+
});
|
|
199
|
+
child.unref();
|
|
200
|
+
} catch {
|
|
201
|
+
// ignored
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function recordCliTelemetry(event, { commandName, host = "", ok = true, args = [], flush = true } = {}) {
|
|
206
|
+
try {
|
|
207
|
+
if (telemetryDisabled()) return;
|
|
208
|
+
if (args && hasFlag(args, "--dry-run")) return;
|
|
209
|
+
const payload = {
|
|
210
|
+
event,
|
|
211
|
+
install_id: telemetryInstallId(),
|
|
212
|
+
host: telemetryHost(host),
|
|
213
|
+
tool: "",
|
|
214
|
+
backend: "",
|
|
215
|
+
plugin_version: packageVersion,
|
|
216
|
+
ok: Boolean(ok),
|
|
217
|
+
ts: Math.floor(Date.now() / 1000),
|
|
218
|
+
source: telemetrySource(),
|
|
219
|
+
command: String(commandName || "").trim().toLowerCase().replace(/-/g, "_").slice(0, 48),
|
|
220
|
+
};
|
|
221
|
+
telemetryAppend(payload);
|
|
222
|
+
if (flush) telemetrySpawnFlush(process.env, true);
|
|
223
|
+
} catch {
|
|
224
|
+
// Telemetry must never affect CLI behavior.
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function telemetryPost(url, line) {
|
|
229
|
+
const controller = new AbortController();
|
|
230
|
+
const timer = setTimeout(() => controller.abort(), TELEMETRY_HTTP_TIMEOUT_MS);
|
|
231
|
+
try {
|
|
232
|
+
const response = await fetch(url, {
|
|
233
|
+
method: "POST",
|
|
234
|
+
body: line,
|
|
235
|
+
headers: { "Content-Type": "application/json" },
|
|
236
|
+
signal: controller.signal,
|
|
237
|
+
});
|
|
238
|
+
return response.ok;
|
|
239
|
+
} catch {
|
|
240
|
+
return false;
|
|
241
|
+
} finally {
|
|
242
|
+
clearTimeout(timer);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function telemetryFlushMain(env = process.env) {
|
|
247
|
+
if (telemetryDisabled(env)) return 0;
|
|
248
|
+
const spool = telemetryPath(TELEMETRY_SPOOL_NAME, env);
|
|
249
|
+
const claim = `${spool}.flushing.${process.pid}`;
|
|
250
|
+
try {
|
|
251
|
+
fs.renameSync(spool, claim);
|
|
252
|
+
} catch {
|
|
253
|
+
return 0;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
let lines = [];
|
|
257
|
+
try {
|
|
258
|
+
lines = fs.readFileSync(claim, "utf8").split(/\r?\n/).filter(Boolean);
|
|
259
|
+
} catch {
|
|
260
|
+
lines = [];
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const url = `${telemetryGatewayUrl(env)}/v1/telemetry`;
|
|
264
|
+
const failed = [];
|
|
265
|
+
let sent = 0;
|
|
266
|
+
for (const line of lines) {
|
|
267
|
+
if (sent >= TELEMETRY_MAX_EVENTS_PER_FLUSH) {
|
|
268
|
+
failed.push(line);
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if (await telemetryPost(url, line)) {
|
|
272
|
+
sent += 1;
|
|
273
|
+
} else {
|
|
274
|
+
failed.push(line);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (failed.length) {
|
|
279
|
+
try {
|
|
280
|
+
fs.appendFileSync(spool, `${failed.join("\n")}\n`, { encoding: "utf8" });
|
|
281
|
+
} catch {
|
|
282
|
+
// ignored
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
try {
|
|
286
|
+
fs.unlinkSync(claim);
|
|
287
|
+
} catch {
|
|
288
|
+
// ignored
|
|
289
|
+
}
|
|
290
|
+
return 0;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function runWithCliTelemetry(fn, { startEvent, successEvent, failedEvent, commandName, host = "", args = [] }) {
|
|
294
|
+
recordCliTelemetry(startEvent, { commandName, host, ok: true, args, flush: false });
|
|
295
|
+
let code;
|
|
296
|
+
try {
|
|
297
|
+
code = fn();
|
|
298
|
+
} catch (error) {
|
|
299
|
+
recordCliTelemetry(failedEvent, {
|
|
300
|
+
commandName,
|
|
301
|
+
host,
|
|
302
|
+
ok: false,
|
|
303
|
+
args,
|
|
304
|
+
});
|
|
305
|
+
throw error;
|
|
306
|
+
}
|
|
307
|
+
recordCliTelemetry(code === 0 ? successEvent : failedEvent, {
|
|
308
|
+
commandName,
|
|
309
|
+
host,
|
|
310
|
+
ok: code === 0,
|
|
311
|
+
args,
|
|
312
|
+
});
|
|
313
|
+
return code;
|
|
314
|
+
}
|
|
315
|
+
|
|
103
316
|
// Shared with the Python runtime (agent_wallet/update_check.py): the cache lives
|
|
104
317
|
// under OPENCLAW_HOME/agent-wallet-runtime regardless of OPENCLAW_INSTALL_ROOT.
|
|
105
318
|
function updateCheckCachePath(env = process.env) {
|
|
@@ -1802,6 +2015,10 @@ function runClaudeCodeInstall(args) {
|
|
|
1802
2015
|
const args = process.argv.slice(2);
|
|
1803
2016
|
const command = args[0] || "install";
|
|
1804
2017
|
|
|
2018
|
+
if (command === "--telemetry-flush") {
|
|
2019
|
+
process.exit(await telemetryFlushMain());
|
|
2020
|
+
}
|
|
2021
|
+
|
|
1805
2022
|
if (command === "--help" || command === "-h" || command === "help") {
|
|
1806
2023
|
printHelp();
|
|
1807
2024
|
process.exit(0);
|
|
@@ -1830,11 +2047,35 @@ if (command === "status") {
|
|
|
1830
2047
|
}
|
|
1831
2048
|
|
|
1832
2049
|
if (command === "install" || command === "setup") {
|
|
1833
|
-
|
|
2050
|
+
const commandArgs = args.slice(1);
|
|
2051
|
+
process.exit(
|
|
2052
|
+
runWithCliTelemetry(
|
|
2053
|
+
() => runInstall(commandArgs, { commandName: "install" }),
|
|
2054
|
+
{
|
|
2055
|
+
startEvent: "install_start",
|
|
2056
|
+
successEvent: "install_success",
|
|
2057
|
+
failedEvent: "install_failed",
|
|
2058
|
+
commandName: "install",
|
|
2059
|
+
args: commandArgs,
|
|
2060
|
+
},
|
|
2061
|
+
),
|
|
2062
|
+
);
|
|
1834
2063
|
}
|
|
1835
2064
|
|
|
1836
2065
|
if (command === "update") {
|
|
1837
|
-
|
|
2066
|
+
const commandArgs = args.slice(1);
|
|
2067
|
+
process.exit(
|
|
2068
|
+
runWithCliTelemetry(
|
|
2069
|
+
() => runUpdate(commandArgs),
|
|
2070
|
+
{
|
|
2071
|
+
startEvent: "update_start",
|
|
2072
|
+
successEvent: "update_success",
|
|
2073
|
+
failedEvent: "update_failed",
|
|
2074
|
+
commandName: "update",
|
|
2075
|
+
args: commandArgs,
|
|
2076
|
+
},
|
|
2077
|
+
),
|
|
2078
|
+
);
|
|
1838
2079
|
}
|
|
1839
2080
|
|
|
1840
2081
|
if (command === "rollback") {
|
|
@@ -1844,7 +2085,20 @@ if (command === "rollback") {
|
|
|
1844
2085
|
if (command === "hermes") {
|
|
1845
2086
|
const subcommand = args[1] || "install";
|
|
1846
2087
|
if (subcommand === "install" || subcommand === "setup") {
|
|
1847
|
-
|
|
2088
|
+
const commandArgs = args.slice(2);
|
|
2089
|
+
process.exit(
|
|
2090
|
+
runWithCliTelemetry(
|
|
2091
|
+
() => runHermesInstall(commandArgs),
|
|
2092
|
+
{
|
|
2093
|
+
startEvent: "plugin_install_start",
|
|
2094
|
+
successEvent: "plugin_install_success",
|
|
2095
|
+
failedEvent: "plugin_install_failed",
|
|
2096
|
+
commandName: "hermes_install",
|
|
2097
|
+
host: "hermes",
|
|
2098
|
+
args: commandArgs,
|
|
2099
|
+
},
|
|
2100
|
+
),
|
|
2101
|
+
);
|
|
1848
2102
|
}
|
|
1849
2103
|
console.error(`Unknown hermes command: ${subcommand}`);
|
|
1850
2104
|
console.error("Run `openclaw-agent-wallet hermes install --yes` to connect Hermes Agent.");
|
|
@@ -1854,7 +2108,20 @@ if (command === "hermes") {
|
|
|
1854
2108
|
if (command === "codex") {
|
|
1855
2109
|
const subcommand = args[1] || "install";
|
|
1856
2110
|
if (subcommand === "install" || subcommand === "setup") {
|
|
1857
|
-
|
|
2111
|
+
const commandArgs = args.slice(2);
|
|
2112
|
+
process.exit(
|
|
2113
|
+
runWithCliTelemetry(
|
|
2114
|
+
() => runCodexInstall(commandArgs),
|
|
2115
|
+
{
|
|
2116
|
+
startEvent: "plugin_install_start",
|
|
2117
|
+
successEvent: "plugin_install_success",
|
|
2118
|
+
failedEvent: "plugin_install_failed",
|
|
2119
|
+
commandName: "codex_install",
|
|
2120
|
+
host: "codex",
|
|
2121
|
+
args: commandArgs,
|
|
2122
|
+
},
|
|
2123
|
+
),
|
|
2124
|
+
);
|
|
1858
2125
|
}
|
|
1859
2126
|
console.error(`Unknown codex command: ${subcommand}`);
|
|
1860
2127
|
console.error("Run `openclaw-agent-wallet codex install --yes` to connect Codex.");
|
|
@@ -1864,7 +2131,20 @@ if (command === "codex") {
|
|
|
1864
2131
|
if (command === "claude-code") {
|
|
1865
2132
|
const subcommand = args[1] || "install";
|
|
1866
2133
|
if (subcommand === "install" || subcommand === "setup") {
|
|
1867
|
-
|
|
2134
|
+
const commandArgs = args.slice(2);
|
|
2135
|
+
process.exit(
|
|
2136
|
+
runWithCliTelemetry(
|
|
2137
|
+
() => runClaudeCodeInstall(commandArgs),
|
|
2138
|
+
{
|
|
2139
|
+
startEvent: "plugin_install_start",
|
|
2140
|
+
successEvent: "plugin_install_success",
|
|
2141
|
+
failedEvent: "plugin_install_failed",
|
|
2142
|
+
commandName: "claude_code_install",
|
|
2143
|
+
host: "claude-code",
|
|
2144
|
+
args: commandArgs,
|
|
2145
|
+
},
|
|
2146
|
+
),
|
|
2147
|
+
);
|
|
1868
2148
|
}
|
|
1869
2149
|
console.error(`Unknown claude-code command: ${subcommand}`);
|
|
1870
2150
|
console.error("Run `openclaw-agent-wallet claude-code install --yes` to connect Claude Code.");
|
|
@@ -1872,7 +2152,18 @@ if (command === "claude-code") {
|
|
|
1872
2152
|
}
|
|
1873
2153
|
|
|
1874
2154
|
if (command.startsWith("-")) {
|
|
1875
|
-
process.exit(
|
|
2155
|
+
process.exit(
|
|
2156
|
+
runWithCliTelemetry(
|
|
2157
|
+
() => runInstall(args, { commandName: "install" }),
|
|
2158
|
+
{
|
|
2159
|
+
startEvent: "install_start",
|
|
2160
|
+
successEvent: "install_success",
|
|
2161
|
+
failedEvent: "install_failed",
|
|
2162
|
+
commandName: "install",
|
|
2163
|
+
args,
|
|
2164
|
+
},
|
|
2165
|
+
),
|
|
2166
|
+
);
|
|
1876
2167
|
}
|
|
1877
2168
|
|
|
1878
2169
|
console.error(`Unknown command: ${command}`);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-wallet",
|
|
3
3
|
"displayName": "Agent Wallet",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.50",
|
|
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"
|
|
@@ -51,6 +51,11 @@ no-op once the backend is healthy.
|
|
|
51
51
|
- `/wallet-setup` — install (or repair) the backend explicitly instead of
|
|
52
52
|
waiting for the hook (requires `/reload-plugins` first so the command is
|
|
53
53
|
registered).
|
|
54
|
+
- `/agentlayer-autonomous-approve` — enable high-trust autonomous Base swaps
|
|
55
|
+
(`swap_evm_tokens` / `swap_evm_uniswap_tokens` on Base only) without
|
|
56
|
+
per-transaction approvals.
|
|
57
|
+
- `/agentlayer-autonomous-revoke` — disable that autonomous Base swap
|
|
58
|
+
permission.
|
|
54
59
|
- `AGENT_WALLET_AUTO_BOOTSTRAP=0` — opt out of the auto-install: the
|
|
55
60
|
`SessionStart` hook then only reminds you to run `/wallet-setup` instead of
|
|
56
61
|
installing the backend itself.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Enable AgentLayer autonomous Base swaps without per-transaction approvals.
|
|
3
|
+
allowed-tools: mcp__agent_wallet__agentlayer_autonomous_approve, mcp__agent_wallet__agentlayer_autonomous_status
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Enable high-trust autonomous Base swaps for AgentLayer.
|
|
7
|
+
|
|
8
|
+
Call `agentlayer_autonomous_approve` with:
|
|
9
|
+
|
|
10
|
+
```json
|
|
11
|
+
{
|
|
12
|
+
"scope": "base_swaps",
|
|
13
|
+
"purpose": "User requested autonomous Base swaps from Claude Code.",
|
|
14
|
+
"user_intent": true
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Then call `agentlayer_autonomous_status` and report whether `base_swaps` is enabled.
|
|
19
|
+
|
|
20
|
+
Be explicit in the response:
|
|
21
|
+
|
|
22
|
+
- This only removes per-transaction approvals for Base Velora/Uniswap swap execute calls.
|
|
23
|
+
- It does not authorize transfers, withdrawals, lending, staking, bridges, Solana swaps, or non-Base networks.
|
|
24
|
+
- The user can run `/agentlayer-autonomous-revoke` to disable it.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Disable AgentLayer autonomous Base swaps.
|
|
3
|
+
allowed-tools: mcp__agent_wallet__agentlayer_autonomous_revoke, mcp__agent_wallet__agentlayer_autonomous_status
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Disable high-trust autonomous Base swaps for AgentLayer.
|
|
7
|
+
|
|
8
|
+
Call `agentlayer_autonomous_revoke` with:
|
|
9
|
+
|
|
10
|
+
```json
|
|
11
|
+
{
|
|
12
|
+
"scope": "base_swaps"
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Then call `agentlayer_autonomous_status` and report whether `base_swaps` is disabled.
|
|
@@ -113,7 +113,7 @@ fi
|
|
|
113
113
|
|
|
114
114
|
log "Installing the AgentLayer wallet backend runtime via npm (this may take a minute)…"
|
|
115
115
|
log " -> npx $PACKAGE_SPEC install --yes"
|
|
116
|
-
if ! npx -y "$PACKAGE_SPEC" install --yes; then
|
|
116
|
+
if ! AGENT_WALLET_INSTALL_SOURCE=claude_marketplace_bootstrap npx -y "$PACKAGE_SPEC" install --yes; then
|
|
117
117
|
log "Backend install failed. Ensure Node.js 18+ and Python >= 3.10 (with venv) are installed, then re-run /wallet-setup."
|
|
118
118
|
exit 1
|
|
119
119
|
fi
|
|
@@ -123,7 +123,7 @@ fi
|
|
|
123
123
|
# --skip-enable: the plugin is already registered via the marketplace, so we only
|
|
124
124
|
# want the file pinning, not another `claude plugin install`.
|
|
125
125
|
log "Wiring the Claude Code bridge to the installed runtime…"
|
|
126
|
-
npx -y "$PACKAGE_SPEC" claude-code install --yes --skip-enable || \
|
|
126
|
+
AGENT_WALLET_INSTALL_SOURCE=claude_marketplace_bootstrap npx -y "$PACKAGE_SPEC" claude-code install --yes --skip-enable || \
|
|
127
127
|
log "Note: could not re-pin the Claude Code bridge automatically; it will still resolve the default runtime."
|
|
128
128
|
|
|
129
129
|
if backend_ready; then
|
|
@@ -59,6 +59,7 @@ PREVIEW_BOUND_SWAP_TOOLS = {
|
|
|
59
59
|
"flash_trade_open_position",
|
|
60
60
|
"flash_trade_close_position",
|
|
61
61
|
}
|
|
62
|
+
AUTONOMOUS_BASE_SWAP_TOOLS = {"swap_evm_tokens", "swap_evm_uniswap_tokens"}
|
|
62
63
|
APPROVAL_PREVIEW_TOOL_ALIASES = {
|
|
63
64
|
"x402_pay_request": "x402_preview_request",
|
|
64
65
|
}
|
|
@@ -642,6 +643,21 @@ def _requires_approved_preview_payload(tool_name: str, params: dict[str, Any]) -
|
|
|
642
643
|
return tool_name in PREVIEW_BOUND_SWAP_TOOLS
|
|
643
644
|
|
|
644
645
|
|
|
646
|
+
def _should_let_backend_authorize_autonomous_base_swap(
|
|
647
|
+
tool_name: str,
|
|
648
|
+
params: dict[str, Any],
|
|
649
|
+
config: dict[str, Any],
|
|
650
|
+
) -> bool:
|
|
651
|
+
if tool_name not in AUTONOMOUS_BASE_SWAP_TOOLS:
|
|
652
|
+
return False
|
|
653
|
+
if str(params.get("mode") or "") != "execute":
|
|
654
|
+
return False
|
|
655
|
+
if str(params.get("approval_token") or "").strip():
|
|
656
|
+
return False
|
|
657
|
+
network = str(params.get("network") or config.get("network") or selected_evm_network or "").strip().lower()
|
|
658
|
+
return network == "base"
|
|
659
|
+
|
|
660
|
+
|
|
645
661
|
def _looks_like_approval_context_error(message: str) -> bool:
|
|
646
662
|
text = str(message or "").lower()
|
|
647
663
|
return any(
|
|
@@ -696,6 +712,8 @@ def _attach_approval_for_execute(
|
|
|
696
712
|
effective_params["_approved_preview"] = cached_preview
|
|
697
713
|
if effective_params.get("approval_token"):
|
|
698
714
|
return None
|
|
715
|
+
if _should_let_backend_authorize_autonomous_base_swap(tool_name, effective_params, config):
|
|
716
|
+
return None
|
|
699
717
|
raise RuntimeError(APPROVAL_CONTEXT_MISSING_MESSAGE)
|
|
700
718
|
|
|
701
719
|
|
package/package.json
CHANGED