@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,272 @@
|
|
|
1
|
+
"""Host-side onboarding helpers for wiring agent-wallet into OpenClaw."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from agent_wallet.approval import issue_approval_token
|
|
9
|
+
from agent_wallet.btc_user_wallets import get_user_btc_wallet_binding
|
|
10
|
+
from agent_wallet.config import settings
|
|
11
|
+
from agent_wallet.evm_user_wallets import ensure_user_evm_wallet_binding, get_user_evm_wallet_binding
|
|
12
|
+
from agent_wallet.models import OpenClawWalletSessionMetadata
|
|
13
|
+
from agent_wallet.openclaw_adapter import OpenClawWalletAdapter
|
|
14
|
+
from agent_wallet.plugin_bundle import build_openclaw_plugin_bundle
|
|
15
|
+
from agent_wallet.providers.wdk_btc_local import WdkBtcLocalClient
|
|
16
|
+
from agent_wallet.providers.wdk_evm_local import WdkEvmLocalClient
|
|
17
|
+
from agent_wallet.user_wallets import create_wallet_backend_for_user, ensure_user_solana_wallet, resolve_user_wallet_path
|
|
18
|
+
from agent_wallet.wallet_layer.base import AgentWalletBackend, WalletBackendError
|
|
19
|
+
from agent_wallet.wallet_layer.wdk_evm import WdkEvmLocalWalletBackend
|
|
20
|
+
from agent_wallet.wallet_layer.wdk_btc import WdkBtcLocalWalletBackend
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(slots=True)
|
|
24
|
+
class OpenClawWalletRuntimeContext:
|
|
25
|
+
"""Runtime-ready wallet context for a single OpenClaw user session."""
|
|
26
|
+
|
|
27
|
+
user_id: str
|
|
28
|
+
wallet_info: dict[str, str]
|
|
29
|
+
created_now: bool
|
|
30
|
+
backend: AgentWalletBackend
|
|
31
|
+
adapter: OpenClawWalletAdapter
|
|
32
|
+
plugin_bundle: dict[str, Any]
|
|
33
|
+
|
|
34
|
+
def issue_execute_approval(
|
|
35
|
+
self,
|
|
36
|
+
*,
|
|
37
|
+
tool_name: str,
|
|
38
|
+
confirmation_summary: dict[str, Any],
|
|
39
|
+
mainnet_confirmed: bool = False,
|
|
40
|
+
ttl_seconds: int | None = None,
|
|
41
|
+
issued_by: str = "host",
|
|
42
|
+
) -> str:
|
|
43
|
+
"""Issue a host approval token bound to an exact execute operation."""
|
|
44
|
+
return issue_approval_token(
|
|
45
|
+
tool_name=tool_name,
|
|
46
|
+
network=str(getattr(self.backend, "network", "mainnet")),
|
|
47
|
+
summary=confirmation_summary,
|
|
48
|
+
mainnet_confirmed=mainnet_confirmed,
|
|
49
|
+
ttl_seconds=ttl_seconds,
|
|
50
|
+
issued_by=issued_by,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def session_metadata(self) -> OpenClawWalletSessionMetadata:
|
|
54
|
+
"""Return serializable metadata for host runtime/session storage."""
|
|
55
|
+
tool_names = [tool["name"] for tool in self.plugin_bundle["tools"]]
|
|
56
|
+
capabilities = self.backend.get_capabilities()
|
|
57
|
+
return OpenClawWalletSessionMetadata(
|
|
58
|
+
user_id=self.user_id,
|
|
59
|
+
chain=capabilities.chain,
|
|
60
|
+
network=str(getattr(self.backend, "network", "mainnet")),
|
|
61
|
+
backend=self.backend.name,
|
|
62
|
+
address=self.wallet_info["address"],
|
|
63
|
+
wallet_path=self.wallet_info["path"],
|
|
64
|
+
storage_format=self.wallet_info["storage_format"],
|
|
65
|
+
created_now=self.created_now,
|
|
66
|
+
sign_only=bool(getattr(self.backend, "sign_only", True)),
|
|
67
|
+
rpc_provider_mode=getattr(self.backend, "rpc_provider_mode", None),
|
|
68
|
+
rpc_provider=getattr(self.backend, "rpc_provider", None),
|
|
69
|
+
rpc_transport=getattr(self.backend, "rpc_transport", None),
|
|
70
|
+
swap_provider=getattr(self.backend, "swap_provider", None),
|
|
71
|
+
swap_transport=getattr(self.backend, "swap_transport", None),
|
|
72
|
+
tool_names=tool_names,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def serializable_bundle(self) -> dict[str, Any]:
|
|
76
|
+
"""Return the plugin bundle without the invoke callable for JSON/session transport."""
|
|
77
|
+
return {
|
|
78
|
+
"manifest": self.plugin_bundle["manifest"],
|
|
79
|
+
"instructions": self.plugin_bundle["instructions"],
|
|
80
|
+
"tools": self.plugin_bundle["tools"],
|
|
81
|
+
"session": self.session_metadata().model_dump(),
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def onboard_openclaw_user_wallet(
|
|
86
|
+
user_id: str,
|
|
87
|
+
*,
|
|
88
|
+
backend: str | None = None,
|
|
89
|
+
sign_only: bool | None = None,
|
|
90
|
+
network: str | None = None,
|
|
91
|
+
rpc_url: str | None = None,
|
|
92
|
+
wdk_btc_service_url: str | None = None,
|
|
93
|
+
wdk_btc_wallet_id: str | None = None,
|
|
94
|
+
wdk_btc_account_index: int | None = None,
|
|
95
|
+
wdk_evm_service_url: str | None = None,
|
|
96
|
+
wdk_evm_wallet_id: str | None = None,
|
|
97
|
+
wdk_evm_account_index: int | None = None,
|
|
98
|
+
) -> OpenClawWalletRuntimeContext:
|
|
99
|
+
"""Provision and assemble a runtime-ready wallet context for one OpenClaw user."""
|
|
100
|
+
backend_name = str(backend or settings.agent_wallet_backend).strip().lower()
|
|
101
|
+
if backend_name in {"wdk_btc_local", "wdk-btc-local", "btc_local", "btc-local"}:
|
|
102
|
+
service_url = str(wdk_btc_service_url or settings.wdk_btc_service_url).strip()
|
|
103
|
+
account_index = (
|
|
104
|
+
settings.wdk_btc_account_index
|
|
105
|
+
if wdk_btc_account_index is None
|
|
106
|
+
else int(wdk_btc_account_index)
|
|
107
|
+
)
|
|
108
|
+
requested_network = (network or settings.solana_network).strip().lower() or "bitcoin"
|
|
109
|
+
effective_network = "bitcoin" if requested_network == "mainnet" else requested_network
|
|
110
|
+
binding: dict[str, Any] | None = None
|
|
111
|
+
wallet_id = str(wdk_btc_wallet_id or settings.wdk_btc_wallet_id).strip()
|
|
112
|
+
if not service_url:
|
|
113
|
+
raise WalletBackendError("wdk_btc_service_url is required for backend=wdk_btc_local.")
|
|
114
|
+
if not wallet_id:
|
|
115
|
+
binding = get_user_btc_wallet_binding(user_id, network=effective_network)
|
|
116
|
+
wallet_id = str(binding.get("wallet_id") or "").strip()
|
|
117
|
+
if not wallet_id:
|
|
118
|
+
raise WalletBackendError(
|
|
119
|
+
"wdk_btc_wallet_id is required for backend=wdk_btc_local, or create a bound user BTC wallet first."
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
client = WdkBtcLocalClient(service_url)
|
|
123
|
+
wallet_meta = client.post_sync("/v1/btc/wallets/get", {"walletId": wallet_id})
|
|
124
|
+
address_payload = client.post_sync(
|
|
125
|
+
"/v1/btc/address/resolve",
|
|
126
|
+
{
|
|
127
|
+
"walletId": wallet_id,
|
|
128
|
+
"accountIndex": account_index,
|
|
129
|
+
"network": effective_network,
|
|
130
|
+
},
|
|
131
|
+
)
|
|
132
|
+
backend = WdkBtcLocalWalletBackend(
|
|
133
|
+
service_url=service_url,
|
|
134
|
+
wallet_id=wallet_id,
|
|
135
|
+
network=effective_network,
|
|
136
|
+
account_index=account_index,
|
|
137
|
+
sign_only=settings.agent_wallet_sign_only if sign_only is None else sign_only,
|
|
138
|
+
address=str(address_payload.get("address") or "").strip() or None,
|
|
139
|
+
)
|
|
140
|
+
wallet_info = {
|
|
141
|
+
"user_id": user_id,
|
|
142
|
+
"address": str(address_payload.get("address") or (binding or {}).get("address") or ""),
|
|
143
|
+
"path": f"{service_url}#walletId={wallet_id}",
|
|
144
|
+
"storage_format": "local_vault",
|
|
145
|
+
"key_scope": "host-managed",
|
|
146
|
+
"wallet_id": wallet_id,
|
|
147
|
+
"label": str(wallet_meta.get("label") or (binding or {}).get("label") or "BTC Wallet"),
|
|
148
|
+
}
|
|
149
|
+
adapter = OpenClawWalletAdapter(backend)
|
|
150
|
+
plugin_bundle = build_openclaw_plugin_bundle(backend)
|
|
151
|
+
return OpenClawWalletRuntimeContext(
|
|
152
|
+
user_id=user_id,
|
|
153
|
+
wallet_info=wallet_info,
|
|
154
|
+
created_now=False,
|
|
155
|
+
backend=backend,
|
|
156
|
+
adapter=adapter,
|
|
157
|
+
plugin_bundle=plugin_bundle,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
if backend_name in {"wdk_evm_local", "wdk-evm-local", "evm_local", "evm-local"}:
|
|
161
|
+
service_url = str(wdk_evm_service_url or settings.wdk_evm_service_url).strip()
|
|
162
|
+
account_index = (
|
|
163
|
+
settings.wdk_evm_account_index
|
|
164
|
+
if wdk_evm_account_index is None
|
|
165
|
+
else int(wdk_evm_account_index)
|
|
166
|
+
)
|
|
167
|
+
requested_network = (network or settings.solana_network).strip().lower() or "ethereum"
|
|
168
|
+
aliases = {
|
|
169
|
+
"mainnet": "ethereum",
|
|
170
|
+
"eth": "ethereum",
|
|
171
|
+
"eth-mainnet": "ethereum",
|
|
172
|
+
"base-mainnet": "base",
|
|
173
|
+
"base_sepolia": "base-sepolia",
|
|
174
|
+
}
|
|
175
|
+
effective_network = aliases.get(requested_network, requested_network)
|
|
176
|
+
binding: dict[str, Any] | None = None
|
|
177
|
+
wallet_id = str(wdk_evm_wallet_id or settings.wdk_evm_wallet_id).strip()
|
|
178
|
+
if not service_url:
|
|
179
|
+
raise WalletBackendError("wdk_evm_service_url is required for backend=wdk_evm_local.")
|
|
180
|
+
if wallet_id:
|
|
181
|
+
try:
|
|
182
|
+
binding = get_user_evm_wallet_binding(user_id, network=effective_network)
|
|
183
|
+
except WalletBackendError:
|
|
184
|
+
binding = ensure_user_evm_wallet_binding(
|
|
185
|
+
user_id,
|
|
186
|
+
network=effective_network,
|
|
187
|
+
service_url=service_url,
|
|
188
|
+
wallet_id=wallet_id,
|
|
189
|
+
account_index=account_index,
|
|
190
|
+
)
|
|
191
|
+
else:
|
|
192
|
+
if str(binding.get("wallet_id") or "").strip() != wallet_id:
|
|
193
|
+
binding = ensure_user_evm_wallet_binding(
|
|
194
|
+
user_id,
|
|
195
|
+
network=effective_network,
|
|
196
|
+
service_url=service_url,
|
|
197
|
+
wallet_id=wallet_id,
|
|
198
|
+
account_index=account_index,
|
|
199
|
+
)
|
|
200
|
+
else:
|
|
201
|
+
binding = ensure_user_evm_wallet_binding(
|
|
202
|
+
user_id,
|
|
203
|
+
network=effective_network,
|
|
204
|
+
service_url=service_url,
|
|
205
|
+
account_index=account_index,
|
|
206
|
+
)
|
|
207
|
+
wallet_id = str(binding.get("wallet_id") or "").strip()
|
|
208
|
+
if not wallet_id:
|
|
209
|
+
raise WalletBackendError(
|
|
210
|
+
"wdk_evm_wallet_id is required for backend=wdk_evm_local, or create a bound user EVM wallet first."
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
client = WdkEvmLocalClient(service_url)
|
|
214
|
+
wallet_meta = client.post_sync("/v1/evm/wallets/get", {"walletId": wallet_id})
|
|
215
|
+
resolved_address = str((binding or {}).get("address") or "").strip()
|
|
216
|
+
if not resolved_address:
|
|
217
|
+
address_payload = client.post_sync(
|
|
218
|
+
"/v1/evm/address/resolve",
|
|
219
|
+
{
|
|
220
|
+
"walletId": wallet_id,
|
|
221
|
+
"accountIndex": account_index,
|
|
222
|
+
"network": effective_network,
|
|
223
|
+
},
|
|
224
|
+
)
|
|
225
|
+
resolved_address = str(address_payload.get("address") or "").strip()
|
|
226
|
+
backend = WdkEvmLocalWalletBackend(
|
|
227
|
+
service_url=service_url,
|
|
228
|
+
wallet_id=wallet_id,
|
|
229
|
+
network=effective_network,
|
|
230
|
+
account_index=account_index,
|
|
231
|
+
sign_only=settings.agent_wallet_sign_only if sign_only is None else sign_only,
|
|
232
|
+
address=resolved_address or None,
|
|
233
|
+
)
|
|
234
|
+
wallet_info = {
|
|
235
|
+
"user_id": user_id,
|
|
236
|
+
"address": resolved_address,
|
|
237
|
+
"path": f"{service_url}#walletId={wallet_id}",
|
|
238
|
+
"storage_format": "local_vault",
|
|
239
|
+
"key_scope": "host-managed",
|
|
240
|
+
"wallet_id": wallet_id,
|
|
241
|
+
"label": str(wallet_meta.get("label") or (binding or {}).get("label") or "EVM Wallet"),
|
|
242
|
+
}
|
|
243
|
+
adapter = OpenClawWalletAdapter(backend)
|
|
244
|
+
plugin_bundle = build_openclaw_plugin_bundle(backend)
|
|
245
|
+
return OpenClawWalletRuntimeContext(
|
|
246
|
+
user_id=user_id,
|
|
247
|
+
wallet_info=wallet_info,
|
|
248
|
+
created_now=False,
|
|
249
|
+
backend=backend,
|
|
250
|
+
adapter=adapter,
|
|
251
|
+
plugin_bundle=plugin_bundle,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
wallet_path = resolve_user_wallet_path(user_id, network=network)
|
|
255
|
+
created_now = not wallet_path.exists()
|
|
256
|
+
wallet_info = ensure_user_solana_wallet(user_id, network=network)
|
|
257
|
+
backend = create_wallet_backend_for_user(
|
|
258
|
+
user_id,
|
|
259
|
+
sign_only=sign_only,
|
|
260
|
+
network=network,
|
|
261
|
+
rpc_url=rpc_url,
|
|
262
|
+
)
|
|
263
|
+
adapter = OpenClawWalletAdapter(backend)
|
|
264
|
+
plugin_bundle = build_openclaw_plugin_bundle(backend)
|
|
265
|
+
return OpenClawWalletRuntimeContext(
|
|
266
|
+
user_id=user_id,
|
|
267
|
+
wallet_info=wallet_info,
|
|
268
|
+
created_now=created_now,
|
|
269
|
+
backend=backend,
|
|
270
|
+
adapter=adapter,
|
|
271
|
+
plugin_bundle=plugin_bundle,
|
|
272
|
+
)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Helpers for exposing agent-wallet as an OpenClaw-style plugin bundle."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from agent_wallet.openclaw_adapter import OpenClawWalletAdapter
|
|
10
|
+
from agent_wallet.wallet_layer.base import AgentWalletBackend
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _package_root() -> Path:
|
|
14
|
+
return Path(__file__).resolve().parents[1]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_plugin_manifest() -> dict[str, Any]:
|
|
18
|
+
"""Load the OpenClaw plugin manifest from disk."""
|
|
19
|
+
manifest_path = _package_root() / "openclaw.plugin.json"
|
|
20
|
+
return json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_skill_text() -> str:
|
|
24
|
+
"""Load the wallet operator skill text."""
|
|
25
|
+
skill_path = _package_root() / "skills" / "wallet-operator" / "SKILL.md"
|
|
26
|
+
return skill_path.read_text(encoding="utf-8")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def build_openclaw_plugin_bundle(backend: AgentWalletBackend) -> dict[str, Any]:
|
|
30
|
+
"""Build a runtime-ready bundle for OpenClaw-style plugin registration."""
|
|
31
|
+
adapter = OpenClawWalletAdapter(backend)
|
|
32
|
+
return {
|
|
33
|
+
"manifest": get_plugin_manifest(),
|
|
34
|
+
"instructions": "\n\n".join(
|
|
35
|
+
[
|
|
36
|
+
adapter.get_runtime_instructions(),
|
|
37
|
+
get_skill_text(),
|
|
38
|
+
]
|
|
39
|
+
),
|
|
40
|
+
"tools": [tool.model_dump() for tool in adapter.list_tools()],
|
|
41
|
+
"invoke": adapter.invoke,
|
|
42
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Wallet providers."""
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"""Bags provider helpers routed through the shared provider gateway."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from agent_wallet.config import settings
|
|
9
|
+
from agent_wallet.exceptions import ProviderError
|
|
10
|
+
from agent_wallet.http_client import get_client
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _gateway_base_url() -> str:
|
|
14
|
+
base_url = os.getenv("PROVIDER_GATEWAY_URL", settings.provider_gateway_url).strip()
|
|
15
|
+
if not base_url:
|
|
16
|
+
raise ProviderError(
|
|
17
|
+
"bags",
|
|
18
|
+
"Provider gateway URL is not configured for Bags integration.",
|
|
19
|
+
)
|
|
20
|
+
return base_url.rstrip("/")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _gateway_headers() -> dict[str, str]:
|
|
24
|
+
headers = {"Accept": "application/json"}
|
|
25
|
+
bearer = os.getenv(
|
|
26
|
+
"PROVIDER_GATEWAY_BEARER_TOKEN",
|
|
27
|
+
settings.provider_gateway_bearer_token,
|
|
28
|
+
).strip()
|
|
29
|
+
if bearer:
|
|
30
|
+
headers["Authorization"] = f"Bearer {bearer}"
|
|
31
|
+
return headers
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _unwrap_gateway_payload(
|
|
35
|
+
status_code: int,
|
|
36
|
+
payload: Any,
|
|
37
|
+
*,
|
|
38
|
+
operation: str,
|
|
39
|
+
) -> Any:
|
|
40
|
+
if isinstance(payload, dict) and payload.get("ok") is False:
|
|
41
|
+
message = str(payload.get("error") or f"{operation} failed.")
|
|
42
|
+
raise ProviderError("bags", f"{operation} failed via provider gateway: {message}")
|
|
43
|
+
|
|
44
|
+
if status_code != 200:
|
|
45
|
+
message = payload
|
|
46
|
+
if isinstance(payload, dict):
|
|
47
|
+
message = payload.get("error") or payload
|
|
48
|
+
raise ProviderError("bags", f"{operation} failed via provider gateway: {message}")
|
|
49
|
+
|
|
50
|
+
return payload
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _unwrap_bags_response(
|
|
54
|
+
payload: Any,
|
|
55
|
+
*,
|
|
56
|
+
operation: str,
|
|
57
|
+
) -> Any:
|
|
58
|
+
if not isinstance(payload, dict):
|
|
59
|
+
raise ProviderError("bags", f"Unexpected {operation} response from provider gateway.")
|
|
60
|
+
|
|
61
|
+
if "success" not in payload:
|
|
62
|
+
return payload
|
|
63
|
+
|
|
64
|
+
if not payload.get("success"):
|
|
65
|
+
error = payload.get("error") or payload.get("message") or f"{operation} failed."
|
|
66
|
+
raise ProviderError("bags", f"{operation} failed: {error}")
|
|
67
|
+
|
|
68
|
+
return payload.get("response")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
async def _gateway_get_json(
|
|
72
|
+
path: str,
|
|
73
|
+
*,
|
|
74
|
+
params: dict[str, Any],
|
|
75
|
+
operation: str,
|
|
76
|
+
) -> Any:
|
|
77
|
+
client = get_client()
|
|
78
|
+
response = await client.get(
|
|
79
|
+
f"{_gateway_base_url()}{path}",
|
|
80
|
+
params=params,
|
|
81
|
+
headers=_gateway_headers(),
|
|
82
|
+
)
|
|
83
|
+
payload = response.json() if response.content else {}
|
|
84
|
+
gateway_payload = _unwrap_gateway_payload(
|
|
85
|
+
response.status_code,
|
|
86
|
+
payload,
|
|
87
|
+
operation=operation,
|
|
88
|
+
)
|
|
89
|
+
return _unwrap_bags_response(gateway_payload, operation=operation)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
async def _gateway_post_json(
|
|
93
|
+
path: str,
|
|
94
|
+
*,
|
|
95
|
+
body: dict[str, Any],
|
|
96
|
+
operation: str,
|
|
97
|
+
) -> Any:
|
|
98
|
+
client = get_client()
|
|
99
|
+
response = await client.post(
|
|
100
|
+
f"{_gateway_base_url()}{path}",
|
|
101
|
+
json=body,
|
|
102
|
+
headers={**_gateway_headers(), "Content-Type": "application/json"},
|
|
103
|
+
)
|
|
104
|
+
payload = response.json() if response.content else {}
|
|
105
|
+
gateway_payload = _unwrap_gateway_payload(
|
|
106
|
+
response.status_code,
|
|
107
|
+
payload,
|
|
108
|
+
operation=operation,
|
|
109
|
+
)
|
|
110
|
+
return _unwrap_bags_response(gateway_payload, operation=operation)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
async def fetch_trade_quote(
|
|
114
|
+
*,
|
|
115
|
+
input_mint: str,
|
|
116
|
+
output_mint: str,
|
|
117
|
+
amount_raw: int,
|
|
118
|
+
slippage_bps: int = 50,
|
|
119
|
+
) -> dict[str, Any]:
|
|
120
|
+
"""Fetch a Bags trade quote via the shared provider gateway."""
|
|
121
|
+
client = get_client()
|
|
122
|
+
response = await client.get(
|
|
123
|
+
f"{_gateway_base_url()}/v1/bags/trade/quote",
|
|
124
|
+
params={
|
|
125
|
+
"inputMint": input_mint,
|
|
126
|
+
"outputMint": output_mint,
|
|
127
|
+
"amount": str(amount_raw),
|
|
128
|
+
"slippageMode": "manual",
|
|
129
|
+
"slippageBps": str(slippage_bps),
|
|
130
|
+
},
|
|
131
|
+
headers=_gateway_headers(),
|
|
132
|
+
)
|
|
133
|
+
payload = response.json() if response.content else {}
|
|
134
|
+
gateway_payload = _unwrap_gateway_payload(
|
|
135
|
+
response.status_code,
|
|
136
|
+
payload,
|
|
137
|
+
operation="Bags trade quote",
|
|
138
|
+
)
|
|
139
|
+
quote = _unwrap_bags_response(gateway_payload, operation="Bags trade quote")
|
|
140
|
+
if not isinstance(quote, dict) or "outAmount" not in quote:
|
|
141
|
+
raise ProviderError("bags", "Unexpected trade quote response from Bags.")
|
|
142
|
+
return quote
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
async def build_swap_transaction(
|
|
146
|
+
*,
|
|
147
|
+
user_public_key: str,
|
|
148
|
+
quote_response: dict[str, Any],
|
|
149
|
+
) -> dict[str, Any]:
|
|
150
|
+
"""Build a serialized Bags swap transaction via the shared provider gateway."""
|
|
151
|
+
client = get_client()
|
|
152
|
+
response = await client.post(
|
|
153
|
+
f"{_gateway_base_url()}/v1/bags/trade/swap",
|
|
154
|
+
json={
|
|
155
|
+
"quoteResponse": quote_response,
|
|
156
|
+
"userPublicKey": user_public_key,
|
|
157
|
+
},
|
|
158
|
+
headers={**_gateway_headers(), "Content-Type": "application/json"},
|
|
159
|
+
)
|
|
160
|
+
payload = response.json() if response.content else {}
|
|
161
|
+
gateway_payload = _unwrap_gateway_payload(
|
|
162
|
+
response.status_code,
|
|
163
|
+
payload,
|
|
164
|
+
operation="Bags swap transaction",
|
|
165
|
+
)
|
|
166
|
+
swap = _unwrap_bags_response(gateway_payload, operation="Bags swap transaction")
|
|
167
|
+
if not isinstance(swap, dict) or "swapTransaction" not in swap:
|
|
168
|
+
raise ProviderError("bags", "Unexpected swap transaction response from Bags.")
|
|
169
|
+
return swap
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
async def create_token_info(payload: dict[str, Any]) -> dict[str, Any]:
|
|
173
|
+
response = await _gateway_post_json(
|
|
174
|
+
"/v1/bags/launch/token-info",
|
|
175
|
+
body=payload,
|
|
176
|
+
operation="Bags token info",
|
|
177
|
+
)
|
|
178
|
+
if not isinstance(response, dict):
|
|
179
|
+
raise ProviderError("bags", "Unexpected token info response from Bags.")
|
|
180
|
+
return response
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
async def create_fee_share_config(payload: dict[str, Any]) -> dict[str, Any]:
|
|
184
|
+
response = await _gateway_post_json(
|
|
185
|
+
"/v1/bags/launch/fee-share-config",
|
|
186
|
+
body=payload,
|
|
187
|
+
operation="Bags fee share config",
|
|
188
|
+
)
|
|
189
|
+
if not isinstance(response, dict):
|
|
190
|
+
raise ProviderError("bags", "Unexpected fee share config response from Bags.")
|
|
191
|
+
return response
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
async def create_launch_transaction(payload: dict[str, Any]) -> str:
|
|
195
|
+
response = await _gateway_post_json(
|
|
196
|
+
"/v1/bags/launch/transaction",
|
|
197
|
+
body=payload,
|
|
198
|
+
operation="Bags launch transaction",
|
|
199
|
+
)
|
|
200
|
+
if isinstance(response, str):
|
|
201
|
+
return response
|
|
202
|
+
raise ProviderError("bags", "Unexpected launch transaction response from Bags.")
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
async def fetch_claimable_positions(wallet: str) -> Any:
|
|
206
|
+
return await _gateway_get_json(
|
|
207
|
+
"/v1/bags/claim/positions",
|
|
208
|
+
params={"wallet": wallet},
|
|
209
|
+
operation="Bags claimable positions",
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
async def build_claim_transactions(payload: dict[str, Any]) -> Any:
|
|
214
|
+
return await _gateway_post_json(
|
|
215
|
+
"/v1/bags/claim/transactions",
|
|
216
|
+
body=payload,
|
|
217
|
+
operation="Bags claim transactions",
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
async def fetch_lifetime_fees(token_mint: str) -> Any:
|
|
222
|
+
return await _gateway_get_json(
|
|
223
|
+
"/v1/bags/fees/lifetime",
|
|
224
|
+
params={"tokenMint": token_mint},
|
|
225
|
+
operation="Bags lifetime fees",
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
async def fetch_claim_stats(token_mint: str) -> Any:
|
|
230
|
+
return await _gateway_get_json(
|
|
231
|
+
"/v1/bags/fees/claim-stats",
|
|
232
|
+
params={"tokenMint": token_mint},
|
|
233
|
+
operation="Bags claim stats",
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
async def fetch_claim_events(
|
|
238
|
+
*,
|
|
239
|
+
token_mint: str,
|
|
240
|
+
mode: str = "offset",
|
|
241
|
+
limit: int | None = None,
|
|
242
|
+
offset: int | None = None,
|
|
243
|
+
from_ts: int | None = None,
|
|
244
|
+
to_ts: int | None = None,
|
|
245
|
+
) -> Any:
|
|
246
|
+
params: dict[str, Any] = {"tokenMint": token_mint, "mode": mode}
|
|
247
|
+
if limit is not None:
|
|
248
|
+
params["limit"] = str(limit)
|
|
249
|
+
if offset is not None:
|
|
250
|
+
params["offset"] = str(offset)
|
|
251
|
+
if from_ts is not None:
|
|
252
|
+
params["from"] = str(from_ts)
|
|
253
|
+
if to_ts is not None:
|
|
254
|
+
params["to"] = str(to_ts)
|
|
255
|
+
return await _gateway_get_json(
|
|
256
|
+
"/v1/bags/fees/claim-events",
|
|
257
|
+
params=params,
|
|
258
|
+
operation="Bags claim events",
|
|
259
|
+
)
|