@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.
Files changed (96) hide show
  1. package/.openclaw/AGENTS.md +98 -0
  2. package/.openclaw/extensions/agent-wallet/README.md +127 -0
  3. package/.openclaw/extensions/agent-wallet/index.ts +1520 -0
  4. package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +184 -0
  5. package/.openclaw/extensions/agent-wallet/package.json +11 -0
  6. package/.openclaw/extensions/agent-wallet/skills/wallet-operator/SKILL.md +20 -0
  7. package/CHANGELOG.md +42 -0
  8. package/LICENSE +104 -0
  9. package/README.md +332 -0
  10. package/RELEASING.md +204 -0
  11. package/agent-wallet/.env.example +62 -0
  12. package/agent-wallet/AGENTS.md +129 -0
  13. package/agent-wallet/README.md +527 -0
  14. package/agent-wallet/agent_wallet/__init__.py +11 -0
  15. package/agent-wallet/agent_wallet/approval.py +161 -0
  16. package/agent-wallet/agent_wallet/bootstrap.py +178 -0
  17. package/agent-wallet/agent_wallet/btc_user_wallets.py +217 -0
  18. package/agent-wallet/agent_wallet/config.py +382 -0
  19. package/agent-wallet/agent_wallet/encrypted_storage.py +161 -0
  20. package/agent-wallet/agent_wallet/evm_user_wallets.py +370 -0
  21. package/agent-wallet/agent_wallet/exceptions.py +9 -0
  22. package/agent-wallet/agent_wallet/file_ops.py +34 -0
  23. package/agent-wallet/agent_wallet/http_client.py +25 -0
  24. package/agent-wallet/agent_wallet/models.py +66 -0
  25. package/agent-wallet/agent_wallet/nonce_registry.py +59 -0
  26. package/agent-wallet/agent_wallet/openclaw_adapter.py +5128 -0
  27. package/agent-wallet/agent_wallet/openclaw_cli.py +626 -0
  28. package/agent-wallet/agent_wallet/openclaw_runtime.py +272 -0
  29. package/agent-wallet/agent_wallet/plugin_bundle.py +42 -0
  30. package/agent-wallet/agent_wallet/providers/__init__.py +1 -0
  31. package/agent-wallet/agent_wallet/providers/bags.py +259 -0
  32. package/agent-wallet/agent_wallet/providers/evm_portfolio.py +470 -0
  33. package/agent-wallet/agent_wallet/providers/jupiter.py +567 -0
  34. package/agent-wallet/agent_wallet/providers/kamino.py +215 -0
  35. package/agent-wallet/agent_wallet/providers/lifi.py +277 -0
  36. package/agent-wallet/agent_wallet/providers/solana_rpc.py +470 -0
  37. package/agent-wallet/agent_wallet/providers/wdk_btc_local.py +114 -0
  38. package/agent-wallet/agent_wallet/providers/wdk_evm_local.py +205 -0
  39. package/agent-wallet/agent_wallet/sealed_keys.py +61 -0
  40. package/agent-wallet/agent_wallet/solana_stake.py +103 -0
  41. package/agent-wallet/agent_wallet/solana_tx.py +93 -0
  42. package/agent-wallet/agent_wallet/spending_limits.py +101 -0
  43. package/agent-wallet/agent_wallet/transaction_policy.py +518 -0
  44. package/agent-wallet/agent_wallet/user_wallets.py +355 -0
  45. package/agent-wallet/agent_wallet/validation.py +31 -0
  46. package/agent-wallet/agent_wallet/wallet_layer/__init__.py +1 -0
  47. package/agent-wallet/agent_wallet/wallet_layer/base.py +808 -0
  48. package/agent-wallet/agent_wallet/wallet_layer/base58.py +44 -0
  49. package/agent-wallet/agent_wallet/wallet_layer/factory.py +102 -0
  50. package/agent-wallet/agent_wallet/wallet_layer/solana.py +4252 -0
  51. package/agent-wallet/agent_wallet/wallet_layer/wdk_btc.py +272 -0
  52. package/agent-wallet/agent_wallet/wallet_layer/wdk_evm.py +1628 -0
  53. package/agent-wallet/examples/bootstrap_wallet.py +21 -0
  54. package/agent-wallet/examples/openclaw_runtime_onboarding.py +28 -0
  55. package/agent-wallet/examples/openclaw_user_wallet_example.py +31 -0
  56. package/agent-wallet/examples/openclaw_wallet_adapter_example.py +33 -0
  57. package/agent-wallet/openclaw.plugin.json +138 -0
  58. package/agent-wallet/pyproject.toml +31 -0
  59. package/agent-wallet/scripts/bootstrap_openclaw_btc.py +278 -0
  60. package/agent-wallet/scripts/build_release_bundle.py +188 -0
  61. package/agent-wallet/scripts/finalize_openclaw_local_wallet_config.py +121 -0
  62. package/agent-wallet/scripts/install_agent_wallet.py +505 -0
  63. package/agent-wallet/scripts/install_openclaw_local_config.py +226 -0
  64. package/agent-wallet/scripts/install_openclaw_sealed_keys.py +105 -0
  65. package/agent-wallet/scripts/manage_openclaw_btc_wallet.py +244 -0
  66. package/agent-wallet/scripts/reveal_btc_seed.sh +130 -0
  67. package/agent-wallet/scripts/security_utils.py +37 -0
  68. package/agent-wallet/scripts/setup_btc_wallet.sh +146 -0
  69. package/agent-wallet/scripts/switch_openclaw_wallet_network.py +106 -0
  70. package/agent-wallet/skills/wallet-operator/SKILL.md +128 -0
  71. package/bin/openclaw-agent-wallet.mjs +487 -0
  72. package/install-from-github.sh +134 -0
  73. package/package.json +61 -0
  74. package/setup.sh +40 -0
  75. package/wdk-btc-wallet/README.md +325 -0
  76. package/wdk-btc-wallet/bootstrap.sh +22 -0
  77. package/wdk-btc-wallet/package-lock.json +1839 -0
  78. package/wdk-btc-wallet/package.json +18 -0
  79. package/wdk-btc-wallet/run-local.sh +21 -0
  80. package/wdk-btc-wallet/src/config.js +160 -0
  81. package/wdk-btc-wallet/src/json.js +35 -0
  82. package/wdk-btc-wallet/src/local_vault.js +432 -0
  83. package/wdk-btc-wallet/src/network_state.js +84 -0
  84. package/wdk-btc-wallet/src/server.js +257 -0
  85. package/wdk-btc-wallet/src/wdk_btc_wallet.js +332 -0
  86. package/wdk-evm-wallet/README.md +183 -0
  87. package/wdk-evm-wallet/bootstrap.sh +8 -0
  88. package/wdk-evm-wallet/package-lock.json +2340 -0
  89. package/wdk-evm-wallet/package.json +23 -0
  90. package/wdk-evm-wallet/run-local.sh +12 -0
  91. package/wdk-evm-wallet/src/config.js +274 -0
  92. package/wdk-evm-wallet/src/json.js +35 -0
  93. package/wdk-evm-wallet/src/local_vault.js +430 -0
  94. package/wdk-evm-wallet/src/network_state.js +92 -0
  95. package/wdk-evm-wallet/src/server.js +575 -0
  96. package/wdk-evm-wallet/src/wdk_evm_wallet.js +4981 -0
