@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,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())
|