@agentlayer.tech/wallet 0.1.0
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/AGENTS.md +98 -0
- package/.openclaw/extensions/agent-wallet/README.md +127 -0
- package/.openclaw/extensions/agent-wallet/index.ts +1520 -0
- package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +184 -0
- package/.openclaw/extensions/agent-wallet/package.json +11 -0
- package/.openclaw/extensions/agent-wallet/skills/wallet-operator/SKILL.md +20 -0
- package/CHANGELOG.md +42 -0
- package/LICENSE +104 -0
- package/README.md +332 -0
- package/RELEASING.md +204 -0
- package/agent-wallet/.env.example +62 -0
- package/agent-wallet/AGENTS.md +129 -0
- package/agent-wallet/README.md +527 -0
- package/agent-wallet/agent_wallet/__init__.py +11 -0
- package/agent-wallet/agent_wallet/approval.py +161 -0
- package/agent-wallet/agent_wallet/bootstrap.py +178 -0
- package/agent-wallet/agent_wallet/btc_user_wallets.py +217 -0
- package/agent-wallet/agent_wallet/config.py +382 -0
- package/agent-wallet/agent_wallet/encrypted_storage.py +161 -0
- package/agent-wallet/agent_wallet/evm_user_wallets.py +370 -0
- package/agent-wallet/agent_wallet/exceptions.py +9 -0
- package/agent-wallet/agent_wallet/file_ops.py +34 -0
- package/agent-wallet/agent_wallet/http_client.py +25 -0
- package/agent-wallet/agent_wallet/models.py +66 -0
- package/agent-wallet/agent_wallet/nonce_registry.py +59 -0
- package/agent-wallet/agent_wallet/openclaw_adapter.py +5128 -0
- package/agent-wallet/agent_wallet/openclaw_cli.py +626 -0
- package/agent-wallet/agent_wallet/openclaw_runtime.py +272 -0
- package/agent-wallet/agent_wallet/plugin_bundle.py +42 -0
- package/agent-wallet/agent_wallet/providers/__init__.py +1 -0
- package/agent-wallet/agent_wallet/providers/bags.py +259 -0
- package/agent-wallet/agent_wallet/providers/evm_portfolio.py +470 -0
- package/agent-wallet/agent_wallet/providers/jupiter.py +567 -0
- package/agent-wallet/agent_wallet/providers/kamino.py +215 -0
- package/agent-wallet/agent_wallet/providers/lifi.py +277 -0
- package/agent-wallet/agent_wallet/providers/solana_rpc.py +470 -0
- package/agent-wallet/agent_wallet/providers/wdk_btc_local.py +114 -0
- package/agent-wallet/agent_wallet/providers/wdk_evm_local.py +205 -0
- package/agent-wallet/agent_wallet/sealed_keys.py +61 -0
- package/agent-wallet/agent_wallet/solana_stake.py +103 -0
- package/agent-wallet/agent_wallet/solana_tx.py +93 -0
- package/agent-wallet/agent_wallet/spending_limits.py +101 -0
- package/agent-wallet/agent_wallet/transaction_policy.py +518 -0
- package/agent-wallet/agent_wallet/user_wallets.py +355 -0
- package/agent-wallet/agent_wallet/validation.py +31 -0
- package/agent-wallet/agent_wallet/wallet_layer/__init__.py +1 -0
- package/agent-wallet/agent_wallet/wallet_layer/base.py +808 -0
- package/agent-wallet/agent_wallet/wallet_layer/base58.py +44 -0
- package/agent-wallet/agent_wallet/wallet_layer/factory.py +102 -0
- package/agent-wallet/agent_wallet/wallet_layer/solana.py +4252 -0
- package/agent-wallet/agent_wallet/wallet_layer/wdk_btc.py +272 -0
- package/agent-wallet/agent_wallet/wallet_layer/wdk_evm.py +1628 -0
- package/agent-wallet/examples/bootstrap_wallet.py +21 -0
- package/agent-wallet/examples/openclaw_runtime_onboarding.py +28 -0
- package/agent-wallet/examples/openclaw_user_wallet_example.py +31 -0
- package/agent-wallet/examples/openclaw_wallet_adapter_example.py +33 -0
- package/agent-wallet/openclaw.plugin.json +138 -0
- package/agent-wallet/pyproject.toml +31 -0
- package/agent-wallet/scripts/bootstrap_openclaw_btc.py +278 -0
- package/agent-wallet/scripts/build_release_bundle.py +188 -0
- package/agent-wallet/scripts/finalize_openclaw_local_wallet_config.py +121 -0
- package/agent-wallet/scripts/install_agent_wallet.py +505 -0
- package/agent-wallet/scripts/install_openclaw_local_config.py +226 -0
- package/agent-wallet/scripts/install_openclaw_sealed_keys.py +105 -0
- package/agent-wallet/scripts/manage_openclaw_btc_wallet.py +244 -0
- package/agent-wallet/scripts/reveal_btc_seed.sh +130 -0
- package/agent-wallet/scripts/security_utils.py +37 -0
- package/agent-wallet/scripts/setup_btc_wallet.sh +146 -0
- package/agent-wallet/scripts/switch_openclaw_wallet_network.py +106 -0
- package/agent-wallet/skills/wallet-operator/SKILL.md +128 -0
- package/bin/openclaw-agent-wallet.mjs +487 -0
- package/install-from-github.sh +134 -0
- package/package.json +61 -0
- package/setup.sh +40 -0
- package/wdk-btc-wallet/README.md +325 -0
- package/wdk-btc-wallet/bootstrap.sh +22 -0
- package/wdk-btc-wallet/package-lock.json +1839 -0
- package/wdk-btc-wallet/package.json +18 -0
- package/wdk-btc-wallet/run-local.sh +21 -0
- package/wdk-btc-wallet/src/config.js +160 -0
- package/wdk-btc-wallet/src/json.js +35 -0
- package/wdk-btc-wallet/src/local_vault.js +432 -0
- package/wdk-btc-wallet/src/network_state.js +84 -0
- package/wdk-btc-wallet/src/server.js +257 -0
- package/wdk-btc-wallet/src/wdk_btc_wallet.js +332 -0
- package/wdk-evm-wallet/README.md +183 -0
- package/wdk-evm-wallet/bootstrap.sh +8 -0
- package/wdk-evm-wallet/package-lock.json +2340 -0
- package/wdk-evm-wallet/package.json +23 -0
- package/wdk-evm-wallet/run-local.sh +12 -0
- package/wdk-evm-wallet/src/config.js +274 -0
- package/wdk-evm-wallet/src/json.js +35 -0
- package/wdk-evm-wallet/src/local_vault.js +430 -0
- package/wdk-evm-wallet/src/network_state.js +92 -0
- package/wdk-evm-wallet/src/server.js +575 -0
- package/wdk-evm-wallet/src/wdk_evm_wallet.js +4981 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"""Patch an OpenClaw config file for the agent-wallet plugin."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
13
|
+
|
|
14
|
+
from agent_wallet.file_ops import atomic_write_text, chmod_if_exists
|
|
15
|
+
from agent_wallet.sealed_keys import resolve_sealed_keys_path, seal_keys, unseal_keys
|
|
16
|
+
from security_utils import write_redacted_backup
|
|
17
|
+
|
|
18
|
+
OPTIONAL_TOOLS = [
|
|
19
|
+
"sign_wallet_message",
|
|
20
|
+
"transfer_sol",
|
|
21
|
+
"transfer_btc",
|
|
22
|
+
"transfer_spl_token",
|
|
23
|
+
"swap_solana_tokens",
|
|
24
|
+
"close_empty_token_accounts",
|
|
25
|
+
"request_devnet_airdrop",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _default_config_path() -> Path:
|
|
30
|
+
return Path(os.path.expanduser("~/.openclaw/openclaw.json"))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _repo_root() -> Path:
|
|
34
|
+
return Path(__file__).resolve().parents[2]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _default_extension_path() -> Path:
|
|
38
|
+
return _repo_root() / ".openclaw" / "extensions" / "agent-wallet"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _default_package_root() -> Path:
|
|
42
|
+
return Path(__file__).resolve().parents[1]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _default_python_bin() -> str:
|
|
46
|
+
return os.getenv("OPENCLAW_AGENT_WALLET_PYTHON", sys.executable)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _default_user_id() -> str:
|
|
50
|
+
return f"{os.getenv('USER', 'openclaw-user')}-local"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _normalize_network(backend: str, network: str) -> str:
|
|
54
|
+
backend_name = backend.strip().lower()
|
|
55
|
+
normalized = network.strip().lower()
|
|
56
|
+
if backend_name in {"wdk_btc_local", "wdk-btc-local", "btc_local", "btc-local"}:
|
|
57
|
+
if normalized == "mainnet":
|
|
58
|
+
return "bitcoin"
|
|
59
|
+
return normalized or "bitcoin"
|
|
60
|
+
return normalized or "devnet"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
64
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
65
|
+
parser.add_argument("--config-path", default=str(_default_config_path()))
|
|
66
|
+
parser.add_argument("--plugin-id", default="agent-wallet")
|
|
67
|
+
parser.add_argument("--user-id", default=_default_user_id())
|
|
68
|
+
parser.add_argument("--backend", default="solana_local")
|
|
69
|
+
parser.add_argument("--network", default="devnet")
|
|
70
|
+
parser.add_argument("--rpc-url", default="")
|
|
71
|
+
parser.add_argument("--rpc-urls", default="")
|
|
72
|
+
parser.add_argument("--wdk-btc-service-url", default="")
|
|
73
|
+
parser.add_argument("--wdk-btc-wallet-id", default="")
|
|
74
|
+
parser.add_argument("--wdk-btc-account-index", type=int, default=0)
|
|
75
|
+
parser.add_argument("--sign-only", action=argparse.BooleanOptionalAction, default=False)
|
|
76
|
+
parser.add_argument("--encrypt-user-wallets", action=argparse.BooleanOptionalAction, default=True)
|
|
77
|
+
parser.add_argument(
|
|
78
|
+
"--migrate-plaintext-user-wallets",
|
|
79
|
+
action=argparse.BooleanOptionalAction,
|
|
80
|
+
default=True,
|
|
81
|
+
)
|
|
82
|
+
parser.add_argument("--extension-path", default=str(_default_extension_path()))
|
|
83
|
+
parser.add_argument("--package-root", default=str(_default_package_root()))
|
|
84
|
+
parser.add_argument("--python-bin", default=_default_python_bin())
|
|
85
|
+
parser.add_argument("--write-master-key", action=argparse.BooleanOptionalAction, default=False)
|
|
86
|
+
return parser
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _collect_sealed_secret_updates() -> dict[str, str]:
|
|
90
|
+
updates: dict[str, str] = {}
|
|
91
|
+
master_key = os.getenv("AGENT_WALLET_MASTER_KEY", "").strip()
|
|
92
|
+
approval_secret = os.getenv("AGENT_WALLET_APPROVAL_SECRET", "").strip()
|
|
93
|
+
private_key = os.getenv("SOLANA_AGENT_PRIVATE_KEY", "").strip()
|
|
94
|
+
if master_key:
|
|
95
|
+
updates["master_key"] = master_key
|
|
96
|
+
if approval_secret:
|
|
97
|
+
updates["approval_secret"] = approval_secret
|
|
98
|
+
if private_key:
|
|
99
|
+
updates["private_key"] = private_key
|
|
100
|
+
return updates
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _maybe_install_sealed_keys() -> str | None:
|
|
104
|
+
boot_key = os.getenv("AGENT_WALLET_BOOT_KEY", "").strip()
|
|
105
|
+
if not boot_key:
|
|
106
|
+
return None
|
|
107
|
+
updates = _collect_sealed_secret_updates()
|
|
108
|
+
if not updates:
|
|
109
|
+
return None
|
|
110
|
+
sealed_path = resolve_sealed_keys_path()
|
|
111
|
+
existing = unseal_keys(boot_key) if sealed_path.exists() else {}
|
|
112
|
+
return str(seal_keys(boot_key, {**existing, **updates}))
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _require_hardened_runtime_secrets(backend: str) -> str | None:
|
|
116
|
+
if backend.strip().lower() in {"", "none"}:
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
boot_key = os.getenv("AGENT_WALLET_BOOT_KEY", "").strip()
|
|
120
|
+
if not boot_key:
|
|
121
|
+
raise SystemExit(
|
|
122
|
+
"AGENT_WALLET_BOOT_KEY is required. Runtime secrets must be loaded from sealed_keys.json."
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
sealed_path = resolve_sealed_keys_path()
|
|
126
|
+
if not sealed_path.exists():
|
|
127
|
+
raise SystemExit(
|
|
128
|
+
"sealed_keys.json was not created. Provide AGENT_WALLET_MASTER_KEY and "
|
|
129
|
+
"AGENT_WALLET_APPROVAL_SECRET during install so the installer can seal them."
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
secrets = unseal_keys(boot_key)
|
|
133
|
+
missing = [name for name in ("master_key", "approval_secret") if not str(secrets.get(name) or "").strip()]
|
|
134
|
+
if missing:
|
|
135
|
+
raise SystemExit(
|
|
136
|
+
"sealed_keys.json is missing required runtime secrets: "
|
|
137
|
+
+ ", ".join(missing)
|
|
138
|
+
+ "."
|
|
139
|
+
)
|
|
140
|
+
return str(sealed_path)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def main() -> None:
|
|
144
|
+
args = build_parser().parse_args()
|
|
145
|
+
config_path = Path(args.config_path).expanduser()
|
|
146
|
+
data = json.loads(config_path.read_text(encoding="utf-8"))
|
|
147
|
+
|
|
148
|
+
backup_path = config_path.with_name(
|
|
149
|
+
f"{config_path.name}.bak.agent-wallet.{datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%SZ')}"
|
|
150
|
+
)
|
|
151
|
+
write_redacted_backup(backup_path, data)
|
|
152
|
+
|
|
153
|
+
plugins = data.setdefault("plugins", {})
|
|
154
|
+
plugins["enabled"] = True
|
|
155
|
+
|
|
156
|
+
load = plugins.setdefault("load", {})
|
|
157
|
+
paths = load.setdefault("paths", [])
|
|
158
|
+
extension_path_text = str(Path(args.extension_path).expanduser().resolve())
|
|
159
|
+
if extension_path_text not in paths:
|
|
160
|
+
paths.append(extension_path_text)
|
|
161
|
+
|
|
162
|
+
entries = plugins.setdefault("entries", {})
|
|
163
|
+
effective_network = _normalize_network(args.backend, args.network)
|
|
164
|
+
plugin_config = {
|
|
165
|
+
"userId": args.user_id,
|
|
166
|
+
"backend": args.backend,
|
|
167
|
+
"network": effective_network,
|
|
168
|
+
"signOnly": args.sign_only,
|
|
169
|
+
"encryptUserWallets": args.encrypt_user_wallets,
|
|
170
|
+
"migratePlaintextUserWallets": args.migrate_plaintext_user_wallets,
|
|
171
|
+
"packageRoot": str(Path(args.package_root).expanduser().resolve()),
|
|
172
|
+
"pythonBin": args.python_bin,
|
|
173
|
+
}
|
|
174
|
+
if args.rpc_url.strip():
|
|
175
|
+
plugin_config["rpcUrl"] = args.rpc_url.strip()
|
|
176
|
+
if args.rpc_urls.strip():
|
|
177
|
+
plugin_config["rpcUrls"] = [
|
|
178
|
+
item.strip() for item in args.rpc_urls.split(",") if item.strip()
|
|
179
|
+
]
|
|
180
|
+
if args.wdk_btc_service_url.strip():
|
|
181
|
+
plugin_config["wdkBtcServiceUrl"] = args.wdk_btc_service_url.strip()
|
|
182
|
+
if args.wdk_btc_wallet_id.strip():
|
|
183
|
+
plugin_config["wdkBtcWalletId"] = args.wdk_btc_wallet_id.strip()
|
|
184
|
+
if args.wdk_btc_account_index is not None:
|
|
185
|
+
plugin_config["wdkBtcAccountIndex"] = int(args.wdk_btc_account_index)
|
|
186
|
+
if args.write_master_key:
|
|
187
|
+
raise SystemExit(
|
|
188
|
+
"Refusing to write masterKey into config. Runtime secrets must live in sealed_keys.json."
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
entries[args.plugin_id] = {
|
|
192
|
+
"enabled": True,
|
|
193
|
+
"config": plugin_config,
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
tools = data.setdefault("tools", {})
|
|
197
|
+
also_allow = tools.setdefault("alsoAllow", [])
|
|
198
|
+
for tool_name in OPTIONAL_TOOLS:
|
|
199
|
+
if tool_name not in also_allow:
|
|
200
|
+
also_allow.append(tool_name)
|
|
201
|
+
|
|
202
|
+
atomic_write_text(config_path, json.dumps(data, indent=2) + "\n", mode=0o600)
|
|
203
|
+
chmod_if_exists(config_path, 0o600)
|
|
204
|
+
_maybe_install_sealed_keys()
|
|
205
|
+
sealed_keys_path = _require_hardened_runtime_secrets(args.backend)
|
|
206
|
+
|
|
207
|
+
print(
|
|
208
|
+
json.dumps(
|
|
209
|
+
{
|
|
210
|
+
"ok": True,
|
|
211
|
+
"config_path": str(config_path),
|
|
212
|
+
"backup_path": str(backup_path),
|
|
213
|
+
"extension_path": extension_path_text,
|
|
214
|
+
"python_bin": args.python_bin,
|
|
215
|
+
"package_root": plugin_config["packageRoot"],
|
|
216
|
+
"plugin_id": args.plugin_id,
|
|
217
|
+
"user_id": args.user_id,
|
|
218
|
+
"sealed_keys_path": sealed_keys_path,
|
|
219
|
+
},
|
|
220
|
+
indent=2,
|
|
221
|
+
)
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
if __name__ == "__main__":
|
|
226
|
+
main()
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Create or update the encrypted sealed_keys.json secret bundle."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
12
|
+
|
|
13
|
+
from agent_wallet.sealed_keys import resolve_sealed_keys_path, seal_keys, unseal_keys
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
17
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
18
|
+
parser.add_argument(
|
|
19
|
+
"--replace",
|
|
20
|
+
action=argparse.BooleanOptionalAction,
|
|
21
|
+
default=False,
|
|
22
|
+
help="Replace the sealed bundle instead of merging with existing entries.",
|
|
23
|
+
)
|
|
24
|
+
parser.add_argument(
|
|
25
|
+
"--boot-key",
|
|
26
|
+
default="",
|
|
27
|
+
help="Deprecated insecure path. Use AGENT_WALLET_BOOT_KEY env instead.",
|
|
28
|
+
)
|
|
29
|
+
parser.add_argument(
|
|
30
|
+
"--master-key",
|
|
31
|
+
default="",
|
|
32
|
+
help="Deprecated insecure path. Use AGENT_WALLET_MASTER_KEY env instead.",
|
|
33
|
+
)
|
|
34
|
+
parser.add_argument(
|
|
35
|
+
"--approval-secret",
|
|
36
|
+
default="",
|
|
37
|
+
help="Deprecated insecure path. Use AGENT_WALLET_APPROVAL_SECRET env instead.",
|
|
38
|
+
)
|
|
39
|
+
parser.add_argument(
|
|
40
|
+
"--private-key",
|
|
41
|
+
default="",
|
|
42
|
+
help="Deprecated insecure path. Use SOLANA_AGENT_PRIVATE_KEY env instead.",
|
|
43
|
+
)
|
|
44
|
+
return parser
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _collect_secret_updates() -> dict[str, str]:
|
|
48
|
+
updates: dict[str, str] = {}
|
|
49
|
+
master_key = os.getenv("AGENT_WALLET_MASTER_KEY", "").strip()
|
|
50
|
+
approval_secret = os.getenv("AGENT_WALLET_APPROVAL_SECRET", "").strip()
|
|
51
|
+
private_key = os.getenv("SOLANA_AGENT_PRIVATE_KEY", "").strip()
|
|
52
|
+
if master_key:
|
|
53
|
+
updates["master_key"] = master_key
|
|
54
|
+
if approval_secret:
|
|
55
|
+
updates["approval_secret"] = approval_secret
|
|
56
|
+
if private_key:
|
|
57
|
+
updates["private_key"] = private_key
|
|
58
|
+
return updates
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def main() -> None:
|
|
62
|
+
args = build_parser().parse_args()
|
|
63
|
+
if (
|
|
64
|
+
args.boot_key.strip()
|
|
65
|
+
or args.master_key.strip()
|
|
66
|
+
or args.approval_secret.strip()
|
|
67
|
+
or args.private_key.strip()
|
|
68
|
+
):
|
|
69
|
+
raise SystemExit(
|
|
70
|
+
"Passing secrets via command-line arguments is insecure. "
|
|
71
|
+
"Use AGENT_WALLET_BOOT_KEY / AGENT_WALLET_MASTER_KEY / "
|
|
72
|
+
"AGENT_WALLET_APPROVAL_SECRET / SOLANA_AGENT_PRIVATE_KEY environment variables instead."
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
boot_key = os.getenv("AGENT_WALLET_BOOT_KEY", "").strip()
|
|
76
|
+
if not boot_key:
|
|
77
|
+
raise SystemExit("AGENT_WALLET_BOOT_KEY is required.")
|
|
78
|
+
|
|
79
|
+
updates = _collect_secret_updates()
|
|
80
|
+
sealed_path = resolve_sealed_keys_path()
|
|
81
|
+
existing = unseal_keys(boot_key) if sealed_path.exists() and not args.replace else {}
|
|
82
|
+
secrets = {**existing, **updates}
|
|
83
|
+
if not secrets:
|
|
84
|
+
raise SystemExit(
|
|
85
|
+
"No secrets provided. Set AGENT_WALLET_MASTER_KEY, AGENT_WALLET_APPROVAL_SECRET, "
|
|
86
|
+
"and/or SOLANA_AGENT_PRIVATE_KEY in the environment."
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
path = seal_keys(boot_key, secrets)
|
|
90
|
+
print(
|
|
91
|
+
json.dumps(
|
|
92
|
+
{
|
|
93
|
+
"ok": True,
|
|
94
|
+
"path": str(path),
|
|
95
|
+
"stored_keys": sorted(secrets.keys()),
|
|
96
|
+
"updated_keys": sorted(updates.keys()),
|
|
97
|
+
"replaced": bool(args.replace),
|
|
98
|
+
},
|
|
99
|
+
indent=2,
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
if __name__ == "__main__":
|
|
105
|
+
main()
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Host-side helper for managing a local OpenClaw BTC wallet binding."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import sys
|
|
9
|
+
from getpass import getpass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
PACKAGE_ROOT = Path(__file__).resolve().parents[1]
|
|
13
|
+
if str(PACKAGE_ROOT) not in sys.path:
|
|
14
|
+
sys.path.insert(0, str(PACKAGE_ROOT))
|
|
15
|
+
|
|
16
|
+
from agent_wallet.btc_user_wallets import ( # noqa: E402
|
|
17
|
+
create_user_btc_wallet,
|
|
18
|
+
get_user_btc_wallet_binding,
|
|
19
|
+
import_user_btc_wallet,
|
|
20
|
+
lock_user_btc_wallet,
|
|
21
|
+
reveal_user_btc_wallet_seed_phrase,
|
|
22
|
+
unlock_user_btc_wallet,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _normalize_network(value: str) -> str:
|
|
27
|
+
network = str(value or "").strip().lower()
|
|
28
|
+
if network == "mainnet":
|
|
29
|
+
return "bitcoin"
|
|
30
|
+
return network or "bitcoin"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _read_secret(
|
|
34
|
+
*,
|
|
35
|
+
prompt: str,
|
|
36
|
+
confirm_prompt: str | None = None,
|
|
37
|
+
stdin_mode: bool = False,
|
|
38
|
+
) -> str:
|
|
39
|
+
if stdin_mode:
|
|
40
|
+
value = sys.stdin.read().strip()
|
|
41
|
+
if not value:
|
|
42
|
+
raise SystemExit(f"{prompt.rstrip(':')} is required on stdin.")
|
|
43
|
+
return value
|
|
44
|
+
value = getpass(prompt)
|
|
45
|
+
if confirm_prompt is not None:
|
|
46
|
+
confirmed = getpass(confirm_prompt)
|
|
47
|
+
if value != confirmed:
|
|
48
|
+
raise SystemExit("Secrets did not match.")
|
|
49
|
+
if not value.strip():
|
|
50
|
+
raise SystemExit(f"{prompt.rstrip(':')} is required.")
|
|
51
|
+
return value.strip()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _read_password_and_seed_from_stdin() -> tuple[str, str]:
|
|
55
|
+
raw = sys.stdin.read().strip()
|
|
56
|
+
if not raw:
|
|
57
|
+
raise SystemExit("Password and seed phrase payload is required on stdin.")
|
|
58
|
+
lines = raw.splitlines()
|
|
59
|
+
if len(lines) < 2:
|
|
60
|
+
raise SystemExit(
|
|
61
|
+
"For import via stdin, provide password on the first line and the seed phrase on the remaining lines."
|
|
62
|
+
)
|
|
63
|
+
password = lines[0].strip()
|
|
64
|
+
seed_phrase = " ".join(line.strip() for line in lines[1:] if line.strip())
|
|
65
|
+
if not password or not seed_phrase:
|
|
66
|
+
raise SystemExit("Both password and seed phrase are required.")
|
|
67
|
+
return password, seed_phrase
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def main() -> int:
|
|
71
|
+
parser = argparse.ArgumentParser(description="Manage a local OpenClaw BTC wallet binding")
|
|
72
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
73
|
+
|
|
74
|
+
common_parent = argparse.ArgumentParser(add_help=False)
|
|
75
|
+
common_parent.add_argument("--user-id", required=True)
|
|
76
|
+
common_parent.add_argument("--network", default="bitcoin")
|
|
77
|
+
common_parent.add_argument("--service-url")
|
|
78
|
+
|
|
79
|
+
get_parser = subparsers.add_parser("get", parents=[common_parent])
|
|
80
|
+
|
|
81
|
+
setup_parser = subparsers.add_parser("setup", parents=[common_parent])
|
|
82
|
+
setup_parser.add_argument("--label")
|
|
83
|
+
setup_parser.add_argument("--account-index", type=int)
|
|
84
|
+
setup_parser.add_argument("--reveal-seed", action="store_true")
|
|
85
|
+
setup_parser.add_argument("--password-stdin", action="store_true")
|
|
86
|
+
|
|
87
|
+
create_parser = subparsers.add_parser("create", parents=[common_parent])
|
|
88
|
+
create_parser.add_argument("--label")
|
|
89
|
+
create_parser.add_argument("--account-index", type=int)
|
|
90
|
+
create_parser.add_argument("--reveal-seed", action="store_true")
|
|
91
|
+
create_parser.add_argument("--password-stdin", action="store_true")
|
|
92
|
+
|
|
93
|
+
import_parser = subparsers.add_parser("import", parents=[common_parent])
|
|
94
|
+
import_parser.add_argument("--label")
|
|
95
|
+
import_parser.add_argument("--account-index", type=int)
|
|
96
|
+
import_parser.add_argument("--password-stdin", action="store_true")
|
|
97
|
+
import_parser.add_argument("--seed-stdin", action="store_true")
|
|
98
|
+
|
|
99
|
+
unlock_parser = subparsers.add_parser("unlock", parents=[common_parent])
|
|
100
|
+
unlock_parser.add_argument("--password-stdin", action="store_true")
|
|
101
|
+
|
|
102
|
+
reveal_parser = subparsers.add_parser("reveal-seed", parents=[common_parent])
|
|
103
|
+
reveal_parser.add_argument("--password-stdin", action="store_true")
|
|
104
|
+
|
|
105
|
+
lock_parser = subparsers.add_parser("lock", parents=[common_parent])
|
|
106
|
+
|
|
107
|
+
args = parser.parse_args()
|
|
108
|
+
effective_network = _normalize_network(args.network)
|
|
109
|
+
|
|
110
|
+
def _config_hint(wallet: dict[str, object]) -> dict[str, object]:
|
|
111
|
+
return {
|
|
112
|
+
"backend": "wdk_btc_local",
|
|
113
|
+
"network": effective_network,
|
|
114
|
+
"wdkBtcServiceUrl": args.service_url,
|
|
115
|
+
"wdkBtcWalletId": wallet.get("wallet_id"),
|
|
116
|
+
"wdkBtcAccountIndex": wallet.get("account_index"),
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if args.command == "get":
|
|
120
|
+
payload = {"ok": True, "wallet": get_user_btc_wallet_binding(args.user_id, network=effective_network)}
|
|
121
|
+
elif args.command == "setup":
|
|
122
|
+
password = _read_secret(
|
|
123
|
+
prompt="BTC wallet password: ",
|
|
124
|
+
confirm_prompt=None,
|
|
125
|
+
stdin_mode=bool(args.password_stdin),
|
|
126
|
+
)
|
|
127
|
+
try:
|
|
128
|
+
existing = get_user_btc_wallet_binding(args.user_id, network=effective_network)
|
|
129
|
+
except Exception:
|
|
130
|
+
existing = None
|
|
131
|
+
|
|
132
|
+
if existing is None:
|
|
133
|
+
wallet = create_user_btc_wallet(
|
|
134
|
+
args.user_id,
|
|
135
|
+
password=password,
|
|
136
|
+
label=args.label,
|
|
137
|
+
network=effective_network,
|
|
138
|
+
service_url=args.service_url,
|
|
139
|
+
reveal_seed_phrase=bool(args.reveal_seed),
|
|
140
|
+
account_index=args.account_index,
|
|
141
|
+
)
|
|
142
|
+
payload = {
|
|
143
|
+
"ok": True,
|
|
144
|
+
"action": "created",
|
|
145
|
+
"wallet": wallet,
|
|
146
|
+
"openclaw_config_hint": _config_hint(wallet),
|
|
147
|
+
}
|
|
148
|
+
else:
|
|
149
|
+
wallet = unlock_user_btc_wallet(
|
|
150
|
+
args.user_id,
|
|
151
|
+
password=password,
|
|
152
|
+
network=effective_network,
|
|
153
|
+
service_url=args.service_url,
|
|
154
|
+
)
|
|
155
|
+
payload = {
|
|
156
|
+
"ok": True,
|
|
157
|
+
"action": "unlocked",
|
|
158
|
+
"wallet": wallet,
|
|
159
|
+
"openclaw_config_hint": _config_hint(wallet),
|
|
160
|
+
}
|
|
161
|
+
elif args.command == "create":
|
|
162
|
+
payload = {
|
|
163
|
+
"ok": True,
|
|
164
|
+
"wallet": create_user_btc_wallet(
|
|
165
|
+
args.user_id,
|
|
166
|
+
password=_read_secret(
|
|
167
|
+
prompt="BTC wallet password: ",
|
|
168
|
+
confirm_prompt="Confirm BTC wallet password: ",
|
|
169
|
+
stdin_mode=bool(args.password_stdin),
|
|
170
|
+
),
|
|
171
|
+
label=args.label,
|
|
172
|
+
network=effective_network,
|
|
173
|
+
service_url=args.service_url,
|
|
174
|
+
reveal_seed_phrase=bool(args.reveal_seed),
|
|
175
|
+
account_index=args.account_index,
|
|
176
|
+
),
|
|
177
|
+
}
|
|
178
|
+
elif args.command == "import":
|
|
179
|
+
if args.password_stdin and args.seed_stdin:
|
|
180
|
+
password, seed_phrase = _read_password_and_seed_from_stdin()
|
|
181
|
+
else:
|
|
182
|
+
password = _read_secret(
|
|
183
|
+
prompt="BTC wallet password: ",
|
|
184
|
+
confirm_prompt="Confirm BTC wallet password: ",
|
|
185
|
+
stdin_mode=bool(args.password_stdin),
|
|
186
|
+
)
|
|
187
|
+
seed_phrase = _read_secret(
|
|
188
|
+
prompt="BTC seed phrase: ",
|
|
189
|
+
stdin_mode=bool(args.seed_stdin),
|
|
190
|
+
)
|
|
191
|
+
payload = {
|
|
192
|
+
"ok": True,
|
|
193
|
+
"wallet": import_user_btc_wallet(
|
|
194
|
+
args.user_id,
|
|
195
|
+
password=password,
|
|
196
|
+
seed_phrase=seed_phrase,
|
|
197
|
+
label=args.label,
|
|
198
|
+
network=effective_network,
|
|
199
|
+
service_url=args.service_url,
|
|
200
|
+
account_index=args.account_index,
|
|
201
|
+
),
|
|
202
|
+
}
|
|
203
|
+
elif args.command == "unlock":
|
|
204
|
+
payload = {
|
|
205
|
+
"ok": True,
|
|
206
|
+
"wallet": unlock_user_btc_wallet(
|
|
207
|
+
args.user_id,
|
|
208
|
+
password=_read_secret(
|
|
209
|
+
prompt="BTC wallet password: ",
|
|
210
|
+
stdin_mode=bool(args.password_stdin),
|
|
211
|
+
),
|
|
212
|
+
network=effective_network,
|
|
213
|
+
service_url=args.service_url,
|
|
214
|
+
),
|
|
215
|
+
}
|
|
216
|
+
elif args.command == "reveal-seed":
|
|
217
|
+
payload = {
|
|
218
|
+
"ok": True,
|
|
219
|
+
"wallet": reveal_user_btc_wallet_seed_phrase(
|
|
220
|
+
args.user_id,
|
|
221
|
+
password=_read_secret(
|
|
222
|
+
prompt="BTC wallet password: ",
|
|
223
|
+
stdin_mode=bool(args.password_stdin),
|
|
224
|
+
),
|
|
225
|
+
network=effective_network,
|
|
226
|
+
service_url=args.service_url,
|
|
227
|
+
),
|
|
228
|
+
}
|
|
229
|
+
else:
|
|
230
|
+
payload = {
|
|
231
|
+
"ok": True,
|
|
232
|
+
"wallet": lock_user_btc_wallet(
|
|
233
|
+
args.user_id,
|
|
234
|
+
network=effective_network,
|
|
235
|
+
service_url=args.service_url,
|
|
236
|
+
),
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
print(json.dumps(payload))
|
|
240
|
+
return 0
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
if __name__ == "__main__":
|
|
244
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
5
|
+
PACKAGE_ROOT=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)
|
|
6
|
+
|
|
7
|
+
resolve_python_bin() {
|
|
8
|
+
if [ -n "${OPENCLAW_AGENT_WALLET_PYTHON:-}" ] && [ -x "${OPENCLAW_AGENT_WALLET_PYTHON}" ]; then
|
|
9
|
+
printf "%s" "${OPENCLAW_AGENT_WALLET_PYTHON}"
|
|
10
|
+
return 0
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
if [ -x "/tmp/agent-wallet-venv/bin/python" ]; then
|
|
14
|
+
printf "%s" "/tmp/agent-wallet-venv/bin/python"
|
|
15
|
+
return 0
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
if [ -x "$PACKAGE_ROOT/.venv/bin/python" ]; then
|
|
19
|
+
printf "%s" "$PACKAGE_ROOT/.venv/bin/python"
|
|
20
|
+
return 0
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
24
|
+
command -v python3
|
|
25
|
+
return 0
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
command -v python
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
PYTHON_BIN=$(resolve_python_bin)
|
|
32
|
+
export OPENCLAW_AGENT_WALLET_PYTHON="$PYTHON_BIN"
|
|
33
|
+
|
|
34
|
+
has_flag() {
|
|
35
|
+
flag=$1
|
|
36
|
+
shift
|
|
37
|
+
for arg in "$@"; do
|
|
38
|
+
case "$arg" in
|
|
39
|
+
"$flag"|"$flag"=*)
|
|
40
|
+
return 0
|
|
41
|
+
;;
|
|
42
|
+
esac
|
|
43
|
+
done
|
|
44
|
+
return 1
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
prompt_with_default() {
|
|
48
|
+
label=$1
|
|
49
|
+
default_value=$2
|
|
50
|
+
if [ -t 0 ]; then
|
|
51
|
+
printf "%s [%s]: " "$label" "$default_value" >&2
|
|
52
|
+
read -r value
|
|
53
|
+
if [ -z "${value:-}" ]; then
|
|
54
|
+
printf "%s" "$default_value"
|
|
55
|
+
else
|
|
56
|
+
printf "%s" "$value"
|
|
57
|
+
fi
|
|
58
|
+
return 0
|
|
59
|
+
fi
|
|
60
|
+
printf "%s" "$default_value"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
normalize_network_value() {
|
|
64
|
+
case $(printf "%s" "$1" | tr '[:upper:]' '[:lower:]') in
|
|
65
|
+
1|mainnet|bitcoin)
|
|
66
|
+
printf "mainnet"
|
|
67
|
+
;;
|
|
68
|
+
2|testnet)
|
|
69
|
+
printf "testnet"
|
|
70
|
+
;;
|
|
71
|
+
3|regtest)
|
|
72
|
+
printf "regtest"
|
|
73
|
+
;;
|
|
74
|
+
*)
|
|
75
|
+
return 1
|
|
76
|
+
;;
|
|
77
|
+
esac
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
prompt_network_choice() {
|
|
81
|
+
default_value=$1
|
|
82
|
+
if ! [ -t 0 ]; then
|
|
83
|
+
printf "%s" "$default_value"
|
|
84
|
+
return 0
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
case "$default_value" in
|
|
88
|
+
mainnet|bitcoin) default_hint="1" ;;
|
|
89
|
+
testnet) default_hint="2" ;;
|
|
90
|
+
regtest) default_hint="3" ;;
|
|
91
|
+
*) default_hint="1" ;;
|
|
92
|
+
esac
|
|
93
|
+
|
|
94
|
+
while true; do
|
|
95
|
+
printf "BTC network:\n" >&2
|
|
96
|
+
printf " 1) mainnet\n" >&2
|
|
97
|
+
printf " 2) testnet\n" >&2
|
|
98
|
+
printf " 3) regtest\n" >&2
|
|
99
|
+
printf "Choose network [%s]: " "$default_hint" >&2
|
|
100
|
+
read -r choice
|
|
101
|
+
if [ -z "${choice:-}" ]; then
|
|
102
|
+
choice=$default_hint
|
|
103
|
+
fi
|
|
104
|
+
if network=$(normalize_network_value "$choice"); then
|
|
105
|
+
printf "%s" "$network"
|
|
106
|
+
return 0
|
|
107
|
+
fi
|
|
108
|
+
printf "Invalid choice. Enter 1, 2, 3, mainnet, testnet, or regtest.\n" >&2
|
|
109
|
+
done
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
DEFAULT_USER_ID=${OPENCLAW_BTC_USER_ID:-${USER:-openclaw-user}-local}
|
|
113
|
+
DEFAULT_NETWORK=${OPENCLAW_BTC_NETWORK:-mainnet}
|
|
114
|
+
DEFAULT_SERVICE_URL=${OPENCLAW_BTC_SERVICE_URL:-http://127.0.0.1:8080}
|
|
115
|
+
|
|
116
|
+
if ! has_flag --user-id "$@"; then
|
|
117
|
+
USER_ID=$(prompt_with_default "OpenClaw user id" "$DEFAULT_USER_ID")
|
|
118
|
+
set -- "$@" --user-id "$USER_ID"
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
if ! has_flag --network "$@"; then
|
|
122
|
+
NETWORK=$(prompt_network_choice "$DEFAULT_NETWORK")
|
|
123
|
+
set -- "$@" --network "$NETWORK"
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
if ! has_flag --service-url "$@"; then
|
|
127
|
+
set -- "$@" --service-url "$DEFAULT_SERVICE_URL"
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
exec "$PYTHON_BIN" "$SCRIPT_DIR/manage_openclaw_btc_wallet.py" reveal-seed "$@"
|