@agentlayer.tech/wallet 0.1.18 → 0.1.20

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 (27) hide show
  1. package/.openclaw/AGENTS.md +0 -7
  2. package/.openclaw/extensions/agent-wallet/README.md +3 -2
  3. package/.openclaw/extensions/agent-wallet/package.json +1 -1
  4. package/README.md +111 -3
  5. package/RELEASING.md +5 -15
  6. package/agent-wallet/README.md +3 -0
  7. package/agent-wallet/agent_wallet/config.py +11 -0
  8. package/agent-wallet/agent_wallet/evm_user_wallets.py +310 -2
  9. package/agent-wallet/agent_wallet/openclaw_runtime.py +10 -41
  10. package/agent-wallet/agent_wallet/providers/wdk_evm_local.py +52 -0
  11. package/agent-wallet/pyproject.toml +1 -1
  12. package/agent-wallet/scripts/build_release_bundle.py +1 -0
  13. package/agent-wallet/scripts/flash-sdk-bridge/bridge.mjs +21 -11
  14. package/agent-wallet/scripts/install_agent_wallet.py +250 -14
  15. package/agent-wallet/scripts/install_openclaw_local_config.py +20 -51
  16. package/agent-wallet/scripts/install_openclaw_sealed_keys.py +9 -1
  17. package/bin/openclaw-agent-wallet.mjs +282 -24
  18. package/package.json +1 -2
  19. package/.openclaw/extensions/pay-bridge/README.md +0 -38
  20. package/.openclaw/extensions/pay-bridge/core.mjs +0 -287
  21. package/.openclaw/extensions/pay-bridge/dist/core.mjs +0 -287
  22. package/.openclaw/extensions/pay-bridge/dist/index.js +0 -196
  23. package/.openclaw/extensions/pay-bridge/index.ts +0 -196
  24. package/.openclaw/extensions/pay-bridge/openclaw.plugin.json +0 -34
  25. package/.openclaw/extensions/pay-bridge/package.json +0 -49
  26. package/.openclaw/extensions/pay-bridge/skills/pay-operator/SKILL.md +0 -20
  27. package/.openclaw/extensions/pay-bridge/smoke_pay_bridge.mjs +0 -38
@@ -13,6 +13,8 @@ const setupPath = path.join(packageRoot, "setup.sh");
13
13
  const packageJsonPath = path.join(packageRoot, "package.json");
14
14
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
15
15
  const packageVersion = packageJson.version;
16
+ const UPDATE_CLI_PATH_ENV = "OPENCLAW_AGENT_WALLET_UPDATE_CLI_PATH";
17
+ const UPDATE_PACKAGE_SPEC_ENV = "OPENCLAW_AGENT_WALLET_UPDATE_PACKAGE_SPEC";
16
18
 