@@ -0,0 +1,21 @@
1
+ """Example bootstrap entrypoint for OpenClaw plugin startup."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+
7
+ from agent_wallet.bootstrap import describe_bootstrap, ensure_solana_wallet_ready
8
+
9
+
10
+ def main() -> None:
11
+ print("Bootstrap configuration:")
12
+ print(json.dumps(describe_bootstrap(), indent=2))
13
+ print()
14
+
15
+ result = ensure_solana_wallet_ready()
16
+ print("Bootstrap result:")
17
+ print(json.dumps(result, indent=2))
18
+
19
+
20
+ if __name__ == "__main__":
21
+ main()
@@ -0,0 +1,28 @@
1
+ """Example: host-side onboarding flow for attaching agent-wallet to OpenClaw."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+
7
+ from agent_wallet.openclaw_runtime import onboard_openclaw_user_wallet
8
+
9
+
10
+ def main() -> None:
11
+ user_id = "demo-user-123"
12
+ # Set AGENT_WALLET_MASTER_KEY in the environment before first run.
13
+ context = onboard_openclaw_user_wallet(
14
+ user_id,
15
+ sign_only=False,
16
+ network="devnet",
17
+ )
18
+
19
+ print("Session metadata:")
20
+ print(context.session_metadata().model_dump_json(indent=2))
21
+ print()
22
+
23
+ print("Serializable bundle for OpenClaw runtime:")
24
+ print(json.dumps(context.serializable_bundle(), indent=2))
25
+
26
+
27
+ if __name__ == "__main__":
28
+ main()
@@ -0,0 +1,31 @@
1
+ """Example: user-scoped wallet integration for an OpenClaw runtime."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import json
7
+
8
+ from agent_wallet.openclaw_adapter import OpenClawWalletAdapter
9
+ from agent_wallet.user_wallets import create_wallet_backend_for_user
10
+
11
+
12
+ async def main() -> None:
13
+ user_id = "demo-user-123"
14
+ # Set AGENT_WALLET_MASTER_KEY in the environment before first run so
15
+ # the per-user wallet is encrypted at rest.
16
+ backend = create_wallet_backend_for_user(
17
+ user_id,
18
+ sign_only=False,
19
+ network="devnet",
20
+ )
21
+ adapter = OpenClawWalletAdapter(backend)
22
+
23
+ print(f"Loaded backend for user_id={user_id}")
24
+ print(json.dumps([tool.model_dump() for tool in adapter.list_tools()], indent=2))
25
+
26
+ address = await adapter.invoke("get_wallet_address")
27
+ print(address.model_dump_json(indent=2))
28
+
29
+
30
+ if __name__ == "__main__":
31
+ asyncio.run(main())
@@ -0,0 +1,33 @@
1
+ """Example: exposing agent-wallet tools to an OpenClaw-style runtime."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import json
7
+
8
+ from agent_wallet.openclaw_adapter import OpenClawWalletAdapter
9
+ from agent_wallet.wallet_layer.factory import create_wallet_backend
10
+
11
+
12
+ async def main() -> None:
13
+ backend = create_wallet_backend()
14
+ if backend is None:
15
+ raise RuntimeError("No wallet backend configured. Set AGENT_WALLET_BACKEND first.")
16
+
17
+ adapter = OpenClawWalletAdapter(backend)
18
+
19
+ print("Runtime instructions:")
20
+ print(adapter.get_runtime_instructions())
21
+ print()
22
+
23
+ print("Registered tools:")
24
+ print(json.dumps([tool.model_dump() for tool in adapter.list_tools()], indent=2))
25
+ print()
26
+
27
+ result = await adapter.invoke("get_wallet_capabilities")
28
+ print("Capabilities:")
29
+ print(result.model_dump_json(indent=2))
30
+
31
+
32
+ if __name__ == "__main__":
33
+ asyncio.run(main())
@@ -0,0 +1,138 @@
1
+ {
2
+ "id": "agent-wallet",
3
+ "name": "Agent Wallet",
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.0",
6
+ "skills": ["skills/wallet-operator"],
7
+ "configSchema": {
8
+ "type": "object",
9
+ "additionalProperties": false,
10
+ "properties": {
11
+ "backend": {
12
+ "type": "string",
13
+ "description": "Wallet backend identifier. Supported values: solana_local, wdk_btc_local, wdk_evm_local."
14
+ },
15
+ "rpcUrl": {
16
+ "type": "string",
17
+ "description": "Optional local fallback Solana RPC URL. Deployment env SOLANA_RPC_URL wins if set."
18
+ },
19
+ "rpcUrls": {
20
+ "type": "array",
21
+ "items": { "type": "string" },
22
+ "description": "Optional local fallback ordered list of Solana RPC URLs. Deployment env SOLANA_RPC_URLS wins if set."
23
+ },
24
+ "rpcProviderMode": {
25
+ "type": "string",
26
+ "description": "RPC mode: auto, user_direct, or shared_proxy. In auto mode, user RPC keys/URLs win; otherwise the shared provider gateway is used when configured."
27
+ },
28
+ "providerGatewayUrl": {
29
+ "type": "string",
30
+ "description": "Optional override for the shared provider gateway base URL. If omitted, the hosted OpenClaw gateway default is used for onboarding-friendly mainnet RPC and Bags launch/fees flows."
31
+ },
32
+ "providerGatewayRpcProvider": {
33
+ "type": "string",
34
+ "description": "Optional shared RPC upstream selection for the provider gateway: auto, shared, helius, or alchemy."
35
+ },
36
+ "swapProvider": {
37
+ "type": "string",
38
+ "description": "Optional Solana swap routing preference: auto or jupiter. Bags launch and fee flows still use the provider gateway separately, but token swaps stay Jupiter-first."
39
+ },
40
+ "network": {
41
+ "type": "string",
42
+ "description": "Backend network selector. Solana uses mainnet/devnet/testnet. BTC uses bitcoin/testnet/regtest. EVM uses ethereum/sepolia/base/base-sepolia."
43
+ },
44
+ "wdkBtcServiceUrl": {
45
+ "type": "string",
46
+ "description": "Base URL for the localhost-only wdk-btc-wallet HTTP service when backend=wdk_btc_local."
47
+ },
48
+ "wdkBtcWalletId": {
49
+ "type": "string",
50
+ "description": "Optional direct walletId override from the local wdk-btc-wallet vault. If omitted, agent-wallet will look for a user-bound BTC wallet under ~/.openclaw/users/<user>/wallets/."
51
+ },
52
+ "wdkBtcAccountIndex": {
53
+ "type": "integer",
54
+ "description": "Optional BTC account index to expose to the agent. Defaults to 0."
55
+ },
56
+ "wdkEvmServiceUrl": {
57
+ "type": "string",
58
+ "description": "Base URL for the localhost-only wdk-evm-wallet HTTP service when backend=wdk_evm_local."
59
+ },
60
+ "wdkEvmWalletId": {
61
+ "type": "string",
62
+ "description": "Optional direct walletId override from the local wdk-evm-wallet vault. If omitted, agent-wallet will look for a user-bound EVM wallet under ~/.openclaw/users/<user>/wallets/."
63
+ },
64
+ "wdkEvmAccountIndex": {
65
+ "type": "integer",
66
+ "description": "Optional EVM account index to expose to the agent. Defaults to 0."
67
+ },
68
+ "publicKey": {
69
+ "type": "string",
70
+ "description": "Read-only wallet public key."
71
+ },
72
+ "privateKey": {
73
+ "type": "string",
74
+ "description": "Deprecated insecure config path. Inject signing key material via environment variables instead of plugin config."
75
+ },
76
+ "keypairPath": {
77
+ "type": "string",
78
+ "description": "Path to Solana CLI JSON keypair file."
79
+ },
80
+ "autoCreateWallet": {
81
+ "type": "boolean",
82
+ "description": "Create a local wallet file automatically on first start if none exists."
83
+ },
84
+ "signOnly": {
85
+ "type": "boolean",
86
+ "description": "If true, the wallet may sign but should not broadcast transactions automatically."
87
+ },
88
+ "masterKey": {
89
+ "type": "string",
90
+ "description": "Deprecated insecure config path. Inject the wallet master secret via environment or a secret manager instead of plugin config."
91
+ },
92
+ "approvalSecret": {
93
+ "type": "string",
94
+ "description": "Deprecated insecure config path. Inject the approval secret via environment or a secret manager instead of plugin config."
95
+ },
96
+ "encryptUserWallets": {
97
+ "type": "boolean",
98
+ "description": "If true, newly created per-user wallet files are encrypted at rest."
99
+ },
100
+ "refuseMainnetWalletRecreation": {
101
+ "type": "boolean",
102
+ "description": "If true, refuse to silently create a new mainnet wallet when a pinned address already exists."
103
+ },
104
+ "jupiterBaseUrl": {
105
+ "type": "string",
106
+ "description": "Optional Jupiter Swap API base URL for quote and swap building."
107
+ },
108
+ "jupiterUltraBaseUrl": {
109
+ "type": "string",
110
+ "description": "Optional Jupiter Ultra API base URL for managed swap execution."
111
+ },
112
+ "jupiterPriceBaseUrl": {
113
+ "type": "string",
114
+ "description": "Optional Jupiter Price API base URL for token price lookup."
115
+ },
116
+ "jupiterPortfolioBaseUrl": {
117
+ "type": "string",
118
+ "description": "Optional Jupiter Portfolio API base URL for Jupiter-specific positions and staking data."
119
+ },
120
+ "jupiterLendBaseUrl": {
121
+ "type": "string",
122
+ "description": "Optional Jupiter Lend API base URL for Earn read and deposit/withdraw flows."
123
+ },
124
+ "jupiterApiKey": {
125
+ "type": "string",
126
+ "description": "Optional Jupiter API key if your deployment uses a keyed endpoint."
127
+ },
128
+ "kaminoBaseUrl": {
129
+ "type": "string",
130
+ "description": "Optional Kamino REST API base URL for lending reads and transaction building."
131
+ },
132
+ "kaminoProgramId": {
133
+ "type": "string",
134
+ "description": "Optional Kamino lending program id for market discovery."
135
+ }
136
+ }
137
+ }
138
+ }
@@ -0,0 +1,31 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "openclaw-agent-wallet"
7
+ version = "0.1.0"
8
+ description = "Plugin-friendly wallet backend for OpenClaw agents"
9
+ requires-python = ">=3.11"
10
+ dependencies = [
11
+ "httpx>=0.27.0",
12
+ "pydantic>=2.0.0",
13
+ "pydantic-settings>=2.0.0",
14
+ "pynacl>=1.5.0",
15
+ "python-dotenv>=1.0.0",
16
+ "solana>=0.36.0",
17
+ ]
18
+
19
+ [project.optional-dependencies]
20
+ dev = [
21
+ "pytest>=8.0.0",
22
+ "pytest-asyncio>=0.23.0",
23
+ "ruff>=0.4.0",
24
+ ]
25
+
26
+ [tool.hatch.build.targets.wheel]
27
+ packages = ["agent_wallet"]
28
+
29
+ [tool.ruff]
30
+ target-version = "py311"
31
+ line-length = 100
@@ -0,0 +1,278 @@
1
+ #!/usr/bin/env python3
2
+ """One-command host bootstrap for the local OpenClaw BTC wallet flow."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import os
9
+ import subprocess
10
+ import sys
11
+ import time
12
+ from urllib.error import URLError
13
+ from urllib.parse import urlparse
14
+ from urllib.request import urlopen
15
+ from pathlib import Path
16
+
17
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
18
+
19
+ from agent_wallet.file_ops import atomic_write_text, chmod_if_exists
20
+
21
+
22
+ def _default_config_path() -> Path:
23
+ return Path(os.path.expanduser("~/.openclaw/openclaw.json"))
24
+
25
+
26
+ def _default_user_id() -> str:
27
+ return f"{os.getenv('USER', 'openclaw-user')}-local"
28
+
29
+
30
+ def _default_python_bin() -> str:
31
+ return os.getenv("OPENCLAW_AGENT_WALLET_PYTHON", sys.executable)
32
+
33
+
34
+ def _package_root() -> Path:
35
+ return Path(__file__).resolve().parents[1]
36
+
37
+
38
+ def _repo_root() -> Path:
39
+ return Path(__file__).resolve().parents[2]
40
+
41
+
42
+ def _script_path(name: str) -> Path:
43
+ return _package_root() / "scripts" / name
44
+
45
+
46
+ def _normalize_network(value: str) -> str:
47
+ network = str(value or "").strip().lower()
48
+ if network == "mainnet":
49
+ return "bitcoin"
50
+ return network or "bitcoin"
51
+
52
+
53
+ def build_parser() -> argparse.ArgumentParser:
54
+ parser = argparse.ArgumentParser(description=__doc__)
55
+ parser.add_argument("--config-path", default=str(_default_config_path()))
56
+ parser.add_argument("--plugin-id", default="agent-wallet")
57
+ parser.add_argument("--user-id", default=_default_user_id())
58
+ parser.add_argument("--network", default="testnet")
59
+ parser.add_argument("--service-url", default="http://127.0.0.1:8080")
60
+ parser.add_argument("--wdk-wallet-root", default=str(_repo_root() / "wdk-btc-wallet"))
61
+ parser.add_argument("--label", default="Agent BTC Wallet")
62
+ parser.add_argument("--account-index", type=int, default=0)
63
+ parser.add_argument("--python-bin", default=_default_python_bin())
64
+ parser.add_argument("--package-root", default=str(_package_root()))
65
+ parser.add_argument("--password-stdin", action=argparse.BooleanOptionalAction, default=False)
66
+ parser.add_argument("--reveal-seed", action=argparse.BooleanOptionalAction, default=False)
67
+ parser.add_argument("--sign-only", action=argparse.BooleanOptionalAction, default=False)
68
+ parser.add_argument("--auto-start-service", action=argparse.BooleanOptionalAction, default=True)
69
+ return parser
70
+
71
+
72
+ def _ensure_openclaw_config(config_path: Path) -> bool:
73
+ if config_path.exists():
74
+ return False
75
+ config_path.parent.mkdir(parents=True, exist_ok=True)
76
+ atomic_write_text(
77
+ config_path,
78
+ json.dumps({"plugins": {"entries": {}}, "tools": {"alsoAllow": []}}, indent=2) + "\n",
79
+ mode=0o600,
80
+ )
81
+ chmod_if_exists(config_path, 0o600)
82
+ return True
83
+
84
+
85
+ def _run_script(
86
+ python_bin: str,
87
+ script_name: str,
88
+ args: list[str],
89
+ *,
90
+ stdin_text: str | None = None,
91
+ ) -> dict:
92
+ completed = subprocess.run(
93
+ [python_bin, str(_script_path(script_name)), *args],
94
+ check=True,
95
+ capture_output=True,
96
+ text=True,
97
+ input=stdin_text,
98
+ env=os.environ.copy(),
99
+ )
100
+ return json.loads(completed.stdout)
101
+
102
+
103
+ def _health_url(service_url: str) -> str:
104
+ return f"{service_url.rstrip('/')}/health"
105
+
106
+
107
+ def _service_is_healthy(service_url: str) -> bool:
108
+ try:
109
+ with urlopen(_health_url(service_url), timeout=1.5) as response:
110
+ return int(getattr(response, "status", 0) or 0) == 200
111
+ except (URLError, TimeoutError, OSError):
112
+ return False
113
+
114
+
115
+ def _is_local_service_url(service_url: str) -> bool:
116
+ parsed = urlparse(service_url)
117
+ return parsed.scheme in {"http", "https"} and parsed.hostname in {"127.0.0.1", "localhost", "::1"}
118
+
119
+
120
+ def _require_local_service_url(service_url: str) -> None:
121
+ if not _is_local_service_url(service_url):
122
+ raise SystemExit(
123
+ f"BTC bootstrap only supports a localhost service URL. Refusing non-local endpoint: {service_url}"
124
+ )
125
+
126
+
127
+ def _service_log_dir(config_path: Path) -> Path:
128
+ return config_path.expanduser().parent / "logs"
129
+
130
+
131
+ def _service_log_path(config_path: Path) -> Path:
132
+ return _service_log_dir(config_path) / "wdk-btc-wallet.log"
133
+
134
+
135
+ def _auto_start_local_service(
136
+ *,
137
+ service_url: str,
138
+ network: str,
139
+ wdk_wallet_root: Path,
140
+ config_path: Path,
141
+ ) -> dict[str, object]:
142
+ if _service_is_healthy(service_url):
143
+ return {"started": False, "already_healthy": True}
144
+
145
+ if not _is_local_service_url(service_url):
146
+ raise SystemExit(
147
+ f"BTC service at {service_url} is unreachable and auto-start is only supported for localhost URLs."
148
+ )
149
+
150
+ run_local = wdk_wallet_root / "run-local.sh"
151
+ if not run_local.exists():
152
+ raise SystemExit(f"Could not find wdk-btc-wallet launcher: {run_local}")
153
+
154
+ parsed = urlparse(service_url)
155
+ host = parsed.hostname or "127.0.0.1"
156
+ port = parsed.port or 8080
157
+ log_dir = _service_log_dir(config_path)
158
+ log_dir.mkdir(parents=True, exist_ok=True)
159
+ log_path = _service_log_path(config_path)
160
+
161
+ env = os.environ.copy()
162
+ env["HOST"] = host
163
+ env["PORT"] = str(port)
164
+ env["WDK_BTC_NETWORK"] = network
165
+
166
+ with log_path.open("a", encoding="utf-8") as log_file:
167
+ process = subprocess.Popen( # noqa: S603
168
+ ["sh", str(run_local)],
169
+ cwd=str(wdk_wallet_root),
170
+ env=env,
171
+ stdin=subprocess.DEVNULL,
172
+ stdout=log_file,
173
+ stderr=log_file,
174
+ start_new_session=True,
175
+ )
176
+
177
+ deadline = time.time() + 30.0
178
+ while time.time() < deadline:
179
+ if _service_is_healthy(service_url):
180
+ return {
181
+ "started": True,
182
+ "already_healthy": False,
183
+ "pid": process.pid,
184
+ "log_path": str(log_path),
185
+ }
186
+ if process.poll() is not None:
187
+ raise SystemExit(
188
+ f"wdk-btc-wallet exited before becoming healthy. Check log: {log_path}"
189
+ )
190
+ time.sleep(0.5)
191
+
192
+ raise SystemExit(
193
+ f"Timed out waiting for wdk-btc-wallet health at {_health_url(service_url)}. Check log: {log_path}"
194
+ )
195
+
196
+
197
+ def main() -> int:
198
+ args = build_parser().parse_args()
199
+ effective_network = _normalize_network(args.network)
200
+ _require_local_service_url(args.service_url)
201
+ config_path = Path(args.config_path).expanduser()
202
+ config_created = _ensure_openclaw_config(config_path)
203
+ service_bootstrap: dict[str, object] | None = None
204
+ if args.auto_start_service:
205
+ service_bootstrap = _auto_start_local_service(
206
+ service_url=args.service_url,
207
+ network=effective_network,
208
+ wdk_wallet_root=Path(args.wdk_wallet_root).expanduser(),
209
+ config_path=config_path,
210
+ )
211
+ elif not _service_is_healthy(args.service_url):
212
+ raise SystemExit(
213
+ f"BTC service is not healthy at {_health_url(args.service_url)} and --no-auto-start-service was set."
214
+ )
215
+
216
+ stdin_text = sys.stdin.read() if args.password_stdin else None
217
+ setup_payload = _run_script(
218
+ args.python_bin,
219
+ "manage_openclaw_btc_wallet.py",
220
+ [
221
+ "setup",
222
+ "--user-id",
223
+ args.user_id,
224
+ "--network",
225
+ effective_network,
226
+ "--service-url",
227
+ args.service_url,
228
+ "--label",
229
+ args.label,
230
+ "--account-index",
231
+ str(args.account_index),
232
+ *([] if not args.password_stdin else ["--password-stdin"]),
233
+ *(["--reveal-seed"] if args.reveal_seed else []),
234
+ ],
235
+ stdin_text=stdin_text,
236
+ )
237
+
238
+ install_args = [
239
+ "--config-path",
240
+ str(config_path),
241
+ "--plugin-id",
242
+ args.plugin_id,
243
+ "--user-id",
244
+ args.user_id,
245
+ "--backend",
246
+ "wdk_btc_local",
247
+ "--network",
248
+ effective_network,
249
+ "--wdk-btc-service-url",
250
+ args.service_url,
251
+ "--wdk-btc-account-index",
252
+ str(args.account_index),
253
+ "--package-root",
254
+ args.package_root,
255
+ "--python-bin",
256
+ args.python_bin,
257
+ "--sign-only" if args.sign_only else "--no-sign-only",
258
+ ]
259
+ install_payload = _run_script(
260
+ args.python_bin,
261
+ "install_openclaw_local_config.py",
262
+ install_args,
263
+ )
264
+
265
+ output = {
266
+ "ok": True,
267
+ "config_created": config_created,
268
+ "config_path": str(config_path),
269
+ "service_bootstrap": service_bootstrap,
270
+ "btc_setup": setup_payload,
271
+ "openclaw_config": install_payload,
272
+ }
273
+ print(json.dumps(output, indent=2))
274
+ return 0
275
+
276
+
277
+ if __name__ == "__main__":
278
+ raise SystemExit(main())