@agentlayer.tech/wallet 0.1.90 → 0.1.91
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/openclaw.plugin.json +1 -1
- package/.openclaw/extensions/agent-wallet/package.json +1 -1
- package/CHANGELOG.md +14 -0
- package/README.md +46 -23
- package/VERSION +1 -1
- package/agent-wallet/README.md +26 -1
- package/agent-wallet/UPGRADE_COMPATIBILITY.md +2 -1
- package/agent-wallet/agent_wallet/__init__.py +1 -1
- package/agent-wallet/openclaw.plugin.json +1 -1
- package/agent-wallet/pyproject.toml +1 -1
- package/agent-wallet/scripts/install_agent_wallet.py +197 -11
- package/agent-wallet/scripts/install_openclaw_local_config.py +4 -1
- package/agent-wallet/scripts/install_openclaw_sealed_keys.py +6 -2
- package/bin/lib/host-detection.mjs +236 -0
- package/bin/lib/integrations.mjs +69 -5
- package/bin/openclaw-agent-wallet.mjs +398 -28
- package/claude-code/plugins/agent-wallet/.claude-plugin/plugin.json +1 -1
- package/codex/plugins/agent-wallet/.codex-plugin/plugin.json +1 -1
- package/hermes/plugins/agent_wallet/plugin.yaml +1 -1
- package/package.json +8 -3
- package/wdk-btc-wallet/package.json +1 -1
- package/wdk-evm-wallet/package.json +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "agent-wallet",
|
|
3
3
|
"name": "Agent Wallet",
|
|
4
4
|
"description": "Official OpenClaw plugin bridge for the agent-wallet backends, including Solana, local BTC, and local EVM.",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.91",
|
|
6
6
|
"contracts": {
|
|
7
7
|
"tools": [
|
|
8
8
|
"agentlayer_autonomous_approve",
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- Added a universal npm installer for OpenClaw, Codex, Claude Code, and Hermes.
|
|
6
|
+
`wallet install` now detects installed agent frameworks, supports explicit
|
|
7
|
+
`--hosts`, `--exclude`, and `--runtime-only` selection, and exposes
|
|
8
|
+
`wallet detect --json`. Runtime updates remain managed-only, so discovering a
|
|
9
|
+
new framework later does not silently enroll it. OpenClaw config creation is
|
|
10
|
+
now a host-specific step; wallet files and sealed secrets remain in their
|
|
11
|
+
existing `~/.openclaw` locations and are not replaced during updates.
|
|
12
|
+
|
|
13
|
+
- Confirmed the native OS keystore as the default boot-key backend. `auto`
|
|
14
|
+
prefers macOS Keychain, Windows DPAPI, or Linux Secret Service and uses the
|
|
15
|
+
local `0600` file only as a fallback. The npm installer now warns macOS users
|
|
16
|
+
that the system may request Keychain confirmation; this does not rotate or
|
|
17
|
+
migrate the boot key during ordinary updates.
|
|
18
|
+
|
|
5
19
|
- Hardened Morpho EVM operations with a 30% gas-limit buffer calculated from
|
|
6
20
|
the exact final calldata immediately before sending. Morpho vault and market
|
|
7
21
|
writes now wait for their final on-chain receipt and report reverts instead
|
package/README.md
CHANGED
|
@@ -6,23 +6,32 @@
|
|
|
6
6
|
[](https://nodejs.org/)
|
|
7
7
|
[](https://docs.agent-layer.tech/)
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Install AgentLayer for the agent frameworks already present on the machine:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
npx @agentlayer.tech/wallet install --yes
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
The installer detects OpenClaw, Codex, Claude Code, and Hermes, installs the
|
|
16
|
+
shared local runtime once, and connects every detected host. Without `--yes`,
|
|
17
|
+
an interactive terminal asks about each detected host.
|
|
18
|
+
|
|
19
|
+
To choose hosts explicitly:
|
|
16
20
|
|
|
17
21
|
```bash
|
|
18
|
-
npx @agentlayer.tech/wallet install --yes
|
|
22
|
+
npx @agentlayer.tech/wallet install --yes --hosts codex,claude-code
|
|
23
|
+
npx @agentlayer.tech/wallet install --yes --hosts detected --exclude hermes
|
|
24
|
+
npx @agentlayer.tech/wallet install --yes --runtime-only
|
|
25
|
+
npx @agentlayer.tech/wallet detect --json
|
|
19
26
|
```
|
|
20
27
|
|
|
21
|
-
|
|
28
|
+
The legacy `wallet codex install`, `wallet claude-code install`, and
|
|
29
|
+
`wallet hermes install` commands remain supported as explicit repair/install
|
|
30
|
+
commands.
|
|
22
31
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
32
|
+
On a machine with an existing AgentLayer runtime, a plain `wallet install`
|
|
33
|
+
touches only already-managed hosts. To enroll a framework installed later, run
|
|
34
|
+
`wallet install --yes --hosts detected` or name it explicitly.
|
|
26
35
|
|
|
27
36
|
Or install entirely from inside the Claude Code CLI, via the plugin marketplace
|
|
28
37
|
(no terminal/npx needed) — two commands, then restart:
|
|
@@ -32,13 +41,6 @@ Or install entirely from inside the Claude Code CLI, via the plugin marketplace
|
|
|
32
41
|
/plugin install agent-wallet@agentlayer
|
|
33
42
|
```
|
|
34
43
|
|
|
35
|
-
For Hermes:
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
npx @agentlayer.tech/wallet install --yes && npx @agentlayer.tech/wallet hermes install --yes
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
|
|
42
44
|
AgentLayer is a beta local-first wallet and finance stack for agents.
|
|
43
45
|
|
|
44
46
|
The repository includes:
|
|
@@ -72,7 +74,7 @@ System prerequisites:
|
|
|
72
74
|
- `node` `24.x`
|
|
73
75
|
- `npm`
|
|
74
76
|
|
|
75
|
-
Install the local runtime:
|
|
77
|
+
Install the local runtime and connect detected agent frameworks:
|
|
76
78
|
|
|
77
79
|
```bash
|
|
78
80
|
npx @agentlayer.tech/wallet install --yes
|
|
@@ -102,13 +104,26 @@ The CLI uses a versioned runtime layout:
|
|
|
102
104
|
~/.openclaw/agent-wallet-runtime/current # symlink → active release
|
|
103
105
|
```
|
|
104
106
|
|
|
105
|
-
|
|
107
|
+
`~/.openclaw` remains the shared AgentLayer runtime root even when OpenClaw is
|
|
108
|
+
not installed. A Codex-only or Claude-only install creates the runtime there
|
|
109
|
+
but does not create or patch `openclaw.json`.
|
|
110
|
+
|
|
111
|
+
On first install, `--yes` generates local runtime secrets. The installer stores
|
|
112
|
+
`master_key` and `approval_secret` in `~/.openclaw/sealed_keys.json`. The boot
|
|
113
|
+
key needed to unlock that sealed bundle uses `AGENT_WALLET_KEYSTORE_BACKEND=auto`
|
|
114
|
+
by default: it prefers the native OS keystore and falls back to a local `0600`
|
|
115
|
+
file only when the native backend is unavailable. On macOS this means the login
|
|
116
|
+
Keychain (`service=ai.agentlayer.wallet`, `account=boot_key`). macOS may show a
|
|
117
|
+
Keychain access or password confirmation depending on the current Keychain
|
|
118
|
+
state; approve it only when you initiated the AgentLayer install or update.
|
|
119
|
+
Existing installs keep the same boot-key value during upgrades.
|
|
106
120
|
|
|
107
121
|
Useful CLI commands (require the global install above):
|
|
108
122
|
|
|
109
123
|
```bash
|
|
110
124
|
wallet status # show active runtime version and health
|
|
111
125
|
wallet doctor # diagnose common config problems
|
|
126
|
+
wallet detect --json # show detected and already-managed hosts
|
|
112
127
|
wallet update --yes # upgrade to the latest published version
|
|
113
128
|
wallet update --yes --dry-run # preview the upgrade without applying it
|
|
114
129
|
wallet rollback # revert to the previous release
|
|
@@ -116,7 +131,14 @@ wallet hermes install --yes # (re)connect Hermes to the current runtime
|
|
|
116
131
|
wallet codex install --yes # (re)connect Codex to the current runtime
|
|
117
132
|
```
|
|
118
133
|
|
|
119
|
-
`wallet update --yes` fetches the latest npm package, installs it under
|
|
134
|
+
`wallet update --yes` fetches the latest npm package, installs it under
|
|
135
|
+
`releases/<version>`, and flips the `current` symlink. Updates repair only hosts
|
|
136
|
+
already recorded as AgentLayer-managed; a newly detected framework is never
|
|
137
|
+
silently enrolled during update. Python and Node dependency snapshots are
|
|
138
|
+
reused when the dependency fingerprint has not changed, so subsequent updates
|
|
139
|
+
are fast. All frameworks that read from `current/` (Claude Code, Codex, Hermes)
|
|
140
|
+
pick up the new code automatically on their next session start; OpenClaw
|
|
141
|
+
requires a gateway restart to reload its TypeScript extension.
|
|
120
142
|
|
|
121
143
|
## Native OpenClaw plugin installs
|
|
122
144
|
|
|
@@ -280,10 +302,10 @@ For the default Solana flow, run the installer directly:
|
|
|
280
302
|
npx @agentlayer.tech/wallet install --yes
|
|
281
303
|
```
|
|
282
304
|
|
|
283
|
-
That installs the runtime,
|
|
305
|
+
That installs the runtime, connects each detected framework, generates local
|
|
284
306
|
runtime secrets when missing, and creates the first encrypted per-user Solana
|
|
285
|
-
mainnet wallet.
|
|
286
|
-
not the private key.
|
|
307
|
+
mainnet wallet. `openclaw.json` is patched only when OpenClaw is selected. The
|
|
308
|
+
agent receives the public address and guarded wallet tools, not the private key.
|
|
287
309
|
|
|
288
310
|
BTC and EVM are separate host-side setup flows.
|
|
289
311
|
|
|
@@ -377,10 +399,11 @@ The installer then:
|
|
|
377
399
|
- creates `agent-wallet/.env` from `agent-wallet/.env.example` if it does not exist
|
|
378
400
|
- creates `agent-wallet/.runtime-venv` and installs the Python backend
|
|
379
401
|
- installs Node dependencies for `wdk-btc-wallet`, `wdk-evm-wallet`, and `flash-sdk-bridge`
|
|
380
|
-
- creates a minimal `~/.openclaw/openclaw.json` if one does not exist
|
|
381
402
|
- if the required secrets are already present, writes or updates `~/.openclaw/sealed_keys.json`
|
|
382
|
-
-
|
|
383
|
-
-
|
|
403
|
+
- detects OpenClaw, Codex, Claude Code, and Hermes before changing host config
|
|
404
|
+
- connects the selected hosts to `agent-wallet-runtime/current`
|
|
405
|
+
- creates or patches `~/.openclaw/openclaw.json` only when OpenClaw is selected
|
|
406
|
+
- records non-secret ownership metadata so later updates repair only managed hosts
|
|
384
407
|
|
|
385
408
|
When the installer reaches the final config step, the default plugin config is:
|
|
386
409
|
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.1.
|
|
1
|
+
0.1.91
|
package/agent-wallet/README.md
CHANGED
|
@@ -138,7 +138,20 @@ If you want a simpler local setup flow, use the installer:
|
|
|
138
138
|
sh ../setup.sh
|
|
139
139
|
```
|
|
140
140
|
|
|
141
|
-
|
|
141
|
+
The public npm entrypoint is a universal host orchestrator:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
npx @agentlayer.tech/wallet install --yes
|
|
145
|
+
npx @agentlayer.tech/wallet install --yes --hosts codex,claude-code
|
|
146
|
+
npx @agentlayer.tech/wallet detect --json
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
It detects OpenClaw, Codex, Claude Code, and Hermes before installation. The
|
|
150
|
+
direct `setup.sh`/Python path retains its OpenClaw-oriented default for
|
|
151
|
+
backward compatibility; the npm CLI passes `--no-configure-openclaw` and lets
|
|
152
|
+
the selected OpenClaw host adapter own `openclaw.json`.
|
|
153
|
+
|
|
154
|
+
If you already use a local coding agent on the same machine, it can run this installer for you. You can simply ask the agent to install the wallet, and it can perform the file setup, Python setup, and selected host-bridge setup automatically.
|
|
142
155
|
|
|
143
156
|
By default it will:
|
|
144
157
|
|
|
@@ -562,6 +575,18 @@ That native plugin package is additive. Keep the existing runtime installer for
|
|
|
562
575
|
npx @agentlayer.tech/wallet install --yes
|
|
563
576
|
```
|
|
564
577
|
|
|
578
|
+
To bind a welcome invite from `https://www.agent-layer.tech/onboard` to the
|
|
579
|
+
local Base address during installation:
|
|
580
|
+
|
|
581
|
+
```bash
|
|
582
|
+
npx @agentlayer.tech/wallet install --yes --invite alw_...
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
The installer sends only the one-time invite and the public EVM address to the
|
|
586
|
+
AgentLayer onboarding API. It never sends a private key, seed, wallet password,
|
|
587
|
+
local user ID, or telemetry install ID. A network or EVM provisioning failure
|
|
588
|
+
does not consume the invite and does not roll back the wallet installation.
|
|
589
|
+
|
|
565
590
|
The ClawHub plugin package auto-checks `~/.openclaw/agent-wallet-runtime/current/agent-wallet` before it falls back to a local workspace checkout.
|
|
566
591
|
|
|
567
592
|
Public-safe helper scripts are available in `agent-wallet/scripts/`:
|
|
@@ -20,8 +20,9 @@ Raising the floor requires all of the following:
|
|
|
20
20
|
|
|
21
21
|
| Path | Purpose | Removal condition |
|
|
22
22
|
|---|---|---|
|
|
23
|
-
| JavaScript boot-key fallback | Updates runtimes that predate the verified Python installer resolver. | The supported floor includes `resolve_boot_key_for_installer
|
|
23
|
+
| JavaScript boot-key fallback | Updates runtimes that predate the verified Python installer resolver. | The supported floor no longer includes releases without `resolve_boot_key_for_installer`; until then, isolated CI upgrades the published `0.1.53` package to the packed candidate and verifies boot-key, sealed-bundle, wallet-file, and address identity. |
|
|
24
24
|
| Directory runtime-pointer migration | Converts pre-versioned `current/` directories into release entries. | The supported floor guarantees symlink-based runtime pointers. |
|
|
25
|
+
| OpenClaw legacy adoption | Recognizes an AgentLayer entry whose package or extension path belongs to the active runtime home. | The supported floor guarantees an `integrations.json` OpenClaw ownership entry. |
|
|
25
26
|
| Hermes legacy adoption | Recognizes an AgentLayer plugin installed before the ownership registry. | The supported floor guarantees an `integrations.json` Hermes ownership entry. |
|
|
26
27
|
| Codex legacy adoption | Recognizes a matching local marketplace registration and AgentLayer manifest. | The supported floor guarantees an `integrations.json` Codex ownership entry. |
|
|
27
28
|
| Claude Code legacy adoption | Recognizes the AgentLayer marketplace and plugin manifest before registry ownership. | The supported floor guarantees an `integrations.json` Claude Code ownership entry. |
|
|
@@ -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.91",
|
|
6
6
|
"skills": ["skills/wallet-operator"],
|
|
7
7
|
"configSchema": {
|
|
8
8
|
"type": "object",
|
|
@@ -7,13 +7,20 @@ import hashlib
|
|
|
7
7
|
import json
|
|
8
8
|
import os
|
|
9
9
|
import platform
|
|
10
|
+
import re
|
|
10
11
|
import shutil
|
|
11
12
|
import subprocess
|
|
12
13
|
import sys
|
|
13
14
|
import tempfile
|
|
15
|
+
import urllib.error
|
|
16
|
+
import urllib.request
|
|
14
17
|
import venv
|
|
15
18
|
from pathlib import Path
|
|
16
19
|
|
|
20
|
+
WELCOME_INVITE_PATTERN = re.compile(r"^alw_[A-Za-z0-9_-]{43}$")
|
|
21
|
+
DEFAULT_ONBOARDING_BIND_URL = "https://www.agent-layer.tech/api/onboarding/bind-wallet"
|
|
22
|
+
ONBOARDING_HTTP_TIMEOUT_SECONDS = 5.0
|
|
23
|
+
|
|
17
24
|
INCLUDED_RUNTIME_ROOT_FILES = [
|
|
18
25
|
".env.example",
|
|
19
26
|
"AGENTS.md",
|
|
@@ -192,9 +199,16 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
192
199
|
parser.add_argument("--network", default="mainnet")
|
|
193
200
|
parser.add_argument("--rpc-url", default="")
|
|
194
201
|
parser.add_argument("--rpc-urls", default="")
|
|
202
|
+
parser.add_argument("--invite", default="")
|
|
195
203
|
parser.add_argument("--sign-only", action=argparse.BooleanOptionalAction, default=False)
|
|
196
204
|
parser.add_argument("--sync-runtime", action=argparse.BooleanOptionalAction, default=True)
|
|
197
205
|
parser.add_argument("--install-from-runtime", action=argparse.BooleanOptionalAction, default=False)
|
|
206
|
+
parser.add_argument(
|
|
207
|
+
"--configure-openclaw",
|
|
208
|
+
action=argparse.BooleanOptionalAction,
|
|
209
|
+
default=True,
|
|
210
|
+
help="Patch openclaw.json during install (disabled by the universal host orchestrator).",
|
|
211
|
+
)
|
|
198
212
|
parser.add_argument("--skip-python-setup", action=argparse.BooleanOptionalAction, default=False)
|
|
199
213
|
parser.add_argument("--skip-node-setup", action=argparse.BooleanOptionalAction, default=False)
|
|
200
214
|
parser.add_argument("--dry-run", action=argparse.BooleanOptionalAction, default=False)
|
|
@@ -856,6 +870,149 @@ def _bootstrap_evm_wallet(
|
|
|
856
870
|
}
|
|
857
871
|
|
|
858
872
|
|
|
873
|
+
def _onboarding_bind_url() -> str:
|
|
874
|
+
return (
|
|
875
|
+
os.getenv("AGENTLAYER_ONBOARDING_BIND_URL", "").strip()
|
|
876
|
+
or DEFAULT_ONBOARDING_BIND_URL
|
|
877
|
+
)
|
|
878
|
+
|
|
879
|
+
|
|
880
|
+
def _onboarding_error_code(payload: object, fallback: str) -> str:
|
|
881
|
+
if not isinstance(payload, dict):
|
|
882
|
+
return fallback
|
|
883
|
+
code = str(payload.get("error") or "").strip().lower()
|
|
884
|
+
allowed = {
|
|
885
|
+
"address_already_used",
|
|
886
|
+
"internal_error",
|
|
887
|
+
"invalid_base_address",
|
|
888
|
+
"invalid_invite",
|
|
889
|
+
"invite_already_bound",
|
|
890
|
+
"invite_expired",
|
|
891
|
+
"invite_revoked",
|
|
892
|
+
"request_too_large",
|
|
893
|
+
}
|
|
894
|
+
return code if code in allowed else fallback
|
|
895
|
+
|
|
896
|
+
|
|
897
|
+
def _decode_onboarding_response(raw: bytes) -> object:
|
|
898
|
+
try:
|
|
899
|
+
return json.loads(raw.decode("utf-8"))
|
|
900
|
+
except (UnicodeDecodeError, json.JSONDecodeError):
|
|
901
|
+
return {}
|
|
902
|
+
|
|
903
|
+
|
|
904
|
+
def _bind_welcome_invite(
|
|
905
|
+
invite: str,
|
|
906
|
+
address: str,
|
|
907
|
+
*,
|
|
908
|
+
api_url: str | None = None,
|
|
909
|
+
timeout_seconds: float = ONBOARDING_HTTP_TIMEOUT_SECONDS,
|
|
910
|
+
opener: object = urllib.request.urlopen,
|
|
911
|
+
attempts: int = 2,
|
|
912
|
+
) -> dict[str, object]:
|
|
913
|
+
normalized_invite = invite.strip()
|
|
914
|
+
normalized_address = address.strip()
|
|
915
|
+
if not WELCOME_INVITE_PATTERN.fullmatch(normalized_invite):
|
|
916
|
+
return {"ok": False, "status": "invalid_invite", "retryable": False}
|
|
917
|
+
if not re.fullmatch(r"0x[0-9a-fA-F]{40}", normalized_address):
|
|
918
|
+
return {"ok": False, "status": "invalid_base_address", "retryable": False}
|
|
919
|
+
|
|
920
|
+
body = json.dumps({"address": normalized_address}).encode("utf-8")
|
|
921
|
+
request = urllib.request.Request(
|
|
922
|
+
api_url or _onboarding_bind_url(),
|
|
923
|
+
data=body,
|
|
924
|
+
method="POST",
|
|
925
|
+
headers={
|
|
926
|
+
"Authorization": f"Bearer {normalized_invite}",
|
|
927
|
+
"Content-Type": "application/json",
|
|
928
|
+
"Accept": "application/json",
|
|
929
|
+
"User-Agent": "AgentLayer-Wallet-Installer",
|
|
930
|
+
},
|
|
931
|
+
)
|
|
932
|
+
max_attempts = max(1, min(int(attempts), 3))
|
|
933
|
+
for attempt in range(max_attempts):
|
|
934
|
+
try:
|
|
935
|
+
with opener(request, timeout=timeout_seconds) as response: # type: ignore[operator]
|
|
936
|
+
status_code = int(getattr(response, "status", 200))
|
|
937
|
+
payload = _decode_onboarding_response(response.read(65_536))
|
|
938
|
+
if status_code < 200 or status_code >= 300 or not isinstance(payload, dict):
|
|
939
|
+
if status_code >= 500 and attempt + 1 < max_attempts:
|
|
940
|
+
continue
|
|
941
|
+
return {
|
|
942
|
+
"ok": False,
|
|
943
|
+
"status": _onboarding_error_code(payload, "service_error"),
|
|
944
|
+
"retryable": status_code >= 500,
|
|
945
|
+
}
|
|
946
|
+
binding_status = str(payload.get("status") or "")
|
|
947
|
+
response_address = str(payload.get("address") or "")
|
|
948
|
+
if (
|
|
949
|
+
payload.get("ok") is not True
|
|
950
|
+
or binding_status not in {"bound", "already_bound"}
|
|
951
|
+
or response_address.lower() != normalized_address.lower()
|
|
952
|
+
):
|
|
953
|
+
return {"ok": False, "status": "invalid_response", "retryable": True}
|
|
954
|
+
return {
|
|
955
|
+
"ok": True,
|
|
956
|
+
"status": binding_status,
|
|
957
|
+
"network": "base",
|
|
958
|
+
"address": response_address,
|
|
959
|
+
}
|
|
960
|
+
except urllib.error.HTTPError as exc:
|
|
961
|
+
payload = _decode_onboarding_response(exc.read(65_536))
|
|
962
|
+
if exc.code >= 500 and attempt + 1 < max_attempts:
|
|
963
|
+
continue
|
|
964
|
+
return {
|
|
965
|
+
"ok": False,
|
|
966
|
+
"status": _onboarding_error_code(payload, "service_error"),
|
|
967
|
+
"retryable": exc.code >= 500,
|
|
968
|
+
}
|
|
969
|
+
except (urllib.error.URLError, TimeoutError, OSError):
|
|
970
|
+
if attempt + 1 < max_attempts:
|
|
971
|
+
continue
|
|
972
|
+
return {"ok": False, "status": "network_error", "retryable": True}
|
|
973
|
+
return {"ok": False, "status": "network_error", "retryable": True}
|
|
974
|
+
|
|
975
|
+
|
|
976
|
+
def _bind_invite_after_evm_onboard(
|
|
977
|
+
invite: str,
|
|
978
|
+
evm_onboard_result: dict[str, object] | None,
|
|
979
|
+
*,
|
|
980
|
+
api_url: str | None = None,
|
|
981
|
+
opener: object = urllib.request.urlopen,
|
|
982
|
+
) -> dict[str, object] | None:
|
|
983
|
+
if not invite.strip():
|
|
984
|
+
return None
|
|
985
|
+
if not isinstance(evm_onboard_result, dict) or not evm_onboard_result.get("ok"):
|
|
986
|
+
return {"ok": False, "status": "pending_evm_wallet", "retryable": True}
|
|
987
|
+
address = str(evm_onboard_result.get("address") or "").strip()
|
|
988
|
+
if not address:
|
|
989
|
+
return {"ok": False, "status": "pending_evm_wallet", "retryable": True}
|
|
990
|
+
return _bind_welcome_invite(
|
|
991
|
+
invite,
|
|
992
|
+
address,
|
|
993
|
+
api_url=api_url,
|
|
994
|
+
opener=opener,
|
|
995
|
+
)
|
|
996
|
+
|
|
997
|
+
|
|
998
|
+
def _invite_binding_warning(binding_result: dict[str, object]) -> str:
|
|
999
|
+
"""Describe a failed invite bind without claiming it can always be retried."""
|
|
1000
|
+
status = str(binding_result.get("status") or "unknown")
|
|
1001
|
+
if status == "invite_already_bound":
|
|
1002
|
+
return (
|
|
1003
|
+
"warning: the welcome invite is already bound to a different Base "
|
|
1004
|
+
"wallet and cannot be used with this wallet. Status: "
|
|
1005
|
+
+ status
|
|
1006
|
+
)
|
|
1007
|
+
if binding_result.get("retryable") is True:
|
|
1008
|
+
return (
|
|
1009
|
+
"warning: the welcome invite was not bound; the invite remains "
|
|
1010
|
+
"available for a safe retry. Status: "
|
|
1011
|
+
+ status
|
|
1012
|
+
)
|
|
1013
|
+
return "warning: the welcome invite was not bound and cannot be retried. Status: " + status
|
|
1014
|
+
|
|
1015
|
+
|
|
859
1016
|
def main() -> None:
|
|
860
1017
|
args = build_parser().parse_args()
|
|
861
1018
|
source_package_root = Path(args.package_root).expanduser().resolve()
|
|
@@ -909,6 +1066,7 @@ def main() -> None:
|
|
|
909
1066
|
wdk_evm_root = source_wdk_evm_root
|
|
910
1067
|
|
|
911
1068
|
install_config_script = package_root / "scripts" / "install_openclaw_local_config.py"
|
|
1069
|
+
install_sealed_keys_script = package_root / "scripts" / "install_openclaw_sealed_keys.py"
|
|
912
1070
|
if args.install_from_runtime:
|
|
913
1071
|
default_source_env_path = source_package_root / ".env"
|
|
914
1072
|
default_source_venv_path = source_package_root / ".venv"
|
|
@@ -923,7 +1081,7 @@ def main() -> None:
|
|
|
923
1081
|
env_created = _ensure_env_file(env_path, env_example_path)
|
|
924
1082
|
boot_key_file_env_updated = _ensure_runtime_boot_key_file_env(env_path)
|
|
925
1083
|
flash_bridge_env = _ensure_flash_bridge_env(env_path, package_root)
|
|
926
|
-
config_created = _ensure_openclaw_config(config_path)
|
|
1084
|
+
config_created = _ensure_openclaw_config(config_path) if args.configure_openclaw else False
|
|
927
1085
|
|
|
928
1086
|
python_bin = Path(sys.executable)
|
|
929
1087
|
venv_created = False
|
|
@@ -988,23 +1146,33 @@ def main() -> None:
|
|
|
988
1146
|
pending_env = _pending_env_names() if backend_enabled else []
|
|
989
1147
|
configured = False
|
|
990
1148
|
configure_stdout = ""
|
|
1149
|
+
sealed_keys_result: dict[str, object] | None = None
|
|
991
1150
|
solana_onboard_result: dict[str, object] | None = None
|
|
992
1151
|
evm_onboard_result: dict[str, object] | None = None
|
|
1152
|
+
invite_binding_result: dict[str, object] | None = None
|
|
993
1153
|
if backend_enabled and not pending_env and not args.dry_run:
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
python_bin,
|
|
997
|
-
install_config_script,
|
|
998
|
-
args,
|
|
999
|
-
package_root=package_root,
|
|
1000
|
-
extension_path=extension_path,
|
|
1001
|
-
),
|
|
1154
|
+
sealed_result = subprocess.run(
|
|
1155
|
+
[str(python_bin), str(install_sealed_keys_script)],
|
|
1002
1156
|
capture_output=True,
|
|
1003
1157
|
text=True,
|
|
1004
1158
|
check=True,
|
|
1005
1159
|
)
|
|
1006
|
-
|
|
1007
|
-
|
|
1160
|
+
sealed_keys_result = json.loads(sealed_result.stdout)
|
|
1161
|
+
if args.configure_openclaw:
|
|
1162
|
+
result = subprocess.run(
|
|
1163
|
+
_build_next_steps(
|
|
1164
|
+
python_bin,
|
|
1165
|
+
install_config_script,
|
|
1166
|
+
args,
|
|
1167
|
+
package_root=package_root,
|
|
1168
|
+
extension_path=extension_path,
|
|
1169
|
+
),
|
|
1170
|
+
capture_output=True,
|
|
1171
|
+
text=True,
|
|
1172
|
+
check=True,
|
|
1173
|
+
)
|
|
1174
|
+
configured = True
|
|
1175
|
+
configure_stdout = result.stdout
|
|
1008
1176
|
solana_onboard_result = _bootstrap_solana_wallet(
|
|
1009
1177
|
python_bin,
|
|
1010
1178
|
package_root,
|
|
@@ -1027,6 +1195,21 @@ def main() -> None:
|
|
|
1027
1195
|
file=sys.stderr,
|
|
1028
1196
|
)
|
|
1029
1197
|
|
|
1198
|
+
if args.invite.strip():
|
|
1199
|
+
if args.dry_run:
|
|
1200
|
+
invite_binding_result = {
|
|
1201
|
+
"ok": False,
|
|
1202
|
+
"status": "skipped_dry_run",
|
|
1203
|
+
"retryable": True,
|
|
1204
|
+
}
|
|
1205
|
+
else:
|
|
1206
|
+
invite_binding_result = _bind_invite_after_evm_onboard(
|
|
1207
|
+
args.invite,
|
|
1208
|
+
evm_onboard_result,
|
|
1209
|
+
)
|
|
1210
|
+
if invite_binding_result and not invite_binding_result.get("ok"):
|
|
1211
|
+
print(_invite_binding_warning(invite_binding_result), file=sys.stderr)
|
|
1212
|
+
|
|
1030
1213
|
print(
|
|
1031
1214
|
json.dumps(
|
|
1032
1215
|
{
|
|
@@ -1037,6 +1220,7 @@ def main() -> None:
|
|
|
1037
1220
|
"flash_bridge_env": flash_bridge_env,
|
|
1038
1221
|
"config_path": str(config_path),
|
|
1039
1222
|
"config_created": config_created,
|
|
1223
|
+
"configure_openclaw": bool(args.configure_openclaw),
|
|
1040
1224
|
"package_root": str(package_root),
|
|
1041
1225
|
"extension_path": str(extension_path),
|
|
1042
1226
|
"wdk_btc_root": str(wdk_btc_root),
|
|
@@ -1051,8 +1235,10 @@ def main() -> None:
|
|
|
1051
1235
|
"runtime_sync": runtime_sync,
|
|
1052
1236
|
"configured": configured,
|
|
1053
1237
|
"pending_env": pending_env,
|
|
1238
|
+
"sealed_keys": sealed_keys_result,
|
|
1054
1239
|
"solana_wallet": solana_onboard_result,
|
|
1055
1240
|
"evm_wallet": evm_onboard_result,
|
|
1241
|
+
"invite_binding": invite_binding_result,
|
|
1056
1242
|
"next_configure_command": _build_next_steps(
|
|
1057
1243
|
python_bin,
|
|
1058
1244
|
install_config_script,
|
|
@@ -271,7 +271,10 @@ def _maybe_install_sealed_keys() -> str | None:
|
|
|
271
271
|
updates["wdk_evm_wallet_password"] = secrets.token_urlsafe(24)
|
|
272
272
|
if not updates:
|
|
273
273
|
return None
|
|
274
|
-
|
|
274
|
+
merged = {**existing, **updates}
|
|
275
|
+
if merged == existing:
|
|
276
|
+
return str(sealed_path)
|
|
277
|
+
return str(seal_keys(boot_key, merged))
|
|
275
278
|
|
|
276
279
|
|
|
277
280
|
def _require_hardened_runtime_secrets(backend: str) -> str | None:
|
|
@@ -94,15 +94,19 @@ def main() -> None:
|
|
|
94
94
|
"and/or SOLANA_AGENT_PRIVATE_KEY in the environment."
|
|
95
95
|
)
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
changed = not sealed_path.exists() or secrets != existing
|
|
98
|
+
path = seal_keys(boot_key, secrets) if changed else sealed_path
|
|
98
99
|
print(
|
|
99
100
|
json.dumps(
|
|
100
101
|
{
|
|
101
102
|
"ok": True,
|
|
102
103
|
"path": str(path),
|
|
103
104
|
"stored_keys": sorted(secrets.keys()),
|
|
104
|
-
"updated_keys":
|
|
105
|
+
"updated_keys": (
|
|
106
|
+
sorted(set(updates.keys()) | set(generated_keys)) if changed else []
|
|
107
|
+
),
|
|
105
108
|
"replaced": bool(args.replace),
|
|
109
|
+
"changed": changed,
|
|
106
110
|
},
|
|
107
111
|
indent=2,
|
|
108
112
|
)
|