17
19
  function printHelp() {
18
20
  console.log(`openclaw-agent-wallet
@@ -37,6 +39,7 @@ Examples:
37
39
  npx @agentlayer.tech/wallet hermes install --yes
38
40
  npx @agentlayer.tech/wallet install --backend none
39
41
  npx @agentlayer.tech/wallet update --yes
42
+ npx @agentlayer.tech/wallet update --yes --dry-run
40
43
  npx @agentlayer.tech/wallet status
41
44
 
42
45
  The installer writes a versioned runtime under:
@@ -46,7 +49,8 @@ After a successful install it switches:
46
49
  ~/.openclaw/agent-wallet-runtime/current
47
50
 
48
51
  Wallet files and sealed secrets remain under OPENCLAW_HOME and are not replaced
49
- by updates.`);
52
+ by updates. The update command fetches the latest published npm package and
53
+ reuses shared dependency snapshots when possible.`);
50
54
  }
51
55
 
52
56
  function expandHome(value) {
@@ -207,6 +211,80 @@ function activeVersion(env = process.env) {
207
211
  return path.basename(path.resolve(path.dirname(current), link));
208
212
  }
209
213
 
214
+ function listDirectories(rootPath) {
215
+ try {
216
+ return fs
217
+ .readdirSync(rootPath, { withFileTypes: true })
218
+ .filter((entry) => entry.isDirectory())
219
+ .map((entry) => entry.name)
220
+ .sort();
221
+ } catch (error) {
222
+ if (error?.code === "ENOENT") return [];
223
+ throw error;
224
+ }
225
+ }
226
+
227
+ function activePythonRuntimeInfo(env = process.env) {
228
+ const currentRoot = resolvedCurrentRuntimeRoot(env);
229
+ if (!currentRoot) return null;
230
+ const linkPath = path.join(currentRoot, "agent-wallet", ".runtime-venv");
231
+ const exists = fs.existsSync(linkPath);
232
+ const symlinkTarget = readLinkOrNull(linkPath);
233
+ const resolvedTarget = exists ? path.resolve(path.dirname(linkPath), symlinkTarget || ".") : null;
234
+ return {
235
+ link_path: linkPath,
236
+ exists,
237
+ symlink: Boolean(symlinkTarget),
238
+ target: symlinkTarget || null,
239
+ resolved_target: resolvedTarget,
240
+ shared: Boolean(resolvedTarget && resolvedTarget.includes(`${path.sep}shared${path.sep}python${path.sep}`)),
241
+ };
242
+ }
243
+
244
+ function activeNodeRuntimeInfo(env = process.env) {
245
+ const currentRoot = resolvedCurrentRuntimeRoot(env);
246
+ if (!currentRoot) return [];
247
+ const projects = [
248
+ path.join(currentRoot, "wdk-btc-wallet"),
249
+ path.join(currentRoot, "wdk-evm-wallet"),
250
+ path.join(currentRoot, "agent-wallet", "scripts", "flash-sdk-bridge"),
251
+ ];
252
+ return projects
253
+ .filter((projectRoot) => fs.existsSync(path.join(projectRoot, "package.json")))
254
+ .map((projectRoot) => {
255
+ const linkPath = path.join(projectRoot, "node_modules");
256
+ const exists = fs.existsSync(linkPath);
257
+ const symlinkTarget = readLinkOrNull(linkPath);
258
+ const resolvedTarget = exists ? path.resolve(path.dirname(linkPath), symlinkTarget || ".") : null;
259
+ return {
260
+ project_root: projectRoot,
261
+ project_name: path.basename(projectRoot),
262
+ link_path: linkPath,
263
+ exists,
264
+ symlink: Boolean(symlinkTarget),
265
+ target: symlinkTarget || null,
266
+ resolved_target: resolvedTarget,
267
+ shared: Boolean(resolvedTarget && resolvedTarget.includes(`${path.sep}shared${path.sep}node${path.sep}`)),
268
+ };
269
+ });
270
+ }
271
+
272
+ function sharedSnapshotInventory(env = process.env) {
273
+ const runtimeBase = resolveRuntimeBase(env);
274
+ const sharedRoot = path.join(runtimeBase, "shared");
275
+ const pythonRoot = path.join(sharedRoot, "python");
276
+ const nodeRoot = path.join(sharedRoot, "node");
277
+ const nodeProjects = listDirectories(nodeRoot).map((projectName) => ({
278
+ project_name: projectName,
279
+ snapshots: listDirectories(path.join(nodeRoot, projectName)),
280
+ }));
281
+ return {
282
+ shared_root: sharedRoot,
283
+ python_snapshots: listDirectories(pythonRoot),
284
+ node_projects: nodeProjects,
285
+ };
286
+ }
287
+
210
288
  function switchSymlink(linkPath, targetPath) {
211
289
  const absoluteTarget = path.resolve(targetPath);
212
290
  if (!fs.existsSync(absoluteTarget)) {
@@ -216,7 +294,7 @@ function switchSymlink(linkPath, targetPath) {
216
294
  fs.mkdirSync(path.dirname(linkPath), { recursive: true });
217
295
  const tempLink = `${linkPath}.tmp-${process.pid}`;
218
296
  try {
219
- fs.rmSync(tempLink, { force: true, recursive: false });
297
+ fs.rmSync(tempLink, { force: true, recursive: true });
220
298
  } catch {
221
299
  // ignored
222
300
  }
@@ -225,7 +303,7 @@ function switchSymlink(linkPath, targetPath) {
225
303
  try {
226
304
  const existing = fs.lstatSync(linkPath);
227
305
  if (!existing.isSymbolicLink()) {
228
- fs.rmSync(tempLink, { force: true });
306
+ fs.rmSync(tempLink, { force: true, recursive: true });
229
307
  throw new Error(`${linkPath} exists and is not a symlink. Refusing to replace it.`);
230
308
  }
231
309
  } catch (error) {
@@ -268,6 +346,60 @@ function withoutCliOnlyArgs(args) {
268
346
  return output;
269
347
  }
270
348
 
349
+ function extractTrailingJson(text) {
350
+ const raw = String(text || "");
351
+ const newlineStart = raw.lastIndexOf("\n{");
352
+ const start = newlineStart >= 0 ? newlineStart + 1 : raw.indexOf("{");
353
+ if (start < 0) {
354
+ throw new Error("Could not find JSON payload in command output.");
355
+ }
356
+ return JSON.parse(raw.slice(start));
357
+ }
358
+
359
+ function pathVersionFromRuntimeRoot(runtimeRoot) {
360
+ if (!runtimeRoot) return null;
361
+ const normalized = path.resolve(String(runtimeRoot));
362
+ if (path.basename(path.dirname(normalized)) !== "releases") return null;
363
+ return path.basename(normalized);
364
+ }
365
+
366
+ function resolveCliPackageMeta(cliPath) {
367
+ try {
368
+ const root = path.resolve(path.dirname(cliPath), "..");
369
+ const pkg = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
370
+ return {
371
+ name: String(pkg.name || packageJson.name),
372
+ version: String(pkg.version || ""),
373
+ root,
374
+ };
375
+ } catch {
376
+ return {
377
+ name: packageJson.name,
378
+ version: "",
379
+ root: "",
380
+ };
381
+ }
382
+ }
383
+
384
+ function summarizeDependencyPlan(payload) {
385
+ const python = payload?.python_runtime && typeof payload.python_runtime === "object"
386
+ ? {
387
+ action: payload.python_runtime.action || "unknown",
388
+ shared: Boolean(payload.python_runtime.shared),
389
+ fingerprint: payload.python_runtime.fingerprint || null,
390
+ }
391
+ : null;
392
+ const nodeProjects = Array.isArray(payload?.node_runtime?.projects)
393
+ ? payload.node_runtime.projects.map((project) => ({
394
+ project_root: project.project_root,
395
+ action: project.action || "unknown",
396
+ shared: Boolean(project.shared),
397
+ fingerprint: project.fingerprint || null,
398
+ }))
399
+ : [];
400
+ return { python, node_projects: nodeProjects };
401
+ }
402
+
271
403
  function token() {
272
404
  return crypto.randomBytes(32).toString("base64url");
273
405
  }
@@ -442,24 +574,25 @@ function runDoctor() {
442
574
  return missing.length === 0 ? 0 : 1;
443
575
  }
444
576
 
445
- function runStatus() {
446
- console.log(
447
- JSON.stringify(
448
- {
449
- ok: true,
450
- package_name: packageJson.name,
451
- package_version: packageVersion,
452
- openclaw_home: resolveOpenclawHome(),
453
- runtime_base: resolveRuntimeBase(),
454
- current_runtime: currentRuntimePath(),
455
- previous_runtime: readLinkOrNull(previousRuntimePath()),
456
- active_version: activeVersion(),
457
- available_releases: listReleases(),
458
- },
459
- null,
460
- 2,
461
- ),
462
- );
577
+ function runStatus(args = []) {
578
+ const payload = {
579
+ ok: true,
580
+ package_name: packageJson.name,
581
+ package_version: packageVersion,
582
+ openclaw_home: resolveOpenclawHome(),
583
+ runtime_base: resolveRuntimeBase(),
584
+ current_runtime: currentRuntimePath(),
585
+ previous_runtime: readLinkOrNull(previousRuntimePath()),
586
+ active_version: activeVersion(),
587
+ available_releases: listReleases(),
588
+ };
589
+ if (hasFlag(args, "--verbose")) {
590
+ payload.verbose = true;
591
+ payload.active_python_runtime = activePythonRuntimeInfo();
592
+ payload.active_node_runtimes = activeNodeRuntimeInfo();
593
+ payload.shared_snapshot_inventory = sharedSnapshotInventory();
594
+ }
595
+ console.log(JSON.stringify(payload, null, 2));
463
596
  return 0;
464
597
  }
465
598
 
@@ -467,14 +600,19 @@ function buildInstallerEnv(args) {
467
600
  const env = { ...process.env };
468
601
  const sealedKeysPath = path.join(resolveOpenclawHome(env), "sealed_keys.json");
469
602
  const sealedKeysExist = fs.existsSync(sealedKeysPath);
603
+ const dryRun = hasFlag(args, "--dry-run");
470
604
  if (!env.AGENT_WALLET_BOOT_KEY) {
471
- const existingBootKey = resolveBootKeyFromFile(env) || currentBootKey(env);
605
+ const existingBootKey =
606
+ resolveBootKeyFromFile(env) ||
607
+ readTextIfExists(defaultBootKeyFile(env)).trim() ||
608
+ currentBootKey(env);
472
609
  if (existingBootKey) {
473
610
  env.AGENT_WALLET_BOOT_KEY = existingBootKey;
474
611
  }
475
612
  }
476
613
 
477
614
  const shouldGenerateSecrets =
615
+ !dryRun &&
478
616
  !hasFlag(args, "--no-auto-secrets") &&
479
617
  (hasFlag(args, "--yes") || !env.AGENT_WALLET_BOOT_KEY);
480
618
 
@@ -554,6 +692,14 @@ function runInstall(args, { commandName = "install" } = {}) {
554
692
  });
555
693
  }
556
694
 
695
+ const pythonInfo = activePythonRuntimeInfo(env);
696
+ const nodeInfo = activeNodeRuntimeInfo(env)
697
+ .map((item) => `${item.project_name}:${item.shared ? "shared" : item.exists ? "local" : "missing"}`)
698
+ .join(", ");
699
+ console.error(
700
+ `Update summary: version=${packageVersion} active=${activeVersion(env) || packageVersion} python=${pythonInfo?.shared ? "shared" : pythonInfo?.exists ? "local" : "missing"} node=[${nodeInfo}]`,
701
+ );
702
+
557
703
  console.error(
558
704
  JSON.stringify(
559
705
  {
@@ -572,6 +718,118 @@ function runInstall(args, { commandName = "install" } = {}) {
572
718
  return 0;
573
719
  }
574
720
 
721
+ function resolveUpdatePackageSpec(env = process.env) {
722
+ const explicit = String(env[UPDATE_PACKAGE_SPEC_ENV] || "").trim();
723
+ if (explicit) return explicit;
724
+ return `${packageJson.name}@latest`;
725
+ }
726
+
727
+ function runDelegatedInstallForUpdate(args, { captureOutput = false } = {}) {
728
+ const localCliPath = String(process.env[UPDATE_CLI_PATH_ENV] || "").trim();
729
+ if (localCliPath) {
730
+ const meta = resolveCliPackageMeta(localCliPath);
731
+ const result = spawnSync("node", [localCliPath, "install", ...args], {
732
+ cwd: packageRoot,
733
+ stdio: captureOutput ? "pipe" : "inherit",
734
+ encoding: captureOutput ? "utf8" : undefined,
735
+ env: process.env,
736
+ });
737
+ return {
738
+ result,
739
+ delegated_via: "cli_path",
740
+ target_package_spec: meta.name || packageJson.name,
741
+ target_version_hint: meta.version || null,
742
+ };
743
+ }
744
+
745
+ const npmBin = commandPath("npm");
746
+ if (!npmBin) {
747
+ throw new Error("npm is required for `wallet update`. Install npm or run `npx @agentlayer.tech/wallet install --yes`.");
748
+ }
749
+
750
+ const packageSpec = resolveUpdatePackageSpec();
751
+ const result = spawnSync(
752
+ npmBin,
753
+ ["exec", "--yes", `--package=${packageSpec}`, "openclaw-agent-wallet", "install", ...args],
754
+ {
755
+ cwd: packageRoot,
756
+ stdio: captureOutput ? "pipe" : "inherit",
757
+ encoding: captureOutput ? "utf8" : undefined,
758
+ env: process.env,
759
+ },
760
+ );
761
+ return {
762
+ result,
763
+ delegated_via: "npm_exec",
764
+ target_package_spec: packageSpec,
765
+ target_version_hint: null,
766
+ };
767
+ }
768
+
769
+ function runUpdate(args) {
770
+ const dryRun = hasFlag(args, "--dry-run");
771
+ if (dryRun) {
772
+ try {
773
+ const delegated = runDelegatedInstallForUpdate(args, { captureOutput: true });
774
+ const { result } = delegated;
775
+ if (result.error) {
776
+ console.error(result.error.message);
777
+ return 1;
778
+ }
779
+ if ((result.status ?? 1) !== 0) {
780
+ const stderr = String(result.stderr || "").trim();
781
+ const stdout = String(result.stdout || "").trim();
782
+ if (stderr) process.stderr.write(`${stderr}\n`);
783
+ if (stdout) process.stdout.write(`${stdout}\n`);
784
+ return result.status ?? 1;
785
+ }
786
+ const payload = extractTrailingJson(result.stdout || "");
787
+ const targetVersion =
788
+ delegated.target_version_hint ||
789
+ pathVersionFromRuntimeRoot(payload.runtime_root) ||
790
+ null;
791
+ console.log(
792
+ JSON.stringify(
793
+ {
794
+ ok: true,
795
+ command: "update",
796
+ dry_run: true,
797
+ current_version: activeVersion(),
798
+ installed_cli_version: packageVersion,
799
+ target_package_spec: delegated.target_package_spec,
800
+ target_version: targetVersion,
801
+ delegated_via: delegated.delegated_via,
802
+ runtime_base: resolveRuntimeBase(),
803
+ current_runtime: currentRuntimePath(),
804
+ target_runtime_root: payload.runtime_root,
805
+ dependency_plan: summarizeDependencyPlan(payload),
806
+ install_plan: payload,
807
+ },
808
+ null,
809
+ 2,
810
+ ),
811
+ );
812
+ return 0;
813
+ } catch (error) {
814
+ console.error(error.message);
815
+ return 1;
816
+ }
817
+ }
818
+
819
+ let delegated;
820
+ try {
821
+ delegated = runDelegatedInstallForUpdate(args, { captureOutput: false });
822
+ } catch (error) {
823
+ console.error(error.message);
824
+ return 1;
825
+ }
826
+ if (delegated.result.error) {
827
+ console.error(delegated.result.error.message);
828
+ return 1;
829
+ }
830
+ return delegated.result.status ?? 1;
831
+ }
832
+
575
833
  function runRollback(args) {
576
834
  const requested = parseFlagValue(args, "--to");
577
835
  const current = activeVersion();
@@ -755,7 +1013,7 @@ if (command === "doctor") {
755
1013
  }
756
1014
 
757
1015
  if (command === "status") {
758
- process.exit(runStatus());
1016
+ process.exit(runStatus(args.slice(1)));
759
1017
  }
760
1018
 
761
1019
  if (command === "install" || command === "setup") {
@@ -763,7 +1021,7 @@ if (command === "install" || command === "setup") {
763
1021
  }
764
1022
 
765
1023
  if (command === "update") {
766
- process.exit(runInstall(args.slice(1), { commandName: "update" }));
1024
+ process.exit(runUpdate(args.slice(1)));
767
1025
  }
768
1026
 
769
1027
  if (command === "rollback") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentlayer.tech/wallet",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "NPM installer for the OpenClaw Agent Wallet local runtime.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -40,7 +40,6 @@
40
40
  "agent-wallet/pyproject.toml",
41
41
  ".openclaw/AGENTS.md",
42
42
  ".openclaw/extensions/agent-wallet/",
43
- ".openclaw/extensions/pay-bridge/",
44
43
  "hermes/plugins/agent_wallet/",
45
44
  "wdk-btc-wallet/src/",
46
45
  "wdk-btc-wallet/bootstrap.sh",
@@ -1,38 +0,0 @@
1
- # pay-bridge
2
-
3
- Thin OpenClaw bridge to the locally installed `pay` CLI.
4
-
5
- External install path:
6
-
7
- ```bash
8
- openclaw plugins install clawhub:@agentlayertech/pay-bridge-plugin
9
- ```
10
-
11
- This plugin is intentionally separate from `agent-wallet`:
12
-
13
- - `agent-wallet` remains the execution wallet stack for Solana/EVM/BTC
14
- - `pay-bridge` only discovers and calls paid APIs through `pay`
15
- - the `pay` wallet stays separate from the AgentLayer wallet runtime
16
-
17
- ## Exposed tools
18
-
19
- - `pay_status`
20
- - `pay_wallet_info`
21
- - `pay_search_services`
22
- - `pay_get_service_endpoints`
23
- - `pay_api_request`
24
-
25
- ## Intended workflow
26
-
27
- 1. `pay_status`
28
- 2. `pay_search_services`
29
- 3. `pay_get_service_endpoints`
30
- 4. `pay_api_request`
31
-
32
- `pay_api_request` is deliberately narrow:
33
-
34
- - it requires a `service_fqn`, `resource`, and `url`
35
- - it validates the URL against `pay skills endpoints`
36
- - it requires `purpose` and `user_confirmed=true`
37
-
38
- This keeps the bridge thin and prevents it from becoming a generic arbitrary paid-curl launcher.