@agentlayer.tech/wallet 0.1.55 → 0.1.64
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 +81 -0
- package/README.md +1 -1
- package/VERSION +1 -1
- package/agent-wallet/.env.example +1 -0
- package/agent-wallet/agent_wallet/__init__.py +1 -1
- package/agent-wallet/agent_wallet/boot_key_migration.py +158 -0
- package/agent-wallet/agent_wallet/boot_key_recovery.py +46 -0
- package/agent-wallet/agent_wallet/config.py +30 -1
- package/agent-wallet/agent_wallet/keystore.py +303 -0
- package/agent-wallet/agent_wallet/openclaw_cli.py +115 -39
- package/agent-wallet/agent_wallet/openclaw_runtime.py +28 -3
- package/agent-wallet/agent_wallet/providers/jupiter.py +34 -0
- package/agent-wallet/agent_wallet/user_wallets.py +46 -3
- package/agent-wallet/agent_wallet/wallet_layer/solana.py +34 -8
- package/agent-wallet/openclaw.plugin.json +1 -1
- package/agent-wallet/pyproject.toml +1 -1
- package/bin/openclaw-agent-wallet.mjs +73 -4
- package/claude-code/plugins/agent-wallet/.claude-plugin/plugin.json +1 -1
- package/claude-code/plugins/agent-wallet/commands/wallet-sol.md +9 -15
- package/codex/plugins/agent-wallet/.codex-plugin/plugin.json +1 -1
- package/codex/plugins/agent-wallet/README.md +9 -0
- package/codex/plugins/agent-wallet/server.py +353 -4
- package/codex/plugins/agent-wallet/skills/wallet-sol/SKILL.md +29 -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
|
@@ -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.64",
|
|
6
6
|
"contracts": {
|
|
7
7
|
"tools": [
|
|
8
8
|
"agentlayer_autonomous_approve",
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,87 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## v0.1.62 - 2026-07-04
|
|
6
|
+
|
|
7
|
+
- Made default boot-key storage prompt-free on macOS. The automatic keystore
|
|
8
|
+
selection no longer probes macOS Keychain because even a read/probe through
|
|
9
|
+
`/usr/bin/security` can open a GUI password dialog during install or session
|
|
10
|
+
startup. macOS Keychain remains available only through explicit opt-in with
|
|
11
|
+
`AGENT_WALLET_KEYSTORE_BACKEND=macos-keychain` (or `native`), while the
|
|
12
|
+
default path uses the local fallback without showing Keychain prompts.
|
|
13
|
+
- `agent-wallet/agent_wallet/keystore.py`
|
|
14
|
+
- `agent-wallet/tests/smoke_keystore.py`
|
|
15
|
+
|
|
16
|
+
## v0.1.61 - 2026-07-04
|
|
17
|
+
|
|
18
|
+
- Hardened boot-key installation and migration around desktop keystores:
|
|
19
|
+
- The npm installer provisions the boot key through the runtime Python
|
|
20
|
+
keystore bridge instead of unconditionally persisting it in the release
|
|
21
|
+
`.env`, while keeping a legacy `.env` fallback when no non-interactive
|
|
22
|
+
keystore round-trip is available.
|
|
23
|
+
- The Node keystore bridge is bounded by a timeout so a hung Python/keychain
|
|
24
|
+
operation cannot stall install or reinstall.
|
|
25
|
+
- macOS keychain writes now run with non-interactive stdin, convert timeouts
|
|
26
|
+
into failed command results, and make the problematic
|
|
27
|
+
`set-generic-password-partition-list` ACL update strictly best-effort.
|
|
28
|
+
- Keystore resolution now verifies that a native backend can read the live
|
|
29
|
+
boot key or complete a write/read/delete probe before selecting it, falling
|
|
30
|
+
back cleanly when the OS tool exists but cannot authorize writes.
|
|
31
|
+
- `agent-wallet/agent_wallet/keystore.py`
|
|
32
|
+
- `bin/openclaw-agent-wallet.mjs`
|
|
33
|
+
- `agent-wallet/tests/smoke_keystore.py`
|
|
34
|
+
|
|
35
|
+
## v0.1.58 - 2026-07-01
|
|
36
|
+
|
|
37
|
+
- Resolved SPL token symbol/name in `get_wallet_portfolio` via a batched,
|
|
38
|
+
concurrent Jupiter token-search lookup, so `/wallet-sol` shows tickers
|
|
39
|
+
(e.g. `USDC`) instead of raw mint addresses. The mint/token_address is
|
|
40
|
+
still returned in full and rendered shortened next to the label rather
|
|
41
|
+
than dropped. Falls back gracefully (symbol=None) when a mint isn't
|
|
42
|
+
indexed or the lookup provider is unavailable.
|
|
43
|
+
- `agent-wallet/agent_wallet/providers/jupiter.py`
|
|
44
|
+
- `agent-wallet/agent_wallet/wallet_layer/solana.py`
|
|
45
|
+
- `claude-code/plugins/agent-wallet/commands/wallet-sol.md`
|
|
46
|
+
- `codex/plugins/agent-wallet/skills/wallet-sol/SKILL.md`
|
|
47
|
+
- Dropped the `/wallet-sol` source-metadata footer line (`source`,
|
|
48
|
+
`token_discovery_source`, `pricing_source`, `pricing_errors`) from the
|
|
49
|
+
rendered chat output for a more compact report.
|
|
50
|
+
- `claude-code/plugins/agent-wallet/commands/wallet-sol.md`
|
|
51
|
+
- `codex/plugins/agent-wallet/skills/wallet-sol/SKILL.md`
|
|
52
|
+
|
|
53
|
+
## v0.1.57 - 2026-07-01
|
|
54
|
+
|
|
55
|
+
- Added a bundled Codex `wallet-sol` skill so Codex users can render the
|
|
56
|
+
connected Solana wallet portfolio in chat the same way Claude Code's
|
|
57
|
+
`/wallet-sol` command does.
|
|
58
|
+
- `codex/plugins/agent-wallet/skills/wallet-sol/SKILL.md`
|
|
59
|
+
- `codex/plugins/agent-wallet/README.md`
|
|
60
|
+
- Fixed the resident read worker (used by `get_wallet_balance` /
|
|
61
|
+
`get_wallet_portfolio`, e.g. `/wallet-sol`) decrypting the wallet's private
|
|
62
|
+
key on every read-only cold start even though it never needed it. Read-only
|
|
63
|
+
onboarding now resolves the address from the existing plaintext wallet pin
|
|
64
|
+
file once a wallet has been provisioned, instead of unsealing secrets and
|
|
65
|
+
deriving a signer just to discard it.
|
|
66
|
+
- `agent-wallet/agent_wallet/user_wallets.py`
|
|
67
|
+
- Reduced `/wallet-sol` context payload by dropping the unused raw Jupiter
|
|
68
|
+
price blob (`price_raw`) from portfolio token entries.
|
|
69
|
+
- `agent-wallet/agent_wallet/wallet_layer/solana.py`
|
|
70
|
+
- Parallelized Jupiter price batch fetches in `get_portfolio` instead of
|
|
71
|
+
awaiting each 20-mint batch sequentially, speeding up portfolio lookups for
|
|
72
|
+
wallets with many SPL token accounts.
|
|
73
|
+
- `agent-wallet/agent_wallet/wallet_layer/solana.py`
|
|
74
|
+
- Added a background prewarm of the resident read worker at MCP server
|
|
75
|
+
startup so interpreter boot and onboarding overlap with the user issuing
|
|
76
|
+
the first read-only command instead of blocking it. Opt out with
|
|
77
|
+
`AGENT_WALLET_PREWARM_READ_WORKER=0`.
|
|
78
|
+
- `codex/plugins/agent-wallet/server.py`
|
|
79
|
+
- Added idle eviction for resident read workers left over from a stale
|
|
80
|
+
config (e.g. after switching Solana network or wallet backend), bounded by
|
|
81
|
+
`AGENT_WALLET_READ_WORKER_IDLE_SECONDS` (default 10 minutes), and closed
|
|
82
|
+
resident read workers on SIGTERM in addition to the existing `atexit`
|
|
83
|
+
cleanup, since Python does not run `atexit` hooks on signal termination.
|
|
84
|
+
- `codex/plugins/agent-wallet/server.py`
|
|
85
|
+
|
|
5
86
|
## v0.1.54 - 2026-06-29
|
|
6
87
|
|
|
7
88
|
- Fixed Claude Code autonomous-mode slash command activation so
|
package/README.md
CHANGED
|
@@ -102,7 +102,7 @@ The CLI uses a versioned runtime layout:
|
|
|
102
102
|
~/.openclaw/agent-wallet-runtime/current # symlink → active release
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
-
On first install, `--yes` generates local runtime secrets. The installer stores `master_key` and `approval_secret` in `~/.openclaw/sealed_keys.json`;
|
|
105
|
+
On first install, `--yes` generates local runtime secrets. The installer stores `master_key` and `approval_secret` in `~/.openclaw/sealed_keys.json`; the boot key needed to unlock that sealed bundle stays in the prompt-free local fallback by default. macOS Keychain is opt-in (`AGENT_WALLET_KEYSTORE_BACKEND=macos-keychain`) because probing it can open a system password dialog during install or startup.
|
|
106
106
|
|
|
107
107
|
Useful CLI commands (require the global install above):
|
|
108
108
|
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.1.
|
|
1
|
+
0.1.64
|
|
@@ -43,6 +43,7 @@ SOLANA_AGENT_KEYPAIR_PATH=
|
|
|
43
43
|
JUPITER_API_BASE_URL=https://lite-api.jup.ag/swap/v1
|
|
44
44
|
JUPITER_ULTRA_API_BASE_URL=https://lite-api.jup.ag/ultra/v1
|
|
45
45
|
JUPITER_PRICE_API_BASE_URL=https://lite-api.jup.ag/price/v3
|
|
46
|
+
JUPITER_TOKEN_SEARCH_API_BASE_URL=https://lite-api.jup.ag/tokens/v2
|
|
46
47
|
JUPITER_API_KEY=
|
|
47
48
|
|
|
48
49
|
# LI.FI cross-chain routing. API key is optional for basic read-only quote/status calls.
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Transparent, verified migration of the boot key into the OS keystore.
|
|
2
|
+
|
|
3
|
+
Runs at backend startup. Non-destructive: plaintext is swept ONLY after the
|
|
4
|
+
keystore read-back verifies. Idempotent and best-effort — a partial sweep
|
|
5
|
+
re-runs cleanly next start. See BOOT_KEY_KEYCHAIN_ARCHITECTURE.md.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from agent_wallet.config import (
|
|
14
|
+
read_boot_key_from_keystore,
|
|
15
|
+
resolve_openclaw_home,
|
|
16
|
+
settings,
|
|
17
|
+
)
|
|
18
|
+
from agent_wallet.file_ops import atomic_write_text
|
|
19
|
+
from agent_wallet.keystore import BOOT_KEY_ITEM, PlaintextFileStore, resolve_keystore
|
|
20
|
+
|
|
21
|
+
_ENV_LINE_PREFIX = "AGENT_WALLET_BOOT_KEY="
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _read_legacy_boot_key() -> str:
|
|
25
|
+
"""The authoritative live key from legacy sources only (env override -> file)."""
|
|
26
|
+
direct = os.getenv("AGENT_WALLET_BOOT_KEY", settings.agent_wallet_boot_key).strip()
|
|
27
|
+
if direct:
|
|
28
|
+
return direct
|
|
29
|
+
key_file = os.getenv("AGENT_WALLET_BOOT_KEY_FILE", settings.agent_wallet_boot_key_file).strip()
|
|
30
|
+
if key_file:
|
|
31
|
+
try:
|
|
32
|
+
return Path(key_file).expanduser().read_text(encoding="utf-8").strip()
|
|
33
|
+
except OSError:
|
|
34
|
+
return ""
|
|
35
|
+
return ""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _env_boot_key_value(line: str) -> str | None:
|
|
39
|
+
"""Return the boot-key value on an AGENT_WALLET_BOOT_KEY= line, else None."""
|
|
40
|
+
stripped = line.strip()
|
|
41
|
+
if not stripped.startswith(_ENV_LINE_PREFIX):
|
|
42
|
+
return None
|
|
43
|
+
return stripped[len(_ENV_LINE_PREFIX):].strip().strip('"').strip("'")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _strip_boot_key_line(env_path: Path, expected: str) -> bool:
|
|
47
|
+
"""Remove only the AGENT_WALLET_BOOT_KEY line whose value == expected.
|
|
48
|
+
|
|
49
|
+
Preserve all other vars, and never strip a line carrying a *different* value
|
|
50
|
+
(that would signal a key mismatch a human should look at). Returns True if
|
|
51
|
+
the file changed.
|
|
52
|
+
"""
|
|
53
|
+
try:
|
|
54
|
+
lines = env_path.read_text(encoding="utf-8").splitlines()
|
|
55
|
+
except OSError:
|
|
56
|
+
return False
|
|
57
|
+
kept = [ln for ln in lines if _env_boot_key_value(ln) != expected]
|
|
58
|
+
if len(kept) == len(lines):
|
|
59
|
+
return False
|
|
60
|
+
atomic_write_text(env_path, ("\n".join(kept) + "\n") if kept else "", mode=0o600)
|
|
61
|
+
return True
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _candidate_env_files(runtime: Path) -> list[Path]:
|
|
65
|
+
"""Boot-key .env locations to sweep, deduped.
|
|
66
|
+
|
|
67
|
+
Deliberately bounded: a recursive ``**/.env`` walk descends into the hundreds
|
|
68
|
+
of ``node_modules`` trees under the release dirs and effectively hangs onboarding
|
|
69
|
+
(observed: 650+ node_modules, glob never returns). The boot key was only ever
|
|
70
|
+
written to ``releases/<v>/agent-wallet/.env``; ``current``/``previous`` are
|
|
71
|
+
symlinks into ``releases`` and are resolved to dedupe against their targets.
|
|
72
|
+
"""
|
|
73
|
+
candidates = list(runtime.glob("releases/*/agent-wallet/.env"))
|
|
74
|
+
candidates.append(runtime / "current" / "agent-wallet" / ".env")
|
|
75
|
+
candidates.append(runtime / "previous" / "agent-wallet" / ".env")
|
|
76
|
+
seen: set[str] = set()
|
|
77
|
+
out: list[Path] = []
|
|
78
|
+
for path in candidates:
|
|
79
|
+
try:
|
|
80
|
+
key = str(path.resolve())
|
|
81
|
+
except OSError:
|
|
82
|
+
key = str(path)
|
|
83
|
+
if key in seen:
|
|
84
|
+
continue
|
|
85
|
+
seen.add(key)
|
|
86
|
+
if path.exists():
|
|
87
|
+
out.append(path)
|
|
88
|
+
return out
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _sweep_plaintext(home: Path, expected: str) -> tuple[int, bool]:
|
|
92
|
+
"""Strip matching boot-key lines from every runtime .env; delete the boot-key file.
|
|
93
|
+
|
|
94
|
+
Idempotent and value-scoped: only plaintext equal to the authoritative
|
|
95
|
+
keystore key is removed, so re-running after a fresh installer write (which
|
|
96
|
+
re-emits the same key) cleans it up on the next start.
|
|
97
|
+
"""
|
|
98
|
+
runtime = home / "agent-wallet-runtime"
|
|
99
|
+
swept = 0
|
|
100
|
+
for env_path in _candidate_env_files(runtime):
|
|
101
|
+
try:
|
|
102
|
+
if _strip_boot_key_line(env_path, expected):
|
|
103
|
+
swept += 1
|
|
104
|
+
except Exception:
|
|
105
|
+
continue # best-effort; re-runs next start
|
|
106
|
+
removed = False
|
|
107
|
+
boot_file = runtime / "boot-key"
|
|
108
|
+
try:
|
|
109
|
+
if boot_file.read_text(encoding="utf-8").strip() == expected:
|
|
110
|
+
boot_file.unlink()
|
|
111
|
+
removed = True
|
|
112
|
+
except FileNotFoundError:
|
|
113
|
+
pass
|
|
114
|
+
except OSError:
|
|
115
|
+
pass
|
|
116
|
+
return swept, removed
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def migrate_boot_key_to_keystore() -> dict:
|
|
120
|
+
"""Move a legacy plaintext boot key into the keystore, verify, then sweep plaintext.
|
|
121
|
+
|
|
122
|
+
Runs on every startup (guarded once per process). When the keystore already
|
|
123
|
+
holds the key, it still re-sweeps any plaintext an installer re-emitted, so a
|
|
124
|
+
per-release .env write never re-establishes a permanent leak.
|
|
125
|
+
"""
|
|
126
|
+
store = resolve_keystore()
|
|
127
|
+
home = resolve_openclaw_home()
|
|
128
|
+
|
|
129
|
+
# Do not sweep into a plaintext fallback: that offers no at-rest improvement,
|
|
130
|
+
# and the user explicitly chose to stay on the current file in that case.
|
|
131
|
+
if isinstance(store, PlaintextFileStore):
|
|
132
|
+
return {"migrated": False, "backend": store.backend_id, "swept_env_files": 0,
|
|
133
|
+
"removed_boot_key_file": False, "reason": "no-os-keystore"}
|
|
134
|
+
|
|
135
|
+
authoritative = read_boot_key_from_keystore()
|
|
136
|
+
first_time = False
|
|
137
|
+
if not authoritative:
|
|
138
|
+
legacy_key = _read_legacy_boot_key()
|
|
139
|
+
if not legacy_key:
|
|
140
|
+
return {"migrated": False, "backend": store.backend_id, "swept_env_files": 0,
|
|
141
|
+
"removed_boot_key_file": False, "reason": "no-legacy-key"}
|
|
142
|
+
try:
|
|
143
|
+
store.set(BOOT_KEY_ITEM, legacy_key)
|
|
144
|
+
except Exception as exc:
|
|
145
|
+
return {"migrated": False, "backend": store.backend_id, "swept_env_files": 0,
|
|
146
|
+
"removed_boot_key_file": False, "reason": f"keystore-set-failed: {exc}"}
|
|
147
|
+
# Verify-before-delete.
|
|
148
|
+
if read_boot_key_from_keystore() != legacy_key:
|
|
149
|
+
return {"migrated": False, "backend": store.backend_id, "swept_env_files": 0,
|
|
150
|
+
"removed_boot_key_file": False, "reason": "verify-failed"}
|
|
151
|
+
authoritative = legacy_key
|
|
152
|
+
first_time = True
|
|
153
|
+
|
|
154
|
+
swept, removed = _sweep_plaintext(home, authoritative)
|
|
155
|
+
migrated = first_time or swept > 0 or removed
|
|
156
|
+
return {"migrated": migrated, "backend": store.backend_id, "swept_env_files": swept,
|
|
157
|
+
"removed_boot_key_file": removed,
|
|
158
|
+
"reason": "ok" if migrated else "already-clean"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""User-facing boot-key export/import for paper backup and machine recovery.
|
|
2
|
+
|
|
3
|
+
The boot key is a retrievable string (baseline A), so recovery is simple: show
|
|
4
|
+
it for a paper backup, or write a recorded value back into the keystore on a new
|
|
5
|
+
machine. Full recovery kit = boot key + the already-encrypted sealed_keys.json +
|
|
6
|
+
wallet files. See BOOT_KEY_KEYCHAIN_ARCHITECTURE.md.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from agent_wallet.config import read_boot_key_from_keystore, resolve_boot_key
|
|
12
|
+
from agent_wallet.keystore import BOOT_KEY_ITEM, resolve_keystore
|
|
13
|
+
from agent_wallet.wallet_layer.base import WalletBackendError
|
|
14
|
+
|
|
15
|
+
_EXPORT_WARNING = (
|
|
16
|
+
"SENSITIVE: anyone with this boot key AND your ~/.openclaw encrypted files "
|
|
17
|
+
"controls every wallet (Solana/EVM/BTC). Write it down and store it offline."
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def export_boot_key() -> str:
|
|
22
|
+
"""Return the live boot key. Raises if none is resolvable."""
|
|
23
|
+
key = resolve_boot_key()
|
|
24
|
+
if not key:
|
|
25
|
+
raise WalletBackendError(
|
|
26
|
+
"No boot key is available to export. The wallet is not set up on this machine."
|
|
27
|
+
)
|
|
28
|
+
return key
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def import_boot_key(value: str) -> dict:
|
|
32
|
+
"""Write a recorded boot key into the OS keystore and verify the read-back."""
|
|
33
|
+
key = str(value or "").strip()
|
|
34
|
+
if not key:
|
|
35
|
+
raise WalletBackendError("A non-empty boot key is required for import.")
|
|
36
|
+
store = resolve_keystore()
|
|
37
|
+
store.set(BOOT_KEY_ITEM, key)
|
|
38
|
+
if read_boot_key_from_keystore() != key:
|
|
39
|
+
raise WalletBackendError(
|
|
40
|
+
f"Boot key import could not be verified from the {store.backend_id} keystore."
|
|
41
|
+
)
|
|
42
|
+
return {"imported": True, "backend": store.backend_id}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def export_warning() -> str:
|
|
46
|
+
return _EXPORT_WARNING
|
|
@@ -48,6 +48,7 @@ class Settings(BaseSettings):
|
|
|
48
48
|
jupiter_swap_v2_api_base_url: str = "https://api.jup.ag/swap/v2"
|
|
49
49
|
jupiter_ultra_api_base_url: str = "https://lite-api.jup.ag/ultra/v1"
|
|
50
50
|
jupiter_price_api_base_url: str = "https://lite-api.jup.ag/price/v3"
|
|
51
|
+
jupiter_token_search_api_base_url: str = "https://lite-api.jup.ag/tokens/v2"
|
|
51
52
|
jupiter_api_key: str = ""
|
|
52
53
|
lifi_api_base_url: str = "https://li.quest/v1"
|
|
53
54
|
lifi_api_key: str = ""
|
|
@@ -74,6 +75,14 @@ class Settings(BaseSettings):
|
|
|
74
75
|
settings = Settings()
|
|
75
76
|
|
|
76
77
|
|
|
78
|
+
def reload_settings() -> Settings:
|
|
79
|
+
"""Reload settings from the current environment without changing object identity."""
|
|
80
|
+
refreshed = Settings()
|
|
81
|
+
for field_name in Settings.model_fields:
|
|
82
|
+
setattr(settings, field_name, getattr(refreshed, field_name))
|
|
83
|
+
return settings
|
|
84
|
+
|
|
85
|
+
|
|
77
86
|
def normalize_solana_network(network: str | None) -> str:
|
|
78
87
|
"""Canonicalize supported Solana network names and reject test clusters."""
|
|
79
88
|
normalized = str(network or "").strip().lower() or "mainnet"
|
|
@@ -376,11 +385,31 @@ def _env_bool(name: str, default: bool) -> bool:
|
|
|
376
385
|
return raw.strip().lower() in {"1", "true", "yes", "on"}
|
|
377
386
|
|
|
378
387
|
|
|
388
|
+
def read_boot_key_from_keystore() -> str:
|
|
389
|
+
"""Read the boot key from the OS keystore. Never raises; '' on any failure."""
|
|
390
|
+
try:
|
|
391
|
+
from agent_wallet.keystore import BOOT_KEY_ITEM, resolve_keystore
|
|
392
|
+
|
|
393
|
+
value = resolve_keystore().get(BOOT_KEY_ITEM)
|
|
394
|
+
return value.strip() if isinstance(value, str) else ""
|
|
395
|
+
except Exception:
|
|
396
|
+
return ""
|
|
397
|
+
|
|
398
|
+
|
|
379
399
|
def resolve_boot_key() -> str:
|
|
380
|
-
"""Resolve the boot key used to unlock sealed secrets from disk.
|
|
400
|
+
"""Resolve the boot key used to unlock sealed secrets from disk.
|
|
401
|
+
|
|
402
|
+
Precedence (superset of the legacy lookup, so existing installs keep working):
|
|
403
|
+
1. AGENT_WALLET_BOOT_KEY env / settings override
|
|
404
|
+
2. OS keystore (the hardened path)
|
|
405
|
+
3. AGENT_WALLET_BOOT_KEY_FILE / boot-key file (legacy)
|
|
406
|
+
"""
|
|
381
407
|
direct = os.getenv("AGENT_WALLET_BOOT_KEY", settings.agent_wallet_boot_key).strip()
|
|
382
408
|
if direct:
|
|
383
409
|
return direct
|
|
410
|
+
from_keystore = read_boot_key_from_keystore()
|
|
411
|
+
if from_keystore:
|
|
412
|
+
return from_keystore
|
|
384
413
|
key_file = os.getenv("AGENT_WALLET_BOOT_KEY_FILE", settings.agent_wallet_boot_key_file).strip()
|
|
385
414
|
if not key_file:
|
|
386
415
|
return ""
|